@astrosheep/keiyaku 4.5.28 → 4.5.29

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 (97) hide show
  1. package/build/src/akuma/akuma-handle.d.ts +7 -0
  2. package/build/src/akuma/akuma-handle.js +19 -6
  3. package/build/src/akuma/akuma-instance.d.ts +0 -1
  4. package/build/src/akuma/akuma-instance.js +2 -4
  5. package/build/src/akuma/akuma-observe.js +0 -1
  6. package/build/src/akuma/akuma-product.d.ts +4 -8
  7. package/build/src/akuma/akuma-product.js +3 -20
  8. package/build/src/akuma/akuma.d.ts +0 -8
  9. package/build/src/akuma/akuma.js +0 -5
  10. package/build/src/akuma/archetype.d.ts +6 -6
  11. package/build/src/akuma/archetype.js +18 -28
  12. package/build/src/akuma/body.d.ts +4 -26
  13. package/build/src/akuma/body.js +18 -2
  14. package/build/src/akuma/call-initial-tell.d.ts +55 -0
  15. package/build/src/akuma/call-initial-tell.js +32 -0
  16. package/build/src/akuma/call-input.d.ts +0 -3
  17. package/build/src/akuma/call-input.js +0 -7
  18. package/build/src/akuma/call-request.d.ts +13 -10
  19. package/build/src/akuma/call-request.js +26 -14
  20. package/build/src/akuma/fleet-execution.d.ts +13 -1
  21. package/build/src/akuma/fleet-execution.js +67 -14
  22. package/build/src/akuma/fleet-observation.d.ts +26 -18
  23. package/build/src/akuma/fleet-observation.js +7 -1
  24. package/build/src/akuma/heart/facts.d.ts +2 -3
  25. package/build/src/akuma/heart/soul.js +1 -6
  26. package/build/src/akuma/projection-read.js +1 -2
  27. package/build/src/akuma/provider-recipe.d.ts +0 -10
  28. package/build/src/akuma/provider-recipe.js +0 -23
  29. package/build/src/akuma/provider.d.ts +1 -2
  30. package/build/src/akuma/providers/acp/index.js +0 -11
  31. package/build/src/akuma/providers/claude/index.js +2 -14
  32. package/build/src/akuma/providers/codex-app-server/index.js +0 -22
  33. package/build/src/akuma/providers/grok-build/index.js +0 -11
  34. package/build/src/akuma/providers/opencode-sdk/index.js +0 -10
  35. package/build/src/akuma/providers/pi/index.js +1 -5
  36. package/build/src/akuma/requests.d.ts +0 -1
  37. package/build/src/akuma/requests.js +0 -7
  38. package/build/src/akuma/turn-drive.d.ts +3 -0
  39. package/build/src/akuma/turn-drive.js +2 -2
  40. package/build/src/{library → cli}/catalog.d.ts +7 -30
  41. package/build/src/cli/catalog.js +1 -0
  42. package/build/src/cli/commands/akuma-invoke.d.ts +3 -3
  43. package/build/src/cli/commands/akuma-invoke.js +70 -97
  44. package/build/src/cli/commands/akuma.js +28 -21
  45. package/build/src/cli/commands/bind.js +4 -5
  46. package/build/src/cli/commands/contract-help.d.ts +21 -18
  47. package/build/src/cli/commands/contract-help.js +44 -84
  48. package/build/src/cli/commands/contract-invoke.js +6 -6
  49. package/build/src/cli/commands/contract.d.ts +1 -1
  50. package/build/src/cli/commands/contract.js +1 -6
  51. package/build/src/cli/commands/install.d.ts +1 -1
  52. package/build/src/cli/commands/install.js +1 -1
  53. package/build/src/cli/commands/nuke.js +2 -2
  54. package/build/src/cli/commands/task.js +48 -16
  55. package/build/src/cli/invoke.js +48 -11
  56. package/build/src/cli/parse.js +2 -0
  57. package/build/src/cli/render/akuma-activity.d.ts +42 -11
  58. package/build/src/cli/render/akuma-activity.js +307 -125
  59. package/build/src/cli/render/akuma-tool.d.ts +7 -1
  60. package/build/src/cli/render/akuma-tool.js +35 -23
  61. package/build/src/cli/render/akuma.d.ts +5 -9
  62. package/build/src/cli/render/akuma.js +68 -98
  63. package/build/src/cli/render/audit.js +2 -2
  64. package/build/src/cli/render/catalog.d.ts +1 -1
  65. package/build/src/cli/render/catalog.js +1 -0
  66. package/build/src/cli/render/contract.js +3 -3
  67. package/build/src/cli/render/kanshi.js +2 -2
  68. package/build/src/cli/render/receipt.js +4 -4
  69. package/build/src/cli/render/refusal.js +2 -2
  70. package/build/src/cli/render/settings.js +2 -2
  71. package/build/src/cli/render/task.js +2 -2
  72. package/build/src/cli/render/terminal.d.ts +5 -0
  73. package/build/src/cli/render/terminal.js +13 -0
  74. package/build/src/cli/result.d.ts +1 -1
  75. package/build/src/cli/runtime.js +10 -4
  76. package/build/src/cli/selectors.js +2 -2
  77. package/build/src/index.d.ts +1 -0
  78. package/build/src/index.js +1 -0
  79. package/build/src/library/akuma-creation.d.ts +28 -15
  80. package/build/src/library/akuma-creation.js +141 -107
  81. package/build/src/library/akumas.d.ts +42 -0
  82. package/build/src/library/akumas.js +61 -0
  83. package/build/src/library/composition.d.ts +14 -402
  84. package/build/src/library/composition.js +24 -25
  85. package/build/src/library/contract-handle.d.ts +7 -2
  86. package/build/src/library/contract-handle.js +14 -2
  87. package/build/src/library/contract-types.d.ts +9 -2
  88. package/build/src/library/contract.d.ts +8 -15
  89. package/build/src/library/contract.js +21 -39
  90. package/build/src/library/fleet.js +12 -12
  91. package/build/src/library/keiyaku.d.ts +8 -424
  92. package/build/src/library/keiyaku.js +3 -17
  93. package/build/src/runtime/proc/windows-launch.exe +0 -0
  94. package/package.json +5 -1
  95. package/build/src/akuma/akuma-product-symbols.d.ts +0 -1
  96. package/build/src/akuma/akuma-product-symbols.js +0 -1
  97. package/build/src/library/catalog.js +0 -130
