@astrosheep/keiyaku 2.9.8 → 2.9.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.
@@ -1,29 +1,8 @@
1
- import { formatTimelineDuration, renderProjectionActivity, renderProjectionActivitySummary, } from "./projection-activity.js";
1
+ import { formatTimelineDuration, renderPendingProjectionTell, renderProjectionActivity, } from "./projection-activity.js";
2
2
  import { displayColumns, resolveLineColumns, truncateColumns } from "./line-width.js";
3
3
  import { formatTellCommand, projectionRejoinHintLines, textResponse } from "./shared.js";
4
4
  import { renderTerminalFailureFace } from "./terminal-failure.js";
5
5
  const WAIT_HEADER_MAX_COLUMNS = 60;
6
- const WAIT_BODY_MAX_ROWS = 10;
7
- const WAIT_ROW_PRIORITY = {
8
- "end-anchor": 100,
9
- "storage-diagnostic": 90,
10
- diagnostic: 90,
11
- tell: 80,
12
- summary: 70,
13
- activity: 60,
14
- plan: 55,
15
- liveness: 50,
16
- silence: 65,
17
- file: 20,
18
- };
19
- /** One owner admits every wait-body row, then restores presentation order. */
20
- export function selectWaitBodyRows(rows, limit = WAIT_BODY_MAX_ROWS) {
21
- const selected = rows
22
- .slice()
23
- .sort((left, right) => WAIT_ROW_PRIORITY[right.kind] - WAIT_ROW_PRIORITY[left.kind] || left.order - right.order)
24
- .slice(0, limit);
25
- return selected.sort((left, right) => left.order - right.order);
26
- }
27
6
  function shortAddress(projectionId) {
28
7
  return /\/([0-9a-f]{8})$/.exec(projectionId)?.[1] ?? projectionId;
29
8
  }
@@ -70,12 +49,23 @@ function waitHeader(projectionId, akuma, snapshot, outputColumns) {
70
49
  const dashCount = Math.max(1, maxColumns - displayColumns(`${prefix} ${state}`));
71
50
  return `${prefix} ${"─".repeat(dashCount)} ${state}`;
72
51
  }
