@astrosheep/keiyaku 4.5.12 → 4.5.13

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 (42) hide show
  1. package/build/src/akuma/akuma-errors.d.ts +1 -0
  2. package/build/src/akuma/akuma-errors.js +1 -0
  3. package/build/src/akuma/body.js +65 -3
  4. package/build/src/akuma/heart/facts.d.ts +18 -0
  5. package/build/src/akuma/heart/facts.js +7 -0
  6. package/build/src/akuma/heart/index.d.ts +18 -3
  7. package/build/src/akuma/heart/index.js +48 -6
  8. package/build/src/akuma/heart/rows.d.ts +4 -1
  9. package/build/src/akuma/heart/rows.js +46 -19
  10. package/build/src/akuma/heart/schema.d.ts +1 -1
  11. package/build/src/akuma/heart/schema.js +18 -5
  12. package/build/src/akuma/heart/storage.d.ts +1 -1
  13. package/build/src/akuma/heart/tells.d.ts +7 -0
  14. package/build/src/akuma/heart/tells.js +48 -5
  15. package/build/src/akuma/heart/timeline.js +9 -3
  16. package/build/src/akuma/projection.js +3 -3
  17. package/build/src/akuma/provider.d.ts +1 -0
  18. package/build/src/akuma/providers/claude/index.js +3 -0
  19. package/build/src/akuma/providers/codex-app-server/index.js +1 -0
  20. package/build/src/akuma/providers/pi/index.js +7 -1
  21. package/build/src/akuma/turn-drive.d.ts +1 -0
  22. package/build/src/akuma/turn-drive.js +21 -7
  23. package/build/src/kanshi/read.js +17 -1
  24. package/build/src/kanshi/select.js +14 -1
  25. package/build/src/library/contract-forwarding.d.ts +2 -2
  26. package/build/src/library/contract-operations.d.ts +1 -1
  27. package/build/src/library/contract-operations.js +2 -2
  28. package/build/src/library/mutation.d.ts +360 -3
  29. package/build/src/library/mutation.js +22 -0
  30. package/build/src/library/refusal.d.ts +2 -0
  31. package/build/src/library/refusal.js +22 -0
  32. package/build/src/library/region.d.ts +0 -8
  33. package/build/src/library/region.js +1 -29
  34. package/build/src/protocol/audit.d.ts +1 -1
  35. package/build/src/protocol/audit.js +1 -0
  36. package/build/src/protocol/deliver.d.ts +1 -1
  37. package/build/src/protocol/deliver.js +1 -0
  38. package/build/src/protocol/review.d.ts +1 -1
  39. package/build/src/protocol/review.js +1 -0
  40. package/package.json +2 -2
  41. package/build/src/library/contract-forwarding-result.d.ts +0 -359
  42. package/build/src/library/contract-forwarding-result.js +0 -44
@@ -24,20 +24,29 @@ function tellDeliveries(database, id) {
24
24
  ? { turnSequence: row.turn_sequence, route: row.route, deliveredAt: row.delivered_at }
25
25
  : { turnSequence: row.turn_sequence, route: row.route, receipt: row.receipt, deliveredAt: row.delivered_at });
26
26
  }
27
+ function tellBinding(database, id) {
28
+ const row = database
29
+ .prepare("SELECT turn_sequence, bound_at FROM tell_bindings WHERE tell_id = ?")
30
+ .get(id);
31
+ return row === undefined ? undefined : { turnSequence: row.turn_sequence, boundAt: row.bound_at };
32
+ }
27
33
  function decodeTellRow(database, row) {
34
+ const binding = tellBinding(database, row.id);
28
35
  return {
29
36
  kind: "tell",
30
37
  sequence: row.sequence,
31
38
  id: row.id,
32
39
  body: row.body,
40
+ ...(row.schema_json === null ? {} : { schemaJson: row.schema_json }),
33
41
  state: row.state,
34
42
  recordedAt: row.recorded_at,
35
43
  deliveries: tellDeliveries(database, row.id),
44
+ ...(binding === undefined ? {} : { binding }),
36
45
  };
37
46
  }
