@astrosheep/keiyaku 4.6.8 → 4.6.9

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
@@ -0,0 +1,9 @@
1
+ import { type OpenCodeClient } from "@opencode/client";
2
+ /** The matching native SDK owns the dialect; this attempt owns its HTTP connections. */
3
+ export declare function createNativeClient(baseUrl: string, password: string): Readonly<{
4
+ sdk: OpenCodeClient;
5
+ subscribe(signal: AbortSignal): Promise<Readonly<{
6
+ stream: AsyncIterable<unknown>;
7
+ }>>;
8
+ close(): Promise<void>;
9
+ }>;
@@ -0,0 +1,69 @@
1
+ import { OpenCode } from "@opencode/client";
2
+ import { Agent } from "undici";
3
+ import { object } from "../decode.js";
4
+ /** The matching native SDK owns the dialect; this attempt owns its HTTP connections. */
5
+ export function createNativeClient(baseUrl, password) {
6
+ const dispatcher = new Agent();
7
+ const controller = new AbortController();
8
+ // Node's fetch accepts an Undici dispatcher in addition to standard RequestInit.
9
+ // Keep that native extension at this one SDK seam.
10
+ const ownedFetch = (input, init) => {
11
+ controller.signal.throwIfAborted();
12
+ const options = {
13
+ ...init,
14
+ dispatcher,
15
+ signal: init?.signal == null ? controller.signal : AbortSignal.any([controller.signal, init.signal]),
16
+ };
17
+ return globalThis.fetch(input, options);
18
+ };
19
+ const sdk = OpenCode.make({
20
+ baseUrl,
21
+ fetch: ownedFetch,
22
+ headers: { authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` },
23
+ });
24
+ const subscribe = async (signal) => {
25
+ const iterator = sdk.event.subscribe({ signal })[Symbol.asyncIterator]();
26
+ try {
27
+ // The producer subscribes before emitting this frame. Headers alone are
28
+ // not a barrier against losing the opening native admission broadcast.
29
+ const connected = await iterator.next();
30
+ if (connected.done || object(connected.value)?.type !== "server.connected") {
31
+ throw new Error("OpenCode event feed did not confirm subscription");
32
+ }
33
+ }
34
+ catch (error) {
35
+ await iterator.return?.();
36
+ throw error;
37
+ }
38
+ return {
39
+ stream: {
40
+ [Symbol.asyncIterator]: () => ({
41
+ next: async () => {
42
+ const next = await iterator.next();
43
+ // Effect stream-failure frames are not native events. The generated
44
+ // SDK parser can yield their payload; never drop that into narration.
45
+ if (!next.done && typeof object(next.value)?.type !== "string") {
46
+ throw new Error("OpenCode native event feed failed");
47
+ }
48
+ return next;
49
+ },
50
+ return: () => iterator.return?.() ?? Promise.resolve({ done: true, value: undefined }),
51
+ }),
52
+ },
53
+ };
54
+ };
55
+ let closing;
56
+ return {
57
+ sdk,
58
+ subscribe,
59
+ close: () => {
60
+ closing ??= (async () => {
61
+ controller.abort();
62
+ // SDK iterator retirement is logical. Dispatcher destruction separately
63
+ // awaits closure of every socket, including requests aborted during setup.
64
+ await dispatcher.destroy();
65
+ })();
66
+ return closing;
67
+ },
68
+ };
69
+ }
@@ -1,24 +1,49 @@
1
- import type { OpencodeClient } from "@opencode-ai/sdk";
1
+ import type { SessionCreateInput, SessionSwitchModelInput } from "@opencode/client";
2
2
  import type { ResumeCoordinate } from "../../heart/index.js";
3
3
  import type { ProviderExecution } from "../../provider-recipe.js";
4
- export type OpencodeSdkSession = Pick<OpencodeClient["session"], "create" | "get" | "fork" | "abort" | "promptAsync" | "messages">;
5
- /**
6
- * The adapter's own event subscription port. It carries the cancellation signal
7
- * and the cancellable retry backoff the installed client honors at runtime,
8
- * without naming the SDK's generated request type at the call site.
9
- */
4
+ export type OpencodeSdkSession = Readonly<{
5
+ create(input: {
6
+ body: SessionCreateInput;
7
+ }): Promise<unknown>;
8
+ get(input: {
9
+ id: string;
10
+ }): Promise<unknown>;
11
+ switchModel(input: {
12
+ id: string;
13
+ model: SessionSwitchModelInput["model"];
14
+ }): Promise<unknown>;
15
+ instructions(input: {
16
+ id: string;
17
+ text: string;
18
+ }): Promise<unknown>;
19
+ active(): Promise<unknown>;
20
+ fork(input: {
21
+ id: string;
22
+ before: string;
23
+ }): Promise<unknown>;
24
+ interrupt(input: {
25
+ id: string;
26
+ }): Promise<unknown>;
27
+ prompt(input: {
28
+ id: string;
29
+ body: {
30
+ id: string;
31
+ text: string;
32
+ };
33
+ }): Promise<unknown>;
34
+ message(input: {
35
+ id: string;
36
+ messageID: string;
37
+ }): Promise<unknown>;
38
+ }>;
10
39
  export type OpencodeEventSubscriptionRequest = Readonly<{
11
40
  directory: string;
12
- /** Cancels the subscription's fetch, reader, and retry waiting. */
13
41
  signal: AbortSignal;
14
- /** Resolves without rejecting when `signal` aborts, and always clears its timer. */
15
- sleep(milliseconds: number): Promise<void>;
16
- }>;
17
- type OpencodeEventSubscription = Readonly<{
18
- stream: AsyncIterable<unknown>;
19
42
  }>;
20
43
  export type OpencodeSdkEvent = Readonly<{
21
- subscribe(request: OpencodeEventSubscriptionRequest): Promise<OpencodeEventSubscription>;
44
+ subscribe(request: OpencodeEventSubscriptionRequest): Promise<Readonly<{
45
+ stream: AsyncIterable<unknown>;
46
+ }>>;
22
47
  }>;
23
48
  export type OpencodeSdkLoader = (cwd: string, execution: ProviderExecution, signal: AbortSignal) => Promise<Readonly<{
24
49
  client: {
@@ -29,31 +54,11 @@ export type OpencodeSdkLoader = (cwd: string, execution: ProviderExecution, sign
29
54
  ready?: Promise<void>;
30
55
  }>>;
31
56
  export declare const OPENCODE_SDK_PROVIDER: "opencode-sdk";
32
- /**
33
- * Adapt the installed client's generated event endpoint to the adapter's
34
- * subscription port.
35
- *
36
- * The generated per-endpoint `Options` type omits the SDK's runtime
37
- * `sseSleepFn` option even though the client forwards every per-request key
38
- * into `createSseClient`, so a caller cannot express a cancellable backoff
39
- * through the published type. Declaring the port here keeps that one typing gap
40
- * and its single adaptation at the SDK seam: no other module repeats a cast,
41
- * the SDK's own parser and retry policy stay untouched, and an injected loader
42
- * supplies the same port shape.
43
- */
44
- export declare function opencodeEventPort(event: OpencodeClient["event"]): OpencodeSdkEvent;
45
57
  export declare function messageId(sequence?: number): string;
46
58
  export declare function parseModel(model: string): Readonly<{
47
59
  providerID: string;
48
- modelID: string;
60
+ id: string;
49
61
  }>;
50
- /**
51
- * Compose the config handed to a headless opencode server. The permission base
52
- * layer fills only the classes the caller leaves unspecified: an explicit caller
53
- * permission key wins per key and every non-permission key passes through
54
- * untouched. A caller that supplies a non-object `permission` shorthand states
55
- * the whole policy and keeps it.
56
- */
57
62
  export declare function composeHeadlessPermissionConfig(config?: Readonly<Record<string, unknown>>): Record<string, unknown>;
58
63
  export type OpencodeRuntime = Readonly<{
59
64
  client: {
@@ -62,6 +67,8 @@ export type OpencodeRuntime = Readonly<{
62
67
  };
63
68
  close: () => Promise<void>;
64
69
  }>;
70
+ /** A successful HTML fallback, or a JSON response from another API, is not readiness. */
71
+ export declare function waitReady(json: (path: string) => Promise<unknown>, signal: AbortSignal): Promise<void>;
65
72
  export declare function loadOpencode(input: Readonly<{
66
73
  execution: ProviderExecution;
67
74
  cwd: string;
@@ -71,4 +78,3 @@ export declare function loadOpencode(input: Readonly<{
71
78
  requests?: string;
72
79
  }>): Promise<OpencodeRuntime>;
73
80
  export declare function coordinate(sessionId: string): ResumeCoordinate;
74
- export {};
@@ -3,90 +3,59 @@ import { randomBytes } from "node:crypto";
3
3
  import { abortable } from "../../abort.js";
4
4
  import { akumaExecutionEnvironment } from "../execution-environment.js";
5
5
  import { spawnDetachedProcess } from "../../../runtime/proc/run.js";
6
- import { isRecord } from "../decode.js";
6
+ import { object } from "../decode.js";
7
+ import { createNativeClient } from "./native-http.js";
7
8
  export const OPENCODE_SDK_PROVIDER = "opencode-sdk";
8
- /**
9
- * Adapt the installed client's generated event endpoint to the adapter's
10
- * subscription port.
11
- *
12
- * The generated per-endpoint `Options` type omits the SDK's runtime
13
- * `sseSleepFn` option even though the client forwards every per-request key
14
- * into `createSseClient`, so a caller cannot express a cancellable backoff
15
- * through the published type. Declaring the port here keeps that one typing gap
16
- * and its single adaptation at the SDK seam: no other module repeats a cast,
17
- * the SDK's own parser and retry policy stay untouched, and an injected loader
18
- * supplies the same port shape.
19
- */
20
- export function opencodeEventPort(event) {
21
- return {
22
- subscribe: (request) => {
23
- const adapted = {
24
- query: { directory: request.directory },
25
- signal: request.signal,
26
- };
27
- // Runtime-only key omitted from the generated Options type; the installed
28
- // client forwards it to createSseClient unchanged.
29
- adapted.sseSleepFn = request.sleep;
30
- return event.subscribe(adapted);
31
- },
32
- };
33
- }
34
9
  export function messageId(sequence = 0) {
35
- const timestamp = BigInt(new Date().getTime()) * 0x1000n + BigInt(sequence);
36
- const bytes = Buffer.alloc(6);
37
- bytes.writeUIntBE(Number(timestamp & 0xffffffffffffn), 0, 6);
38
- const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
39
- const suffix = [...randomBytes(14)].map((byte) => alphabet[byte % alphabet.length]).join("");
40
- return `msg_${bytes.toString("hex")}${suffix}`;
10
+ const timestamp = BigInt(Date.now()) * 0x1000n + BigInt(sequence);
11
+ return `msg_${timestamp.toString(16)}${randomBytes(14).toString("hex")}`;
41
12
  }
42
13
  export function parseModel(model) {
43
14
  const slash = model.indexOf("/");
44
15
  if (slash <= 0 || slash === model.length - 1)
45
16
  throw new Error("OpenCode model must be <provider>/<model>");
46
- return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
17
+ return { providerID: model.slice(0, slash), id: model.slice(slash + 1) };
47
18
  }
48
- /**
49
- * Permission classes whose upstream defaults can await a human reply. A headless
50
- * server has no reply channel, so such a pending ask never settles.
51
- *
52
- * Verified against the installed opencode 1.18.33 agent permission ruleset: the
53
- * primary agents start from `{"*":"allow"}` with `external_directory` and
54
- * `doom_loop` set to ask, `read` set to ask for `.env` files, and `question`
55
- * allowed (the question tool awaits an answer by semantics). Every other class
56
- * matches the allow-all rule and cannot await.
57
- */
58
- const HEADLESS_PERMISSION_BASE = Object.freeze({
59
- external_directory: "allow",
60
- doom_loop: "allow",
61
- read: "allow",
62
- question: "deny",
63
- });
64
- /**
65
- * Compose the config handed to a headless opencode server. The permission base
66
- * layer fills only the classes the caller leaves unspecified: an explicit caller
67
- * permission key wins per key and every non-permission key passes through
68
- * untouched. A caller that supplies a non-object `permission` shorthand states
69
- * the whole policy and keeps it.
70
- */
19
+ // v2.0.20 Agent.Info.default asks only for external_directory and env reads;
20
+ // question separately requests permission before awaiting its form reply.
21
+ const HEADLESS_PERMISSION_BASE = [
22
+ { action: "external_directory", resource: "*", effect: "allow" },
23
+ { action: "read", resource: "*", effect: "allow" },
24
+ { action: "question", resource: "*", effect: "deny" },
25
+ ];
71
26
  export function composeHeadlessPermissionConfig(config) {
72
- const permission = config?.permission;
73
- if (permission !== undefined && !isRecord(permission))
74
- return { ...config };
75
- return { ...config, permission: { ...HEADLESS_PERMISSION_BASE, ...permission } };
27
+ const permissions = config?.permissions;
28
+ if (permissions !== undefined && !Array.isArray(permissions))
29
+ throw new Error("OpenCode v2 permissions must be a ruleset");
30
+ const actions = new Set((permissions ?? []).map((rule) => object(rule)?.action));
31
+ return {
32
+ ...config,
33
+ permissions: [...HEADLESS_PERMISSION_BASE.filter((rule) => !actions.has(rule.action)), ...(permissions ?? [])],
34
+ };
76
35
  }
77
- async function waitReady(client, cwd, signal) {
36
+ /** A successful HTML fallback, or a JSON response from another API, is not readiness. */
37
+ export async function waitReady(json, signal) {
38
+ let last;
78
39
  for (let attempt = 0; attempt < 100; attempt += 1) {
79
- if (signal.aborted)
80
- throw new Error("OpenCode startup aborted");
40
+ signal.throwIfAborted();
81
41
  try {
82
- await abortable(client.session.list({ query: { directory: cwd }, throwOnError: true }), signal);
42
+ const info = object(await abortable(json("/api/info"), signal));
43
+ if (typeof info?.version !== "string" ||
44
+ !info.version.startsWith("2.") ||
45
+ typeof info.pid !== "number" ||
46
+ !Array.isArray(info.urls) ||
47
+ typeof object(info.paths)?.tmp !== "string") {
48
+ throw new Error("OpenCode readiness requires native v2 server identity");
49
+ }
83
50
  return;
84
51
  }
85
- catch {
52
+ catch (error) {
53
+ last = error;
54
+ signal.throwIfAborted();
86
55
  await new Promise((resolve) => setTimeout(resolve, 100));
87
56
  }
88
57
  }
89
- throw new Error("OpenCode server did not become ready within 10000ms");
58
+ throw new Error(`OpenCode server did not become ready within 10000ms: ${String(last)}`);
90
59
  }
91
60
  async function availablePort() {
92
61
  return await new Promise((resolve, reject) => {
@@ -105,15 +74,17 @@ async function availablePort() {
105
74
  }
106
75
  export async function loadOpencode(input) {
107
76
  const { execution, cwd, signal, loader, onRuntime, requests } = input;
108
- if (loader) {
109
- const loaded = await abortable(loader(cwd, { ...execution, env: akumaExecutionEnvironment(process.env, execution.env, requests) }, signal), signal);
77
+ if (loader !== undefined) {
78
+ const loaded = await abortable(loader(cwd, {
79
+ ...execution,
80
+ env: akumaExecutionEnvironment(process.env, execution.env, requests),
81
+ }, signal), signal);
110
82
  let closing;
111
83
  const runtime = {
112
84
  client: loaded.client,
113
- close: async () => {
114
- closing ??= Promise.resolve(loaded.close?.()).then(() => undefined);
115
- await closing;
116
- },
85
+ close: () => (closing ??= Promise.resolve()
86
+ .then(() => loaded.close?.())
87
+ .then(() => undefined)),
117
88
  };
118
89
  onRuntime?.(runtime);
119
90
  try {
@@ -128,34 +99,56 @@ export async function loadOpencode(input) {
128
99
  }
129
100
  }
130
101
  const port = await availablePort();
102
+ signal.throwIfAborted();
103
+ const password = execution.env?.OPENCODE_PASSWORD ??
104
+ execution.env?.OPENCODE_SERVER_PASSWORD ??
105
+ randomBytes(32).toString("base64url");
106
+ const config = composeHeadlessPermissionConfig(execution.config);
131
107
  const owned = await spawnDetachedProcess({
132
108
  argv: [execution.executable ?? "opencode", "serve", "--hostname", "127.0.0.1", "--port", String(port)],
133
109
  cwd,
134
110
  env: akumaExecutionEnvironment(process.env, {
135
- OPENCODE_CONFIG_CONTENT: JSON.stringify(composeHeadlessPermissionConfig(execution.config)),
111
+ OPENCODE_CONFIG_CONTENT: JSON.stringify(config),
136
112
  ...execution.env,
113
+ OPENCODE_PASSWORD: password,
114
+ OPENCODE_SERVER_PASSWORD: password,
137
115
  }, requests),
138
116
  log: `${cwd}/.opencode.log`,
139
117
  });
140
- const { createOpencodeClient } = await import("@opencode-ai/sdk");
141
- const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}`, directory: cwd });
118
+ const native = createNativeClient(`http://127.0.0.1:${port}`, password);
142
119
  let closing;
120
+ const options = { signal };
143
121
  const runtime = {
144
- client: { session: client.session, event: opencodeEventPort(client.event) },
145
- close: async () => {
146
- closing ??= owned.terminate();
147
- await closing;
122
+ client: {
123
+ session: {
124
+ create: async ({ body }) => ({ data: await native.sdk.session.create(body, options) }),
125
+ get: async ({ id }) => ({ data: await native.sdk.session.get({ sessionID: id }, options) }),
126
+ switchModel: ({ id, model }) => native.sdk.session.switchModel({ sessionID: id, model }, options),
127
+ instructions: ({ id, text }) => native.sdk.session.instructions.entry.put({ sessionID: id, key: "keiyaku.system", value: text }, options),
128
+ active: async () => ({ data: await native.sdk.session.active(options) }),
129
+ fork: async ({ id, before }) => ({ data: await native.sdk.session.fork({ sessionID: id, before }, options) }),
130
+ interrupt: ({ id }) => native.sdk.session.interrupt({ sessionID: id, resume: false }),
131
+ prompt: async ({ id, body }) => ({
132
+ data: await native.sdk.session.prompt({ sessionID: id, ...body }, options),
133
+ }),
134
+ message: async ({ id, messageID }) => ({
135
+ data: await native.sdk.session.message.get({ sessionID: id, messageID }, options),
136
+ }),
137
+ },
138
+ event: { subscribe: ({ signal }) => native.subscribe(signal) },
148
139
  },
140
+ close: () => (closing ??= Promise.all([native.close(), owned.terminate()]).then(() => undefined)),
149
141
  };
150
142
  onRuntime?.(runtime);
151
143
  try {
152
- await waitReady(client, cwd, signal);
144
+ await waitReady(() => native.sdk.server.info({ signal }), signal);
145
+ signal.throwIfAborted();
146
+ return runtime;
153
147
  }
154
148
  catch (error) {
155
149
  await runtime.close();
156
150
  throw error;
157
151
  }
158
- return runtime;
159
152
  }
160
153
  export function coordinate(sessionId) {
161
154
  return { sessionId };
@@ -1,5 +1,13 @@
1
1
  import type { AgentStreamEventPayload } from "@getpaseo/protocol/messages";
2
+ import type { AgentRuntimeInfo } from "@getpaseo/protocol/agent-types";
2
3
  import { AgentEventChannel, type TurnResult } from "../../provider.js";
4
+ /**
5
+ * The runtime's own confirmation at establishment, reported as a native
6
+ * provider model id. The installed Paseo wire carries no mid-attempt model
7
+ * change on this timeline subscription, so a later re-selection is not
8
+ * observable here and is never invented.
9
+ */
10
+ export declare function paseoRuntimeModel(info: AgentRuntimeInfo | undefined): string | undefined;
3
11
  /** Live fragments belong to a canonical admitted turn, not agent-wide status. */
4
12
  export declare class PaseoTurn {
5
13
  readonly messageId: string;
@@ -1,4 +1,45 @@
1
1
  import { AgentEventChannel, otherToolCall } from "../../provider.js";
2
+ import { stringValue } from "../decode.js";
3
+ /**
4
+ * The runtime's own confirmation at establishment, reported as a native
5
+ * provider model id. The installed Paseo wire carries no mid-attempt model
6
+ * change on this timeline subscription, so a later re-selection is not
7
+ * observable here and is never invented.
8
+ */
9
+ export function paseoRuntimeModel(info) {
10
+ const model = info?.model;
11
+ return typeof model === "string" && model.trim().length > 0 ? model : undefined;
12
+ }
13
+ /**
14
+ * The observed result text one native detail carries. A detail that observes no
15
+ * text stays absent; a present empty string remains observed and empty.
16
+ */
17
+ function detailOutput(detail) {
18
+ switch (detail.type) {
19
+ case "shell":
20
+ return stringValue(detail.output);
21
+ case "read":
22
+ case "search":
23
+ return stringValue(detail.content);
24
+ case "fetch":
25
+ return stringValue(detail.result);
26
+ default:
27
+ return undefined;
28
+ }
29
+ }
30
+ /** The verified unified patch a completed edit detail observed, when it supplied one. */
31
+ function detailPatch(detail) {
32
+ return detail.type === "edit" ? stringValue(detail.unifiedDiff) : undefined;
33
+ }
34
+ /** Attach the observed patch to the one change its correlated path already named. */
35
+ function withObservedPatch(call, patch) {
36
+ if (call.kind !== "fileChange")
37
+ return call;
38
+ const change = call.changes[0];
39
+ if (patch === undefined || change === undefined || call.changes.length !== 1)
40
+ return call;
41
+ return { kind: "fileChange", changes: [{ ...change, patch }] };
42
+ }
2
43
  function toolCall(item) {
3
44
  const detail = item.detail;
4
45
  switch (detail.type) {
@@ -25,18 +66,24 @@ function toolCall(item) {
25
66
  function timelineNarration(item, calls) {
26
67
  switch (item.type) {
27
68
  case "tool_call": {
28
- const call = calls.get(item.callId) ?? (item.status === "running" ? toolCall(item) : otherToolCall(item.name));
69
+ const base = calls.get(item.callId) ?? (item.status === "running" ? toolCall(item) : otherToolCall(item.name));
70
+ const call = item.status === "running" ? base : withObservedPatch(base, detailPatch(item.detail));
29
71
  calls.set(item.callId, call);
30
- return item.status === "running"
31
- ? { type: "tool", phase: "started", id: item.callId, name: item.name, call }
32
- : {
33
- type: "tool",
34
- phase: "completed",
35
- id: item.callId,
36
- name: item.name,
37
- call,
38
- result: { status: item.status === "completed" ? "ok" : "error" },
39
- };
72
+ if (item.status === "running") {
73
+ return { type: "tool", phase: "started", id: item.callId, name: item.name, call };
74
+ }
75
+ const output = detailOutput(item.detail);
76
+ return {
77
+ type: "tool",
78
+ phase: "completed",
79
+ id: item.callId,
80
+ name: item.name,
81
+ call,
82
+ result: {
83
+ status: item.status === "completed" ? "ok" : "error",
84
+ ...(output === undefined ? {} : { output }),
85
+ },
86
+ };
40
87
  }
41
88
  case "error":
42
89
  case "notification":
@@ -5,8 +5,9 @@ import { z } from "zod";
5
5
  import { DaemonClient } from "@getpaseo/client/internal/daemon-client";
6
6
  import { bindWsHandler, createWebSocketTransportFactory, defaultWebSocketFactory, } from "@getpaseo/client/internal/daemon-client-websocket-transport";
7
7
  import { abortable } from "../../abort.js";
8
+ import { launchPrompt, modelEvent, } from "../../provider.js";
8
9
  import { AKUMA_REQUESTS_ENTRY_ENV, akumaExecutionEnvironment } from "../execution-environment.js";
9
- import { PaseoTurn } from "./events.js";
10
+ import { PaseoTurn, paseoRuntimeModel } from "./events.js";
10
11
  import { providerLifecycle } from "../lifecycle.js";
11
12
  const configSchema = z
12
13
  .object({
@@ -169,6 +170,11 @@ async function applyOptions(client, agent, input, config) {
169
170
  (observed.agent.effectiveThinkingOptionId ?? observed.agent.runtimeInfo?.thinkingOptionId) !== effort) {
170
171
  throw new Error(`Paseo effort selection was not confirmed: ${effort}`);
171
172
  }
173
+ // The runtime's own answer names the model this agent will run, whether or
174
+ // not the caller requested one. The snapshot's top-level model may be a
175
+ // configured fallback rather than what the runtime loaded, so it is never
176
+ // runtime evidence; an unnamed runtime stays unknown and clears nothing.
177
+ return paseoRuntimeModel(observed.agent.runtimeInfo);
172
178
  }
173
179
  class PaseoDrive {
174
180
  client;
@@ -216,18 +222,16 @@ class PaseoDrive {
216
222
  }))
217
223
  : await this.existingAgent(coordinate, input.signal);
218
224
  this.agentId = agent.id;
219
- await applyOptions(this.client, agent, input, config);
225
+ const startupModel = await applyOptions(this.client, agent, input, config);
220
226
  input.signal.throwIfAborted();
221
227
  const subscription = this.client.subscribeAgentTimeline(agent.id, (message) => this.receive(message));
222
228
  await abortable(subscription.ready, input.signal);
223
229
  this.turn.events.emit({ type: "session", coordinate: { sessionId: agent.id } });
230
+ if (startupModel !== undefined)
231
+ this.turn.events.emit(modelEvent(startupModel));
224
232
  // Native steer may still replace work if a human wins the check/send race.
225
233
  input.signal.throwIfAborted();
226
- const parts = [input.body, ...input.launchTells.map((tell) => tell.text)];
227
- if (input.schemaJson !== undefined) {
228
- parts.push("Respond with JSON matching this JSON Schema and no other text:", input.schemaJson);
229
- }
230
- const prompt = parts.filter((text) => text.length > 0).join("\n\n");
234
+ const prompt = launchPrompt(input.body, input.launchTells, input.schemaJson);
231
235
  await setup(input.signal, () => {
232
236
  this.submitted = true;
233
237
  return this.client.sendMessage(agent.id, prompt, { messageId: this.turn.messageId, activeTurnBehavior: "steer" });
@@ -25,6 +25,8 @@ export declare const PI_EVENT_DISPOSITIONS: {
25
25
  readonly turn_end: "drop";
26
26
  readonly turn_start: "drop";
27
27
  };
28
+ /** The model this native session will run, from its own selection. */
29
+ export declare function piSessionModel(selected: unknown): string | undefined;
28
30
  export type PiEventState = {
29
31
  answer: string;
30
32
  assistantSeen: boolean;
@@ -1,5 +1,5 @@
1
- import { noteEvent, otherToolCall, unknownEvent } from "../../provider.js";
2
- import { nonblank, positiveLine, searchToolCall } from "../decode.js";
1
+ import { modelEvent, noteEvent, otherToolCall, unknownEvent } from "../../provider.js";
2
+ import { nonblank, observedTextContent, positiveLine, searchToolCall } from "../decode.js";
3
3
  import { diffstatFromUnifiedPatch } from "../unified-patch.js";
4
4
  export const PI_EVENT_DISPOSITIONS = {
5
5
  agent_end: "drop",
@@ -32,25 +32,25 @@ function record(value) {
32
32
  : null;
33
33
  }
34
34
  function textContent(content) {
35
- if (typeof content === "string")
36
- return content;
37
- if (!Array.isArray(content))
38
- return "";
39
- return content
40
- .map((block) => {
41
- const value = record(block);
42
- return value?.type === "text" && typeof value.text === "string" ? value.text : "";
43
- })
44
- .join("");
35
+ return observedTextContent(content) ?? "";
45
36
  }
46
37
  function hasTextContent(content) {
47
- if (typeof content === "string")
48
- return true;
49
- return (Array.isArray(content) &&
50
- content.some((block) => {
51
- const value = record(block);
52
- return value?.type === "text" && typeof value.text === "string";
53
- }));
38
+ return observedTextContent(content) !== undefined;
39
+ }
40
+ /**
41
+ * Pi identifies a model as `<provider>/<id>`, the same convention its model
42
+ * option uses. The selected model and every recorded response carry both
43
+ * halves; a response without them names no model and stays unknown.
44
+ */
45
+ function piModel(provider, id) {
46
+ const owner = nonblank(provider);
47
+ const name = nonblank(id);
48
+ return owner === undefined || name === undefined ? undefined : `${owner}/${name}`;
49
+ }
50
+ /** The model this native session will run, from its own selection. */
51
+ export function piSessionModel(selected) {
52
+ const value = record(selected);
53
+ return piModel(value?.provider, value?.id);
54
54
  }
55
55
  function runCall(name, value) {
56
56
  return name === "bash" && typeof value.command === "string" ? { kind: "run", command: value.command } : undefined;
@@ -89,13 +89,14 @@ function refineEditCall(call, result) {
89
89
  if (call.kind !== "fileChange")
90
90
  return call;
91
91
  const details = record(record(result)?.details);
92
- if (typeof details?.patch !== "string")
92
+ const patch = typeof details?.patch === "string" ? details.patch : undefined;
93
+ if (patch === undefined)
93
94
  return call;
94
- const diffstat = diffstatFromUnifiedPatch(details.patch);
95
+ const diffstat = diffstatFromUnifiedPatch(patch);
95
96
  const change = call.changes[0];
96
- if (diffstat === undefined || change === undefined)
97
+ if (change === undefined)
97
98
  return call;
98
- return { kind: "fileChange", changes: [{ ...change, diffstat }] };
99
+ return { kind: "fileChange", changes: [{ ...change, ...(diffstat === undefined ? {} : { diffstat }), patch }] };
99
100
  }
100
101
  function toolCall(name, args) {
101
102
  const value = record(args) ?? {};
@@ -121,6 +122,7 @@ function translateToolEnd(event, state) {
121
122
  state.tools.delete(event.toolCallId);
122
123
  const name = started?.name ?? event.toolName;
123
124
  const call = started?.call ?? otherToolCall(name);
125
+ const output = observedTextContent(record(event.result)?.content);
124
126
  return [
125
127
  {
126
128
  type: "tool",
@@ -128,7 +130,10 @@ function translateToolEnd(event, state) {
128
130
  id: event.toolCallId,
129
131
  name,
130
132
  call: event.isError ? call : refineEditCall(call, event.result),
131
- result: { status: event.isError ? "error" : "ok" },
133
+ result: {
134
+ status: event.isError ? "error" : "ok",
135
+ ...(output === undefined ? {} : { output }),
136
+ },
132
137
  },
133
138
  ];
134
139
  }
@@ -137,6 +142,9 @@ function translateMessage(event, state) {
137
142
  if (message?.role !== "assistant")
138
143
  return [];
139
144
  const translated = [];
145
+ const model = piModel(message.provider, message.model);
146
+ if (model !== undefined)
147
+ translated.push(modelEvent(model));
140
148
  if (Array.isArray(message.content)) {
141
149
  for (const block of message.content) {
142
150
  const value = record(block);