73
- function pendingTellLines(snapshot, maxColumns) {
74
- return snapshot.tells.window.map((tell) => {
75
- const age = Math.max(0, snapshot.observedAtMs - Date.parse(tell.createdAt));
76
- const fixed = `${"".padStart(5)}│ ${"tell".padEnd(7)} `;
77
- const suffix = ` · undelivered ${formatTimelineDuration(Number.isFinite(age) ? age : 0)}`;
78
- return `${fixed}${truncateColumns(`“${tell.text}”`, Math.max(0, maxColumns - displayColumns(fixed) - displayColumns(suffix)))}${suffix}`;
52
+ function pendingTellRows(snapshot, maxColumns, orderOffset) {
53
+ return snapshot.tells.window.map((tell, index) => ({
54
+ line: renderPendingProjectionTell(tell, {
55
+ anchorMs: snapshot.observedAtMs,
56
+ maxColumns,
57
+ }),
58
+ at: tell.createdAt,
59
+ order: orderOffset + index,
60
+ }));
61
+ }
62
+ function orderFlowRows(rows) {
63
+ return rows.slice().sort((left, right) => {
64
+ const leftAt = Date.parse(left.at ?? "");
65
+ const rightAt = Date.parse(right.at ?? "");
66
+ const safeLeft = Number.isFinite(leftAt) ? leftAt : Number.POSITIVE_INFINITY;
67
+ const safeRight = Number.isFinite(rightAt) ? rightAt : Number.POSITIVE_INFINITY;
68
+ return safeLeft - safeRight || left.order - right.order;
79
69
  });
80
70
  }
81
71
  function ledgerHint(projectionId, snapshot) {
@@ -142,14 +132,13 @@ function unknownResponse(projectionId, akuma, snapshot, maxColumns) {
142
132
  anchorMs: snapshot.observedAtMs,
143
133
  state: "unknown",
144
134
  showPlan: false,
135
+ showSilence: false,
136
+ showTerminalFiles: false,
145
137
  ...(snapshot.workspaceRoot ? { workspaceRoot: snapshot.workspaceRoot } : {}),
146
138
  maxColumns,
139
+ leadingOmittedCount: snapshot.activity.omittedSettledCount,
147
140
  });
148
- const historicalLines = selectWaitBodyRows(rendered.lines.map((line, index) => ({
149
- kind: rendered.rowKinds[index] ?? "activity",
150
- line,
151
- order: index,
152
- }))).map((row) => row.line.replace(/[┆├]/, "│"));
141
+ const historicalLines = rendered.lines.map((line) => line.replace(/[┆├]/, "│"));
153
142
  return {
154
143
  ...textResponse([
155
144
  `? ${akuma} · projection: ${projectionId} · unknown`,
@@ -216,43 +205,32 @@ export function buildWaitResponse(projectionId, result, options = {}) {
216
205
  if (state === "returned") {
217
206
  return returnedResponse(projectionId, result);
218
207
  }
219
- const tells = pendingTellLines(snapshot, maxColumns);
220
- const summary = ["running", "stalled", "unknown"].includes(state)
221
- ? renderProjectionActivitySummary(snapshot.activity, {
222
- state,
223
- ...(snapshot.workspaceRoot ? { workspaceRoot: snapshot.workspaceRoot } : {}),
224
- maxColumns,
225
- })
226
- : undefined;
227
208
  const rendered = renderProjectionActivity(snapshot.activity, {
228
209
  anchorMs: snapshot.observedAtMs,
229
210
  state,
230
- showPlan: state === "running",
211
+ showPlan: false,
212
+ showSilence: false,
213
+ showTerminalFiles: false,
231
214
  ...(snapshot.workspaceRoot ? { workspaceRoot: snapshot.workspaceRoot } : {}),
232
215
  maxColumns,
216
+ leadingOmittedCount: snapshot.activity.omittedSettledCount,
233
217
  });
234
- let order = 0;
235
- const bodyPlan = [];
236
- for (const diagnostic of snapshot.eventDiagnostics ?? []) {
237
- bodyPlan.push({ kind: "storage-diagnostic", line: `‼ ${diagnostic.kind} · ${diagnostic.detail}`, order: order++ });
238
- }
239
- rendered.lines.forEach((line, index) => {
240
- bodyPlan.push({ kind: rendered.rowKinds[index] ?? "activity", line, order: order++ });
241
- });
242
- for (const line of tells)
243
- bodyPlan.push({ kind: "tell", line, order: order++ });
244
- if (rendered.lines.length === 0 && tells.length === 0 && state === "running") {
245
- bodyPlan.push({ kind: "liveness", line: " not a word out of it yet — idle", order: order++ });
246
- }
247
- if (!rendered.hasEndAnchor && summary) {
248
- bodyPlan.push({ kind: "summary", line: summary, order: order++ });
249
- }
250
- const bodyLines = selectWaitBodyRows(bodyPlan).map((row) => truncateColumns(row.line, maxColumns));
251
- const lines = [waitHeader(projectionId, result.pact.akuma, snapshot, maxColumns), ...bodyLines];
252
- const pendingTellCount = snapshot.tells.totals.undelivered;
253
- if (pendingTellCount > 0) {
254
- lines.push(`${pendingTellCount} tell${pendingTellCount === 1 ? "" : "s"} undelivered`);
255
- }
218
+ const diagnosticLines = (snapshot.eventDiagnostics ?? [])
219
+ .map((diagnostic) => `‼ ${diagnostic.kind} · ${diagnostic.detail}`);
220
+ const activityRows = rendered.lines.map((line, index) => ({
221
+ line,
222
+ at: rendered.rowAts[index],
223
+ order: index,
224
+ }));
225
+ const flowLines = orderFlowRows([
226
+ ...activityRows,
227
+ ...pendingTellRows(snapshot, maxColumns, activityRows.length),
228
+ ]).map((row) => truncateColumns(row.line, maxColumns));
229
+ const lines = [
230
+ waitHeader(projectionId, result.pact.akuma, snapshot, maxColumns),
231
+ ...diagnosticLines,
232
+ ...flowLines,
233
+ ];
256
234
  const nextActions = state === "running" && options.runningActions === false
257
235
  ? []
258
236
  : nextActionLines(options.alias ?? projectionId, result, state);
@@ -27,7 +27,7 @@ const ENV_KEY_ENTRIES = [
27
27
  { key: ENV_KEYS.AGENT_OVERRIDE, defaultValue: "", description: "Override default agent name", category: "subagent" },
28
28
  { key: ENV_KEYS.DISABLE_SUBAGENT_SPAWN, defaultValue: "0", description: "Disable subprocess subagent spawning when set to 1", category: "subagent" },
29
29
  { key: ENV_KEYS.SUBAGENT_EXEC_IDLE_TIMEOUT_MS, defaultValue: "300000", description: "Subagent provider-ingress idle timeout in milliseconds", category: "subagent" },
30
- { key: ENV_KEYS.SUBAGENT_EXEC_MAX_DURATION_MS, defaultValue: "3600000", description: "Subagent absolute execution policy budget in milliseconds", category: "subagent" },
30
+ { key: ENV_KEYS.SUBAGENT_EXEC_MAX_DURATION_MS, defaultValue: "0", description: "Subagent absolute execution policy budget in milliseconds; 0 disables it", category: "subagent" },
31
31
  { key: ENV_KEYS.CODEX_EXEC_MAX_CAPTURE_CHARS, defaultValue: "200000", description: "Codex runtime output/diagnostics capture cap", category: "subagent" },
32
32
  { key: ENV_KEYS.CLAUDE_EXEC_MAX_CAPTURE_CHARS, defaultValue: "200000", description: "Claude runtime output/diagnostics capture cap", category: "subagent" },
33
33
  { key: ENV_KEYS.OPENCODE_EXEC_MAX_CAPTURE_CHARS, defaultValue: "200000", description: "OpenCode runtime output/diagnostics capture cap", category: "subagent" },
@@ -141,7 +141,7 @@ const subagentSchema = z.object({
141
141
  nameOverride: z.string().default(getEnvDefaultValue(ENV_KEYS.AGENT_OVERRIDE)).transform((value) => value.trim()),
142
142
  disableSpawn: booleanFlagEnv(ENV_KEYS.DISABLE_SUBAGENT_SPAWN),
143
143
  execIdleTimeoutMs: positiveIntEnv(ENV_KEYS.SUBAGENT_EXEC_IDLE_TIMEOUT_MS),
144
- execMaxDurationMs: positiveIntEnv(ENV_KEYS.SUBAGENT_EXEC_MAX_DURATION_MS),
144
+ execMaxDurationMs: nonNegativeIntEnv(ENV_KEYS.SUBAGENT_EXEC_MAX_DURATION_MS),
145
145
  });
146
146
  const codexSchema = z.object({
147
147
  execMaxCaptureChars: positiveIntEnv(ENV_KEYS.CODEX_EXEC_MAX_CAPTURE_CHARS),
@@ -26,6 +26,12 @@ function requirePositiveSafeInteger(value, field) {
26
26
  }
27
27
  return value;
28
28
  }
29
+ function requireNonNegativeSafeInteger(value, field) {
30
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
31
+ throw new Error(`generation launch ${field} must be a non-negative safe integer`);
32
+ }
33
+ return value;
34
+ }
29
35
  function parseLaunchInputs(launch) {
30
36
  const raw = launch.facts.inputs;
31
37
  if (!isObject(raw))
@@ -58,7 +64,7 @@ function parseLaunchInputs(launch) {
58
64
  ...(context ? { context } : {}),
59
65
  launchSnapshot,
60
66
  idleTimeoutMs: requirePositiveSafeInteger(raw.idleTimeoutMs, "idleTimeoutMs"),
61
- executionTimeoutMs: requirePositiveSafeInteger(raw.executionTimeoutMs, "executionTimeoutMs"),
67
+ executionTimeoutMs: requireNonNegativeSafeInteger(raw.executionTimeoutMs, "executionTimeoutMs"),
62
68
  persistProjectionResponse: raw.persistProjectionResponse,
63
69
  ...(raw.session ? { session: raw.session } : {}),
64
70
  ...(resumePath ? { resumePath } : {}),
@@ -211,6 +211,7 @@ export function foldAgentActivity(events) {
211
211
  settled.sort((left, right) => left.order - right.order);
212
212
  pinned.sort((left, right) => left.order - right.order);
213
213
  const window = settled.slice(-AGENT_ACTIVITY_WINDOW_SIZE);
214
+ const omittedSettledCount = Math.max(0, settled.length - window.length);
214
215
  const visibleOrders = [...window, ...pinned].map((row) => row.order);
215
216
  const relevantTurns = visibleOrders.length === 0
216
217
  ? turns.slice(-1)
@@ -232,6 +233,7 @@ export function foldAgentActivity(events) {
232
233
  }
233
234
  return {
234
235
  window,
236
+ omittedSettledCount,
235
237
  pinned,
236
238
  timeline,
237
239
  ledgerActivities,
@@ -6,7 +6,7 @@ import { readProjectionAlias } from "./projection-alias.js";
6
6
  import { ProjectionStateError } from "../atomic-publish.js";
7
7
  import { foldPublicProjectionActivity, } from "./projection-activity.js";
8
8
  import { readProjectionIdentity } from "./projection-identity.js";
9
- import { readProjectionTellCounts } from "./tell/store.js";
9
+ import { readPendingTellWindow, readProjectionTellCounts } from "./tell/store.js";
10
10
  import { ProjectionGenerationStoreError, } from "./generation/store.js";
11
11
  import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./generation/projection-generation-launcher.js";
12
12
  import { observeProjectionLifeSnapshot, } from "./projection-life-observer.js";
@@ -169,6 +169,7 @@ function readIdentityFacts(projectionDir, diagnostics) {
169
169
  pid: identity.pid,
170
170
  akuma: identity.akuma,
171
171
  provider: identity.provider,
172
+ binding: identity.binding,
172
173
  mintedAt: identity.mintedAt,
173
174
  workspaceRoot: identity.workspaceRoot,
174
175
  };
@@ -176,18 +177,18 @@ function readIdentityFacts(projectionDir, diagnostics) {
176
177
  catch (error) {
177
178
  if (isErrnoException(error) && error.code === "ENOENT") {
178
179
  diagnostics.push({ kind: "missing-identity", detail: "heart identity missing" });
179
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
180
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
180
181
  }
181
182
  if (error instanceof ProjectionGenerationStoreError) {
182
183
  diagnostics.push({ kind: "invalid-identity", detail: error.message });
183
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
184
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
184
185
  }
185
186
  if (error instanceof ProjectionStateError) {
186
187
  diagnostics.push({ kind: "invalid-identity", detail: error.message });
187
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
188
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
188
189
  }
189
190
  diagnostics.push({ kind: "unreadable-identity", detail: `heart identity unreadable: ${String(error)}` });
190
- return { identity: null, pid: undefined, akuma: undefined, provider: undefined, mintedAt: undefined, workspaceRoot: undefined };
191
+ return { identity: null, pid: undefined, akuma: undefined, provider: undefined, binding: undefined, mintedAt: undefined, workspaceRoot: undefined };
191
192
  }
192
193
  }
193
194
  function readHeartTellCounts(projectionDir, diagnostics) {
@@ -203,6 +204,22 @@ function readHeartTellCounts(projectionDir, diagnostics) {
203
204
  return emptyTellCounts();
204
205
  }
205
206
  }
207
+ function readPendingTells(projectionDir, diagnostics) {
208
+ try {
209
+ // Kanshi is an operator surface: unconsumed tells are actionable facts, not
210
+ // a count to hide behind. This is one heart read and does not enumerate a
211
+ // second projection topology.
212
+ return readPendingTellWindow(projectionDir, Number.MAX_SAFE_INTEGER).window;
213
+ }
214
+ catch (error) {
215
+ if (error instanceof ProjectionGenerationStoreError || error instanceof ProjectionStateError) {
216
+ diagnostics.push({ kind: "unreadable-tells", detail: error.message });
217
+ return [];
218
+ }
219
+ diagnostics.push({ kind: "unreadable-tells", detail: `heart tells unreadable: ${String(error)}` });
220
+ return [];
221
+ }
222
+ }
206
223
  function readStoredEvents(projectionDir, executionId, diagnostics) {
207
224
  let content;
208
225
  let truncated = false;
@@ -322,6 +339,7 @@ function readProjectionRow(summary, nowMs, readDetails) {
322
339
  if (snapshot.compatibility === "incompatible") {
323
340
  return {
324
341
  id,
342
+ observedAtMs: nowMs,
325
343
  ...(alias ? { alias } : {}),
326
344
  state: "incompatible",
327
345
  observation: snapshot,
@@ -335,6 +353,7 @@ function readProjectionRow(summary, nowMs, readDetails) {
335
353
  ?? "projection identity could not be observed";
336
354
  return {
337
355
  id,
356
+ observedAtMs: nowMs,
338
357
  ...(alias ? { alias } : {}),
339
358
  state: "unknown",
340
359
  observation: {
@@ -354,6 +373,9 @@ function readProjectionRow(summary, nowMs, readDetails) {
354
373
  const tellCounts = readDetails
355
374
  ? readHeartTellCounts(projectionDir, diagnostics)
356
375
  : emptyTellCounts();
376
+ const pendingTells = readDetails
377
+ ? readPendingTells(projectionDir, diagnostics)
378
+ : [];
357
379
  const activityFacts = readDetails
358
380
  ? readActivityFacts(projectionDir, life.executionId, nowMs, diagnostics, typeof adoptedAt === "string" && Number.isFinite(adoptedAtMs) ? adoptedAt : identityFacts.mintedAt)
359
381
  : {};
@@ -377,11 +399,13 @@ function readProjectionRow(summary, nowMs, readDetails) {
377
399
  : undefined;
378
400
  return {
379
401
  id,
402
+ observedAtMs: nowMs,
380
403
  ...(alias ? { alias } : {}),
381
404
  state,
382
405
  observation: { compatibility: "current", lifecycle: life },
383
406
  akuma: identityFacts.akuma,
384
407
  provider: identityFacts.provider,
408
+ binding: identityFacts.binding,
385
409
  pid: identityFacts.pid,
386
410
  mintedAt: identityFacts.mintedAt,
387
411
  ...(terminalAt ? { terminalAt } : {}),
@@ -391,6 +415,7 @@ function readProjectionRow(summary, nowMs, readDetails) {
391
415
  ...(displayAgeMs !== undefined ? { activityAgeMs: displayAgeMs } : {}),
392
416
  ...(terminalFailure ? { terminalFailure } : {}),
393
417
  tellCounts,
418
+ ...(pendingTells.length > 0 ? { pendingTells } : {}),
394
419
  diagnostics,
395
420
  };
396
421
  }
@@ -433,7 +458,7 @@ function readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs) {
433
458
  const akumaDir = path.join(root, akuma);
434
459
  const hexes = readProjectionTopologyEntries(akumaDir);
435
460
  if (!hexes)
436
- return [];
461
+ return null;
437
462
  const summaries = [];
438
463
  for (const hex of hexes) {
439
464
  const id = `${akuma}/${hex}`;
@@ -465,10 +490,17 @@ export async function readOperationalProjectionStatusBoard(cwd, nowMs) {
465
490
  for (const akuma of akumas) {
466
491
  if (akuma.startsWith("."))
467
492
  continue;
468
- summaries.push(...readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs));
493
+ const scoped = readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs);
494
+ // Seeing an entry and then being unable to observe its namespace is not an
495
+ // observable zero. Keep the whole aggregate surface unavailable.
496
+ if (scoped === null)
497
+ return null;
498
+ summaries.push(...scoped);
469
499
  }
470
500
  const rows = summaries.map((summary) => readProjectionRow(summary, nowMs, requiresProjectionStatusDetails(summary.state)));
471
- return rows.length > 0 ? prioritizeProjectionStatusRows(rows) : null;
501
+ // A published root is an observable surface even when it currently has no
502
+ // rows. The caller must distinguish that fact from an unavailable root.
503
+ return prioritizeProjectionStatusRows(rows);
472
504
  }
473
505
  export async function readExactProjectionStatus(cwd, nowMs, projectionId) {
474
506
  if (!isProjectionId(projectionId))
@@ -511,7 +543,10 @@ export async function readAkumaNonterminalProjectionStatus(cwd, nowMs, akuma) {
511
543
  throw error;
512
544
  }
513
545
  const aliases = await readProjectionStatusAliases(cwd, root);
514
- const rows = readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs)
546
+ const summaries = readAkumaProjectionStatusSummaries(root, akuma, aliases, nowMs);
547
+ if (summaries === null)
548
+ return null;
549
+ const rows = summaries
515
550
  .filter(isNonterminalProjectionStatusSummary)
516
551
  .map((summary) => readProjectionRow(summary, nowMs, true))
517
552
  .sort(compareProjectionStatusRows);
@@ -26,11 +26,9 @@ function timestampAge(timestamp, now) {
26
26
  export function deriveTaskStatusSummary(board, now) {
27
27
  const eligible = board.tasks.filter((task) => !task.contractId && (task.state === "open" || task.state === "in_progress"));
28
28
  const inProgress = eligible.filter((task) => task.state === "in_progress").length;
29
- const topTasks = [
30
- ...eligible.filter((task) => task.state === "in_progress").sort(compareTaskStatusRows),
31
- ...eligible.filter((task) => task.state === "open" && !task.blocked).sort(compareTaskStatusRows),
32
- ]
33
- .slice(0, 5)
29
+ const topTasks = eligible
30
+ .filter((task) => task.state === "in_progress")
31
+ .sort(compareTaskStatusRows)
34
32
  .map((task) => {
35
33
  const updatedAt = taskUpdatedAt(task);
36
34
  return {
@@ -47,14 +45,12 @@ export function deriveTaskStatusSummary(board, now) {
47
45
  });
48
46
  const ready = eligible.filter((task) => task.state === "open" && !task.blocked).length;
49
47
  const blocked = eligible.filter((task) => task.state === "open" && task.blocked).length;
50
- if (board.tasks.length === 0 && board.diseases.length === 0)
51
- return undefined;
52
48
  return {
53
- total: board.tasks.length,
49
+ total: eligible.length,
54
50
  inProgress,
55
51
  ready,
56
52
  blocked,
57
- omitted: inProgress + ready - topTasks.length,
53
+ omitted: ready + blocked,
58
54
  topTasks,
59
55
  diseases: board.diseases,
60
56
  };
@@ -199,6 +195,8 @@ export async function readKanshiBoard(cwd, now, opts) {
199
195
  : await readExactProjectionStatus(cwd, now, opts.projectionId);
200
196
  return {
201
197
  contracts: [],
198
+ contractsObservable: false,
199
+ commissionPlaces: new Map(),
202
200
  projections: opts?.projectionId === undefined
203
201
  ? await readOperationalProjectionStatusBoard(cwd, now)
204
202
  : exactProjection ? { rows: [exactProjection] } : null,
@@ -206,7 +204,8 @@ export async function readKanshiBoard(cwd, now, opts) {
206
204
  diseases: loadedSettings.diseases,
207
205
  claimReconciliation: null,
208
206
  targetFilter,
209
- ...(tasks ? { tasks } : {}),
207
+ // Task and contract boards are repository organs; a non-repository cwd
208
+ // cannot truthfully turn their absence into a zero count.
210
209
  };
211
210
  }
212
211
  // Status is a pure reader: use the same resolver, but retain its ability to
@@ -237,6 +236,7 @@ export async function readKanshiBoard(cwd, now, opts) {
237
236
  })();
238
237
  }), defaultBranch);
239
238
  const contracts = [];
239
+ const commissionPlaces = new Map();
240
240
  // Readable-ledger commission targets retained for post-assembly filter only.
241
241
  const derivedTargetById = new Map();
242
242
  for (const item of ledgers) {
@@ -261,6 +261,7 @@ export async function readKanshiBoard(cwd, now, opts) {
261
261
  continue;
262
262
  }
263
263
  const bind = findBindEntry(item.entries);
264
+ commissionPlaces.set(item.contractId, bind?.data.place ?? null);
264
265
  derivedTargetById.set(item.contractId, commissionTarget(item.entries)?.ref);
265
266
  const base = latestBase(item.entries);
266
267
  const state = deriveContractState(item.entries);
@@ -334,6 +335,8 @@ export async function readKanshiBoard(cwd, now, opts) {
334
335
  : await readExactProjectionStatus(cwd, now, opts.projectionId);
335
336
  return {
336
337
  contracts: filteredContracts,
338
+ contractsObservable: true,
339
+ commissionPlaces,
337
340
  projections: opts?.projectionId === undefined
338
341
  ? await readOperationalProjectionStatusBoard(cwd, now)
339
342
  : exactProjection ? { rows: [exactProjection] } : null,
@@ -72,7 +72,7 @@ export async function createTaskMutationCommit(input) {
72
72
  ], undefined, env);
73
73
  }
74
74
  const tree = (await git(input.cwd, ["write-tree"], undefined, env)).trim();
75
- const parentTree = (await git(input.cwd, ["rev-parse", `${input.parent}^{tree}`])).trim();
75
+ const parentTree = (await git(input.cwd, ["show", "-s", "--format=%T", input.parent])).trim();
76
76
  if (tree === parentTree)
77
77
  return input.parent;
78
78
  return (await git(input.cwd, ["commit-tree", tree, "-p", input.parent, "-m", input.subject])).trim();
@@ -354,7 +354,7 @@ export async function findAcceptedLandingCommit(input) {
354
354
  delivery: input.delivery,
355
355
  sources,
356
356
  });
357
- const actualTree = (await git(input.cwd, ["rev-parse", `${commit}^{tree}`])).trim();
357
+ const actualTree = (await git(input.cwd, ["show", "-s", "--format=%T", commit])).trim();
358
358
  if (actualTree === expectedTree)
359
359
  return commit;
360
360
  }
@@ -1,3 +1,3 @@
1
1
  // Generated into build output by scripts/build.mjs
2
- export const VERSION = "2.9.8";
3
- export const GIT_HASH = "c9cfce1";
2
+ export const VERSION = "2.9.9";
3
+ export const GIT_HASH = "79df272";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "2.9.8",
3
+ "version": "2.9.9",
4
4
  "description": "CLI for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",