@astrosheep/keiyaku 4.6.8 → 4.6.10

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 (253) hide show
  1. package/README.md +40 -14
  2. package/build/plugins/claude/.claude-plugin/plugin.json +1 -1
  3. package/build/plugins/claude/skills/keiyaku/SKILL.md +5 -6
  4. package/build/plugins/claude/skills/keiyaku-akuma/SKILL.md +1 -1
  5. package/build/plugins/claude/skills/keiyaku-akuma/references/automation.md +8 -8
  6. package/build/plugins/claude/skills/keiyaku-babysit/SKILL.md +1 -1
  7. package/build/plugins/claude/skills/keiyaku-bind/SKILL.md +9 -9
  8. package/build/plugins/claude/skills/keiyaku-debt/SKILL.md +91 -0
  9. package/build/plugins/claude/skills/keiyaku-workflow/SKILL.md +2 -2
  10. package/build/plugins/codex/.codex-plugin/plugin.json +1 -1
  11. package/build/plugins/codex/skills/keiyaku/SKILL.md +5 -6
  12. package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +1 -1
  13. package/build/plugins/codex/skills/keiyaku-akuma/references/automation.md +8 -8
  14. package/build/plugins/codex/skills/keiyaku-babysit/SKILL.md +1 -1
  15. package/build/plugins/codex/skills/keiyaku-bind/SKILL.md +9 -9
  16. package/build/plugins/codex/skills/keiyaku-debt/SKILL.md +91 -0
  17. package/build/plugins/codex/skills/keiyaku-workflow/SKILL.md +2 -2
  18. package/build/plugins/opencode/index.js +1 -1
  19. package/build/plugins/opencode/skills/keiyaku/SKILL.md +5 -6
  20. package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +1 -1
  21. package/build/plugins/opencode/skills/keiyaku-akuma/references/automation.md +8 -8
  22. package/build/plugins/opencode/skills/keiyaku-babysit/SKILL.md +1 -1
  23. package/build/plugins/opencode/skills/keiyaku-bind/SKILL.md +9 -9
  24. package/build/plugins/opencode/skills/keiyaku-debt/SKILL.md +91 -0
  25. package/build/plugins/opencode/skills/keiyaku-workflow/SKILL.md +2 -2
  26. package/build/skills/keiyaku/SKILL.md +5 -6
  27. package/build/skills/keiyaku-akuma/SKILL.md +1 -1
  28. package/build/skills/keiyaku-akuma/references/automation.md +8 -8
  29. package/build/skills/keiyaku-babysit/SKILL.md +1 -1
  30. package/build/skills/keiyaku-bind/SKILL.md +9 -9
  31. package/build/skills/keiyaku-debt/SKILL.md +91 -0
  32. package/build/skills/keiyaku-workflow/SKILL.md +2 -2
  33. package/build/src/akuma/akuma-instance.d.ts +2 -0
  34. package/build/src/akuma/akuma-instance.js +10 -0
  35. package/build/src/akuma/akuma-observe.d.ts +33 -8
  36. package/build/src/akuma/akuma-observe.js +88 -64
  37. package/build/src/akuma/akuma-owner.d.ts +11 -2
  38. package/build/src/akuma/akuma-owner.js +45 -35
  39. package/build/src/akuma/akuma-selection-execution.js +9 -1
  40. package/build/src/akuma/akuma-stream.d.ts +22 -0
  41. package/build/src/akuma/akuma-stream.js +116 -0
  42. package/build/src/akuma/akuma-wait.d.ts +1 -40
  43. package/build/src/akuma/akuma-wait.js +23 -84
  44. package/build/src/akuma/akuma.d.ts +17 -11
  45. package/build/src/akuma/akuma.js +2 -0
  46. package/build/src/akuma/allowed.d.ts +7 -12
  47. package/build/src/akuma/allowed.js +13 -4
  48. package/build/src/akuma/body-supervisor.d.ts +5 -5
  49. package/build/src/akuma/body-supervisor.js +22 -20
  50. package/build/src/akuma/body.js +28 -1
  51. package/build/src/akuma/call-request.d.ts +12 -22
  52. package/build/src/akuma/heart/activity-schema.d.ts +12 -0
  53. package/build/src/akuma/heart/activity-schema.js +6 -0
  54. package/build/src/akuma/heart/index.d.ts +59 -2
  55. package/build/src/akuma/heart/index.js +87 -8
  56. package/build/src/akuma/heart/rows.js +5 -3
  57. package/build/src/akuma/heart/soul.d.ts +9 -0
  58. package/build/src/akuma/heart/soul.js +29 -0
  59. package/build/src/akuma/heart/storage.d.ts +15 -0
  60. package/build/src/akuma/heart/storage.js +55 -0
  61. package/build/src/akuma/heart/timeline.d.ts +7 -0
  62. package/build/src/akuma/heart/timeline.js +10 -0
  63. package/build/src/akuma/index.d.ts +1 -0
  64. package/build/src/akuma/projection.d.ts +25 -0
  65. package/build/src/akuma/projection.js +24 -7
  66. package/build/src/akuma/provider-recipe.d.ts +11 -0
  67. package/build/src/akuma/provider-recipe.js +23 -0
  68. package/build/src/akuma/provider.d.ts +25 -0
  69. package/build/src/akuma/provider.js +99 -7
  70. package/build/src/akuma/providers/acp/events.js +25 -2
  71. package/build/src/akuma/providers/claude/events.js +76 -10
  72. package/build/src/akuma/providers/claude/index.js +2 -5
  73. package/build/src/akuma/providers/codex-app-server/events.js +17 -2
  74. package/build/src/akuma/providers/codex-app-server/index.js +15 -9
  75. package/build/src/akuma/providers/decode.d.ts +8 -0
  76. package/build/src/akuma/providers/decode.js +21 -0
  77. package/build/src/akuma/providers/grok-build/index.js +10 -26
  78. package/build/src/akuma/providers/opencode-sdk/event-stream.d.ts +3 -3
  79. package/build/src/akuma/providers/opencode-sdk/event-stream.js +2 -25
  80. package/build/src/akuma/providers/opencode-sdk/events.d.ts +7 -43
  81. package/build/src/akuma/providers/opencode-sdk/events.js +147 -284
  82. package/build/src/akuma/providers/opencode-sdk/index.js +267 -421
  83. package/build/src/akuma/providers/opencode-sdk/native-http.d.ts +9 -0
  84. package/build/src/akuma/providers/opencode-sdk/native-http.js +69 -0
  85. package/build/src/akuma/providers/opencode-sdk/session.d.ts +42 -36
  86. package/build/src/akuma/providers/opencode-sdk/session.js +75 -82
  87. package/build/src/akuma/providers/paseo/events.d.ts +8 -0
  88. package/build/src/akuma/providers/paseo/events.js +58 -11
  89. package/build/src/akuma/providers/paseo/index.js +11 -7
  90. package/build/src/akuma/providers/pi/events.d.ts +2 -0
  91. package/build/src/akuma/providers/pi/events.js +32 -24
  92. package/build/src/akuma/providers/pi/index.js +21 -35
  93. package/build/src/akuma/request-lifecycle.d.ts +1 -2
  94. package/build/src/akuma/request-lifecycle.js +2 -8
  95. package/build/src/akuma/request-rendezvous.js +38 -55
  96. package/build/src/akuma/request-serve.d.ts +1 -1
  97. package/build/src/akuma/request-serve.js +77 -113
  98. package/build/src/akuma/selection-observation.d.ts +57 -51
  99. package/build/src/akuma/selection-observation.js +3 -3
  100. package/build/src/akuma/turn-drive.d.ts +3 -0
  101. package/build/src/akuma/turn-drive.js +26 -5
  102. package/build/src/akuma-body.js +12 -22
  103. package/build/src/cli/akuma-caller.js +10 -0
  104. package/build/src/cli/commands/akuma-grammar.js +3 -4
  105. package/build/src/cli/commands/akuma.js +13 -51
  106. package/build/src/cli/commands/contract-grammar.d.ts +7 -6
  107. package/build/src/cli/commands/contract-grammar.js +27 -32
  108. package/build/src/cli/commands/contract-help.d.ts +16 -7
  109. package/build/src/cli/commands/contract-help.js +20 -11
  110. package/build/src/cli/commands/contract.js +11 -11
  111. package/build/src/cli/commands/debt-grammar.d.ts +19 -0
  112. package/build/src/cli/commands/debt-grammar.js +323 -0
  113. package/build/src/cli/commands/debt-query.d.ts +5 -0
  114. package/build/src/cli/commands/{task-query.js → debt-query.js} +9 -9
  115. package/build/src/cli/commands/{task.d.ts → debt.d.ts} +2 -2
  116. package/build/src/cli/commands/{task.js → debt.js} +83 -143
  117. package/build/src/cli/coordinates.d.ts +1 -1
  118. package/build/src/cli/coordinates.js +1 -1
  119. package/build/src/cli/parse.d.ts +4 -4
  120. package/build/src/cli/parse.js +17 -16
  121. package/build/src/cli/render/akuma-activity.d.ts +11 -83
  122. package/build/src/cli/render/akuma-activity.js +20 -382
  123. package/build/src/cli/render/catalog.d.ts +2 -2
  124. package/build/src/cli/render/catalog.js +5 -5
  125. package/build/src/cli/render/contract.js +2 -2
  126. package/build/src/cli/render/debt.d.ts +28 -0
  127. package/build/src/cli/render/{task.js → debt.js} +51 -112
  128. package/build/src/cli/render/kanshi.js +28 -28
  129. package/build/src/cli/render/marks.d.ts +5 -5
  130. package/build/src/cli/render/marks.js +4 -10
  131. package/build/src/cli/render/nuke.js +1 -1
  132. package/build/src/cli/runtime.js +3 -3
  133. package/build/src/cli/scan.d.ts +1 -1
  134. package/build/src/cli/usage.d.ts +1 -1
  135. package/build/src/cli/usage.js +4 -4
  136. package/build/src/contract-worktree.js +3 -3
  137. package/build/src/debt/board.d.ts +160 -0
  138. package/build/src/{task → debt}/board.js +89 -89
  139. package/build/src/debt/catalog.d.ts +11 -0
  140. package/build/src/debt/catalog.js +59 -0
  141. package/build/src/{task → debt}/context.d.ts +1 -1
  142. package/build/src/{task → debt}/context.js +1 -1
  143. package/build/src/{task → debt}/created-observation.d.ts +6 -10
  144. package/build/src/{task → debt}/created-observation.js +7 -7
  145. package/build/src/debt/document.d.ts +37 -0
  146. package/build/src/{task → debt}/document.js +37 -37
  147. package/build/src/debt/identity.d.ts +21 -0
  148. package/build/src/{task → debt}/identity.js +35 -35
  149. package/build/src/debt/index.d.ts +111 -0
  150. package/build/src/debt/index.js +203 -0
  151. package/build/src/{task → debt}/input.d.ts +8 -8
  152. package/build/src/{task → debt}/input.js +21 -21
  153. package/build/src/debt/lifecycle-operations.d.ts +6 -0
  154. package/build/src/{task → debt}/lifecycle-operations.js +27 -31
  155. package/build/src/debt/mutation-result.d.ts +981 -0
  156. package/build/src/debt/mutation-result.js +109 -0
  157. package/build/src/debt/mutation.d.ts +424 -0
  158. package/build/src/debt/mutation.js +223 -0
  159. package/build/src/debt/operation-types.d.ts +71 -0
  160. package/build/src/debt/operations.d.ts +28 -0
  161. package/build/src/{task → debt}/operations.js +59 -60
  162. package/build/src/debt/query.d.ts +76 -0
  163. package/build/src/{task → debt}/query.js +55 -55
  164. package/build/src/{task → debt}/store.d.ts +13 -13
  165. package/build/src/{task → debt}/store.js +58 -49
  166. package/build/src/debt/view.d.ts +4 -0
  167. package/build/src/debt/view.js +7 -0
  168. package/build/src/identity/coordinates.d.ts +1 -1
  169. package/build/src/index.d.ts +13 -13
  170. package/build/src/index.js +9 -9
  171. package/build/src/kanshi/index.d.ts +1 -1
  172. package/build/src/kanshi/read.js +33 -33
  173. package/build/src/kanshi/report.d.ts +7 -7
  174. package/build/src/kanshi/select.js +4 -4
  175. package/build/src/library/akumas.d.ts +2 -3
  176. package/build/src/library/bind.d.ts +3 -3
  177. package/build/src/library/bind.js +5 -5
  178. package/build/src/library/contract-creation.js +5 -5
  179. package/build/src/library/contract-execution.js +4 -4
  180. package/build/src/library/contract-types.d.ts +2 -2
  181. package/build/src/library/input.d.ts +2 -2
  182. package/build/src/library/input.js +5 -5
  183. package/build/src/library/invocation.js +2 -2
  184. package/build/src/library/nuke.js +3 -3
  185. package/build/src/library/outcome.d.ts +126 -126
  186. package/build/src/library/outcome.js +3 -3
  187. package/build/src/library/selection.d.ts +1 -4
  188. package/build/src/library/selection.js +11 -62
  189. package/build/src/plugin/public.d.ts +5 -0
  190. package/build/src/plugin/runtime.js +3 -1
  191. package/build/src/runtime/proc/windows-launch.exe +0 -0
  192. package/build/src/settlement/fence.d.ts +3 -3
  193. package/build/src/settlement/fence.js +4 -4
  194. package/build/src/settlement/holder.d.ts +16 -16
  195. package/build/src/settlement/holder.js +69 -63
  196. package/build/src/settlement/settle.d.ts +5 -5
  197. package/build/src/settlement/settle.js +53 -53
  198. package/package.json +12 -6
  199. package/plugins/paseo/plugin/index.server.ts +17 -0
  200. package/plugins/paseo/plugin/paseo-plugin.json +4 -0
  201. package/plugins/paseo/plugin/server/attach.ts +52 -0
  202. package/plugins/paseo/plugin/server/keiyaku.ts +44 -0
  203. package/plugins/paseo/plugin/server/message.ts +60 -0
  204. package/plugins/paseo/plugin/server/mirror.ts +193 -0
  205. package/plugins/paseo/plugin/server/model.ts +14 -0
  206. package/plugins/paseo/plugin/server/provider.ts +276 -0
  207. package/plugins/paseo/plugin/server/sdk-worker.ts +237 -0
  208. package/plugins/paseo/plugin/server/sdk.ts +289 -0
  209. package/plugins/paseo/plugin/server/stop.ts +26 -0
  210. package/plugins/paseo/plugin/server/transport.ts +35 -0
  211. package/plugins/paseo/plugin/shared/attach-rpc.ts +10 -0
  212. package/plugins/paseo/plugin/shared/refresh-rpc.ts +6 -0
  213. package/plugins/paseo/plugin/tsconfig.json +6 -0
  214. package/skills/keiyaku/SKILL.md +5 -6
  215. package/skills/keiyaku-akuma/SKILL.md +1 -1
  216. package/skills/keiyaku-akuma/references/automation.md +8 -8
  217. package/skills/keiyaku-babysit/SKILL.md +1 -1
  218. package/skills/keiyaku-bind/SKILL.md +9 -9
  219. package/skills/keiyaku-debt/SKILL.md +91 -0
  220. package/skills/keiyaku-workflow/SKILL.md +2 -2
  221. package/build/plugins/claude/skills/keiyaku-task/SKILL.md +0 -104
  222. package/build/plugins/codex/skills/keiyaku-task/SKILL.md +0 -104
  223. package/build/plugins/opencode/skills/keiyaku-task/SKILL.md +0 -104
  224. package/build/skills/keiyaku-task/SKILL.md +0 -104
  225. package/build/src/cli/commands/task-grammar.d.ts +0 -19
  226. package/build/src/cli/commands/task-grammar.js +0 -384
  227. package/build/src/cli/commands/task-query.d.ts +0 -5
  228. package/build/src/cli/render/task.d.ts +0 -39
  229. package/build/src/task/board.d.ts +0 -168
  230. package/build/src/task/catalog.d.ts +0 -11
  231. package/build/src/task/catalog.js +0 -59
  232. package/build/src/task/compose-language.d.ts +0 -57
  233. package/build/src/task/compose-language.js +0 -364
  234. package/build/src/task/compose-parser.d.ts +0 -32
  235. package/build/src/task/compose-parser.js +0 -180
  236. package/build/src/task/compose.d.ts +0 -16
  237. package/build/src/task/compose.js +0 -189
  238. package/build/src/task/document.d.ts +0 -39
  239. package/build/src/task/identity.d.ts +0 -21
  240. package/build/src/task/index.d.ts +0 -138
  241. package/build/src/task/index.js +0 -269
  242. package/build/src/task/lifecycle-operations.d.ts +0 -6
  243. package/build/src/task/mutation-result.d.ts +0 -1726
  244. package/build/src/task/mutation-result.js +0 -198
  245. package/build/src/task/mutation.d.ts +0 -590
  246. package/build/src/task/mutation.js +0 -262
  247. package/build/src/task/operation-types.d.ts +0 -71
  248. package/build/src/task/operations.d.ts +0 -28
  249. package/build/src/task/query.d.ts +0 -76
  250. package/build/src/task/view.d.ts +0 -4
  251. package/build/src/task/view.js +0 -7
  252. package/skills/keiyaku-task/SKILL.md +0 -104
  253. /package/build/src/{task → debt}/operation-types.js +0 -0
