relay-companion
Version:
Install Relay for Claude Code, Cowork, and Codex, then sign in from the Relay pill.
704 lines (651 loc) • 31.8 kB
JavaScript
import { createHash } from "node:crypto";
export * from "./claude-native-work-feed.js";
const MAX_NATIVE_EVENTS = 8_000;
const HIDDEN_CONTROL_SUBTYPES = new Set([
"set_permission_mode", "set_max_thinking_tokens", "mcp_set_servers",
"mcp_toggle", "apply_flag_settings",
]);
const CONTROL_RESOLUTION_TYPES = new Set([
"control_response", "control_cancel", "control_cancel_request",
]);
function string(value) {
return String(value == null ? "" : value);
}
function hash(value) {
return createHash("sha256").update(string(value)).digest("hex").slice(0, 20);
}
function parseTime(...values) {
for (const value of values) {
const parsed = Date.parse(string(value));
if (Number.isFinite(parsed)) return parsed;
}
return null;
}
function sequence(event) {
const value = BigInt(string(event?.sequence_num || event?.sequenceNum || "0").replace(/[^0-9-]/g, "") || "0");
return value;
}
function nativeIdentity(event, ordinal = 0) {
return string(event?.event_id || event?.id || event?.payload?.uuid).trim()
|| `cowork:${string(event?.sequence_num || event?.sequenceNum || ordinal)}:${hash(JSON.stringify(event?.payload || event || {}))}`;
}
/**
* Cowork pages overlap and occasionally return a previous page boundary again.
* Event identity, not object identity or arrival order, is authoritative. The
* highest sequence wins when a later page contains a richer copy.
*/
export function reconcileCoworkNativeEvents(input) {
const rows = Array.isArray(input) ? input : [];
const byId = new Map();
rows.forEach((row, ordinal) => {
if (!row || typeof row !== "object") return;
const key = nativeIdentity(row, ordinal);
const previous = byId.get(key);
const rowJson = JSON.stringify(row);
const previousJson = previous ? JSON.stringify(previous.row) : "";
const newer = !previous || sequence(row) > sequence(previous.row);
const richer = previous && sequence(row) === sequence(previous.row)
&& (rowJson.length > previousJson.length || (rowJson.length === previousJson.length && rowJson > previousJson));
if (newer || richer) byId.set(key, { row, ordinal });
});
const ordered = [...byId.values()].sort((left, right) => {
const delta = sequence(left.row) - sequence(right.row);
if (delta < 0n) return -1;
if (delta > 0n) return 1;
const byEvent = nativeIdentity(left.row, left.ordinal).localeCompare(nativeIdentity(right.row, right.ordinal));
return byEvent || left.ordinal - right.ordinal;
}).map((entry) => entry.row);
if (ordered.length <= MAX_NATIVE_EVENTS) return ordered;
const start = ordered.length - MAX_NATIVE_EVENTS;
const boundary = ordered.slice(0, start).findLast(isTrueHuman);
return boundary ? [boundary, ...ordered.slice(start)] : ordered.slice(start);
}
function payloadOf(event) {
return event?.payload && typeof event.payload === "object" ? event.payload : event || {};
}
function typeOf(event) {
const payload = payloadOf(event);
return string(payload.type || event?.event_type || event?.type).toLowerCase();
}
function contentBlocks(content) {
if (Array.isArray(content)) return content.filter((block) => block && typeof block === "object");
if (typeof content === "string") return [{ type: "text", text: content }];
return [];
}
function publicText(content) {
return contentBlocks(content)
.filter((block) => block.type === "text")
.map((block) => string(block.text))
.filter(Boolean)
.join("\n");
}
function messageText(payload) {
if (typeof payload?.message === "string") return payload.message;
return publicText(payload?.message?.content ?? payload?.content);
}
function hasToolResult(payload) {
const type = string(payload?.type).toLowerCase();
if (/tool_result/.test(type)) return true;
return contentBlocks(payload?.message?.content ?? payload?.content)
.some((block) => block.type === "tool_result" || block.type === "custom_tool_result");
}
function isRuntimeSeed(text) {
const value = string(text);
return /<relay-documents\b|<relay-runtime-contract\b|<for-agent\b|Begin the task as briefed\.?/i.test(value);
}
function isTrueHuman(event) {
const payload = payloadOf(event);
if (typeOf(event) !== "user" || hasToolResult(payload)) return false;
// Cowork also labels worker protocol frames as `user`. Only the independent
// outer client provenance plus its Desktop-platform corroboration proves a
// real human boundary; prose and role labels are not evidence.
if (string(event?.source).toLowerCase() !== "client" || !string(payload.client_platform).trim()) return false;
if (string(payload.source).toLowerCase() === "worker" || payload.isSynthetic || payload.isCompactSummary
|| payload.isVisibleInTranscriptOnly || payload.synthetic || payload.internal) return false;
return Boolean(messageText(payload).trim());
}
function sourceUuid(event) {
const payload = payloadOf(event);
return string(payload.uuid || event?.event_id || event?.id).trim();
}
function eventTime(event) {
const payload = payloadOf(event);
return parseTime(payload.timestamp, payload.created_at, payload.processed_at, event?.created_at, event?.processed_at);
}
function workEvent(event, suffix, method, turnId, params = {}) {
const sourceId = nativeIdentity(event);
const at = eventTime(event);
return {
eventId: `cowork:${sourceId}:${suffix}`,
method,
emittedAtMs: at,
params: { turnId, ...params },
};
}
function itemEvent(event, suffix, turnId, item, lifecycle = "completed") {
const at = eventTime(event);
return workEvent(event, suffix, `item/${lifecycle}`, turnId, {
[`${lifecycle}AtMs`]: at,
item: {
provider: "cowork",
nativeType: typeOf(event),
nativeEventId: nativeIdentity(event),
...item,
},
});
}
function compactObject(value) {
return string(value).replace(/\s+/g, " ").trim().slice(0, 240);
}
function basename(value) {
const clean = string(value).replace(/[\\/]+$/, "");
return clean.split(/[\\/]/).filter(Boolean).at(-1) || clean;
}
function coworkToolPresentation(name, input = {}) {
const tool = string(name || "Tool");
const path = basename(input.file_path || input.path || input.filename || "a file");
const query = compactObject(input.query || input.pattern || input.search_term || "files");
const description = compactObject(input.description || input.task || input.prompt || "");
if (/^(Read|View|Open)$/i.test(tool)) return { kind: "read", activeVerb: "Reading", doneVerb: "Read", object: path, objectType: "file" };
if (/^(Write|Create)$/i.test(tool)) return { kind: "edit", activeVerb: "Creating", doneVerb: "Created", object: path, objectType: "file" };
if (/^(Edit|MultiEdit|ApplyPatch)$/i.test(tool)) return { kind: "edit", activeVerb: "Editing", doneVerb: "Edited", object: path, objectType: "file" };
if (/^(Grep|Glob|Search|Find)$/i.test(tool)) return { kind: "search", activeVerb: "Searching", doneVerb: "Searched", object: query };
if (/^(WebFetch|Fetch)$/i.test(tool)) return { kind: "web", activeVerb: "Fetching", doneVerb: "Fetched", object: compactObject(input.url || "the web") };
if (/^(WebSearch)$/i.test(tool)) return { kind: "web", activeVerb: "Searching", doneVerb: "Searched", object: query || "the web" };
if (/^(Bash|Shell|Execute|RunCommand)$/i.test(tool)) return {
kind: "command", activeVerb: "Running", doneVerb: "Ran", object: compactObject(input.command || description || "command"),
};
if (/^(Agent|Task|Subagent)$/i.test(tool)) return { kind: "subagent", activeVerb: "Running", doneVerb: "Ran", object: description || "agent" };
if (/Skill/i.test(tool)) return { kind: "call", activeVerb: "Running", doneVerb: "Ran", object: description || "skill" };
if (/ToolSearch|FindTools/i.test(tool)) return { kind: "search", activeVerb: "Finding", doneVerb: "Found", object: "tools" };
if (/SendMessage/i.test(tool)) return { kind: "call", activeVerb: "Sending", doneVerb: "Sent", object: "message" };
if (/PresentFiles/i.test(tool)) return { kind: "call", activeVerb: "Presenting", doneVerb: "Presented", object: "files" };
if (/TaskOutput|CommandOutput/i.test(tool)) return { kind: "read", activeVerb: "Checking", doneVerb: "Checked", object: "command output" };
if (/Multi/i.test(tool)) return { kind: "call", activeVerb: "Multitasking", doneVerb: "Multitasked", object: "" };
return { kind: "call", activeVerb: "Using", doneVerb: "Used", object: compactObject(tool.replace(/[_-]+/g, " ")) || "tool" };
}
function nativeToolItem(block, event, status = "inProgress") {
const toolUseId = string(block.id || block.tool_use_id || payloadOf(event).tool_use_id || nativeIdentity(event));
const name = string(block.name || block.tool_name || payloadOf(event).tool_name || "Tool");
return {
id: `cowork-tool:${toolUseId}`,
type: "dynamicToolCall",
name,
tool: name,
status,
presentation: coworkToolPresentation(name, block.input || {}),
metadata: { provider: "cowork", nativeToolUseId: toolUseId },
};
}
function questionText(payload) {
const request = payload?.request || {};
const questions = request.questions || request.input?.questions || payload.questions || [];
const rows = Array.isArray(questions) ? questions : [];
const prompt = rows.map((question) => string(question?.question || question?.prompt || question)).filter(Boolean).join("\n");
const fallback = string(request.question || request.prompt || payload.question || payload.prompt || payload.reason);
const header = string(rows[0]?.header || request.header).trim();
const text = prompt || fallback || "Input required";
return header && !text.toLowerCase().includes(header.toLowerCase()) ? `${header}: ${text}` : text;
}
function requestId(payload, event) {
return string(payload?.request_id || payload?.requestId || payload?.request?.request_id || payload?.response?.request_id || nativeIdentity(event));
}
function controlSubtype(payload) {
return string(payload?.request?.subtype || payload?.response?.subtype || payload?.subtype).toLowerCase();
}
function resultFailed(payload) {
const subtype = string(payload?.subtype).toLowerCase();
return Boolean(payload?.is_error) || ["error", "failed", "failure"].includes(subtype)
|| ["provider_error", "failed", "error"].includes(string(payload?.terminal_reason).toLowerCase());
}
function retryDisposition(payload) {
return string(payload?.error?.retry_status?.type || payload?.retry_status?.type || payload?.retry_status).toLowerCase();
}
function errorText(payload, fallback = "Cowork could not finish this turn.") {
return string(payload?.error?.message || payload?.error || payload?.result || payload?.message || payload?.status_detail || fallback).trim();
}
function latestSummary(turn) {
return turn.rows.filter((event) => typeOf(event) === "system" && /post_turn_summary/i.test(string(payloadOf(event).subtype))).at(-1) || null;
}
function summaryState(turn) {
const summary = latestSummary(turn);
const payload = payloadOf(summary);
return {
event: summary,
category: string(payload.status_category).toLowerCase(),
detail: string(payload.status_detail).trim(),
action: string(payload.needs_action).trim(),
summarizesUuid: string(payload.summarizes_uuid).trim(),
};
}
function resultFor(turn) {
return turn.rows.filter((event) => typeOf(event) === "result").at(-1) || null;
}
function sessionRequiresAction(session) {
return Array.isArray(session?.requires_action_details_list) && session.requires_action_details_list.length > 0;
}
function providerStateFor(turn, session, isLatest) {
const summary = summaryState(turn);
const result = resultFor(turn);
const failed = result && resultFailed(payloadOf(result));
const pendingRequest = turn.pendingRequestCount > 0;
const sessionBlocked = isLatest && (string(session?.status_bucket).toLowerCase() === "blocked"
|| string(session?.worker_status).toLowerCase() === "requires_action" || sessionRequiresAction(session));
if (failed) return { state: "failed", label: "Needs attention", terminal: true };
if (summary.category === "blocked" || pendingRequest || sessionBlocked) return { state: "blocked", label: "Needs input", terminal: false };
if (result || summary.category === "review_ready") return { state: "review_ready", label: "Ready for review", terminal: true };
const worker = string(session?.worker_status).toLowerCase();
const bucket = string(session?.status_bucket).toLowerCase();
if (isLatest && bucket === "review_ready") return { state: "review_ready", label: "Ready for review", terminal: true };
if (isLatest && (worker === "idle" || ["completed", "archived"].includes(bucket))) return { state: "completed", label: "Completed", terminal: true };
return { state: "working", label: "Working", terminal: false };
}
function assistantTextRows(turn) {
const rows = [];
for (const event of turn.rows) {
if (typeOf(event) !== "assistant") continue;
const payload = payloadOf(event);
const value = publicText(payload?.message?.content ?? payload?.content).trim();
if (value) rows.push({ event, uuid: sourceUuid(event), text: value });
}
return rows;
}
function finalAssistantUuid(turn) {
const summary = summaryState(turn);
if (summary.summarizesUuid) return summary.summarizesUuid;
const result = resultFor(turn);
const resultPayload = payloadOf(result);
if (!result || resultFailed(resultPayload)) return "";
const exact = assistantTextRows(turn).findLast(({ text }) => text.trim() === string(resultPayload.result).trim());
return exact?.uuid || (string(resultPayload.stop_reason).toLowerCase() === "end_turn" ? assistantTextRows(turn).at(-1)?.uuid || "" : "");
}
function turnStartEvent(turn) {
const source = turn.seed || turn.rows[0];
const startedAtMs = eventTime(source);
return workEvent(source, "turn-start", "turn/started", turn.id, { startedAtMs });
}
function nativeStateEvent(turn, source, state) {
return workEvent(source || turn.seed || turn.rows.at(-1), `native-state:${state.state}`, "provider/state", turn.id, {
nativeState: state.state,
nativeLabel: state.label,
composerAvailable: true,
requiresAction: state.state === "blocked",
});
}
function assignTurns(events, sessionId) {
const turns = [];
const byUserUuid = new Map();
const byRequestId = new Map();
let current = null;
const ensure = (seed = null) => {
if (current) return current;
const uuid = sourceUuid(seed) || `orphan-${turns.length}`;
current = { id: `cowork-turn:${sessionId}:${uuid}`, seed, rows: [], byRequestId, pendingRequestCount: 0 };
turns.push(current);
return current;
};
for (const event of events) {
const payload = payloadOf(event);
const type = typeOf(event);
if (isTrueHuman(event)) {
const uuid = sourceUuid(event) || nativeIdentity(event);
current = { id: `cowork-turn:${sessionId}:${uuid}`, seed: event, rows: [], byRequestId, pendingRequestCount: 0 };
turns.push(current);
byUserUuid.set(uuid, current);
current.rows.push(event);
continue;
}
if (type === "result" && payload.user_message_uuid && byUserUuid.has(string(payload.user_message_uuid))) {
byUserUuid.get(string(payload.user_message_uuid)).rows.push(event);
continue;
}
if (type === "control_request") {
if (!current && HIDDEN_CONTROL_SUBTYPES.has(controlSubtype(payload))) continue;
const owner = ensure(event);
byRequestId.set(requestId(payload, event), owner);
owner.rows.push(event);
continue;
}
if (CONTROL_RESOLUTION_TYPES.has(type)) {
const owner = byRequestId.get(requestId(payload, event));
if (!owner) continue; // response to hidden Desktop protocol, not a human turn
owner.rows.push(event);
continue;
}
// Startup/configuration noise before the first actual prompt is not a turn.
if (!current && ["env_manager_log", "control_request", "control_response", "system", "rate_limit_event"].includes(type)) continue;
ensure(event).rows.push(event);
}
return turns;
}
function resolvedRequestIds(turn) {
const ids = new Set();
for (const event of turn.rows) {
if (CONTROL_RESOLUTION_TYPES.has(typeOf(event))) ids.add(requestId(payloadOf(event), event));
}
return ids;
}
function explicitControlToolIds(turn) {
const ids = new Set();
for (const event of turn.rows) {
if (typeOf(event) !== "control_request") continue;
const payload = payloadOf(event);
if (controlSubtype(payload) === "can_use_tool") ids.add(string(payload.request?.tool_use_id));
}
return ids;
}
function toolResultBlocks(payload) {
const direct = /tool_result/.test(string(payload?.type)) ? [payload] : [];
return [...direct, ...contentBlocks(payload?.message?.content ?? payload?.content)
.filter((block) => block.type === "tool_result" || block.type === "custom_tool_result")];
}
function toolUseBlocks(payload) {
const direct = /tool_use/.test(string(payload?.type)) ? [payload] : [];
return [...direct, ...contentBlocks(payload?.message?.content ?? payload?.content)
.filter((block) => block.type === "tool_use" || block.type === "custom_tool_use")];
}
function publicQuestionDetails(payload) {
const request = payload?.request || payload || {};
const questions = request.questions || request.input?.questions || payload?.questions || [];
return {
subtype: controlSubtype(payload) || null,
questions: (Array.isArray(questions) ? questions : []).slice(0, 20).map((question) => ({
id: string(question?.id || question?.question_id).trim() || null,
header: string(question?.header).trim() || null,
question: string(question?.question || question?.prompt || question).trim(),
multiSelect: Boolean(question?.multiSelect || question?.multi_select),
options: (Array.isArray(question?.options) ? question.options : []).slice(0, 50).map((option) => ({
label: string(option?.label || option?.value || option).trim(),
description: string(option?.description).trim() || null,
})),
})),
};
}
function requestEvent(event, turnId, id, text, method = "cowork/requestUserInput", details = null) {
return {
eventId: `cowork:${nativeIdentity(event)}:request:${id}`,
id,
method,
emittedAtMs: eventTime(event),
params: {
turnId,
requestId: id,
question: string(text).trim() || "Input required",
provider: "cowork",
request: details && typeof details === "object" ? details : undefined,
},
};
}
function buildTurnEvents(turn, { session, isLatest }) {
const events = [turnStartEvent(turn)];
const resolved = resolvedRequestIds(turn);
const controlledToolIds = explicitControlToolIds(turn);
const summary = summaryState(turn);
const result = resultFor(turn);
const resultPayload = payloadOf(result);
const finalUuid = finalAssistantUuid(turn);
const hasLaterSuccess = Boolean(result && !resultFailed(resultPayload));
const requestIds = new Set();
const nativeTools = new Map();
for (const candidate of turn.rows) {
for (const block of toolUseBlocks(payloadOf(candidate))) {
nativeTools.set(string(block.id || block.tool_use_id || sourceUuid(candidate)), block);
}
}
for (const event of turn.rows) {
const payload = payloadOf(event);
const type = typeOf(event);
const uuid = sourceUuid(event) || nativeIdentity(event);
if (event === turn.seed && isTrueHuman(event) && !isRuntimeSeed(messageText(payload))) {
events.push(itemEvent(event, "human", turn.id, {
id: `cowork-user:${uuid}`,
type: "userMessage",
text: messageText(payload),
status: "completed",
attachments: contentBlocks(payload?.message?.content ?? payload?.content)
.filter((block) => ["image", "document", "file", "attachment"].includes(block.type)),
}));
continue;
}
if (type === "assistant") {
const text = publicText(payload?.message?.content ?? payload?.content).trim();
if (text) events.push(itemEvent(event, `assistant:${finalUuid === uuid ? "final" : "commentary"}`, turn.id, {
id: `cowork-message:${uuid}`,
type: "agentMessage",
text,
phase: finalUuid === uuid ? "final_answer" : "commentary",
status: "completed",
}));
for (const block of toolUseBlocks(payload)) {
const toolUseId = string(block.id || block.tool_use_id || uuid);
if (/AskUserQuestion/i.test(string(block.name))) {
if (!controlledToolIds.has(toolUseId)) {
const id = `ask:${toolUseId}`;
const questions = Array.isArray(block.input?.questions) ? block.input.questions : [];
const question = questions.map((entry) => string(entry?.question || entry)).filter(Boolean).join("\n") || "Input required";
events.push(requestEvent(event, turn.id, id, question, "cowork/requestUserInput", publicQuestionDetails(block.input || {})));
if (!resolved.has(id)) requestIds.add(id);
}
continue;
}
events.push(itemEvent(event, `tool-start:${toolUseId}`, turn.id, nativeToolItem(block, event, "inProgress"), "started"));
}
continue;
}
if (type === "user" || /tool_result/.test(type)) {
for (const block of toolResultBlocks(payload)) {
const toolUseId = string(block.tool_use_id || block.id || uuid);
const existing = nativeTools.get(toolUseId);
const item = nativeToolItem(existing || { id: toolUseId, name: block.tool_name || "Tool", input: {} }, event,
block.is_error ? "failed" : "completed");
events.push(itemEvent(event, `tool-result:${toolUseId}`, turn.id, item));
if (block.is_error) events.push(workEvent(event, `tool-error:${toolUseId}`, "error", turn.id, {
error: { message: publicText(block.content) || string(block.content) || `${item.name} failed`, code: "COWORK_TOOL_FAILED" },
}));
}
continue;
}
if (type === "tool_progress") {
const progressKind = string(payload.kind || payload.subtype).toLowerCase();
if (["heartbeat", "subagent_retry", "auth_status", "tool_use_summary"].includes(progressKind)) continue;
const toolUseId = string(payload.tool_use_id || payload.id || uuid);
events.push(itemEvent(event, `tool-progress:${toolUseId}`, turn.id,
nativeToolItem({ id: toolUseId, name: payload.tool_name || "Tool", input: payload.input || {} }, event, "inProgress"), "started"));
continue;
}
if (type === "active_goal") {
const goal = payload.goal || payload.active_goal || payload.value || {};
const text = string(goal.title || goal.description || goal.condition || payload.title || payload.detail).trim();
if (text) events.push(itemEvent(event, "active-goal", turn.id, {
id: `cowork-goal:${uuid}`, type: "plan", text, status: /complete|done/i.test(string(goal.status)) ? "completed" : "inProgress",
}));
continue;
}
if (type === "autocompact_state" || (type === "system" && /compact/i.test(string(payload.subtype)))) {
const state = string(payload.state || payload.status || payload.subtype).toLowerCase();
if (/compact(?:ed|ion)|complete|done/.test(state)) events.push(itemEvent(event, "compaction", turn.id, {
id: `cowork-compaction:${uuid}`, type: "contextCompaction", text: "Context compacted", status: "completed",
}));
continue;
}
if (type === "system" && string(payload.subtype).toLowerCase() === "task_summary") {
const detail = string(payload.detail || payload.status_detail || payload.summary).trim();
if (detail) events.push(itemEvent(event, "task-summary", turn.id, {
id: `cowork-summary:${uuid}`, type: "plan", text: detail, status: "inProgress",
}));
continue;
}
if (type === "control_request") {
const subtype = controlSubtype(payload);
if (HIDDEN_CONTROL_SUBTYPES.has(subtype)) continue;
const id = requestId(payload, event);
if (subtype === "can_use_tool" || /question|permission|confirm|approve/.test(subtype)) {
events.push(requestEvent(event, turn.id, id, questionText(payload),
subtype === "can_use_tool" ? "cowork/permissions/request" : `cowork/${subtype || "requestUserInput"}`,
publicQuestionDetails(payload)));
if (!resolved.has(id)) requestIds.add(id);
}
continue;
}
if (CONTROL_RESOLUTION_TYPES.has(type)) {
const id = requestId(payload, event);
events.push({
eventId: `cowork:${nativeIdentity(event)}:request-resolved:${id}`,
method: "serverRequest/resolved",
emittedAtMs: eventTime(event),
params: { turnId: turn.id, requestId: id, result: { status: /cancel/.test(type) ? "cancelled" : "resolved" } },
});
continue;
}
if (type === "mcp_auth_required") {
// A later successful result proves this historical authentication gate
// was cleared. Cowork keeps the raw event in session history.
if (hasLaterSuccess && sequence(result) > sequence(event)) continue;
const id = `mcp-auth:${uuid}`;
events.push(requestEvent(event, turn.id, id,
`${string(payload.server_name || payload.mcp_server_name || "MCP server")} needs authentication. Connect it to continue.`,
"cowork/mcp/elicitation/request", {
subtype: "mcp_auth_required",
action: "authenticate",
serverName: string(payload.server_name || payload.mcp_server_name || "MCP server"),
questions: [],
}));
requestIds.add(id);
continue;
}
if (type === "rate_limit_event") {
const info = payload.rate_limit_info || payload;
if (string(info.overageStatus).toLowerCase() === "allowed" || info.overageInUse === true) continue;
const disposition = retryDisposition(info);
events.push(workEvent(event, "rate-limit", disposition === "retrying" ? "error" : "error", turn.id, {
error: {
message: string(info.message || "Cowork is temporarily rate limited."),
code: "COWORK_RATE_LIMITED",
willRetry: disposition === "retrying",
},
willRetry: disposition === "retrying",
}));
continue;
}
if (type === "session.error" || type === "error") {
const disposition = retryDisposition(payload);
events.push(workEvent(event, "native-error", "error", turn.id, {
error: { message: errorText(payload), code: string(payload.error?.type || payload.type || "COWORK_ERROR"), willRetry: disposition === "retrying" },
willRetry: disposition === "retrying",
}));
continue;
}
// env_manager_log, generic system frames, protocol controls and result
// bookkeeping intentionally do not become visible transcript units.
}
if (summary.category === "blocked") {
if (requestIds.size === 0) {
const id = `post-turn:${nativeIdentity(summary.event)}`;
events.push(requestEvent(summary.event, turn.id, id, summary.action || summary.detail || "Cowork needs input."));
requestIds.add(id);
}
}
if (isLatest && sessionRequiresAction(session)) {
for (const [index, detail] of session.requires_action_details_list.entries()) {
const id = string(detail?.request_id || detail?.event_id || `session-action-${index}`);
if (requestIds.has(id) || resolved.has(id)) continue;
const source = turn.rows.at(-1) || turn.seed;
events.push(requestEvent(source, turn.id, id,
detail?.question || detail?.message || detail?.status_detail || "Cowork needs your input to continue.",
`cowork/${string(detail?.type || "requires_action")}`));
requestIds.add(id);
}
}
if (isLatest && string(session?.status_bucket).toLowerCase() === "blocked" && requestIds.size === 0) {
const source = turn.rows.at(-1) || turn.seed;
const id = `session-blocked:${turn.id}`;
events.push(requestEvent(source, turn.id, id, "Cowork needs your input to continue."));
requestIds.add(id);
}
turn.pendingRequestCount = requestIds.size;
const nativeState = providerStateFor(turn, session, isLatest);
if (result && resultFailed(resultPayload)) {
events.push(workEvent(result, "result-error", "error", turn.id, {
error: { message: errorText(resultPayload), code: string(resultPayload.terminal_reason || resultPayload.subtype || "COWORK_FAILED") },
}));
events.push(workEvent(result, "turn-failed", "turn/completed", turn.id, {
status: "failed", completedAtMs: eventTime(result), durationMs: Number(resultPayload.duration_ms) || null,
error: { message: errorText(resultPayload), code: string(resultPayload.terminal_reason || resultPayload.subtype || "COWORK_FAILED") },
}));
} else if (nativeState.terminal) {
const source = result || summary.event || turn.rows.at(-1);
events.push(workEvent(source, `turn-completed:${nativeState.state}`, "turn/completed", turn.id, {
status: "completed", completedAtMs: eventTime(source), durationMs: Number(resultPayload.duration_ms) || null,
}));
}
events.push(nativeStateEvent(turn, result || summary.event || turn.rows.at(-1), nativeState));
return events;
}
/**
* Convert raw Claude Desktop Cowork events directly to the bounded Work event
* envelope. This bypasses the lossy legacy transcript projection entirely:
* native sequence/event identity, blocking controls and result association are
* retained before shared rendering primitives are used.
*/
export function coworkNativeEventsToWorkEvents(input, { sessionId = "", session = {} } = {}) {
const rows = reconcileCoworkNativeEvents(input);
const turns = assignTurns(rows, string(sessionId || session?.id || "cowork"));
return turns.flatMap((turn, index) => buildTurnEvents(turn, {
session,
isLatest: index === turns.length - 1,
}));
}
/**
* One bounded main-process refresh owner. Cowork's current Desktop transport is
* paged rather than token-delta streaming; refreshing authoritative pages must
* never be represented to the renderer as fabricated token streaming.
*/
export function subscribeManagedProviderRefresh(refresh, listener, {
intervalMs = 1_000,
maxIntervalMs = 15_000,
maxEventsPerRefresh = 8_192,
setTimer = setTimeout,
clearTimer = clearTimeout,
} = {}) {
let closed = false;
let running = false;
let timer = null;
let delay = Math.max(250, Number(intervalMs) || 1_000);
const ceiling = Math.max(delay, Number(maxIntervalMs) || 15_000);
const schedule = () => {
if (closed) return;
timer = setTimer(run, delay);
timer?.unref?.();
};
const run = async () => {
if (closed || running) return;
running = true;
try {
const result = await refresh();
if (closed) return;
const events = Array.isArray(result) ? result : result?.events;
for (const event of (Array.isArray(events) ? events : []).slice(-Math.max(1, maxEventsPerRefresh))) listener(event);
delay = Math.max(250, Number(intervalMs) || 1_000);
if (result?.terminal) closed = true;
} catch (error) {
if (!closed) listener({
eventId: `provider-refresh-error:${Date.now()}`,
method: "error",
emittedAtMs: Date.now(),
params: { error: { message: string(error?.message || "Provider refresh failed"), code: "PROVIDER_REFRESH_FAILED", willRetry: true }, willRetry: true },
});
delay = Math.min(ceiling, delay * 2);
} finally {
running = false;
schedule();
}
};
void run();
return () => {
closed = true;
if (timer != null) clearTimer(timer);
};
}
// Transitional shared entry point. Provider-specific adapters live in their
// own modules so Claude and Cowork can evolve independently.
export * from "./claude-native-work-feed.js";