work-ally 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/AGENTS.md +110 -0
  2. package/DASHBOARD.md +160 -0
  3. package/PRODUCT.md +113 -0
  4. package/README.md +403 -0
  5. package/ally.sh +171 -0
  6. package/bridge/src/approval-rules.ts +360 -0
  7. package/bridge/src/channel-delivery.ts +207 -0
  8. package/bridge/src/channel-types.ts +22 -0
  9. package/bridge/src/channels/fake/adapter.ts +31 -0
  10. package/bridge/src/channels/feishu/adapter.ts +411 -0
  11. package/bridge/src/channels/feishu/approvals.ts +6 -0
  12. package/bridge/src/channels/feishu/formatter.ts +276 -0
  13. package/bridge/src/channels/feishu/normalize.ts +368 -0
  14. package/bridge/src/codex-config.ts +52 -0
  15. package/bridge/src/config.ts +240 -0
  16. package/bridge/src/fake-runtime-client.ts +505 -0
  17. package/bridge/src/handoff-service.ts +494 -0
  18. package/bridge/src/logger.ts +194 -0
  19. package/bridge/src/memory-digest.ts +186 -0
  20. package/bridge/src/receiver-approval-autonomy.ts +158 -0
  21. package/bridge/src/receiver-control-core.ts +140 -0
  22. package/bridge/src/receiver-control-work-session.ts +218 -0
  23. package/bridge/src/receiver-control.ts +83 -0
  24. package/bridge/src/receiver-delivery.ts +136 -0
  25. package/bridge/src/receiver-helpers.ts +96 -0
  26. package/bridge/src/receiver-human-gate.ts +333 -0
  27. package/bridge/src/receiver-inbound-preflight.ts +162 -0
  28. package/bridge/src/receiver-recovery.ts +236 -0
  29. package/bridge/src/receiver-runtime-callbacks.ts +367 -0
  30. package/bridge/src/receiver-runtime-policy.ts +132 -0
  31. package/bridge/src/receiver-runtime-state.ts +124 -0
  32. package/bridge/src/receiver-support-actions.ts +189 -0
  33. package/bridge/src/receiver-thread-start.ts +57 -0
  34. package/bridge/src/receiver-turn-coordination.ts +94 -0
  35. package/bridge/src/receiver-turn-execution.ts +257 -0
  36. package/bridge/src/receiver-turn-failure.ts +143 -0
  37. package/bridge/src/receiver-turn-result.ts +185 -0
  38. package/bridge/src/receiver-turn-steer.ts +70 -0
  39. package/bridge/src/receiver-work-session.ts +76 -0
  40. package/bridge/src/receiver.ts +329 -0
  41. package/bridge/src/router.ts +62 -0
  42. package/bridge/src/runtime-client-agent-messages.ts +150 -0
  43. package/bridge/src/runtime-client-message-dispatch.ts +176 -0
  44. package/bridge/src/runtime-client-protocol.ts +411 -0
  45. package/bridge/src/runtime-client-request-ops.ts +56 -0
  46. package/bridge/src/runtime-client-run-turn.ts +158 -0
  47. package/bridge/src/runtime-client-thread-ops.ts +270 -0
  48. package/bridge/src/runtime-client-transport.ts +309 -0
  49. package/bridge/src/runtime-client-turn-poll.ts +224 -0
  50. package/bridge/src/runtime-client-turn-read.ts +185 -0
  51. package/bridge/src/runtime-client-turn-state.ts +105 -0
  52. package/bridge/src/runtime-client.ts +344 -0
  53. package/bridge/src/runtime-user-input.ts +403 -0
  54. package/bridge/src/scheduler.ts +239 -0
  55. package/bridge/src/server-handoff-command.ts +364 -0
  56. package/bridge/src/server-main.ts +80 -0
  57. package/bridge/src/server-routine-command.ts +60 -0
  58. package/bridge/src/server-routine-execution.ts +222 -0
  59. package/bridge/src/server-runtime-app-support.ts +107 -0
  60. package/bridge/src/server-runtime-app.ts +238 -0
  61. package/bridge/src/server-thread-sync-command.ts +63 -0
  62. package/bridge/src/server.ts +17 -0
  63. package/bridge/src/session-store-delivery.ts +220 -0
  64. package/bridge/src/session-store-human-gate.ts +380 -0
  65. package/bridge/src/session-store-inbound-acceptance.ts +66 -0
  66. package/bridge/src/session-store-meta.ts +134 -0
  67. package/bridge/src/session-store-turn-ledger.ts +272 -0
  68. package/bridge/src/session-store.ts +380 -0
  69. package/bridge/src/system-notify.ts +220 -0
  70. package/bridge/src/thread-sync.ts +200 -0
  71. package/bridge/src/translator.ts +494 -0
  72. package/bridge/src/types.ts +289 -0
  73. package/bridge/src/utils.ts +104 -0
  74. package/bridge/src/work-session-store.ts +471 -0
  75. package/docs/.gitkeep +0 -0
  76. package/docs/architecture/codex-feishu-bridge-proposal.md +2742 -0
  77. package/docs/completed/FEATURE-feishu-markdown-and-reply-support.md +327 -0
  78. package/docs/completed/README.md +21 -0
  79. package/docs/completed/SPEC-approval-autonomy-and-safe-defaults.md +205 -0
  80. package/docs/completed/SPEC-approval-batch-and-strict-reply-shortcuts.md +153 -0
  81. package/docs/completed/SPEC-conversation-noise-reduction-and-busy-input-gate.md +538 -0
  82. package/docs/completed/SPEC-engineering-sop-skillization.md +190 -0
  83. package/docs/completed/SPEC-faithful-bridge-core-thinning-v2.md +376 -0
  84. package/docs/completed/SPEC-faithful-bridge-core-thinning.md +1071 -0
  85. package/docs/completed/SPEC-group-chat-sender-identity.md +301 -0
  86. package/docs/completed/SPEC-middleware-exception-visibility.md +227 -0
  87. package/docs/completed/SPEC-nightly-memory-digest-visibility.md +121 -0
  88. package/docs/completed/SPEC-project-group-chat-human-centered-conversation-mapping.md +326 -0
  89. package/docs/completed/SPEC-remove-cli-persona-bootstrap.md +201 -0
  90. package/docs/developer-workflow.md +49 -0
  91. package/docs/implementation/SPEC-codex-same-machine-session-handoff-implementation.md +239 -0
  92. package/docs/implementation/test-coverage-map.md +363 -0
  93. package/docs/implementation/work-ally-implementation-guide.md +790 -0
  94. package/docs/issues/README.md +10 -0
  95. package/docs/issues/pending/ANALYSIS-ally-premature-recovery-notice-and-task-state-semantics-2026-03-18.md +295 -0
  96. package/docs/issues/resolved/ANALYSIS-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +466 -0
  97. package/docs/issues/resolved/ANALYSIS-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +261 -0
  98. package/docs/issues/resolved/ANALYSIS-codex-app-server-transport-disconnect-semantics-2026-03-14.md +606 -0
  99. package/docs/issues/resolved/ANALYSIS-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +348 -0
  100. package/docs/issues/resolved/ANALYSIS-runtime-turn-delivery-and-recovery-2026-03-14.md +603 -0
  101. package/docs/issues/resolved/ANALYSIS-self-test-gap-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +166 -0
  102. package/docs/issues/resolved/ANALYSIS-self-test-gap-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +186 -0
  103. package/docs/issues/resolved/ANALYSIS-self-test-gap-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +166 -0
  104. package/docs/issues/resolved/REPORT-ally-runtime-turn-delivery-3b42fb8-2026-03-15.md +373 -0
  105. package/docs/manual-acceptance.md +127 -0
  106. package/docs/ops-runbook.md +44 -0
  107. package/docs/planning/FEATURE-memory-system.md +748 -0
  108. package/docs/planning/SPEC-active-turn-steer-and-context-compaction-visibility.md +269 -0
  109. package/docs/planning/SPEC-approval-rules-inheritance-and-local-validation-lane.md +450 -0
  110. package/docs/planning/SPEC-assistant-persona-bootstrap.md +199 -0
  111. package/docs/planning/SPEC-assistant-rename.md +610 -0
  112. package/docs/planning/SPEC-bridge-app-server-protocol-alignment.md +667 -0
  113. package/docs/planning/SPEC-claude-runtime-host-for-work-ally.md +434 -0
  114. package/docs/planning/SPEC-cli-feishu-codex-session-unification.md +236 -0
  115. package/docs/planning/SPEC-codex-same-machine-session-handoff.md +873 -0
  116. package/docs/planning/SPEC-feishu-reaction-shortcuts.md +282 -0
  117. package/docs/planning/SPEC-local-stable-release-boundary.md +166 -0
  118. package/docs/planning/SPEC-managed-thread-entry-and-surface-mobility.md +862 -0
  119. package/docs/planning/SPEC-minimal-bridge-semantics-and-user-visible-surface.md +362 -0
  120. package/docs/planning/SPEC-npm-alpha-distribution-and-install-first-release.md +222 -0
  121. package/docs/planning/SPEC-remove-websocket-runtime-transport.md +364 -0
  122. package/docs/planning/SPEC-runtime-abstraction-phase-1.md +424 -0
  123. package/docs/planning/SPEC-runtime-connection-and-turn-recovery-semantics.md +274 -0
  124. package/docs/planning/SPEC-session-presence-and-state-visibility.md +397 -0
  125. package/docs/planning/SPEC-skill-first-capability-packaging.md +338 -0
  126. package/docs/planning/SPEC-stable-archive-contract.md +456 -0
  127. package/docs/planning/SPEC-supervised-start-boundary.md +127 -0
  128. package/docs/planning/SPEC-user-barrier-reduction-and-activation.md +832 -0
  129. package/docs/planning/ally-next.md +1278 -0
  130. package/docs/planning/assistant-workbench-spec.md +725 -0
  131. package/docs/planning/product-workbench.md +283 -0
  132. package/docs/product-onboarding.md +227 -0
  133. package/docs/product-spec-standard.md +528 -0
  134. package/docs/troubleshooting.md +45 -0
  135. package/docs/user-quickstart.md +46 -0
  136. package/internal/dispatch.sh +95 -0
  137. package/internal/lib/common.sh +1450 -0
  138. package/internal/modules/assistant/manage.sh +1312 -0
  139. package/internal/modules/bootstrap/setup.sh +144 -0
  140. package/internal/modules/config/init-env.sh +10 -0
  141. package/internal/modules/global/manage.sh +154 -0
  142. package/internal/modules/handoff/manage.sh +54 -0
  143. package/internal/modules/mcp/manage.sh +83 -0
  144. package/internal/modules/ops/logs.sh +76 -0
  145. package/internal/modules/routines/manage.sh +55 -0
  146. package/internal/modules/runtime/assistant-autosave.sh +26 -0
  147. package/internal/modules/runtime/restart.sh +6 -0
  148. package/internal/modules/runtime/start.sh +283 -0
  149. package/internal/modules/runtime/status.sh +194 -0
  150. package/internal/modules/runtime/stop.sh +55 -0
  151. package/internal/modules/runtime/supervisor.sh +216 -0
  152. package/internal/modules/runtime/update.sh +26 -0
  153. package/package.json +41 -0
  154. package/runtime/config/.gitkeep +0 -0
  155. package/runtime/host/.gitkeep +0 -0
  156. package/runtime/host/healthcheck-codex-app-server.ts +22 -0
  157. package/runtime/host/ping-pong-codex-app-server.ts +66 -0
  158. package/runtime/host/probe-codex-app-server.ts +115 -0
  159. package/skills/archive-reader/SKILL.md +9 -0
  160. package/skills/feishu-production-debug/SKILL.md +37 -0
  161. package/skills/feishu-production-debug/references/feishu-debug-order.md +49 -0
  162. package/skills/feishu-production-debug/references/platform-permission-baseline.md +23 -0
  163. package/skills/issue-to-spec-triage/SKILL.md +44 -0
  164. package/skills/issue-to-spec-triage/references/triage-rules.md +66 -0
  165. package/skills/memory-digest/SKILL.md +9 -0
  166. package/skills/post-implementation-closure/SKILL.md +39 -0
  167. package/skills/post-implementation-closure/references/closure-checklist.md +45 -0
  168. package/skills/post-implementation-closure/references/doc-drift-map.md +49 -0
  169. package/skills/product-spec/SKILL.md +244 -0
  170. package/templates/env.example +5 -0
  171. package/templates/routines/nightly-memory-digest.yaml +10 -0
  172. package/templates/workspace/AGENTS.md +26 -0