@@ -1,445 +1,301 @@
1
- import { AgentEventChannel, createProviderAttempt, } from "../../provider.js";
1
+ import { AgentEventChannel, createProviderAttempt, launchPrompt, tellRegistry, } from "../../provider.js";
2
2
  import { nonblank, object } from "../decode.js";
3
3
  import { providerLifecycle } from "../lifecycle.js";
4
4
  import { ownEventStream } from "./event-stream.js";
5
- import { createEventState, mapEvent } from "./events.js";
5
+ import { createEventState, diagnostic, mapEvent } from "./events.js";
6
6
  import { coordinate, loadOpencode, messageId, OPENCODE_SDK_PROVIDER, parseModel, } from "./session.js";
7
- function diagnostic(error) {
8
- const value = object(error);
9
- const nested = object(value?.data);
10
- return nonblank(nested?.message) ?? nonblank(value?.message) ?? String(error);
11
- }
12
- function opencodeSessionId(coordinateValue) {
13
- if (!("sessionId" in coordinateValue) || coordinateValue.sessionId === undefined) {
7
+ function sessionId(value) {
8
+ if (!("sessionId" in value) || value.sessionId === undefined)
14
9
  throw new Error("OpenCode resume requires sessionId");
15
- }
16
- return coordinateValue.sessionId;
10
+ return value.sessionId;
17
11
  }
18
12
  function admit(options) {
19
13
  if (options.network !== undefined)
20
14
  throw new Error("OpenCode does not support explicit network");
21
- if (options.systemPromptMode === "replace") {
22
- throw new Error("OpenCode V1 does not support replacing the native system prompt");
23
- }
15
+ if (options.systemPromptMode === "replace")
16
+ throw new Error("OpenCode v2 does not support replacing the native system prompt per Turn");
24
17
  if (options.model !== undefined)
25
18
  parseModel(options.model);
19
+ if (options.effort !== undefined && options.model === undefined)
20
+ throw new Error("OpenCode effort requires an explicit model");
26
21
  }
27
- function eventValue(value) {
28
- if (typeof value !== "string")
29
- return value;
30
- try {
31
- return JSON.parse(value);
32
- }
33
- catch {
34
- return { type: value };
35
- }
36
- }
37
- function promptBody(input, messageID) {
38
- const model = input.options.model === undefined ? undefined : parseModel(input.options.model);
39
- const promptText = [input.body, ...input.launchTells.map((tell) => tell.text)]
40
- .filter((part) => part.length > 0)
41
- .join("\n\n");
42
- return {
43
- messageID,
44
- ...(model === undefined ? {} : { model }),
45
- ...(input.options.effort === undefined ? {} : { variant: input.options.effort }),
46
- ...(input.options.systemPrompt === undefined ? {} : { system: input.options.systemPrompt }),
47
- parts: [{ type: "text", text: promptText }],
48
- };
49
- }
50
- function messageRows(value, sessionId) {
51
- const response = object(value);
52
- const rows = Array.isArray(response?.data) ? response.data : Array.isArray(value) ? value : [];
53
- return rows.flatMap((row) => {
54
- const entry = object(row);
55
- const info = object(entry?.info);
56
- if (info?.sessionID !== sessionId)
57
- return [];
58
- return [{ info, parts: Array.isArray(entry?.parts) ? entry.parts : [] }];
22
+ function ownRuntime(custody, runtime) {
23
+ let closing;
24
+ let resolve;
25
+ let reject;
26
+ const closed = new Promise((yes, no) => {
27
+ resolve = yes;
28
+ reject = no;
59
29
  });
60
- }
61
- function newest(rows) {
62
- return rows.reduce((latest, row) => {
63
- if (latest === undefined)
64
- return row;
65
- const at = Number(object(row.info.time)?.created ?? 0);
66
- const latestAt = Number(object(latest.info.time)?.created ?? 0);
67
- return at > latestAt || (at === latestAt && String(row.info.id) > String(latest.info.id)) ? row : latest;
68
- }, undefined);
69
- }
70
- async function readTurnResult(input) {
71
- try {
72
- const response = await input.session.messages({
73
- path: { id: input.sessionId },
74
- query: { directory: input.cwd },
75
- throwOnError: true,
76
- });
77
- const rows = messageRows(response, input.sessionId);
78
- const user = rows.find((row) => row.info.role === "user" && row.info.id === input.messageID);
79
- if (user === undefined)
80
- return { kind: "failed", diagnostic: "OpenCode completed without the admitted user message" };
81
- const userId = nonblank(user.info.id);
82
- const assistant = newest(rows.filter((row) => row.info.role === "assistant" && row.info.parentID === userId && row.info.summary !== true));
83
- const historyId = nonblank(assistant?.info.id);
84
- if (input.state.failure !== undefined)
85
- return { kind: "failed", diagnostic: input.state.failure };
86
- if (assistant === undefined)
87
- return { kind: "failed", diagnostic: "OpenCode completed without a native assistant answer" };
88
- if (assistant.info.error !== undefined)
89
- return { kind: "failed", diagnostic: diagnostic(assistant.info.error) };
90
- const answer = assistant.parts
91
- .map((part) => object(part))
92
- .filter((part) => part?.type === "text")
93
- .map((part) => part?.text)
94
- .filter((part) => typeof part === "string")
95
- .join("\n\n");
96
- return historyId === undefined ? { kind: "answered", answer } : { kind: "answered", answer, historyId };
97
- }
98
- catch (error) {
99
- return { kind: "failed", diagnostic: diagnostic(error) };
100
- }
101
- }
102
- function nativeSessionId(value) {
103
- const event = object(value);
104
- const properties = object(event?.properties);
105
- const direct = nonblank(properties?.sessionID);
106
- if (direct !== undefined)
107
- return direct;
108
- const info = object(properties?.info);
109
- const part = object(properties?.part);
110
- return nonblank(info?.sessionID) ?? nonblank(part?.sessionID);
111
- }
112
- function scopedProperties(value, sessionId) {
113
- const properties = object(object(value)?.properties);
114
- return nativeSessionId(value) === sessionId ? properties : undefined;
115
- }
116
- function nativeProgress(type, properties, busy) {
117
- if (type === "session.idle")
118
- return { busy, terminal: busy };
119
- if (type !== "session.status")
120
- return { busy, terminal: false };
121
- const status = object(properties.status)?.type;
122
- const nextBusy = busy || status === "busy";
123
- return { busy: nextBusy, terminal: status === "idle" && nextBusy };
124
- }
125
- function startsTurn(value, sessionId, messageID) {
126
- const event = object(value);
127
- const info = object(object(event?.properties)?.info);
128
- return (event?.type === "message.updated" && info?.sessionID === sessionId && info.role === "user" && info.id === messageID);
129
- }
130
- function recognizeAdmission(value, sessionId, admission) {
131
- const event = object(value);
132
- const properties = object(event?.properties);
133
- const info = object(properties?.info);
134
- if (event?.type === "message.updated" &&
135
- info?.sessionID === sessionId &&
136
- info.role === "user" &&
137
- typeof info.id === "string") {
138
- admission.admit(info.id);
139
- }
140
- }
141
- function observeEvent(input) {
142
- const observedSessionId = nativeSessionId(input.value);
143
- if (observedSessionId !== undefined && observedSessionId !== input.sessionId) {
144
- return { active: input.active, busy: input.busy, terminal: false };
145
- }
146
- const event = object(input.value);
147
- const properties = scopedProperties(input.value, input.sessionId);
148
- if (properties === undefined) {
149
- if (input.active)
150
- mapEvent(input.value, input.events, input.state);
151
- return { active: input.active, busy: input.busy, terminal: false };
152
- }
153
- const starts = !input.active && startsTurn(input.value, input.sessionId, input.messageID);
154
- const active = input.active || starts;
155
- const busy = starts ? false : input.busy;
156
- recognizeAdmission(input.value, input.sessionId, input.admission);
157
- const submittedError = event?.type === "session.error" && input.submissionState.started;
158
- if (!active && !submittedError)
159
- return { active, busy, terminal: false };
160
- mapEvent(input.value, input.events, input.state);
161
- if (event?.type === "session.error") {
162
- input.admission.end();
163
- return {
164
- active,
165
- busy,
166
- terminal: true,
167
- result: { kind: "failed", diagnostic: input.state.failure ?? diagnostic(properties.error) },
168
- };
169
- }
170
- const progress = nativeProgress(event?.type, properties, busy);
171
- return { active, busy: progress.busy, terminal: progress.terminal };
172
- }
173
- async function observeTurn(input) {
174
- let active = false;
175
- let busy = false;
176
- try {
177
- for (;;) {
178
- const next = await input.stream.next();
179
- if (next.done)
180
- return { kind: "failed", diagnostic: "OpenCode event stream ended before Turn completion" };
181
- const observed = observeEvent({
182
- value: eventValue(next.value),
183
- sessionId: input.sessionId,
184
- messageID: input.messageID,
185
- active,
186
- busy,
187
- admission: input.admission,
188
- events: input.events,
189
- state: input.state,
190
- submissionState: input.submissionState,
191
- });
192
- active = observed.active;
193
- busy = observed.busy;
194
- if (observed.result !== undefined)
195
- return observed.result;
196
- if (!observed.terminal)
197
- continue;
198
- input.admission.end();
199
- return await readTurnResult({ ...input, messageID: input.admission.latest() });
30
+ const close = () => {
31
+ if (closing === undefined) {
32
+ closing = runtime.close();
33
+ void closing.then(resolve, reject);
200
34
  }
201
- }
202
- catch (error) {
203
- return { kind: "failed", diagnostic: `OpenCode event stream failed: ${diagnostic(error)}` };
204
- }
205
- finally {
206
- input.admission.end();
207
- }
35
+ return closing;
36
+ };
37
+ custody.own({ closed, abort: close, forceDispose: close });
38
+ return close;
208
39
  }
209
40
  function liveAdmission(initial) {
210
41
  let latest = initial;
211
42
  let ended = false;
212
- const pending = new Map();
43
+ const submitted = new Set([initial]);
44
+ const tells = tellRegistry();
213
45
  const wait = (id) => {
214
- if (ended)
215
- return Promise.resolve({ kind: "turn-ended" });
216
- return new Promise((resolve, reject) => {
217
- pending.set(id, { resolve, reject });
218
- });
46
+ submitted.add(id);
47
+ return tells.register(id, id);
219
48
  };
220
49
  return {
221
50
  initial: wait(initial),
222
51
  latest: () => latest,
223
52
  open: () => !ended,
53
+ owns: (id) => submitted.has(id),
224
54
  wait,
225
55
  admit(id) {
226
- const waiter = pending.get(id);
227
- if (waiter === undefined || ended)
228
- return;
229
- pending.delete(id);
230
- latest = id;
231
- waiter.resolve({ kind: "accepted", fence: id });
56
+ if (tells.accept(id))
57
+ latest = id;
232
58
  },
233
- fail(id, error) {
234
- pending.get(id)?.reject(error);
235
- pending.delete(id);
236
- },
237
- end() {
238
- if (ended)
239
- return;
59
+ fail: tells.fail,
60
+ end: () => {
240
61
  ended = true;
241
- for (const waiter of pending.values())
242
- waiter.resolve({ kind: "turn-ended" });
243
- pending.clear();
62
+ tells.end();
244
63
  },
245
64
  };
246
65
  }
247
- function terminalSettlement(events, close) {
248
- let finish;
249
- const completion = new Promise((resolve) => {
250
- let settlement;
251
- finish = (result) => {
252
- settlement ??= (async () => {
253
- // Whole closure is proved before narration ends. The driver therefore
254
- // stays inside its Heart/control observation race while retirement is
255
- // outstanding, so a rejected or unprovable closure still reaches attempt
256
- // custody's bounded hung handling instead of parking on a pending
257
- // completion. Ending narration first would move that await outside the
258
- // driver's race and make the Body unresponsive to stop or Heart loss.
259
- await close();
260
- events.end();
261
- resolve(result);
262
- })();
263
- return settlement;
264
- };
265
- });
266
- return { completion, finish };
267
- }
268
- /**
269
- * One memoized whole-close: stream retirement and existing server retirement
270
- * start together on every terminal path, so a live socket can never outwait the
271
- * server's death.
272
- */
273
- function wholeClosure(retireStream, closeServer) {
274
- let closing;
275
- return () => {
276
- closing ??= Promise.all([retireStream(), closeServer()]).then(() => undefined);
277
- return closing;
278
- };
279
- }
280
- async function forceDisposeOpencode(abortController, close, finish) {
281
- abortController.abort();
282
- await close();
283
- await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
284
- }
285
- async function loadDriveRuntime(execution, input, signal, loader, onRuntime) {
286
- const runtime = await loadOpencode({
287
- execution,
288
- cwd: input.cwd,
289
- signal,
290
- ...(loader === undefined ? {} : { loader }),
291
- ...(onRuntime === undefined ? {} : { onRuntime }),
292
- ...(input.requests === undefined ? {} : { requests: input.requests.dir }),
293
- });
294
- if (signal.aborted) {
295
- await runtime.close();
296
- signal.throwIfAborted();
66
+ async function answer(runtime, id, assistantID, terminalID) {
67
+ const response = object(await runtime.client.session.message({ id, messageID: assistantID }));
68
+ const message = object(response?.data);
69
+ if (message?.id !== assistantID || message.type !== "assistant" || object(message.time)?.completed === undefined) {
70
+ return { kind: "failed", diagnostic: "OpenCode completed without its exact completed assistant message" };
297
71
  }
298
- return runtime;
72
+ if (message.error !== undefined)
73
+ return { kind: "failed", diagnostic: diagnostic(message.error) };
74
+ if (!Array.isArray(message.content))
75
+ return { kind: "failed", diagnostic: "OpenCode assistant content missing" };
76
+ const text = message.content
77
+ .flatMap((item) => {
78
+ const part = object(item);
79
+ return part?.type === "text" && typeof part.text === "string" ? [part.text] : [];
80
+ })
81
+ .join("\n\n");
82
+ // v2's terminal event projects a real idle message with this exact ID. Forking
83
+ // BEFORE that message includes the answer, without copying later native work.
84
+ const boundary = terminalID.replace(/^evt_/u, "msg_");
85
+ const projected = object(object(await runtime.client.session.message({ id, messageID: boundary }))?.data);
86
+ if (projected?.id !== boundary || projected.type !== "idle" || projected.outcome !== "succeeded") {
87
+ return { kind: "failed", diagnostic: "OpenCode terminal fork boundary was not projected" };
88
+ }
89
+ return { kind: "answered", answer: text, historyId: boundary };
299
90
  }
300
- function ownRuntime(custody, runtime) {
301
- let closing;
302
- let settleRuntimeClosed;
303
- let rejectRuntimeClosed;
304
- const runtimeClosed = new Promise((resolve, reject) => {
305
- settleRuntimeClosed = resolve;
306
- rejectRuntimeClosed = reject;
307
- });
308
- const close = () => {
309
- if (closing === undefined) {
310
- closing = runtime.close();
311
- void closing.then(settleRuntimeClosed, rejectRuntimeClosed);
91
+ async function observe(input) {
92
+ const state = createEventState(input.id);
93
+ let delivered;
94
+ let step;
95
+ try {
96
+ for (;;) {
97
+ const next = await input.stream.next();
98
+ if (next.done)
99
+ return { kind: "failed", diagnostic: "OpenCode event stream ended before Turn completion" };
100
+ const event = object(next.value);
101
+ const data = object(event?.data);
102
+ if (data?.sessionID !== input.id)
103
+ continue;
104
+ const type = event?.type;
105
+ if (type === "session.inbox.enqueued" && typeof data.inboxID === "string" && object(data.item)?.type === "user") {
106
+ input.admission.admit(data.inboxID);
107
+ }
108
+ if (type === "session.inbox.delivered" && typeof data.inboxID === "string") {
109
+ if (!input.admission.owns(data.inboxID)) {
110
+ return {
111
+ kind: "failed",
112
+ diagnostic: "OpenCode consumed another writer's input; exact answer attribution unavailable",
113
+ };
114
+ }
115
+ input.admission.admit(data.inboxID);
116
+ delivered = data.inboxID;
117
+ }
118
+ if (type === "session.step.started" && delivered !== undefined && typeof data.assistantMessageID === "string") {
119
+ step = { id: data.assistantMessageID, input: delivered, ended: false };
120
+ }
121
+ if (data.assistantMessageID !== undefined && data.assistantMessageID !== step?.id)
122
+ continue;
123
+ if (delivered !== undefined)
124
+ mapEvent(event, input.events, state);
125
+ if (type === "session.step.ended" && step !== undefined)
126
+ step.ended = true;
127
+ if (type === "session.step.failed" || type === "session.execution.failed") {
128
+ return { kind: "failed", diagnostic: diagnostic(data.error) };
129
+ }
130
+ if (type === "session.execution.interrupted")
131
+ return { kind: "failed", diagnostic: "OpenCode native execution interrupted" };
132
+ if (type !== "session.execution.succeeded" || delivered === undefined)
133
+ continue;
134
+ if (step === undefined || !step.ended || step.input !== input.admission.latest()) {
135
+ return { kind: "failed", diagnostic: "OpenCode completed without an assistant for the admitted input" };
136
+ }
137
+ const terminalID = nonblank(event?.id);
138
+ if (terminalID === undefined || !terminalID.startsWith("evt_"))
139
+ return { kind: "failed", diagnostic: "OpenCode terminal identity missing" };
140
+ return await answer(input.runtime, input.id, step.id, terminalID);
312
141
  }
313
- return closing;
314
- };
315
- custody?.own({ closed: runtimeClosed, abort: close, forceDispose: close });
316
- return close;
142
+ }
143
+ catch (error) {
144
+ return { kind: "failed", diagnostic: `OpenCode observation failed: ${diagnostic(error)}` };
145
+ }
146
+ finally {
147
+ input.admission.end();
148
+ }
317
149
  }
318
- async function openDriveSession(runtime, input, resumeSessionId, signal) {
150
+ async function openSession(runtime, input) {
151
+ const model = input.options.model === undefined
152
+ ? undefined
153
+ : {
154
+ ...parseModel(input.options.model),
155
+ ...(input.options.effort === undefined ? {} : { variant: input.options.effort }),
156
+ };
319
157
  const session = runtime.client.session;
320
- const response = await (input.session.kind === "fresh"
321
- ? session.create({ query: { directory: input.cwd }, throwOnError: true })
322
- : session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }));
323
- signal.throwIfAborted();
324
- const info = object(object(response)?.data) ?? object(response);
325
- const sessionId = nonblank(info?.id) ?? resumeSessionId;
326
- if (sessionId === undefined)
158
+ const response = input.session.kind === "fresh"
159
+ ? await session.create({
160
+ body: { location: { directory: input.cwd }, ...(model === undefined ? {} : { model }) },
161
+ })
162
+ : await session.get({ id: sessionId(input.session.coordinate) });
163
+ const info = object(object(response)?.data);
164
+ const id = nonblank(info?.id);
165
+ if (id === undefined)
327
166
  throw new Error("OpenCode did not return a session id");
328
- return { session, sessionId };
329
- }
330
- function createLiveSession(input) {
331
- let submissions = input.admission.initial.then(() => undefined);
332
- const submit = (tell) => {
333
- const submission = async () => {
334
- if (!input.admission.open())
335
- return { kind: "turn-ended" };
336
- const receipt = input.admission.wait(tell.id);
337
- void input.session
338
- .promptAsync({
339
- path: { id: input.sessionId },
340
- query: { directory: input.cwd },
341
- body: input.promptBody(tell.id, tell.text),
342
- throwOnError: true,
343
- })
344
- .catch((error) => input.admission.fail(tell.id, error));
345
- return await receipt;
346
- };
347
- const next = submissions.then(submission);
348
- submissions = next.then(() => undefined, () => undefined);
349
- return next;
350
- };
351
- return {
352
- admission: { fence: input.sessionId },
353
- events: input.events,
354
- completion: input.completion,
355
- tell: (tell) => submit({ ...tell, id: input.nextMessageId() }),
356
- abort: async () => {
357
- input.admission.end();
358
- input.abortController.abort();
359
- void input.session
360
- .abort({ path: { id: input.sessionId }, query: { directory: input.cwd }, throwOnError: true })
361
- .catch(() => undefined);
362
- await input.finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
363
- },
364
- forceDispose: () => {
365
- input.admission.end();
366
- return forceDisposeOpencode(input.abortController, input.close, input.finish);
367
- },
368
- };
167
+ const active = object(object(await session.active())?.data);
168
+ if (active === undefined)
169
+ throw new Error("OpenCode active execution response missing");
170
+ if (active[id] !== undefined)
171
+ throw new Error("OpenCode native session is already busy");
172
+ let selected = object(info?.model);
173
+ if (input.session.kind === "resume" && model !== undefined) {
174
+ await session.switchModel({ id, model });
175
+ selected = object(object(object(await session.get({ id }))?.data)?.model);
176
+ }
177
+ if (model !== undefined &&
178
+ (selected?.providerID !== model.providerID ||
179
+ selected.id !== model.id ||
180
+ (selected.variant ?? "default") !== (input.options.effort ?? "default"))) {
181
+ throw new Error("OpenCode did not select the requested model");
182
+ }
183
+ if (input.options.systemPrompt !== undefined) {
184
+ // Native instruction entries join the system transcript and survive resume
185
+ // and fork. Updating the same entry announces an upstream instruction change.
186
+ await session.instructions({ id, text: input.options.systemPrompt });
187
+ }
188
+ return id;
369
189
  }
370
190
  async function drive(execution, input, loader, custody) {
371
191
  admit(input.options);
372
- const signal = input.signal ?? new AbortController().signal;
373
- const resumeSessionId = input.session.kind === "resume" ? opencodeSessionId(input.session.coordinate) : undefined;
374
- const abortController = new AbortController();
375
- const abortSetup = () => abortController.abort(signal.reason);
376
- signal.addEventListener("abort", abortSetup, { once: true });
377
- signal.throwIfAborted();
378
- let closeOnce;
379
- const runtime = await loadDriveRuntime(execution, input, abortController.signal, loader, (ready) => {
380
- closeOnce = ownRuntime(custody, ready);
192
+ if (input.schemaJson !== undefined)
193
+ throw new Error("OpenCode v2 does not expose native structured answer schemas");
194
+ if (input.session.kind === "resume")
195
+ sessionId(input.session.coordinate);
196
+ let closeRuntime;
197
+ const runtime = await loadOpencode({
198
+ execution,
199
+ cwd: input.cwd,
200
+ signal: custody.signal,
201
+ ...(loader === undefined ? {} : { loader }),
202
+ onRuntime: (ready) => {
203
+ closeRuntime = ownRuntime(custody, ready);
204
+ },
205
+ ...(input.requests === undefined ? {} : { requests: input.requests.dir }),
381
206
  });
382
207
  const stream = ownEventStream({
383
208
  directory: input.cwd,
384
- signal: abortController.signal,
385
- ...(custody === undefined ? {} : { custody }),
386
- subscribe: (request) => runtime.client.event.subscribe(request),
209
+ signal: custody.signal,
210
+ custody,
211
+ subscribe: runtime.client.event.subscribe,
387
212
  });
388
- const wholeClose = wholeClosure(() => stream.retire(), () => closeOnce());
213
+ let closing;
214
+ const close = () => (closing ??= Promise.all([stream.retire(), closeRuntime()]).then(() => undefined));
389
215
  try {
390
- const { session, sessionId } = await openDriveSession(runtime, input, resumeSessionId, abortController.signal);
391
- const events = new AgentEventChannel();
392
- const state = createEventState(sessionId);
393
- const messageSequence = { value: 0 };
394
- const nextMessageId = () => messageId(messageSequence.value++);
395
- const messageID = nextMessageId();
396
- const admission = liveAdmission(messageID);
397
- const submissionState = { started: false };
398
- events.emit({ type: "session", coordinate: coordinate(sessionId) });
216
+ const id = await openSession(runtime, input);
399
217
  await stream.ready;
400
- if (abortController.signal.aborted)
401
- abortController.signal.throwIfAborted();
402
- const { completion, finish } = terminalSettlement(events, wholeClose);
403
- const observation = observeTurn({
404
- stream,
405
- session,
406
- sessionId,
407
- cwd: input.cwd,
408
- messageID,
409
- admission,
410
- events,
411
- state,
412
- submissionState,
413
- });
414
- submissionState.started = true;
415
- await session.promptAsync({
416
- path: { id: sessionId },
417
- query: { directory: input.cwd },
418
- body: promptBody(input, messageID),
419
- throwOnError: true,
218
+ custody.signal.throwIfAborted();
219
+ const events = new AgentEventChannel();
220
+ events.emit({ type: "session", coordinate: coordinate(id) });
221
+ let sequence = 0;
222
+ const initial = messageId(sequence++);
223
+ const admission = liveAdmission(initial);
224
+ let control;
225
+ let finishing = false;
226
+ let finish;
227
+ const completion = new Promise((resolve) => {
228
+ let settlement;
229
+ finish = (result) => {
230
+ finishing = true;
231
+ return (settlement ??= (async () => {
232
+ await close();
233
+ events.end();
234
+ resolve(result);
235
+ })());
236
+ };
420
237
  });
421
- abortController.signal.throwIfAborted();
422
- // The observer never awaits its own completion: finishing retires the whole
423
- // stream, but the observer that invoked finish stays outside that wait. Its
424
- // rejection is owned by attempt custody, not by an unobserved promise.
425
- void observation.then(finish).catch(() => undefined);
426
- return createLiveSession({
427
- session,
428
- sessionId,
429
- cwd: input.cwd,
430
- promptBody: (messageID, text) => promptBody({ ...input, body: text, launchTells: [] }, messageID),
431
- admission,
432
- nextMessageId,
238
+ const observed = observe({ runtime, stream, id, admission, events });
239
+ // Start observation before submission: admission broadcasts can precede HTTP completion.
240
+ void observed
241
+ .then(async (result) => {
242
+ // Explicit interrupt owns its response too: a terminal broadcast can be
243
+ // published before that response, and must not close its connection early.
244
+ if (control !== undefined)
245
+ await control;
246
+ await finish(result);
247
+ })
248
+ .catch(() => undefined);
249
+ const prompt = async (nativeID, text) => {
250
+ const response = object(object(await runtime.client.session.prompt({ id, body: { id: nativeID, text } }))?.data);
251
+ if (response?.id !== nativeID || response.sessionID !== id || response.type !== "user") {
252
+ throw new Error("OpenCode returned no exact native inbox admission");
253
+ }
254
+ };
255
+ await prompt(initial, launchPrompt(input.body, input.launchTells));
256
+ custody.signal.throwIfAborted();
257
+ let submissions = admission.initial.then(() => undefined);
258
+ return {
259
+ admission: { fence: initial },
433
260
  events,
434
261
  completion,
435
- abortController,
436
- close: wholeClose,
437
- finish,
438
- });
262
+ tell(tell) {
263
+ const submit = async () => {
264
+ if (!admission.open())
265
+ return { kind: "turn-ended" };
266
+ const nativeID = messageId(sequence++);
267
+ const receipt = admission.wait(nativeID);
268
+ void prompt(nativeID, tell.text).catch((error) => admission.fail(nativeID, error));
269
+ return await receipt;
270
+ };
271
+ const next = submissions.then(submit);
272
+ submissions = next.then(() => undefined, () => undefined);
273
+ return next;
274
+ },
275
+ abort: async () => {
276
+ admission.end();
277
+ if (finishing) {
278
+ await close();
279
+ return;
280
+ }
281
+ // The v2 interrupt response follows execution settlement. Do not hide its
282
+ // refusal; server and connection retirement must still run on failure.
283
+ control ??= runtime.client.session.interrupt({ id });
284
+ try {
285
+ await control;
286
+ }
287
+ finally {
288
+ await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
289
+ }
290
+ },
291
+ forceDispose: async () => {
292
+ admission.end();
293
+ await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
294
+ },
295
+ };
439
296
  }
440
297
  catch (error) {
441
- signal.removeEventListener("abort", abortSetup);
442
- await wholeClose();
298
+ await close();
443
299
  throw error;
444
300
  }
445
301
  }
@@ -450,18 +306,15 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
450
306
  admitOptions(options) {
451
307
  try {
452
308
  admit(options);
309
+ return { kind: "admitted", options: Object.freeze({ ...options }) };
453
310
  }
454
311
  catch (error) {
455
312
  return { kind: "refused", diagnostic: diagnostic(error) };
456
313
  }
457
- return {
458
- kind: "admitted",
459
- options: Object.freeze({ ...options }),
460
- };
461
314
  },
462
315
  ...providerLifecycle((input, custody) => drive(execution, input, loader, custody)),
463
316
  fork: (input) => createProviderAttempt(new AbortController().signal, async (custody) => {
464
- const sessionId = opencodeSessionId(input.session);
317
+ const id = sessionId(input.session);
465
318
  let close;
466
319
  const runtime = await loadOpencode({
467
320
  execution,
@@ -469,34 +322,27 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
469
322
  signal: custody.signal,
470
323
  ...(loader === undefined ? {} : { loader }),
471
324
  onRuntime: (ready) => {
472
- let closing;
473
- let settleRuntimeClosed;
474
- let rejectRuntimeClosed;
475
- const runtimeClosed = new Promise((resolve, reject) => {
476
- settleRuntimeClosed = resolve;
477
- rejectRuntimeClosed = reject;
478
- });
479
- close = () => {
480
- if (closing === undefined) {
481
- closing = ready.close();
482
- void closing.then(settleRuntimeClosed, rejectRuntimeClosed);
483
- }
484
- return closing;
485
- };
486
- custody.own({ closed: runtimeClosed, abort: close, forceDispose: close });
325
+ close = ownRuntime(custody, ready);
487
326
  },
488
327
  });
489
328
  try {
490
- const result = await runtime.client.session.fork({
491
- path: { id: sessionId },
492
- query: { directory: input.cwd },
493
- body: { messageID: input.at },
494
- });
495
- const info = object(object(result)?.data) ?? object(result);
496
- const id = nonblank(info?.id);
497
- if (id === undefined || id === sessionId)
498
- throw new Error("OpenCode fork returned an invalid session id");
499
- return { session: coordinate(id) };
329
+ custody.signal.throwIfAborted();
330
+ const point = object(object(await runtime.client.session.message({ id, messageID: input.at }))?.data);
331
+ if (point?.id !== input.at || point.type !== "idle" || point.outcome !== "succeeded") {
332
+ throw new Error("OpenCode fork requires the retained successful native terminal boundary");
333
+ }
334
+ const response = object(object(await runtime.client.session.fork({ id, before: input.at }))?.data);
335
+ const child = nonblank(response?.id);
336
+ const fork = object(response?.fork);
337
+ const boundary = object(fork?.boundary);
338
+ if (child === undefined ||
339
+ child === id ||
340
+ fork?.sessionID !== id ||
341
+ boundary?.type !== "before" ||
342
+ boundary.messageID !== input.at) {
343
+ throw new Error("OpenCode fork did not preserve the exact native boundary");
344
+ }
345
+ return { session: coordinate(child) };
500
346
  }
501
347
  finally {
502
348
  await close();