@astrosheep/keiyaku 2.8.5 → 2.9.0

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 (228) hide show
  1. package/README.md +35 -15
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/{call-terms_v2.js → call-terms.js} +12 -8
  4. package/build/agents/harness/control-types.js +1 -0
  5. package/build/agents/harness/execution-handle.js +23 -3
  6. package/build/agents/harness/index.js +6 -607
  7. package/build/agents/harness/outcome.js +141 -0
  8. package/build/agents/harness/projection.js +310 -0
  9. package/build/agents/harness/router.js +201 -0
  10. package/build/agents/harness/runtime.js +2 -1
  11. package/build/agents/{launch-snapshot_v2.js → launch-snapshot/model.js} +69 -74
  12. package/build/agents/launch-snapshot/resolve.js +72 -0
  13. package/build/agents/opencode-sdk.js +24 -3
  14. package/build/agents/providers/{claude-agent-sdk.js → claude-agent-sdk/adapter.js} +46 -368
  15. package/build/agents/providers/claude-agent-sdk/events.js +218 -0
  16. package/build/agents/providers/claude-agent-sdk/session.js +158 -0
  17. package/build/agents/providers/codex-app-server/adapter.js +347 -0
  18. package/build/agents/providers/codex-app-server/events.js +352 -0
  19. package/build/agents/providers/codex-app-server/session.js +266 -0
  20. package/build/agents/providers/codex-sdk.js +23 -5
  21. package/build/agents/providers/opencode-sdk/adapter.js +344 -0
  22. package/build/agents/providers/opencode-sdk/events.js +354 -0
  23. package/build/agents/providers/opencode-sdk/session.js +180 -0
  24. package/build/agents/providers/pi/adapter.js +269 -0
  25. package/build/agents/providers/pi/checkpoint.js +41 -0
  26. package/build/agents/providers/pi/events.js +282 -0
  27. package/build/agents/{selector_v2.js → selector.js} +8 -6
  28. package/build/cli/commands/akuma/akuma/handler.js +4 -0
  29. package/build/cli/commands/akuma/akuma/meta.js +16 -0
  30. package/build/cli/commands/akuma/catalog.js +8 -0
  31. package/build/cli/commands/akuma/list/handler.js +15 -0
  32. package/build/cli/commands/akuma/list/meta-ls.js +9 -0
  33. package/build/cli/commands/akuma/view/handler.js +34 -0
  34. package/build/cli/commands/akuma/view/meta.js +8 -0
  35. package/build/cli/commands/akuma.js +1 -1
  36. package/build/cli/commands/contract/amend/handler.js +10 -0
  37. package/build/cli/commands/contract/amend/meta.js +12 -0
  38. package/build/cli/commands/contract/arc/handler.js +9 -0
  39. package/build/cli/commands/contract/arc/meta.js +26 -0
  40. package/build/cli/commands/contract/bind/handler.js +23 -0
  41. package/build/cli/commands/contract/bind/meta.js +30 -0
  42. package/build/cli/commands/contract/catalog.js +16 -0
  43. package/build/cli/commands/contract/forfeit/handler.js +13 -0
  44. package/build/cli/commands/contract/forfeit/meta.js +12 -0
  45. package/build/cli/commands/contract/log/handler.js +12 -0
  46. package/build/cli/commands/contract/log/meta.js +12 -0
  47. package/build/cli/commands/contract/petition/handler.js +26 -0
  48. package/build/cli/commands/contract/petition/meta.js +23 -0
  49. package/build/cli/commands/contract/renew/handler.js +10 -0
  50. package/build/cli/commands/contract/renew/meta.js +17 -0
  51. package/build/cli/commands/projection/call/handler.js +39 -0
  52. package/build/cli/commands/projection/call/meta.js +9 -0
  53. package/build/cli/commands/projection/catalog.js +14 -0
  54. package/build/cli/commands/projection/kill/handler.js +35 -0
  55. package/build/cli/commands/projection/kill/meta.js +11 -0
  56. package/build/cli/commands/projection/revive/handler.js +53 -0
  57. package/build/cli/commands/projection/revive/meta.js +9 -0
  58. package/build/cli/commands/projection/status/handler.js +23 -0
  59. package/build/cli/commands/projection/status/meta.js +9 -0
  60. package/build/cli/commands/projection/tell/handler.js +34 -0
  61. package/build/cli/commands/projection/tell/meta.js +9 -0
  62. package/build/cli/commands/projection/wait/handler.js +21 -0
  63. package/build/cli/commands/projection/wait/meta.js +11 -0
  64. package/build/cli/commands/registry.js +100 -0
  65. package/build/cli/commands/shared.js +71 -0
  66. package/build/cli/commands/skills/catalog.js +6 -0
  67. package/build/cli/commands/skills/install/handler.js +10 -0
  68. package/build/cli/commands/skills/install/meta.js +9 -0
  69. package/build/cli/commands/skills/skills/handler.js +4 -0
  70. package/build/cli/commands/skills/skills/meta.js +10 -0
  71. package/build/cli/commands/system/catalog.js +8 -0
  72. package/build/cli/commands/system/completion/handler.js +10 -0
  73. package/build/cli/commands/system/completion/meta.js +9 -0
  74. package/build/cli/commands/system/dump-env/handler.js +3 -0
  75. package/build/cli/commands/system/dump-env/meta.js +10 -0
  76. package/build/cli/commands/system/guide/handler.js +4 -0
  77. package/build/cli/commands/system/guide/meta.js +9 -0
  78. package/build/cli/commands/task/add/handler.js +21 -0
  79. package/build/cli/commands/task/add/meta.js +8 -0
  80. package/build/cli/commands/task/catalog.js +22 -0
  81. package/build/cli/commands/task/doctor/handler.js +4 -0
  82. package/build/cli/commands/task/doctor/meta.js +8 -0
  83. package/build/cli/commands/task/done/handler.js +17 -0
  84. package/build/cli/commands/task/done/meta.js +8 -0
  85. package/build/cli/commands/task/drop/handler.js +6 -0
  86. package/build/cli/commands/task/drop/meta.js +8 -0
  87. package/build/cli/commands/task/ls/handler.js +13 -0
  88. package/build/cli/commands/task/ls/meta.js +8 -0
  89. package/build/cli/commands/task/shared.js +131 -0
  90. package/build/cli/commands/task/start/handler.js +6 -0
  91. package/build/cli/commands/task/start/meta.js +8 -0
  92. package/build/cli/commands/task/stop/handler.js +17 -0
  93. package/build/cli/commands/task/stop/meta.js +8 -0
  94. package/build/cli/commands/task/task/handler.js +5 -0
  95. package/build/cli/commands/task/task/meta.js +11 -0
  96. package/build/cli/commands/task/update/handler.js +18 -0
  97. package/build/cli/commands/task/update/meta.js +8 -0
  98. package/build/cli/commands/task/view/handler.js +14 -0
  99. package/build/cli/commands/task/view/meta.js +8 -0
  100. package/build/cli/completion.js +26 -27
  101. package/build/cli/flags.js +79 -2
  102. package/build/cli/help.js +27 -49
  103. package/build/cli/index.js +121 -374
  104. package/build/cli/output-stream-error-policy.js +17 -0
  105. package/build/cli/parse.js +159 -72
  106. package/build/cli/projection-address.js +5 -0
  107. package/build/cli/render/arc.js +1 -1
  108. package/build/cli/render/errors.js +4 -3
  109. package/build/cli/render/format.js +14 -0
  110. package/build/cli/render/kanshi.js +106 -0
  111. package/build/cli/render/petition.js +24 -1
  112. package/build/cli/render/projection-activity.js +5 -1
  113. package/build/cli/render/shared.js +2 -2
  114. package/build/cli/render/status.js +79 -13
  115. package/build/cli/render/task.js +113 -0
  116. package/build/cli/render/wait.js +23 -3
  117. package/build/cli/types.js +1 -1
  118. package/build/config/{akuma-loader_v2.js → akuma-loader.js} +2 -2
  119. package/build/config/env.js +9 -4
  120. package/build/config/settings/disease.js +26 -0
  121. package/build/config/settings/knobs.js +8 -0
  122. package/build/config/settings/loader.js +244 -0
  123. package/build/config/settings/schema.js +276 -0
  124. package/build/core/addressing.js +1 -1
  125. package/build/core/amend.js +3 -3
  126. package/build/core/arc.js +5 -4
  127. package/build/core/bind-workspace.js +123 -0
  128. package/build/core/bind.js +75 -155
  129. package/build/core/call/call.js +224 -0
  130. package/build/core/call/context.js +238 -0
  131. package/build/core/call/execution.js +217 -0
  132. package/build/core/call/prompt.js +42 -0
  133. package/build/core/call/reference.js +24 -0
  134. package/build/core/call-persist.js +1 -1
  135. package/build/core/claim-delivery.js +247 -0
  136. package/build/core/claim.js +39 -277
  137. package/build/core/context.js +2 -1
  138. package/build/core/{render.js → contract-view.js} +12 -140
  139. package/build/core/draft.js +107 -9
  140. package/build/core/entry.js +9 -1
  141. package/build/core/execution-coordinate.js +4 -20
  142. package/build/core/execution-pact.js +16 -31
  143. package/build/core/forfeit.js +35 -22
  144. package/build/core/hints.js +2 -2
  145. package/build/core/log.js +2 -2
  146. package/build/core/petition-claim-gates.js +8 -6
  147. package/build/core/petition-preview.js +97 -0
  148. package/build/core/petition-run.js +3 -2
  149. package/build/core/petition.js +39 -26
  150. package/build/core/projection/generation/database.js +306 -0
  151. package/build/core/projection/generation/identity.js +84 -0
  152. package/build/core/projection/generation/model.js +201 -0
  153. package/build/core/projection/generation/store.js +73 -0
  154. package/build/core/projection/generation/transitions.js +258 -0
  155. package/build/core/projection/heart.js +0 -307
  156. package/build/core/projection/tell/database.js +109 -0
  157. package/build/core/projection/tell/model.js +97 -0
  158. package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
  159. package/build/core/projection-activity.js +23 -2
  160. package/build/core/projection-core.js +2 -1
  161. package/build/core/projection-execution-observer.js +259 -0
  162. package/build/core/projection-generation-continuation.js +1 -1
  163. package/build/core/projection-generation-execution.js +90 -20
  164. package/build/core/projection-generation-launcher.js +1 -1
  165. package/build/core/projection-generation-runner.js +198 -4
  166. package/build/core/projection-identity.js +1 -1
  167. package/build/core/projection-kill.js +50 -17
  168. package/build/core/projection-life-observer.js +1 -1
  169. package/build/core/projection-life-protocol.js +10 -0
  170. package/build/core/projection-mint.js +1 -1
  171. package/build/core/projection-status.js +45 -32
  172. package/build/core/projection-wait.js +12 -2
  173. package/build/core/projection-wake.js +5 -2
  174. package/build/core/{queue_v2.js → queue.js} +2 -1
  175. package/build/core/registry.js +1 -1
  176. package/build/core/renew.js +7 -7
  177. package/build/core/response-artifact-catalog-sqlite.js +112 -0
  178. package/build/core/response-artifact-catalog.js +7 -0
  179. package/build/core/seal.js +44 -16
  180. package/build/core/status/board.js +286 -0
  181. package/build/core/status/drift.js +99 -0
  182. package/build/core/status/lifecycle.js +84 -0
  183. package/build/core/status/reconciliation.js +131 -0
  184. package/build/core/stored-agent-event.js +28 -4
  185. package/build/core/target-ref.js +10 -0
  186. package/build/core/task/board.js +175 -0
  187. package/build/core/task/commands.js +211 -0
  188. package/build/core/task/document.js +189 -0
  189. package/build/core/task/model.js +1 -0
  190. package/build/core/task/settlement-git.js +207 -0
  191. package/build/core/task/settlement-policy.js +82 -0
  192. package/build/core/task/source-board.js +84 -0
  193. package/build/core/task/source-model.js +1 -0
  194. package/build/core/task/validation.js +44 -0
  195. package/build/core/task-contract.js +224 -0
  196. package/build/core/task-doctor.js +14 -0
  197. package/build/core/task-git-actor.js +38 -0
  198. package/build/core/task-git-runtime.js +34 -0
  199. package/build/core/task-git-store.js +375 -0
  200. package/build/core/task-repository.js +1 -0
  201. package/build/core/task-store-repository.js +86 -0
  202. package/build/core/task-store.js +1 -0
  203. package/build/core/task-worktree.js +51 -0
  204. package/build/core/task.js +4 -0
  205. package/build/core/transcripts.js +138 -56
  206. package/build/core/validation.js +17 -0
  207. package/build/core/verdict.js +2 -2
  208. package/build/core/worktree-path.js +24 -6
  209. package/build/flow-error.js +3 -2
  210. package/build/git/commits.js +3 -0
  211. package/build/git/diff/parsers.js +0 -94
  212. package/build/index.js +2 -0
  213. package/build/keiyaku-constants.js +2 -0
  214. package/build/keiyaku.js +4 -3
  215. package/package.json +3 -2
  216. package/skills/keiyaku/SKILL.md +1 -1
  217. package/skills/keiyaku-akuma/SKILL.md +11 -6
  218. package/build/agents/harness/pump.js +0 -158
  219. package/build/agents/providers/codex-app-server.js +0 -864
  220. package/build/agents/providers/opencode-sdk.js +0 -363
  221. package/build/agents/providers/pi.js +0 -557
  222. package/build/config/settings.js +0 -533
  223. package/build/core/call.js +0 -686
  224. package/build/core/projection/generation.js +0 -412
  225. package/build/core/projection/identity.js +0 -81
  226. package/build/core/projection/mint.js +0 -174
  227. package/build/core/projection-generation-store.js +0 -707
  228. package/build/core/status.js +0 -539