@@ -39,24 +39,14 @@ class ReceiptChannel {
39
39
  }
40
40
  }
41
41
  function admitClaudeOptions(options) {
42
- if (options.sandbox === "full-access" && options.readonly === true) {
43
- return { kind: "refused", diagnostic: "Claude full-access sandbox cannot combine with readonly" };
44
- }
45
- if (options.sandbox === "full-access" && options.network === "disabled") {
46
- return { kind: "refused", diagnostic: "Claude full-access sandbox cannot combine with disabled network" };
47
- }
48
42
  if (options.network !== undefined) {
49
43
  return { kind: "refused", diagnostic: "Claude provider does not support the network option" };
50
44
  }
51
45
  return {
52
46
  kind: "admitted",
53
47
  options: Object.freeze({ ...options }),
54
- ...(options.readonly === undefined ? {} : { readonly: { enforcement: "native" } }),
55
48
  };
56
49
  }
57
- function permissionMode(readonly) {
58
- return readonly === true ? "plan" : "bypassPermissions";
59
- }
60
50
  function claudeSessionId(coordinate) {
61
51
  if (!("sessionId" in coordinate) || coordinate.sessionId === undefined) {
62
52
  throw new Error("Claude resume requires sessionId");
@@ -64,7 +54,6 @@ function claudeSessionId(coordinate) {
64
54
  return coordinate.sessionId;
65
55
  }
66
56
  function claudeQueryOptions(input, execution, abortController) {
67
- const mode = permissionMode(input.options.readonly);
68
57
  return {
69
58
  ...(execution.config ?? {}),
70
59
  cwd: input.cwd,
@@ -72,10 +61,9 @@ function claudeQueryOptions(input, execution, abortController) {
72
61
  ...(input.requests === undefined ? {} : { additionalDirectories: [input.requests.dir] }),
73
62
  ...(execution.executable === undefined ? {} : { pathToClaudeCodeExecutable: execution.executable }),
74
63
  env: akumaExecutionEnvironment(process.env, execution.env, input.requests?.dir),
75
- permissionMode: mode,
76
- ...(mode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
64
+ permissionMode: "bypassPermissions",
65
+ allowDangerouslySkipPermissions: true,
77
66
  settingSources: ["user", "project", "local"],
78
- ...(input.options.sandbox === "full-access" ? { sandbox: { enabled: false } } : {}),
79
67
  ...(input.options.model === undefined ? {} : { model: input.options.model }),
80
68
  ...(input.options.effort === undefined ? {} : { effort: input.options.effort }),
81
69
  ...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0
@@ -48,20 +48,6 @@ async function steerTurn(server, state, tell) {
48
48
  throw new Error("codex app-server steer acknowledged a different turn");
49
49
  return { kind: "accepted", fence: `${accepted}:${tell.id}` };
50
50
  }
51
- function sandbox(cwd, options, requests) {
52
- if (options.sandbox === "full-access")
53
- return { type: "dangerFullAccess" };
54
- if (options.readonly === true) {
55
- return { type: "readOnly", networkAccess: options.network === "enabled" };
56
- }
57
- return {
58
- type: "workspaceWrite",
59
- writableRoots: [cwd, ...(requests === undefined ? [] : [requests.dir])],
60
- networkAccess: options.network === "enabled",
61
- excludeTmpdirEnvVar: false,
62
- excludeSlashTmp: false,
63
- };
64
- }
65
51
  async function admitTurn(server, input, state, events, config) {
66
52
  await initialize(server);
67
53
  const threadParams = {
@@ -97,7 +83,6 @@ async function admitTurn(server, input, state, events, config) {
97
83
  ...(input.options.model === undefined ? {} : { model: input.options.model }),
98
84
  ...(input.options.effort === undefined ? {} : { effort: input.options.effort }),
99
85
  approvalPolicy: "never",
100
- sandboxPolicy: sandbox(input.cwd, input.options, input.requests),
101
86
  ...(input.schemaJson === undefined ? {} : { outputSchema: JSON.parse(input.schemaJson) }),
102
87
  }));
103
88
  }
@@ -223,16 +208,9 @@ export function createCodexAppServerProvider(input = "codex") {
223
208
  const execution = typeof input === "string" ? { name: "codex-app-server", kind: "codex-app-server", executable: input } : input;
224
209
  return {
225
210
  admitOptions(options) {
226
- if (options.sandbox === "full-access" && options.readonly === true) {
227
- return { kind: "refused", diagnostic: "Codex full-access sandbox cannot combine with readonly" };
228
- }
229
- if (options.sandbox === "full-access" && options.network === "disabled") {
230
- return { kind: "refused", diagnostic: "Codex full-access sandbox cannot combine with disabled network" };
231
- }
232
211
  return {
233
212
  kind: "admitted",
234
213
  options: Object.freeze({ ...options }),
235
- ...(options.readonly === undefined ? {} : { readonly: { enforcement: "native" } }),
236
214
  };
237
215
  },
238
216
  fork: (input) => createProviderAttempt(new AbortController().signal, async (custody) => await forkCodex(execution, input, custody)),
@@ -67,9 +67,6 @@ export const interpretGrokTool = (update) => {
67
67
  fileChangeCall(name, input));
68
68
  };
69
69
  function optionAdmission(options) {
70
- if (options.sandbox !== undefined) {
71
- return { kind: "refused", diagnostic: "Grok Build does not support the sandbox option" };
72
- }
73
70
  if (options.network !== undefined) {
74
71
  return { kind: "refused", diagnostic: "Grok Build does not support the network option" };
75
72
  }
@@ -79,14 +76,6 @@ function optionAdmission(options) {
79
76
  return {
80
77
  kind: "admitted",
81
78
  options,
82
- ...(options.readonly === undefined
83
- ? {}
84
- : {
85
- readonly: {
86
- enforcement: "none",
87
- diagnostic: "Grok Build cannot remove task-surface mutation capabilities",
88
- },
89
- }),
90
79
  };
91
80
  }
92
81
  function grokSessionMeta(config, options) {
@@ -22,8 +22,6 @@ function opencodeSessionId(coordinateValue) {
22
22
  return coordinateValue.sessionId;
23
23
  }
24
24
  function admit(options) {
25
- if (options.sandbox !== undefined)
26
- throw new Error("OpenCode does not support the sandbox option");
27
25
  if (options.network !== undefined)
28
26
  throw new Error("OpenCode does not support explicit network");
29
27
  if (options.systemPromptMode === "replace") {
@@ -329,14 +327,6 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
329
327
  return {
330
328
  kind: "admitted",
331
329
  options: Object.freeze({ ...options }),
332
- ...(options.readonly === undefined
333
- ? {}
334
- : {
335
- readonly: {
336
- enforcement: "none",
337
- diagnostic: "OpenCode V1 cannot remove task-surface mutation capabilities",
338
- },
339
- }),
340
330
  };
341
331
  },
342
332
  start: (input) => createProviderAttempt(input.signal, async (custody) => await drive(execution, { ...input, signal: custody.signal }, loader, custody)),
@@ -8,8 +8,6 @@ function diagnostic(error) {
8
8
  return error instanceof Error ? error.message : String(error);
9
9
  }
10
10
  function admitPiOptions(options) {
11
- if (options.sandbox !== undefined)
12
- return { kind: "refused", diagnostic: "Pi provider does not support the sandbox option" };
13
11
  if (options.network !== undefined)
14
12
  return { kind: "refused", diagnostic: "Pi provider does not support the network option" };
15
13
  if (options.model !== undefined && !MODEL_PATTERN.test(options.model)) {
@@ -21,7 +19,6 @@ function admitPiOptions(options) {
21
19
  return {
22
20
  kind: "admitted",
23
21
  options: Object.freeze({ ...options }),
24
- ...(options.readonly === undefined ? {} : { readonly: { enforcement: "native" } }),
25
22
  };
26
23
  }
27
24
  async function piCreateOptions(sdk, input) {
@@ -51,7 +48,7 @@ async function piCreateOptions(sdk, input) {
51
48
  : (() => {
52
49
  throw new Error("Pi resume requires sessionFile");
53
50
  })();
54
- const customTools = input.requests === undefined || input.options.readonly === true
51
+ const customTools = input.requests === undefined
55
52
  ? undefined
56
53
  : [
57
54
  sdk.createBashToolDefinition(input.cwd, {
@@ -67,7 +64,6 @@ async function piCreateOptions(sdk, input) {
67
64
  ...(model === undefined || modelRuntime === undefined ? {} : { model, modelRuntime }),
68
65
  ...(resourceLoader === undefined ? {} : { resourceLoader }),
69
66
  ...(input.options.effort === undefined ? {} : { thinkingLevel: input.options.effort }),
70
- ...(input.options.readonly === undefined ? {} : { tools: ["read", "grep", "find", "ls"] }),
71
67
  ...(customTools === undefined ? {} : { customTools }),
72
68
  };
73
69
  }
@@ -17,5 +17,4 @@ export declare function bodyRequestExecution(input: Readonly<{
17
17
  }>): LibraryExecution;
18
18
  /** Normalizes the public carrier before a composition captures its immutable channel. */
19
19
  export declare function libraryExecution(value: unknown): LibraryExecution;
20
- export declare function libraryExecutionInput(input: unknown): LibraryExecution;
21
20
  export declare function executionChannel(context?: ExecutionContext): ExecutionChannel;
@@ -52,13 +52,6 @@ export function libraryExecution(value) {
52
52
  }
53
53
  throw new TypeError("execution channel must be local or body-request");
54
54
  }
55
- export function libraryExecutionInput(input) {
56
- const value = valueRecord(input, "Keiyaku.withExecution input");
57
- closed(value, ["execution"], "Keiyaku.withExecution input");
58
- if (value.execution === undefined)
59
- throw new TypeError("Keiyaku.withExecution input requires execution");
60
- return libraryExecution(value.execution);
61
- }
62
55
  export function executionChannel(context) {
63
56
  return context?.channel ?? LOCAL_CHANNEL;
64
57
  }
@@ -1,5 +1,7 @@
1
1
  import { type ResumeCoordinate, type Soul, type TellFact } from "./heart/index.js";
2
2
  import type { AkumaPaths } from "./identity.js";
3
+ import type { CallInitialTellAdmission } from "./call-initial-tell.js";
4
+ import type { InitialTellAdmissionRequest } from "./call-request.js";
3
5
  import { type ProviderAdapter, type TurnResult } from "./provider.js";
4
6
  import { type AkumaCallRequestChildLaunch } from "./call-request.js";
5
7
  import { type ErasedRequestCommand } from "./request-wire.js";
@@ -29,6 +31,7 @@ export type DriveTurnInput = Readonly<{
29
31
  schemaJson?: string;
30
32
  supervisor: BodySupervisor;
31
33
  runtimeSpawn(launch: AkumaCallRequestChildLaunch): Promise<OwnedProcess | void>;
34
+ admitInitialTell(input: InitialTellAdmissionRequest): Promise<CallInitialTellAdmission>;
32
35
  world: import("../world.js").WorldRoot;
33
36
  externalCommands: Readonly<Record<string, ErasedRequestCommand>>;
34
37
  now(): string;
@@ -87,8 +87,8 @@ async function startTurnDrive(input) {
87
87
  boundAt: input.now(),
88
88
  });
89
89
  }
90
- const { world, paths, soul: parent, runtimeSpawn: spawn, externalCommands } = input;
91
- const commands = composeRequestCommands(akumaCallRequestCommands({ world, paths, parent, spawn }), externalCommands);
90
+ const { world, paths, soul: parent, runtimeSpawn: spawn, externalCommands, admitInitialTell } = input;
91
+ const commands = composeRequestCommands(akumaCallRequestCommands({ world, paths, parent, spawn, admitInitialTell }), externalCommands);
92
92
  const requests = await BodyRequestPump.open({
93
93
  paths: input.paths,
94
94
  allowed: input.soul.allowed,
@@ -1,10 +1,8 @@
1
- /** @architectureCompositionRoot */
2
- import { type AkumaList } from "../akuma/akuma.js";
3
- import { type ArchetypeCatalogRow } from "../akuma/archetype.js";
1
+ import type { AkumaList } from "../akuma/akuma.js";
2
+ import type { ArchetypeCatalogRow } from "../akuma/archetype.js";
4
3
  import type { TaskRow } from "../task/index.js";
5
- import { type ContractCatalogue } from "./contract.js";
6
- import { Repo } from "./repo.js";
7
- import { type WorldRoot } from "../world.js";
4
+ import type { ContractRow } from "../library/contract.js";
5
+ import type { WorldRoot } from "../world.js";
8
6
  export type CatalogQuery = Readonly<{
9
7
  kind: "tasks";
10
8
  namespace?: readonly string[];
@@ -14,33 +12,12 @@ export type CatalogQuery = Readonly<{
14
12
  limit?: number;
15
13
  }> | Readonly<{
16
14
  kind: "archetypes";
15
+ limit?: number;
17
16
  }> | Readonly<{
18
17
  kind: "akuma";
19
18
  archetype?: string;
20
19
  limit?: number;
21
20
  }>;
22
- export type CatalogInput = Readonly<{
23
- query: Extract<CatalogQuery, {
24
- kind: "tasks";
25
- }>;
26
- path: WorldRoot;
27
- }> | Readonly<{
28
- query: Extract<CatalogQuery, {
29
- kind: "contracts";
30
- }>;
31
- repo: Repo;
32
- }> | Readonly<{
33
- query: Extract<CatalogQuery, {
34
- kind: "archetypes";
35
- }>;
36
- path?: WorldRoot;
37
- home?: string;
38
- }> | Readonly<{
39
- query: Extract<CatalogQuery, {
40
- kind: "akuma";
41
- }>;
42
- path: WorldRoot;
43
- }>;
44
21
  export type Catalog = Readonly<{
45
22
  kind: "tasks";
46
23
  root: WorldRoot;
@@ -51,11 +28,12 @@ export type Catalog = Readonly<{
51
28
  root: string;
52
29
  state: string | null;
53
30
  observedAt: string;
54
- rows: ContractCatalogue["rows"];
31
+ rows: readonly ContractRow[];
55
32
  hasMore: boolean;
56
33
  }> | Readonly<{
57
34
  kind: "archetypes";
58
35
  rows: readonly ArchetypeCatalogRow[];
36
+ hasMore: boolean;
59
37
  }> | Readonly<{
60
38
  kind: "akuma";
61
39
  root: WorldRoot;
@@ -65,4 +43,3 @@ export type Catalog = Readonly<{
65
43
  searched: readonly string[];
66
44
  hasMore: boolean;
67
45
  }>;
68
- export declare function listCatalog(input: CatalogInput): Promise<Catalog>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { type AkuId } from "../../akuma/identity.js";
2
2
  import { type ActivityHistory } from "../../akuma/akuma.js";
3
- import { Keiyaku, type AkumaKillResult, type AkumaHistoryResult, type AkumaObservation, type AkumaTellResult, type AkumaWaitResult, type CallResult, type ForkResult, type Keiyaku as KeiyakuContract, type Repo } from "../../index.js";
3
+ import { type AkumaInterruptResult, type AkumaKillResult, type AkumaHistoryResult, type AkumaObservation, type AkumaTellResult, type AkumaWaitResult, type CallResult, type ForkResult, type Keiyaku as KeiyakuContract, type Repo } from "../../index.js";
4
4
  import { type WaitSelectedIdentity } from "../render/akuma-activity.js";
5
5
  import type { Settings } from "../../settings.js";
6
6
  import type { WorldRoot } from "../../world.js";
@@ -12,7 +12,6 @@ export type AkumaInvocationResult = Readonly<{
12
12
  action: "call";
13
13
  result: CallResult;
14
14
  world: WorldRoot;
15
- schemaAnswer?: unknown;
16
15
  streamed?: boolean;
17
16
  }> | Readonly<{
18
17
  kind: "akuma";
@@ -50,11 +49,12 @@ export type AkumaInvocationResult = Readonly<{
50
49
  result: AkumaTellWaitResult;
51
50
  body: string;
52
51
  alias?: string;
52
+ structured?: true;
53
53
  }> | Readonly<{
54
54
  kind: "akuma";
55
55
  action: "tell";
56
56
  mode: "interrupt";
57
- result: Awaited<ReturnType<typeof Keiyaku.interrupt>>;
57
+ result: AkumaInterruptResult;
58
58
  body: string;
59
59
  alias?: string;
60
60
  }> | Readonly<{
@@ -1,72 +1,36 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { squareAssignedParticipantName } from "@astrosheep/square";
3
3
  import { emitInitiatingPluginSignal } from "../../plugin/akuma-signals.js";
4
- import { observeAkumaStatus } from "../../akuma/akuma-observe.js";
5
- import { AuthorityCorruptionError, Keiyaku, } from "../../index.js";
6
- import { callObservationStream, waitObservationStream } from "../render/akuma-activity.js";
7
- import { callObservationHead, tellWaitProgressStream } from "../render/akuma.js";
8
- import { killAkuma, tellAkuma, tellWaitAkuma, waitAkuma } from "../../library/fleet.js";
4
+ import { decodeTellWaitObservation } from "../../akuma/fleet-execution.js";
5
+ import { Akumas, } from "../../index.js";
6
+ import { callObservationStream, inputWaitStream, waitObservationStream, } from "../render/akuma-activity.js";
7
+ import { callObservationHead, tellWaitProgressStream, waitedTellProgress } from "../render/akuma.js";
8
+ import { DEFAULT_CLI_COLUMNS } from "../render/terminal.js";
9
+ import { tellWaitAkuma, waitAkuma } from "../../library/fleet.js";
9
10
  import { localExecutionContext } from "../../akuma/requests.js";
10
11
  import { Akuma, Schema } from "../../akuma/index.js";
11
12
  import { addressAkuma, resolveAkuma } from "../../library/address.js";
12
13
  import { executionChannel } from "../../akuma/requests.js";
13
- import { AkumaDecodeError } from "../../akuma/akuma-errors.js";
14
14
  import { requestForwardedFleetTellAnswer } from "../../akuma/fleet-request.js";
15
- /** The window a call observes for when the caller gives no `--wait` duration. */
16
- const CALL_TIMEOUT_MS = 300_000;
15
+ function akumas(input) {
16
+ return Akumas.of(input.path, { execution: input.execution ?? localExecutionContext() });
17
+ }
17
18
  function callSignalOption(signal) {
18
19
  return signal === undefined ? {} : { signal };
19
20
  }
20
- function integrationFailure(error) {
21
- return {
22
- kind: error instanceof AuthorityCorruptionError ? "authority-corruption" : "infrastructure",
23
- diagnostic: error instanceof Error ? error.message : String(error),
24
- };
25
- }
26
21
  /** The display context the command result renders with, so live progress rows match the result. */
27
22
  function resultContext() {
28
23
  const tty = process.stderr.isTTY === true;
29
24
  return {
30
- columns: tty && Number.isInteger(process.stderr.columns) ? process.stderr.columns : 80,
25
+ columns: tty && Number.isInteger(process.stderr.columns) ? process.stderr.columns : DEFAULT_CLI_COLUMNS,
31
26
  color: tty && process.env.NO_COLOR === undefined,
32
27
  };
33
28
  }
34
29
  function writeProgress(body) {
30
+ if (body.length === 0)
31
+ return;
35
32
  process.stderr.write(body.endsWith("\n") ? body : `${body}\n`);
36
33
  }
37
- /**
38
- * The live half of an observe-mode call: the resolved birth opens one identity
39
- * frame (never the detached receipt's cwd row), each later observation prints
40
- * the settled rows that arrived since the previous one, and the return appends
41
- * one conclusion. The stream is append-only, so no final snapshot replays.
42
- */
43
- async function observeCallUntilComplete(command, input, born) {
44
- const stream = callObservationStream(resultContext(), callObservationHead(born));
45
- try {
46
- const observed = await observeAkumaStatus(input.path, born.akuma, {
47
- timeoutMs: command.timeoutMs ?? CALL_TIMEOUT_MS,
48
- ...(input.signal === undefined ? {} : { signal: input.signal }),
49
- observe: (observed) => {
50
- const lines = stream.observe(observed);
51
- if (lines.length > 0)
52
- writeProgress(lines.join("\n"));
53
- },
54
- });
55
- const conclusion = stream.conclude({ kind: "observed", reason: observed.reason, status: observed.status });
56
- if (conclusion.length > 0)
57
- writeProgress(conclusion);
58
- return { kind: "observed", reason: observed.reason, status: observed.status };
59
- }
60
- catch (error) {
61
- if (input.signal?.aborted)
62
- throw error;
63
- const failure = integrationFailure(error);
64
- const conclusion = stream.conclude({ kind: "failed", failure });
65
- if (conclusion.length > 0)
66
- writeProgress(conclusion);
67
- return { kind: "failed", failure };
68
- }
69
- }
70
34
  async function schemaFromFile(path) {
71
35
  try {
72
36
  const parsed = JSON.parse(await readFile(path, "utf8"));
@@ -166,30 +130,13 @@ async function inputInitiator(input) {
166
130
  }
167
131
  return { initiator };
168
132
  }
169
- function decodeWaitedTellSchema(result, schema) {
170
- if (result.observation.reason !== "answered")
171
- return result;
172
- if (typeof result.observation.answer !== "string") {
173
- throw new AkumaDecodeError("Answer is not valid JSON", String(result.observation.answer));
174
- }
175
- let parsed;
176
- try {
177
- parsed = JSON.parse(result.observation.answer);
178
- }
179
- catch (error) {
180
- throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer is not valid JSON", result.observation.answer);
181
- }
182
- try {
183
- return { ...result, observation: { reason: "answered", answer: schema.decode(parsed) } };
184
- }
185
- catch (error) {
186
- throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer failed schema decode", result.observation.answer);
187
- }
188
- }
189
133
  async function invokeWaitedTell(command, input, body) {
190
134
  const schema = command.schema === undefined ? undefined : await schemaFromFile(command.schema);
191
135
  const alias = inputAlias(command.akuma);
192
- const progress = command.output === "text" ? tellWaitProgressStream(undefined, alias, resultContext()) : undefined;
136
+ const channel = executionChannel(input.execution);
137
+ const progress = command.output === "text" && channel.kind === "local"
138
+ ? tellWaitProgressStream(undefined, alias, resultContext())
139
+ : undefined;
193
140
  const result = await tellWaitAkuma({
194
141
  ...(await inputInitiator(input)),
195
142
  path: input.path,
@@ -209,9 +156,11 @@ async function invokeWaitedTell(command, input, body) {
209
156
  },
210
157
  }),
211
158
  }, input.execution ?? localExecutionContext());
212
- const rendered = schema === undefined ? result : decodeWaitedTellSchema(result, schema);
159
+ const rendered = schema === undefined ? result : { ...result, observation: decodeTellWaitObservation(result.observation, schema) };
213
160
  if (progress !== undefined)
214
161
  writeProgress(progress.conclude(rendered).join("\n"));
162
+ else if (command.output === "text" && channel.kind === "body-request")
163
+ writeProgress(waitedTellProgress(rendered, alias, resultContext()));
215
164
  return {
216
165
  kind: "akuma",
217
166
  action: "tell",
@@ -219,6 +168,7 @@ async function invokeWaitedTell(command, input, body) {
219
168
  result: rendered,
220
169
  body,
221
170
  ...(alias === undefined ? {} : { alias }),
171
+ ...(schema === undefined ? {} : { structured: true }),
222
172
  };
223
173
  }
224
174
  async function invokeTell(command, input) {
@@ -265,9 +215,8 @@ async function invokeTell(command, input) {
265
215
  }
266
216
  const initiator = await inputInitiator(input);
267
217
  if (command.interrupt) {
268
- const result = await Keiyaku.interrupt({
218
+ const result = await akumas(input).interrupt({
269
219
  ...initiator,
270
- path: input.path,
271
220
  akuma: command.akuma,
272
221
  body,
273
222
  ...(input.repo === undefined ? {} : { repo: input.repo }),
@@ -283,20 +232,18 @@ async function invokeTell(command, input) {
283
232
  ...(alias === undefined ? {} : { alias }),
284
233
  };
285
234
  }
286
- const result = await tellAkuma({
235
+ const result = await akumas(input).tell({
287
236
  ...initiator,
288
- path: input.path,
289
237
  akuma: command.akuma,
290
238
  body,
291
239
  ...(input.repo === undefined ? {} : { repo: input.repo }),
292
240
  ...(input.signal === undefined ? {} : { signal: input.signal }),
293
- }, input.execution ?? localExecutionContext());
241
+ });
294
242
  const alias = inputAlias(command.akuma);
295
243
  return { kind: "akuma", action: "tell", mode: "ordinary", result, body, ...(alias === undefined ? {} : { alias }) };
296
244
  }
297
245
  async function invokeHistory(command, input) {
298
- const result = await Keiyaku.history({
299
- path: input.path,
246
+ const result = await akumas(input).history({
300
247
  akuma: command.akuma,
301
248
  ...(input.repo === undefined ? {} : { repo: input.repo }),
302
249
  ...(command.before === undefined ? {} : { before: command.before }),
@@ -324,8 +271,7 @@ async function invokeFork(command, input) {
324
271
  return {
325
272
  kind: "akuma",
326
273
  action: "fork",
327
- receipt: await Keiyaku.fork({
328
- path: input.path,
274
+ receipt: await akumas(input).fork({
329
275
  akuma: command.akuma,
330
276
  at: command.at,
331
277
  ...(input.repo === undefined ? {} : { repo: input.repo }),
@@ -337,12 +283,11 @@ async function invokeKill(command, input) {
337
283
  return {
338
284
  kind: "akuma",
339
285
  action: "kill",
340
- result: await killAkuma({
341
- path: input.path,
286
+ result: await akumas(input).kill({
342
287
  akuma: command.akuma,
343
288
  ...(input.repo === undefined ? {} : { repo: input.repo }),
344
289
  ...(input.signal === undefined ? {} : { signal: input.signal }),
345
- }, input.execution ?? localExecutionContext()),
290
+ }),
346
291
  ...(alias === undefined ? {} : { alias }),
347
292
  };
348
293
  }
@@ -351,10 +296,9 @@ export async function invokeAkuma(command, input) {
351
296
  case "call": {
352
297
  const body = await promptBody(command, input);
353
298
  const schema = command.schema === undefined ? undefined : await schemaFromFile(command.schema);
354
- const caller = input.execution === undefined ? Keiyaku : Keiyaku.withExecution({ execution: input.execution });
299
+ const caller = akumas(input);
355
300
  const request = {
356
301
  ...(await inputInitiator(input)),
357
- path: input.path,
358
302
  archetype: command.archetype,
359
303
  body,
360
304
  ...(input.home === undefined ? {} : { home: input.home }),
@@ -366,19 +310,48 @@ export async function invokeAkuma(command, input) {
366
310
  ...(schema === undefined ? {} : { schema }),
367
311
  ...callSignalOption(input.signal),
368
312
  };
369
- const observing = schema === undefined && command.mode === "wait" && command.output === "text";
370
- const born = await caller.call(observing
371
- ? { ...request, mode: "detach" }
372
- : {
373
- ...request,
374
- mode: command.mode,
375
- ...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
376
- });
377
- const result = observing ? { ...born, observation: await observeCallUntilComplete(command, input, born) } : born;
378
- if (schema !== undefined && command.mode === "wait" && result.schemaAnswer !== undefined) {
379
- return { kind: "akuma", action: "call", result, world: input.path, schemaAnswer: result.schemaAnswer };
313
+ let stream;
314
+ const observing = command.mode === "wait" && command.output === "text";
315
+ const observe = observing
316
+ ? {
317
+ admitted: (tell, id, head) => {
318
+ stream = callObservationStream(resultContext(), callObservationHead({ akuma: id, ...head }), {
319
+ admittedAt: tell.row.at,
320
+ });
321
+ },
322
+ observe: (observation) => {
323
+ const lines = stream?.observe(observation) ?? [];
324
+ if (lines.length > 0)
325
+ writeProgress(lines.join("\n"));
326
+ },
327
+ }
328
+ : undefined;
329
+ const result = await caller.call({
330
+ ...request,
331
+ mode: command.mode,
332
+ ...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
333
+ ...(observe === undefined ? {} : { observe }),
334
+ });
335
+ let streamed = stream !== undefined;
336
+ if (stream !== undefined) {
337
+ writeProgress(stream.conclude(result.observation));
380
338
  }
381
- return { kind: "akuma", action: "call", result, world: input.path, ...(observing ? { streamed: true } : {}) };
339
+ else if (observing && result.observation.kind === "failed") {
340
+ const fallback = inputWaitStream(resultContext(), () => callObservationHead({
341
+ akuma: result.akuma,
342
+ dispatch: result.dispatch,
343
+ alias: result.alias,
344
+ }), { cursor: "empty", answerSeparator: true });
345
+ writeProgress(fallback.conclude({ kind: "failed", diagnostic: result.observation.failure.diagnostic }));
346
+ streamed = true;
347
+ }
348
+ return {
349
+ kind: "akuma",
350
+ action: "call",
351
+ result,
352
+ world: input.path,
353
+ ...(streamed ? { streamed: true } : {}),
354
+ };
382
355
  }
383
356
  case "wait":
384
357
  return await invokeWait(command, input);
@@ -396,7 +369,7 @@ export async function invokeAkumaStatus(path, akuma, alias, repo) {
396
369
  return {
397
370
  kind: "akuma",
398
371
  action: "status",
399
- status: await Keiyaku.status({ path, akuma, ...(repo === undefined ? {} : { repo }) }),
372
+ status: await Akumas.of(path).status({ akuma, ...(repo === undefined ? {} : { repo }) }),
400
373
  ...(alias === undefined ? {} : { alias }),
401
374
  };
402
375
  }