@@ -0,0 +1,158 @@
1
+ import type { RuntimeTurnState } from './runtime-client-agent-messages.ts';
2
+ import { logWarn } from './logger.ts';
3
+ import { approvalRequestFrom, elicitationRequestFrom, userInputRequestFrom, type JsonRpcMessage, type RunTurnOptions } from './runtime-client-protocol.ts';
4
+ import { logInfo } from './logger.ts';
5
+ import type { RuntimeProgressMeta, RuntimeThreadStatus, RuntimeTurnResult } from './types.ts';
6
+
7
+ interface RunTurnEventBridgeDeps {
8
+ safeEmitTurnProgress(turnId: string, text: string, meta?: RuntimeProgressMeta): Promise<void>;
9
+ resolveApproval(requestId: string | number, decision: 'accept' | 'acceptForSession' | 'decline' | 'cancel'): Promise<void>;
10
+ resolveUserInput(requestId: string | number, response: { answers: Record<string, { answers: string[] }> }): Promise<void>;
11
+ resolveElicitation(
12
+ requestId: string | number,
13
+ response: { action: 'accept' | 'decline' | 'cancel'; content: unknown | null },
14
+ ): Promise<void>;
15
+ }
16
+
17
+ interface RunTurnEventBridgeState {
18
+ getActiveTurnId(): string | null;
19
+ }
20
+
21
+ interface StartRuntimeTurnDeps {
22
+ request(method: string, params: unknown): Promise<any>;
23
+ cacheThreadStatus(threadId: string, status: RuntimeThreadStatus | null | undefined): RuntimeThreadStatus | null;
24
+ turnStates: Map<string, RuntimeTurnState>;
25
+ startTurnFinalPoll(threadId: string, turnId: string): void;
26
+ rejectTurnResult(turnId: string, error: Error): boolean;
27
+ }
28
+
29
+ function createRuntimeTurnPromise(
30
+ turnStates: Map<string, RuntimeTurnState>,
31
+ options: RunTurnOptions,
32
+ turnId: string,
33
+ ): Promise<RuntimeTurnResult> {
34
+ return new Promise<RuntimeTurnResult>((resolve, reject) => {
35
+ turnStates.set(turnId, {
36
+ threadId: options.threadId,
37
+ agentMessages: new Map(),
38
+ nextAgentMessageOrder: 0,
39
+ onProgress: options.onProgress,
40
+ onThreadStatus: options.onThreadStatus,
41
+ completedEventResult: null,
42
+ emittedContextCompactionKeys: new Set(),
43
+ resolve,
44
+ reject,
45
+ });
46
+ });
47
+ }
48
+
49
+ export async function startRuntimeTurn(
50
+ deps: StartRuntimeTurnDeps,
51
+ options: RunTurnOptions,
52
+ ): Promise<{ turnId: string; turnPromise: Promise<RuntimeTurnResult> }> {
53
+ logInfo('runtime', 'turn_starting', {
54
+ threadId: options.threadId,
55
+ messageId: options.messageId,
56
+ });
57
+ const result = await deps.request('turn/start', {
58
+ threadId: options.threadId,
59
+ cwd: options.cwd,
60
+ ...(options.approvalPolicy ? { approvalPolicy: options.approvalPolicy } : {}),
61
+ ...(options.sandboxPolicy ? { sandboxPolicy: options.sandboxPolicy } : {}),
62
+ input: [{ type: 'text', text: options.prompt, text_elements: [] }],
63
+ });
64
+ const turnId = result.turn.id;
65
+ deps.cacheThreadStatus(options.threadId, { type: 'active', activeFlags: [] });
66
+ logInfo('runtime', 'turn_started', {
67
+ threadId: options.threadId,
68
+ turnId,
69
+ messageId: options.messageId,
70
+ });
71
+ const turnPromise = createRuntimeTurnPromise(deps.turnStates, options, turnId);
72
+ deps.startTurnFinalPoll(options.threadId, turnId);
73
+ try {
74
+ await options.onTurnStarted?.(turnId);
75
+ } catch (error) {
76
+ deps.rejectTurnResult(turnId, error instanceof Error ? error : new Error(String(error)));
77
+ throw error;
78
+ }
79
+ return { turnId, turnPromise };
80
+ }
81
+
82
+ export function createRunTurnEventBridge(
83
+ deps: RunTurnEventBridgeDeps,
84
+ options: RunTurnOptions,
85
+ state: RunTurnEventBridgeState,
86
+ ): {
87
+ progressHandler: (eventTurnId: string, text: string, meta?: RuntimeProgressMeta) => Promise<void>;
88
+ serverRequestHandler: (msg: JsonRpcMessage) => Promise<void>;
89
+ } {
90
+ const progressHandler = async (eventTurnId: string, text: string, meta?: RuntimeProgressMeta) => {
91
+ const activeTurnId = state.getActiveTurnId();
92
+ if (activeTurnId && eventTurnId !== activeTurnId) {
93
+ return;
94
+ }
95
+ await deps.safeEmitTurnProgress(activeTurnId || eventTurnId, text, meta);
96
+ };
97
+
98
+ const serverRequestHandler = async (msg: JsonRpcMessage) => {
99
+ const activeTurnId = state.getActiveTurnId();
100
+ const eventTurnId = typeof msg.params?.turnId === 'string' ? msg.params.turnId : null;
101
+ if (activeTurnId && eventTurnId && eventTurnId !== activeTurnId) {
102
+ return;
103
+ }
104
+
105
+ const approval = approvalRequestFrom(msg, options.messageId);
106
+ if (approval) {
107
+ if (activeTurnId && approval.turnId !== activeTurnId) {
108
+ return;
109
+ }
110
+ if (!options.onApproval) {
111
+ await deps.resolveApproval(approval.requestId, 'cancel');
112
+ return;
113
+ }
114
+ await options.onApproval(approval);
115
+ return;
116
+ }
117
+
118
+ const userInput = userInputRequestFrom(msg, options.messageId);
119
+ if (userInput) {
120
+ if (activeTurnId && userInput.turnId !== activeTurnId) {
121
+ return;
122
+ }
123
+ if (!options.onUserInput) {
124
+ logWarn('runtime', 'user_input_requested_without_handler', {
125
+ threadId: userInput.threadId,
126
+ turnId: userInput.turnId,
127
+ requestId: userInput.requestId,
128
+ });
129
+ await deps.resolveUserInput(userInput.requestId, { answers: {} });
130
+ return;
131
+ }
132
+ await options.onUserInput(userInput);
133
+ return;
134
+ }
135
+
136
+ const elicitation = elicitationRequestFrom(msg, options.messageId);
137
+ if (!elicitation) {
138
+ return;
139
+ }
140
+ if (activeTurnId && elicitation.turnId && elicitation.turnId !== activeTurnId) {
141
+ return;
142
+ }
143
+ if (!options.onUserInput) {
144
+ logWarn('runtime', 'elicitation_requested_without_handler', {
145
+ threadId: elicitation.threadId,
146
+ turnId: elicitation.turnId || null,
147
+ requestId: elicitation.requestId,
148
+ serverName: elicitation.elicitation?.serverName || null,
149
+ mode: elicitation.elicitation?.mode || null,
150
+ });
151
+ await deps.resolveElicitation(elicitation.requestId, { action: 'cancel', content: null });
152
+ return;
153
+ }
154
+ await options.onUserInput(elicitation);
155
+ };
156
+
157
+ return { progressHandler, serverRequestHandler };
158
+ }
@@ -0,0 +1,270 @@
1
+ import { buildReplyFromThreadItems } from './runtime-client-agent-messages.ts';
2
+ import { readPersistedFinalReplyFromCodexHome, readTurnResult } from './runtime-client-turn-read.ts';
3
+ import { logInfo, logWarn } from './logger.ts';
4
+ import type { ApprovalDecision, RuntimeThreadStatus, RuntimeTurnResult } from './types.ts';
5
+ import { normalizeRuntimeError, normalizeThreadStatus, type ThreadOptions } from './runtime-client-protocol.ts';
6
+
7
+ const TURN_RESULT_RECOVERY_TIMEOUT_MS = Number(process.env.WORK_ALLY_TURN_RESULT_RECOVERY_TIMEOUT_MS || '60000');
8
+
9
+ function sleep(ms: number): Promise<void> {
10
+ return new Promise((resolve) => setTimeout(resolve, ms));
11
+ }
12
+
13
+ interface RequestLike {
14
+ (method: string, params: unknown): Promise<any>;
15
+ }
16
+
17
+ interface ConnectLike {
18
+ (): Promise<void>;
19
+ }
20
+
21
+ interface ThreadStatusCacheLike {
22
+ set(threadId: string, status: RuntimeThreadStatus): void;
23
+ get(threadId: string): RuntimeThreadStatus | undefined;
24
+ }
25
+
26
+ interface ThreadRequestDeps {
27
+ request: RequestLike;
28
+ cacheThreadStatus(threadId: string, status: RuntimeThreadStatus | null | undefined): RuntimeThreadStatus | null;
29
+ }
30
+
31
+ interface ResumeThreadDeps extends ThreadRequestDeps {
32
+ startThread(cwd: string, options?: ThreadOptions): Promise<{ id: string }> ;
33
+ }
34
+
35
+ interface ThreadStatusReadDeps extends ThreadRequestDeps {
36
+ cachedThreadStatus(threadId: string): RuntimeThreadStatus | null;
37
+ }
38
+
39
+ interface SteerTurnDeps {
40
+ connect: ConnectLike;
41
+ request: RequestLike;
42
+ isRecoverableRuntimeError(error: unknown): boolean;
43
+ scheduleReconnect(reason: string): void;
44
+ }
45
+
46
+ interface RecoverTurnResultDeps {
47
+ request: RequestLike;
48
+ cacheThreadStatus(threadId: string, status: RuntimeThreadStatus | null | undefined): RuntimeThreadStatus | null;
49
+ readPersistedFinalReply(turnId: string): string | null;
50
+ }
51
+
52
+ interface SendRawLike {
53
+ (payload: unknown): void;
54
+ }
55
+
56
+ export function threadParamsFor(cwd: string, options: ThreadOptions = {}) {
57
+ return {
58
+ cwd,
59
+ ...(options.approvalPolicy ? { approvalPolicy: options.approvalPolicy } : {}),
60
+ ...(options.sandbox ? { sandbox: options.sandbox } : {}),
61
+ ...(options.developerInstructions ? { developerInstructions: options.developerInstructions } : {}),
62
+ ...(options.baseInstructions ? { baseInstructions: options.baseInstructions } : {}),
63
+ };
64
+ }
65
+
66
+ export function cacheThreadStatusFor(
67
+ cache: ThreadStatusCacheLike,
68
+ threadId: string,
69
+ status: RuntimeThreadStatus | null | undefined,
70
+ ): RuntimeThreadStatus | null {
71
+ if (!threadId || !status) {
72
+ return null;
73
+ }
74
+ const normalized = {
75
+ type: status.type,
76
+ activeFlags: [...status.activeFlags],
77
+ } satisfies RuntimeThreadStatus;
78
+ cache.set(threadId, normalized);
79
+ return normalized;
80
+ }
81
+
82
+ export function cachedThreadStatusFor(cache: ThreadStatusCacheLike, threadId: string): RuntimeThreadStatus | null {
83
+ const status = cache.get(threadId);
84
+ if (!status) {
85
+ return null;
86
+ }
87
+ return {
88
+ type: status.type,
89
+ activeFlags: [...status.activeFlags],
90
+ };
91
+ }
92
+
93
+ export function readPersistedFinalReply(codexHome: string | null, turnId: string): string | null {
94
+ return readPersistedFinalReplyFromCodexHome(codexHome, turnId);
95
+ }
96
+
97
+ export async function startThreadRequest(
98
+ deps: ThreadRequestDeps,
99
+ cwd: string,
100
+ options: ThreadOptions = {},
101
+ ): Promise<{ id: string }> {
102
+ const result = await deps.request('thread/start', {
103
+ ...threadParamsFor(cwd, options),
104
+ experimentalRawEvents: false,
105
+ persistExtendedHistory: true,
106
+ });
107
+ const threadId = result.thread.id;
108
+ deps.cacheThreadStatus(threadId, normalizeThreadStatus(result?.thread?.status));
109
+ return { id: threadId };
110
+ }
111
+
112
+ export async function resumeThreadRequest(
113
+ deps: ResumeThreadDeps,
114
+ threadId: string,
115
+ cwd: string,
116
+ options: ThreadOptions = {},
117
+ ): Promise<{ id: string }> {
118
+ try {
119
+ const result = await deps.request('thread/resume', {
120
+ threadId,
121
+ ...threadParamsFor(cwd, options),
122
+ persistExtendedHistory: true,
123
+ });
124
+ const resumedThreadId = result.thread.id;
125
+ deps.cacheThreadStatus(resumedThreadId, normalizeThreadStatus(result?.thread?.status));
126
+ return { id: resumedThreadId };
127
+ } catch (error: any) {
128
+ const message = error?.message || '';
129
+ const code = error?.code;
130
+ if (String(code || '') === '-32600' && typeof message === 'string' && /no rollout found for thread id/i.test(message)) {
131
+ logWarn('runtime', 'thread_resume_missing_rollout', {
132
+ threadId,
133
+ cwd,
134
+ });
135
+ return deps.startThread(cwd, options);
136
+ }
137
+ throw error;
138
+ }
139
+ }
140
+
141
+ export async function readThreadRequest(
142
+ deps: ThreadRequestDeps,
143
+ threadId: string,
144
+ includeTurns = false,
145
+ ): Promise<{ id: string; status: RuntimeThreadStatus | null; turns: any[] | null }> {
146
+ const result = await deps.request('thread/read', { threadId, includeTurns });
147
+ const resolvedThreadId = result?.thread?.id || threadId;
148
+ const status = deps.cacheThreadStatus(resolvedThreadId, normalizeThreadStatus(result?.thread?.status));
149
+ if (resolvedThreadId !== threadId && status) {
150
+ deps.cacheThreadStatus(threadId, status);
151
+ }
152
+ const turns = includeTurns && Array.isArray(result?.thread?.turns) ? result.thread.turns : null;
153
+ return { id: resolvedThreadId, status, turns };
154
+ }
155
+
156
+ export async function readThreadStatusRequest(
157
+ deps: ThreadStatusReadDeps,
158
+ threadId: string,
159
+ ): Promise<RuntimeThreadStatus | null> {
160
+ try {
161
+ const { status } = await readThreadRequest(deps, threadId, false);
162
+ return status || deps.cachedThreadStatus(threadId);
163
+ } catch (error) {
164
+ logWarn('runtime', 'thread_read_failed', {
165
+ threadId,
166
+ error: error instanceof Error ? (error.stack || error.message) : String(error),
167
+ });
168
+ return deps.cachedThreadStatus(threadId);
169
+ }
170
+ }
171
+
172
+ export async function interruptTurnRequest(request: RequestLike, threadId: string, turnId: string): Promise<void> {
173
+ await request('turn/interrupt', { threadId, turnId });
174
+ }
175
+
176
+ export async function steerTurnRequest(
177
+ deps: SteerTurnDeps,
178
+ threadId: string,
179
+ turnId: string,
180
+ prompt: string,
181
+ ): Promise<void> {
182
+ await deps.connect();
183
+ logInfo('runtime', 'turn_steering', {
184
+ threadId,
185
+ turnId,
186
+ });
187
+ try {
188
+ await deps.request('turn/steer', {
189
+ threadId,
190
+ turnId,
191
+ expectedTurnId: turnId,
192
+ input: [{ type: 'text', text: prompt, text_elements: [] }],
193
+ });
194
+ } catch (error) {
195
+ if (deps.isRecoverableRuntimeError(error)) {
196
+ const message = error instanceof Error ? error.message : String(error);
197
+ deps.scheduleReconnect(message);
198
+ }
199
+ throw error;
200
+ }
201
+ }
202
+
203
+ export async function recoverTurnResultRequest(
204
+ deps: RecoverTurnResultDeps,
205
+ threadId: string,
206
+ turnId: string,
207
+ options: { timeoutMs?: number; pollMs?: number } = {},
208
+ ): Promise<RuntimeTurnResult | null> {
209
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : TURN_RESULT_RECOVERY_TIMEOUT_MS;
210
+ const pollMs = Number.isFinite(options.pollMs) ? Math.max(1, Number(options.pollMs)) : 1000;
211
+ const deadline = Date.now() + timeoutMs;
212
+ let lastError: Error | null = null;
213
+
214
+ while (Date.now() <= deadline) {
215
+ try {
216
+ const recovered = await readTurnResult({
217
+ request: (method, params) => deps.request(method, params),
218
+ cacheThreadStatus: (currentThreadId, status) => deps.cacheThreadStatus(currentThreadId, status),
219
+ buildReplyFromThreadItems: (items) => buildReplyFromThreadItems(items),
220
+ readPersistedFinalReply: (currentTurnId) => deps.readPersistedFinalReply(currentTurnId),
221
+ }, threadId, turnId);
222
+ if (recovered && recovered.status !== 'inProgress') {
223
+ deps.cacheThreadStatus(recovered.threadId, { type: 'idle', activeFlags: [] });
224
+ return recovered;
225
+ }
226
+ } catch (error) {
227
+ lastError = normalizeRuntimeError(error);
228
+ }
229
+ await sleep(pollMs);
230
+ }
231
+
232
+ logWarn('runtime', 'turn_recovery_unavailable', {
233
+ threadId,
234
+ turnId,
235
+ error: lastError ? (lastError.stack || lastError.message) : null,
236
+ });
237
+ return null;
238
+ }
239
+
240
+ export function resolveApprovalRequest(sendRaw: SendRawLike, requestId: string | number, decision: ApprovalDecision): void {
241
+ sendRaw({
242
+ jsonrpc: '2.0',
243
+ id: requestId,
244
+ result: { decision },
245
+ });
246
+ }
247
+
248
+ export function resolveUserInputRequest(
249
+ sendRaw: SendRawLike,
250
+ requestId: string | number,
251
+ response: { answers: Record<string, { answers: string[] }> },
252
+ ): void {
253
+ sendRaw({
254
+ jsonrpc: '2.0',
255
+ id: requestId,
256
+ result: response,
257
+ });
258
+ }
259
+
260
+ export function resolveElicitationRequest(
261
+ sendRaw: SendRawLike,
262
+ requestId: string | number,
263
+ response: { action: 'accept' | 'decline' | 'cancel'; content: unknown | null },
264
+ ): void {
265
+ sendRaw({
266
+ jsonrpc: '2.0',
267
+ id: requestId,
268
+ result: response,
269
+ });
270
+ }