@@ -0,0 +1,354 @@
1
+ import { activityCallFromNamedTool } from "../../harness/activity-values.js";
2
+ import { AGENT_THOUGHT_TEXT_MAX_CHARS, } from "../../harness/events.js";
3
+ import { coerceString, emitProviderEvidence } from "../../harness/runtime.js";
4
+ import { appendEventType } from "../event-type-tail.js";
5
+ import { OPENCODE_SDK_PROVIDER } from "./session.js";
6
+ const OPENCODE_EVENT_PROMPTED = "session.next.prompted";
7
+ function isSessionDurableEvent(value) {
8
+ return Boolean(value
9
+ && typeof value === "object"
10
+ && "type" in value
11
+ && typeof value.type === "string"
12
+ && String(value.type).startsWith("session.next."));
13
+ }
14
+ function isRecord(value) {
15
+ return value !== null && typeof value === "object" && !Array.isArray(value);
16
+ }
17
+ function hasMappedDurableShape(event) {
18
+ const data = event.data;
19
+ switch (event.type) {
20
+ case "session.next.text.ended":
21
+ return isRecord(data)
22
+ && typeof data.assistantMessageID === "string"
23
+ && typeof data.text === "string";
24
+ case "session.next.reasoning.ended":
25
+ return isRecord(data)
26
+ && typeof data.assistantMessageID === "string"
27
+ && typeof data.reasoningID === "string"
28
+ && typeof data.text === "string";
29
+ case "session.next.tool.called":
30
+ return isRecord(data)
31
+ && typeof data.callID === "string"
32
+ && typeof data.tool === "string"
33
+ && isRecord(data.input);
34
+ case "session.next.tool.progress":
35
+ return isRecord(data) && typeof data.callID === "string";
36
+ case "session.next.tool.success":
37
+ return isRecord(data)
38
+ && typeof data.callID === "string"
39
+ && Array.isArray(data.content);
40
+ case "session.next.tool.failed":
41
+ return isRecord(data)
42
+ && typeof data.callID === "string"
43
+ && isRecord(data.error);
44
+ case "session.next.step.ended":
45
+ return isRecord(data)
46
+ && isRecord(data.tokens)
47
+ && typeof data.tokens.input === "number"
48
+ && Number.isFinite(data.tokens.input)
49
+ && typeof data.tokens.output === "number"
50
+ && Number.isFinite(data.tokens.output);
51
+ case "session.next.step.failed":
52
+ return isRecord(data) && isRecord(data.error);
53
+ default:
54
+ return true;
55
+ }
56
+ }
57
+ function durableSeq(event) {
58
+ const durable = event.durable;
59
+ return durable && typeof durable.seq === "number" && Number.isFinite(durable.seq) ? durable.seq : undefined;
60
+ }
61
+ function extractToolCall(tool, input) {
62
+ const stringFact = (primary, fallback) => {
63
+ const first = input[primary];
64
+ if (typeof first === "string" && first.length > 0)
65
+ return first;
66
+ const second = fallback === undefined ? undefined : input[fallback];
67
+ return typeof second === "string" && second.length > 0 ? second : undefined;
68
+ };
69
+ const integerFact = (key) => {
70
+ const value = input[key];
71
+ return typeof value === "number" && Number.isInteger(value) ? value : undefined;
72
+ };
73
+ const lower = tool.toLowerCase();
74
+ let facts = {};
75
+ if (lower === "bash" || lower === "shell") {
76
+ facts = { command: stringFact("command") };
77
+ }
78
+ else if (lower === "read") {
79
+ facts = {
80
+ path: stringFact("filePath", "path"),
81
+ offset: integerFact("offset"),
82
+ limit: integerFact("limit"),
83
+ };
84
+ }
85
+ else if (lower === "write" || lower === "edit") {
86
+ facts = { path: stringFact("filePath", "path") };
87
+ }
88
+ else if (lower === "grep" || lower === "search" || lower === "glob") {
89
+ facts = { query: stringFact("pattern", "query") };
90
+ }
91
+ const classifierTool = lower === "grep" || lower === "glob" ? "search" : tool;
92
+ return activityCallFromNamedTool(classifierTool, facts);
93
+ }
94
+ function toolResultFromSuccess(event) {
95
+ const text = event.data.content
96
+ .map((part) => {
97
+ const value = part;
98
+ return isRecord(value) && typeof value.text === "string" ? value.text : "";
99
+ })
100
+ .join("");
101
+ const message = text.trim() || undefined;
102
+ return { status: "ok", ...(message ? { message } : {}) };
103
+ }
104
+ function toolResultFromFailure(event) {
105
+ const message = coerceString(event.data.error?.message);
106
+ return { status: "error", ...(message ? { message } : {}) };
107
+ }
108
+ function extractAssistantText(message) {
109
+ const text = message.content
110
+ .filter((part) => part.type === "text")
111
+ .map((part) => part.text)
112
+ .join("");
113
+ return text.trim() || undefined;
114
+ }
115
+ function extractFinalAssistantMessage(messages) {
116
+ return messages.find((message) => message.type === "assistant");
117
+ }
118
+ export function createOpencodeEventController(options) {
119
+ const { builder, control, idleMonitorIngress, onEvidence, onSyncCheckpoint, onDeliveryOpportunity, getSessionId, client, } = options;
120
+ const recordedEventTypes = [];
121
+ let totalEventCount = 0;
122
+ let currentTurnAdmitted = false;
123
+ let currentTurnFailure;
124
+ let lastTurnFailure;
125
+ let lastSeenSeq = 0;
126
+ let turnOpen = false;
127
+ const toolCalls = new Map();
128
+ const requestDelivery = (steerControl) => {
129
+ try {
130
+ onDeliveryOpportunity?.(steerControl);
131
+ }
132
+ catch {
133
+ // Delivery observation cannot alter provider control flow.
134
+ }
135
+ };
136
+ const openTurn = () => {
137
+ if (turnOpen)
138
+ builder.emit({ type: "turn", phase: "completed" });
139
+ builder.emit({ type: "turn", phase: "started" });
140
+ turnOpen = true;
141
+ };
142
+ const closeTurn = () => {
143
+ if (!turnOpen)
144
+ return;
145
+ builder.emit({ type: "turn", phase: "completed" });
146
+ turnOpen = false;
147
+ };
148
+ const clearLastTurnFailureOnTerminalEvidence = () => {
149
+ if (currentTurnAdmitted && currentTurnFailure === undefined) {
150
+ lastTurnFailure = undefined;
151
+ }
152
+ };
153
+ const mapDurableEvent = (event) => {
154
+ const seq = durableSeq(event);
155
+ if (seq !== undefined && seq > lastSeenSeq)
156
+ lastSeenSeq = seq;
157
+ totalEventCount += 1;
158
+ appendEventType(recordedEventTypes, event.type);
159
+ if (!hasMappedDurableShape(event)) {
160
+ builder.ingress(OPENCODE_SDK_PROVIDER, event.type, event, "unknown", () => undefined);
161
+ requestDelivery(control);
162
+ return;
163
+ }
164
+ builder.ingress(OPENCODE_SDK_PROVIDER, event.type, event, "mapped", () => {
165
+ switch (event.type) {
166
+ case OPENCODE_EVENT_PROMPTED: {
167
+ currentTurnAdmitted = true;
168
+ currentTurnFailure = undefined;
169
+ openTurn();
170
+ if (seq !== undefined) {
171
+ const checkpoint = { fence: { kind: "ordinal", ordinal: seq }, consumptionEvidence: true };
172
+ if (onEvidence) {
173
+ emitProviderEvidence(onEvidence, { type: "checkpoint", provider: OPENCODE_SDK_PROVIDER, fence: checkpoint.fence, source: "notification" }, (text) => builder.emit({ type: "diagnostic", text }));
174
+ }
175
+ onSyncCheckpoint?.(control, checkpoint);
176
+ }
177
+ break;
178
+ }
179
+ case "session.next.text.ended": {
180
+ const text = coerceString(event.data.text)?.trim();
181
+ if (text) {
182
+ builder.emit({
183
+ type: "message",
184
+ id: event.data.assistantMessageID || "opencode-text",
185
+ text,
186
+ });
187
+ }
188
+ clearLastTurnFailureOnTerminalEvidence();
189
+ break;
190
+ }
191
+ case "session.next.reasoning.ended": {
192
+ const text = coerceString(event.data.text) ?? "";
193
+ if (!text)
194
+ break;
195
+ const truncated = text.length > AGENT_THOUGHT_TEXT_MAX_CHARS;
196
+ builder.emit({
197
+ type: "thought",
198
+ id: event.data.reasoningID || event.data.assistantMessageID || "opencode-reasoning",
199
+ text: text.slice(0, AGENT_THOUGHT_TEXT_MAX_CHARS),
200
+ final: true,
201
+ ...(truncated ? { truncated: true } : {}),
202
+ });
203
+ break;
204
+ }
205
+ case "session.next.tool.called": {
206
+ const call = extractToolCall(event.data.tool, event.data.input);
207
+ toolCalls.set(event.data.callID, { tool: event.data.tool, call });
208
+ builder.emit({
209
+ type: "activity",
210
+ phase: "started",
211
+ id: event.data.callID,
212
+ tool: event.data.tool,
213
+ call,
214
+ });
215
+ break;
216
+ }
217
+ case "session.next.tool.success": {
218
+ const prior = toolCalls.get(event.data.callID);
219
+ const call = prior?.call ?? { kind: "other", display: "tool" };
220
+ const tool = prior?.tool ?? "tool";
221
+ builder.emit({
222
+ type: "activity",
223
+ phase: "completed",
224
+ id: event.data.callID,
225
+ tool,
226
+ call,
227
+ result: toolResultFromSuccess(event),
228
+ });
229
+ toolCalls.delete(event.data.callID);
230
+ break;
231
+ }
232
+ case "session.next.tool.failed": {
233
+ const prior = toolCalls.get(event.data.callID);
234
+ const call = prior?.call ?? { kind: "other", display: "tool" };
235
+ const tool = prior?.tool ?? "tool";
236
+ builder.emit({
237
+ type: "activity",
238
+ phase: "completed",
239
+ id: event.data.callID,
240
+ tool,
241
+ call,
242
+ result: toolResultFromFailure(event),
243
+ });
244
+ toolCalls.delete(event.data.callID);
245
+ break;
246
+ }
247
+ case "session.next.tool.progress": {
248
+ const prior = toolCalls.get(event.data.callID);
249
+ builder.emit({
250
+ type: "activity",
251
+ phase: "updated",
252
+ id: event.data.callID,
253
+ tool: prior?.tool ?? "tool",
254
+ call: prior?.call ?? { kind: "other", display: "tool" },
255
+ });
256
+ break;
257
+ }
258
+ case "session.next.step.ended": {
259
+ const tokens = event.data.tokens;
260
+ builder.emit({
261
+ type: "usage",
262
+ inputTokens: tokens.input,
263
+ outputTokens: tokens.output,
264
+ });
265
+ clearLastTurnFailureOnTerminalEvidence();
266
+ break;
267
+ }
268
+ case "session.next.step.failed": {
269
+ const message = coerceString(event.data.error?.message) ?? `${OPENCODE_SDK_PROVIDER} step failed`;
270
+ const failure = new Error(message);
271
+ currentTurnFailure = failure;
272
+ lastTurnFailure = failure;
273
+ builder.emit({ type: "diagnostic", text: message });
274
+ builder.emit({ type: "fail", message });
275
+ break;
276
+ }
277
+ default:
278
+ break;
279
+ }
280
+ });
281
+ requestDelivery(control);
282
+ };
283
+ const processUnknownStreamItem = (item) => {
284
+ idleMonitorIngress();
285
+ if (isSessionDurableEvent(item)) {
286
+ mapDurableEvent(item);
287
+ return;
288
+ }
289
+ if (item && typeof item === "object" && "data" in item) {
290
+ const data = item.data;
291
+ if (typeof data === "string") {
292
+ try {
293
+ const parsed = JSON.parse(data);
294
+ if (isSessionDurableEvent(parsed)) {
295
+ mapDurableEvent(parsed);
296
+ return;
297
+ }
298
+ }
299
+ catch {
300
+ // fall through to unknown
301
+ }
302
+ }
303
+ else if (isSessionDurableEvent(data)) {
304
+ mapDurableEvent(data);
305
+ return;
306
+ }
307
+ }
308
+ const nativeType = item && typeof item === "object" && "type" in item && typeof item.type === "string"
309
+ ? item.type
310
+ : item && typeof item === "object" && "event" in item && typeof item.event === "string"
311
+ ? item.event
312
+ : "session.event";
313
+ builder.ingress(OPENCODE_SDK_PROVIDER, nativeType, item, "unknown", () => undefined);
314
+ requestDelivery(control);
315
+ };
316
+ const drainHistory = async (after) => {
317
+ let cursor = after;
318
+ for (;;) {
319
+ const priorCursor = cursor;
320
+ const page = await client.v2.session.history({ sessionID: getSessionId(), after: cursor }, { throwOnError: true, signal: options.executionSignal });
321
+ const body = page.data;
322
+ const events = body.data ?? [];
323
+ for (const event of events) {
324
+ if (!isSessionDurableEvent(event)) {
325
+ builder.ingress(OPENCODE_SDK_PROVIDER, "session.history", event, "unknown", () => undefined);
326
+ requestDelivery(control);
327
+ continue;
328
+ }
329
+ const seq = durableSeq(event);
330
+ if (seq !== undefined && seq <= cursor)
331
+ continue;
332
+ mapDurableEvent(event);
333
+ if (seq !== undefined)
334
+ cursor = seq;
335
+ }
336
+ if (!body.hasMore || events.length === 0 || cursor === priorCursor)
337
+ break;
338
+ }
339
+ };
340
+ return {
341
+ processUnknownStreamItem,
342
+ drainHistory,
343
+ closeTurn,
344
+ lastSeenSeq: () => lastSeenSeq,
345
+ lastTurnFailure: () => lastTurnFailure,
346
+ recordedEventTypes: () => recordedEventTypes,
347
+ totalEventCount: () => totalEventCount,
348
+ finalAssistantText(messages) {
349
+ const finalAssistant = extractFinalAssistantMessage(messages);
350
+ const finalMessage = finalAssistant ? extractAssistantText(finalAssistant) : undefined;
351
+ return { finalAssistant, finalMessage };
352
+ },
353
+ };
354
+ }
@@ -0,0 +1,180 @@
1
+ import { createOpencode } from "../../opencode-sdk.js";
2
+ import { coerceString, createAbortError } from "../../harness/runtime.js";
3
+ export const OPENCODE_SDK_PROVIDER = "opencode-sdk";
4
+ export const OPENCODE_DEFAULT_EXECUTABLE = "opencode";
5
+ const OPENCODE_COMPLETE_EVENT_TRAILING_DEBOUNCE_MS = 200;
6
+ const OPENCODE_SILENT_FALLBACK_INITIAL_MS = 1_000;
7
+ const OPENCODE_SILENT_FALLBACK_MAX_MS = 5_000;
8
+ export class OpencodeIdleMonitor {
9
+ session;
10
+ sessionId;
11
+ signal;
12
+ cycle;
13
+ resolveCycle;
14
+ rejectCycle;
15
+ timer;
16
+ queryRunning = false;
17
+ ingressEpoch = 0;
18
+ settledIngressEpoch = 0;
19
+ observedIngressEpoch = 0;
20
+ fallbackDelayMs = OPENCODE_SILENT_FALLBACK_INITIAL_MS;
21
+ disposed = false;
22
+ constructor(session, sessionId, signal) {
23
+ this.session = session;
24
+ this.sessionId = sessionId;
25
+ this.signal = signal;
26
+ this.signal.addEventListener("abort", this.onAbort, { once: true });
27
+ }
28
+ waitForIdle() {
29
+ if (this.disposed)
30
+ return Promise.reject(createAbortError(`${OPENCODE_SDK_PROVIDER} exec cancelled by client`));
31
+ if (this.cycle)
32
+ return this.cycle;
33
+ this.cycle = new Promise((resolve, reject) => {
34
+ this.resolveCycle = resolve;
35
+ this.rejectCycle = reject;
36
+ });
37
+ this.observedIngressEpoch = this.settledIngressEpoch;
38
+ this.query();
39
+ return this.cycle;
40
+ }
41
+ ingress() {
42
+ this.ingressEpoch += 1;
43
+ if (!this.cycle)
44
+ return;
45
+ this.fallbackDelayMs = OPENCODE_SILENT_FALLBACK_INITIAL_MS;
46
+ this.clearTimer();
47
+ if (!this.queryRunning)
48
+ this.scheduleTrailingQuery();
49
+ }
50
+ dispose() {
51
+ if (this.disposed)
52
+ return;
53
+ this.disposed = true;
54
+ this.clearTimer();
55
+ this.signal.removeEventListener("abort", this.onAbort);
56
+ this.fail(createAbortError(`${OPENCODE_SDK_PROVIDER} exec cancelled by client`));
57
+ }
58
+ onAbort = () => {
59
+ this.dispose();
60
+ };
61
+ clearTimer() {
62
+ if (this.timer === undefined)
63
+ return;
64
+ clearTimeout(this.timer);
65
+ this.timer = undefined;
66
+ }
67
+ schedule(delayMs) {
68
+ if (!this.cycle || this.disposed)
69
+ return;
70
+ this.clearTimer();
71
+ this.timer = setTimeout(() => {
72
+ this.timer = undefined;
73
+ this.query();
74
+ }, delayMs);
75
+ }
76
+ scheduleTrailingQuery() {
77
+ this.observedIngressEpoch = this.ingressEpoch;
78
+ this.schedule(OPENCODE_COMPLETE_EVENT_TRAILING_DEBOUNCE_MS);
79
+ }
80
+ finish() {
81
+ this.clearTimer();
82
+ const resolve = this.resolveCycle;
83
+ this.cycle = undefined;
84
+ this.resolveCycle = undefined;
85
+ this.rejectCycle = undefined;
86
+ this.settledIngressEpoch = this.ingressEpoch;
87
+ this.fallbackDelayMs = OPENCODE_SILENT_FALLBACK_INITIAL_MS;
88
+ resolve?.();
89
+ }
90
+ fail(error) {
91
+ this.clearTimer();
92
+ const reject = this.rejectCycle;
93
+ this.cycle = undefined;
94
+ this.resolveCycle = undefined;
95
+ this.rejectCycle = undefined;
96
+ reject?.(error);
97
+ }
98
+ async query() {
99
+ if (!this.cycle || this.queryRunning || this.disposed)
100
+ return;
101
+ this.queryRunning = true;
102
+ const queryIngressEpoch = this.ingressEpoch;
103
+ const hadUnobservedIngress = queryIngressEpoch > this.observedIngressEpoch;
104
+ this.observedIngressEpoch = queryIngressEpoch;
105
+ try {
106
+ const response = await withAbort(this.session.active({ throwOnError: true, signal: this.signal }), this.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
107
+ if (!this.cycle || this.disposed)
108
+ return;
109
+ const active = response.data.data;
110
+ if (this.ingressEpoch > queryIngressEpoch) {
111
+ this.scheduleTrailingQuery();
112
+ return;
113
+ }
114
+ if (!Object.prototype.hasOwnProperty.call(active, this.sessionId)) {
115
+ this.finish();
116
+ return;
117
+ }
118
+ if (hadUnobservedIngress) {
119
+ this.scheduleTrailingQuery();
120
+ }
121
+ else {
122
+ const delayMs = this.fallbackDelayMs;
123
+ this.fallbackDelayMs = Math.min(delayMs * 2, OPENCODE_SILENT_FALLBACK_MAX_MS);
124
+ this.schedule(delayMs);
125
+ }
126
+ }
127
+ catch (error) {
128
+ this.fail(error);
129
+ }
130
+ finally {
131
+ this.queryRunning = false;
132
+ if (this.ingressEpoch > this.observedIngressEpoch && this.timer === undefined && !this.disposed) {
133
+ this.scheduleTrailingQuery();
134
+ }
135
+ }
136
+ }
137
+ }
138
+ let opencodeSdkLoader;
139
+ export function sessionIdFrom(info, fallback) {
140
+ return coerceString(info.id) ?? fallback;
141
+ }
142
+ export async function withAbort(promise, signal, message) {
143
+ if (signal.aborted)
144
+ throw createAbortError(message);
145
+ return new Promise((resolve, reject) => {
146
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
147
+ const onAbort = () => {
148
+ cleanup();
149
+ reject(createAbortError(message));
150
+ };
151
+ signal.addEventListener("abort", onAbort, { once: true });
152
+ promise.then((value) => {
153
+ cleanup();
154
+ resolve(value);
155
+ }, (error) => {
156
+ cleanup();
157
+ reject(error);
158
+ });
159
+ });
160
+ }
161
+ export async function loadOpencodeSdk(executable, cwd, env, signal) {
162
+ if (opencodeSdkLoader)
163
+ return opencodeSdkLoader(cwd);
164
+ const sdk = await createOpencode({
165
+ executable,
166
+ directory: cwd,
167
+ env,
168
+ signal,
169
+ });
170
+ return {
171
+ client: sdk.client,
172
+ close: sdk.server?.close,
173
+ };
174
+ }
175
+ export function setOpencodeSdkLoaderForTests(loader) {
176
+ opencodeSdkLoader = loader;
177
+ }
178
+ export function resetOpencodeSdkLoaderForTests() {
179
+ opencodeSdkLoader = undefined;
180
+ }