38
47
  export function decodeTellAtSequence(database, sequence) {
39
48
  const row = database
40
- .prepare(`SELECT sequence, id, body, recorded_at, ${tellStateSql} AS state
49
+ .prepare(`SELECT sequence, id, body, schema_json, recorded_at, ${tellStateSql} AS state
41
50
  FROM tells WHERE sequence = ?`)
42
51
  .get(sequence);
43
52
  if (row === undefined)
@@ -47,13 +56,13 @@ export function decodeTellAtSequence(database, sequence) {
47
56
  export function insertTellFact(database, tell) {
48
57
  const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('tell')").run().lastInsertRowid);
49
58
  database
50
- .prepare("INSERT INTO tells(id, sequence, body, recorded_at) VALUES (?, ?, ?, ?)")
51
- .run(tell.id, sequence, tell.body, tell.recordedAt);
59
+ .prepare("INSERT INTO tells(id, sequence, body, schema_json, recorded_at) VALUES (?, ?, ?, ?, ?)")
60
+ .run(tell.id, sequence, tell.body, tell.schemaJson ?? null, tell.recordedAt);
52
61
  return sequence;
53
62
  }
54
63
  export function tellFact(database, id) {
55
64
  const row = database
56
- .prepare(`SELECT sequence, id, body, recorded_at, ${tellStateSql} AS state
65
+ .prepare(`SELECT sequence, id, body, schema_json, recorded_at, ${tellStateSql} AS state
57
66
  FROM tells WHERE id = ?`)
58
67
  .get(id);
59
68
  return row === undefined ? null : decodeTellRow(database, row);
@@ -90,7 +99,7 @@ export function tellIdsForFence(database, turnSequence, fence) {
90
99
  }
91
100
  export function pendingTellFacts(database) {
92
101
  const rows = database
93
- .prepare(`SELECT sequence, id, body, recorded_at, ${tellStateSql} AS state FROM tells
102
+ .prepare(`SELECT sequence, id, body, schema_json, recorded_at, ${tellStateSql} AS state FROM tells
94
103
  WHERE ${tellStateSql} = 'pending' ORDER BY sequence`)
95
104
  .all();
96
105
  return rows.map((row) => decodeTellRow(database, row));
@@ -182,3 +191,37 @@ export function insertUndeliveredTellReceipts(database, tellIds, at) {
182
191
  });
183
192
  }
184
193
  }
194
+ export function insertTellBindingFact(database, input) {
195
+ const result = database
196
+ .prepare("INSERT OR IGNORE INTO tell_bindings(tell_id, turn_sequence, bound_at) VALUES (?, ?, ?)")
197
+ .run(input.tellId, input.turnSequence, input.boundAt);
198
+ const row = database
199
+ .prepare("SELECT turn_sequence FROM tell_bindings WHERE tell_id = ?")
200
+ .get(input.tellId);
201
+ if (row === undefined || (result.changes === 0 && row.turn_sequence !== input.turnSequence)) {
202
+ throw new Error(`tell ${input.tellId} is already bound to a different Turn`);
203
+ }
204
+ }
205
+ export function drainPendingTells(pending) {
206
+ const unbound = pending.filter((tell) => tell.binding === undefined);
207
+ if (unbound.length === 0)
208
+ return [];
209
+ const first = unbound[0];
210
+ const drained = [first];
211
+ for (const tell of unbound.slice(1)) {
212
+ if (tell.schemaJson !== undefined)
213
+ break;
214
+ drained.push(tell);
215
+ }
216
+ return drained;
217
+ }
218
+ export function openBoundTurns(database, bodySequence) {
219
+ return database
220
+ .prepare(`SELECT DISTINCT turns.sequence AS sequence
221
+ FROM turns
222
+ JOIN tell_bindings ON tell_bindings.turn_sequence = turns.sequence
223
+ WHERE turns.body_sequence = ? AND turns.end_sequence IS NULL
224
+ ORDER BY turns.sequence`)
225
+ .all(bodySequence)
226
+ .map((row) => row.sequence);
227
+ }
@@ -49,7 +49,7 @@ export function pruneActivityFacts(database, limit) {
49
49
  function turn(database, sequence) {
50
50
  const row = database
51
51
  .prepare(`SELECT sequence, body_sequence, started_at, end_sequence, outcome,
52
- history_id, session_json, answer, diagnostic, completed_at FROM turns WHERE sequence = ?`)
52
+ history_id, session_json, answer, answer_json, schema_json, diagnostic, completed_at FROM turns WHERE sequence = ?`)
53
53
  .get(sequence);
54
54
  if (row === undefined)
55
55
  throw new Error(`Akuma timeline references missing Turn ${sequence}`);
@@ -57,11 +57,17 @@ function turn(database, sequence) {
57
57
  }
58
58
  function turnStart(database, sequence) {
59
59
  const row = database
60
- .prepare("SELECT sequence, body_sequence, started_at FROM turns WHERE sequence = ?")
60
+ .prepare("SELECT sequence, body_sequence, started_at, schema_json FROM turns WHERE sequence = ?")
61
61
  .get(sequence);
62
62
  if (row === undefined)
63
63
  throw new Error(`Akuma timeline references missing Turn ${sequence}`);
64
- return { kind: "turn-start", sequence: row.sequence, bodySequence: row.body_sequence, startedAt: row.started_at };
64
+ return {
65
+ kind: "turn-start",
66
+ sequence: row.sequence,
67
+ bodySequence: row.body_sequence,
68
+ startedAt: row.started_at,
69
+ ...(row.schema_json === null ? {} : { schemaJson: row.schema_json }),
70
+ };
65
71
  }
66
72
  function decodeTimelineRow(database, row) {
67
73
  if (row.kind === "turn-start")
@@ -192,9 +192,9 @@ export const activitySnapshotSchema = z.union([
192
192
  ]);
193
193
  function publicOutcome(turnSequence, outcome) {
194
194
  const historyId = `turn/${turnSequence}`;
195
- return outcome.kind === "answered"
196
- ? { kind: "answered", historyId, answer: outcome.answer }
197
- : { kind: "failed", historyId, diagnostic: outcome.diagnostic };
195
+ if (outcome.kind === "answered")
196
+ return { kind: "answered", historyId, answer: outcome.answer };
197
+ return { kind: "failed", historyId, diagnostic: outcome.diagnostic };
198
198
  }
199
199
  export function selectExactHistory(rows, id) {
200
200
  const outcome = rows.find((row) => row.kind === "outcome" && row.outcome.historyId === id);
@@ -172,6 +172,7 @@ export type DriveInput = Readonly<{
172
172
  requests: Readonly<{
173
173
  dir: string;
174
174
  }>;
175
+ schemaJson?: string;
175
176
  }>;
176
177
  export type ProviderOptionAdmission = Readonly<{
177
178
  kind: "admitted";
@@ -86,6 +86,9 @@ function claudeQueryOptions(input, execution, abortController) {
86
86
  : { type: "preset", preset: "claude_code", append: input.options.systemPrompt },
87
87
  }),
88
88
  ...(input.session.kind === "fresh" ? {} : { resume: claudeSessionId(input.session.coordinate) }),
89
+ ...(input.schemaJson === undefined
90
+ ? {}
91
+ : { outputFormat: { type: "json_schema", schema: JSON.parse(input.schemaJson) } }),
89
92
  };
90
93
  }
91
94
  async function forkClaude(load, execution, input) {
@@ -95,6 +95,7 @@ async function admitTurn(server, input, state, events, config) {
95
95
  ...(input.options.effort === undefined ? {} : { effort: input.options.effort }),
96
96
  approvalPolicy: "never",
97
97
  sandboxPolicy: sandbox(input.cwd, input.options, input.requests),
98
+ ...(input.schemaJson === undefined ? {} : { outputSchema: JSON.parse(input.schemaJson) }),
98
99
  }));
99
100
  }
100
101
  async function forkCodex(execution, input, custody) {
@@ -79,7 +79,13 @@ async function createPiSession(sdk, execution, input) {
79
79
  }
80
80
  async function runPiPrompt(native, input, events, state, settle) {
81
81
  try {
82
- await native.prompt([input.body, ...input.launchTells.map((tell) => tell.text)].join("\n\n"));
82
+ const prompt = [input.body, ...input.launchTells.map((tell) => tell.text)].filter((part) => part.length > 0).join("\n\n");
83
+ const schemaPrompt = input.schemaJson === undefined
84
+ ? prompt
85
+ : [prompt, "Respond with JSON matching this JSON Schema and no other text:", input.schemaJson]
86
+ .filter((part) => part.length > 0)
87
+ .join("\n\n");
88
+ await native.prompt(schemaPrompt);
83
89
  let result;
84
90
  if (state.aborting)
85
91
  result = { kind: "failed", diagnostic: "Pi session aborted" };
@@ -25,6 +25,7 @@ export type DriveTurnInput = Readonly<{
25
25
  body: string;
26
26
  call?: string;
27
27
  launchTells: readonly TellFact[];
28
+ schemaJson?: string;
28
29
  supervisor: BodySupervisor;
29
30
  runtimeSpawn(launch: AkumaCallRequestChildLaunch): Promise<OwnedProcess | void>;
30
31
  world: import("../world.js").WorldRoot;
@@ -1,6 +1,6 @@
1
1
  import { abortableDelay } from "./abort.js";
2
2
  /* eslint-disable max-lines-per-function -- Turn setup and consumption each preserve one ordered lifecycle transaction. */
3
- import { appendActivity, beginTurn, breakBody, heartExists, readHeart, recordSession, recordTellDeliveries, recordTellReceipt, } from "./heart/index.js";
3
+ import { appendActivity, beginTurn, bindTellsToTurn, breakBody, drainPendingTells, heartExists, readHeart, recordSession, recordTellDeliveries, recordTellReceipt, } from "./heart/index.js";
4
4
  import { encodeAgentEvent, } from "./provider.js";
5
5
  import { akumaCallRequestCommands } from "./call-request.js";
6
6
  import { BodyRequestPump } from "./request-serve.js";
@@ -70,11 +70,21 @@ async function startTurnDrive(input) {
70
70
  const { cwd, options, session } = await turnRecipe(input.paths, input.soul);
71
71
  if (session !== undefined && input.adapter.resume === undefined)
72
72
  return { kind: "resume-unsupported" };
73
+ const boundTells = drainPendingTells(input.launchTells);
74
+ const schemaJson = input.schemaJson ?? boundTells.find((tell) => tell.schemaJson !== undefined)?.schemaJson;
73
75
  const turn = await beginTurn(input.paths, {
74
76
  bodySequence: input.bodySequence,
75
77
  startedAt: input.now(),
76
78
  ...(input.call === undefined ? {} : { call: input.call }),
79
+ ...(schemaJson === undefined ? {} : { schemaJson }),
77
80
  });
81
+ if (boundTells.length > 0) {
82
+ await bindTellsToTurn(input.paths, {
83
+ turnSequence: turn.sequence,
84
+ tellIds: boundTells.map((tell) => tell.id),
85
+ boundAt: input.now(),
86
+ });
87
+ }
78
88
  const { world, paths, soul: parent, runtimeSpawn: spawn, externalCommands } = input;
79
89
  const commands = composeRequestCommands(akumaCallRequestCommands({ world, paths, parent, spawn }), externalCommands);
80
90
  const requests = await BodyRequestPump.open({
@@ -99,11 +109,12 @@ async function startTurnDrive(input) {
99
109
  });
100
110
  const driveInput = {
101
111
  body: input.body,
102
- launchTells: input.launchTells.map((tell) => ({ id: tell.id, text: tell.body })),
112
+ launchTells: boundTells.map((tell) => ({ id: tell.id, text: tell.body })),
103
113
  cwd,
104
114
  options,
105
115
  signal: driveController.signal,
106
116
  requests: { dir: requests.directory },
117
+ ...(schemaJson === undefined ? {} : { schemaJson }),
107
118
  };
108
119
  const attempt = session === undefined
109
120
  ? input.adapter.start({ ...driveInput, session: { kind: "fresh" } })
@@ -123,8 +134,8 @@ async function startTurnDrive(input) {
123
134
  }
124
135
  return { kind: "stopped" };
125
136
  }
126
- if (input.launchTells.length > 0)
127
- await recordTellDeliveries(input.paths, input.launchTells.map((tell) => ({
137
+ if (boundTells.length > 0)
138
+ await recordTellDeliveries(input.paths, boundTells.map((tell) => ({
128
139
  tellId: tell.id,
129
140
  route: "launch",
130
141
  turnSequence: turn.sequence,
@@ -228,13 +239,14 @@ function observeCompletionFailure(drive) {
228
239
  async function submitPendingLiveTells(writers, pending, attempted, tellLive) {
229
240
  const { input, turnSequence, drive, writeWitness, mayWrite } = writers;
230
241
  for (const tell of pending) {
231
- if (attempted.has(tell.id))
242
+ if (attempted.has(tell.id) || tell.schemaJson !== undefined)
232
243
  continue;
233
244
  attempted.add(tell.id);
234
245
  const outcome = await writeWitness(async () => {
235
246
  const submission = await tellLive({ id: tell.id, text: tell.body });
236
247
  if (!mayWrite() || submission.kind === "turn-ended")
237
248
  return "turn-ended";
249
+ await bindTellsToTurn(input.paths, { turnSequence, tellIds: [tell.id], boundAt: input.now() });
238
250
  await recordTellDeliveries(input.paths, [
239
251
  {
240
252
  tellId: tell.id,
@@ -263,7 +275,9 @@ async function stopActiveDrive(input, active) {
263
275
  }
264
276
  }
265
277
  function hasUnattemptedTell(liveTells, tellPump, pending, attempted) {
266
- return liveTells && tellPump === null && pending.some((tell) => !attempted.has(tell.id));
278
+ return (liveTells &&
279
+ tellPump === null &&
280
+ pending.some((tell) => !attempted.has(tell.id) && tell.schemaJson === undefined));
267
281
  }
268
282
  async function settleCompletion(result, session, active) {
269
283
  try {
@@ -278,7 +292,7 @@ async function consumeTurnDrive(input, active) {
278
292
  const { turnSequence, drive, requests, resume } = active;
279
293
  let heart = input.supervisor.current();
280
294
  let turnSession = resume;
281
- const attempted = new Set(input.launchTells.map((tell) => tell.id));
295
+ const attempted = new Set(drainPendingTells(input.launchTells).map((tell) => tell.id));
282
296
  const writeWitness = serializeEffects();
283
297
  let writesOpen = true;
284
298
  const mayWrite = () => writesOpen && !input.supervisor.signal.aborted;
@@ -9,8 +9,9 @@ import { readTaskHolderProjectionAt } from "../settlement/holder.js";
9
9
  import { observeCurrentPhysicalIssue } from "../protocol/read/observation.js";
10
10
  import { readContractBoard, readContractCatalogue } from "../protocol/read/status.js";
11
11
  import { withGitDecodeChannel, withGitReadObservation } from "../git/read-observation.js";
12
+ import { decodeContractDocument } from "../body/decode.js";
13
+ import { assertRegionPattern } from "../body/region.js";
12
14
  import { readDocuments } from "../protocol/read/documents.js";
13
- import { readRegionDeclarations, validateRegionPatterns } from "../library/region.js";
14
15
  import { contractId } from "../core/facts/types.js";
15
16
  import { selectKanshi, selectRegion } from "./select.js";
16
17
  import { FLEET_SNAPSHOT_ROWS, FLEET_VISIBLE_ROWS } from "./fleet.js";
@@ -42,6 +43,21 @@ function exactKeys(value, allowed, label) {
42
43
  if (!allowed.includes(key))
43
44
  throw new TypeError(`kanshi ${label} has unknown field: ${key}`);
44
45
  }
46
+ function validateRegionPatterns(patterns) {
47
+ if (!Array.isArray(patterns) || patterns.length === 0)
48
+ throw new Error("Region query requires one or more path patterns");
49
+ return patterns.map((pattern) => {
50
+ if (typeof pattern !== "string")
51
+ throw new Error("Region path patterns must be strings");
52
+ return assertRegionPattern(pattern);
53
+ });
54
+ }
55
+ function readRegionDeclarations(documents) {
56
+ return documents.map((document) => ({
57
+ contract: document.contract,
58
+ patterns: decodeContractDocument(document.documentBytes).region,
59
+ }));
60
+ }
45
61
  function regionSelection(value) {
46
62
  const selection = record(value, "region selection");
47
63
  if (typeof selection.kind !== "string")
@@ -1,4 +1,17 @@
1
- import { regionOverlaps } from "../library/region.js";
1
+ import { regionsOverlap } from "../body/region.js";
2
+ function regionOverlaps(mine, declarations) {
3
+ const overlaps = [];
4
+ for (const declaration of declarations) {
5
+ const pairs = regionsOverlap(mine, declaration.patterns);
6
+ if (pairs.length === 0)
7
+ continue;
8
+ overlaps.push({
9
+ contract: declaration.contract,
10
+ patterns: pairs.map(([minePattern, theirsPattern]) => ({ mine: minePattern, theirs: theirsPattern })),
11
+ });
12
+ }
13
+ return overlaps;
14
+ }
2
15
  export function selectKanshi(input) {
3
16
  const { report, contract } = input;
4
17
  return {
@@ -6,8 +6,8 @@ import type { AuditReport } from "../protocol/audit.js";
6
6
  import type { WorktreeHooks } from "./configuration.js";
7
7
  import type { DeliveryValue } from "./delivery.js";
8
8
  import { type MutationResult } from "./mutation.js";
9
- import type { Review } from "./contract-forwarding-result.js";
10
- export type { Review } from "./contract-forwarding-result.js";
9
+ import type { Review } from "./mutation.js";
10
+ export type { Review } from "./mutation.js";
11
11
  import { Repo } from "./repo.js";
12
12
  export type DeliveryExecutionInput = Readonly<{
13
13
  scope: RepositoryScope;
@@ -2,7 +2,7 @@ import { type ErasedRequestCommand, type RequestProtocol, type ServiceRequestCom
2
2
  import type { AuditReport } from "../protocol/audit.js";
3
3
  import type { IntegrationConflictMaterialized } from "../protocol/deliver.js";
4
4
  import type { DeliveryValue } from "./delivery.js";
5
- import type { MutationResult } from "./mutation.js";
5
+ import { type MutationResult } from "./mutation.js";
6
6
  import type { DeliveryExecutionInput, Review } from "./contract-forwarding.js";
7
7
  import { z } from "zod";
8
8
  type DeliveryResult = MutationResult<DeliveryValue> | IntegrationConflictMaterialized;
@@ -1,8 +1,8 @@
1
1
  import { contractId, snapshotId } from "../core/facts/types.js";
2
2
  import { requestBodyCommand } from "../akuma/request-rendezvous.js";
3
3
  import { eraseRequestCommand, } from "../akuma/request-wire.js";
4
- import { auditResultSchema, decodeContractLiveFailure, deliveryResultSchema, encodeContractLiveFailure, reviewResultSchema, } from "./contract-forwarding-result.js";
5
- import { auditReportSchema } from "./contract-forwarding-result.js";
4
+ import { auditReportSchema, auditResultSchema, deliveryResultSchema, reviewResultSchema, } from "./mutation.js";
5
+ import { decodeContractLiveFailure, encodeContractLiveFailure } from "./refusal.js";
6
6
  import { isAbsolute, resolve } from "node:path";
7
7
  import { z } from "zod";
8
8
  const absolutePathSchema = z.string().refine((value) => isAbsolute(value) && resolve(value) === value);