@astrosheep/keiyaku 4.5.26 → 4.5.28

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 (98) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +82 -162
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +153 -76
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +68 -64
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +115 -146
  9. package/build/src/akuma/akuma-errors.d.ts +6 -0
  10. package/build/src/akuma/akuma-errors.js +11 -0
  11. package/build/src/akuma/akuma-handle.d.ts +54 -1
  12. package/build/src/akuma/akuma-handle.js +79 -8
  13. package/build/src/akuma/akuma-instance.js +6 -22
  14. package/build/src/akuma/akuma-observe.d.ts +6 -0
  15. package/build/src/akuma/akuma-observe.js +3 -1
  16. package/build/src/akuma/akuma-probe.d.ts +20 -1
  17. package/build/src/akuma/akuma-probe.js +20 -3
  18. package/build/src/akuma/akuma.d.ts +13 -0
  19. package/build/src/akuma/akuma.js +1 -0
  20. package/build/src/akuma/allowed.d.ts +2 -1
  21. package/build/src/akuma/allowed.js +9 -4
  22. package/build/src/akuma/archetype.js +3 -3
  23. package/build/src/akuma/body.js +8 -3
  24. package/build/src/akuma/fleet-execution.d.ts +13 -2
  25. package/build/src/akuma/fleet-execution.js +45 -0
  26. package/build/src/akuma/fleet-observation.d.ts +241 -0
  27. package/build/src/akuma/fleet-observation.js +13 -0
  28. package/build/src/akuma/fleet-owner-port.d.ts +12 -0
  29. package/build/src/akuma/fleet-owner-port.js +46 -0
  30. package/build/src/akuma/fleet-request.d.ts +55 -4
  31. package/build/src/akuma/fleet-request.js +101 -6
  32. package/build/src/akuma/heart/index.d.ts +1 -0
  33. package/build/src/akuma/heart/index.js +4 -1
  34. package/build/src/akuma/heart/rows.d.ts +1 -0
  35. package/build/src/akuma/heart/rows.js +8 -0
  36. package/build/src/akuma/heart/soul.js +2 -2
  37. package/build/src/akuma/identity.js +11 -9
  38. package/build/src/akuma/projection.d.ts +36 -0
  39. package/build/src/akuma/projection.js +2 -1
  40. package/build/src/akuma/provider.d.ts +14 -0
  41. package/build/src/akuma/provider.js +66 -4
  42. package/build/src/akuma/providers/acp/events.js +16 -8
  43. package/build/src/akuma/providers/claude/events.js +5 -7
  44. package/build/src/akuma/providers/opencode-sdk/events.js +9 -5
  45. package/build/src/akuma/providers/pi/events.js +4 -3
  46. package/build/src/akuma/request-lifecycle.js +10 -1
  47. package/build/src/akuma-body.js +1 -33
  48. package/build/src/alias/index.js +3 -3
  49. package/build/src/cli/commands/akuma-invoke.d.ts +8 -0
  50. package/build/src/cli/commands/akuma-invoke.js +67 -6
  51. package/build/src/cli/commands/akuma.d.ts +1 -0
  52. package/build/src/cli/commands/akuma.js +19 -30
  53. package/build/src/cli/commands/contract-help.d.ts +1 -0
  54. package/build/src/cli/commands/contract-help.js +9 -5
  55. package/build/src/cli/invoke.js +1 -1
  56. package/build/src/cli/render/akuma-activity.d.ts +7 -1
  57. package/build/src/cli/render/akuma-activity.js +164 -50
  58. package/build/src/cli/render/akuma-tool.d.ts +5 -0
  59. package/build/src/cli/render/akuma-tool.js +118 -14
  60. package/build/src/cli/render/akuma.d.ts +12 -1
  61. package/build/src/cli/render/akuma.js +74 -2
  62. package/build/src/cli/render/catalog.js +2 -3
  63. package/build/src/cli/render/contract-observation.d.ts +1 -0
  64. package/build/src/cli/render/contract-observation.js +20 -1
  65. package/build/src/cli/render/contract.js +4 -4
  66. package/build/src/cli/render/execution-progress.d.ts +1 -0
  67. package/build/src/cli/render/execution-progress.js +77 -3
  68. package/build/src/cli/render/kanshi.js +5 -19
  69. package/build/src/cli/render/terminal.js +3 -1
  70. package/build/src/cli/runtime.d.ts +6 -0
  71. package/build/src/cli/runtime.js +35 -17
  72. package/build/src/core/facts/offer.d.ts +1 -2
  73. package/build/src/core/verbs/abandon.js +1 -1
  74. package/build/src/core/verbs/amend.js +1 -1
  75. package/build/src/core/verbs/arc.js +1 -1
  76. package/build/src/core/verbs/attestation.js +1 -1
  77. package/build/src/core/verbs/bind.js +1 -1
  78. package/build/src/core/verbs/deliver.js +1 -1
  79. package/build/src/core/verbs/placement.js +1 -1
  80. package/build/src/core/verbs/reintegrate.js +1 -1
  81. package/build/src/git/admission.js +18 -7
  82. package/build/src/identity/coordinates.js +1 -1
  83. package/build/src/identity/normalize.d.ts +3 -0
  84. package/build/src/identity/normalize.js +10 -0
  85. package/build/src/identity/selector.d.ts +1 -0
  86. package/build/src/identity/selector.js +15 -4
  87. package/build/src/library/address.d.ts +33 -3
  88. package/build/src/library/address.js +90 -29
  89. package/build/src/library/akuma-creation.d.ts +1 -1
  90. package/build/src/library/akuma-creation.js +10 -52
  91. package/build/src/library/composition.d.ts +14 -1
  92. package/build/src/library/fleet.d.ts +13 -2
  93. package/build/src/library/fleet.js +142 -48
  94. package/build/src/library/keiyaku.d.ts +14 -1
  95. package/build/src/plugin/public.d.ts +2 -0
  96. package/build/src/protocol/completion.js +9 -2
  97. package/build/src/runtime/proc/windows-launch.exe +0 -0
  98. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { parseAkumaStatus } from "../../akuma/akuma.js";
2
2
  import { defaultWaitComplete } from "../../akuma/akuma-observe.js";
3
- import { toolRepr } from "./akuma-tool.js";
4
- import { displayColumns, renderBoundedTextBlock, safeText, takeDisplayColumns, truncateMiddleDisplayText, } from "./terminal.js";
3
+ import { toolContent, toolRepr } from "./akuma-tool.js";
4
+ import { displayColumns, renderBoundedTextBlock, safeText, takeDisplayColumns, truncateDisplayText, truncateMiddleDisplayText, } from "./terminal.js";
5
5
  export const DEFAULT_CONTEXT = { columns: 80, color: false };
6
6
  const TIME_WIDTH = 5;
7
7
  const VERB_WIDTH = 6;
@@ -38,7 +38,7 @@ function contractFacts(contract) {
38
38
  return contract.kind === "failed" ? [`! contract failed ${safeText(contract.diagnostic)}`] : [];
39
39
  }
40
40
  function unobservedText(id, diagnostic) {
41
- return `! ${id} unobserved: ${safeText(diagnostic)}`;
41
+ return `× Akuma observation failed ${safeText(id)} — ${safeText(diagnostic)}`;
42
42
  }
43
43
  function lifeLabel(life) {
44
44
  if (life === "running")
@@ -57,7 +57,7 @@ function clock(at) {
57
57
  ? `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`
58
58
  : "unknown";
59
59
  }
60
- function label(row) {
60
+ function label(row, tool) {
61
61
  if (row.kind === "said")
62
62
  return "say";
63
63
  if (row.kind === "thought")
@@ -74,7 +74,7 @@ function label(row) {
74
74
  return "call";
75
75
  if (row.kind !== "tool")
76
76
  return row.kind;
77
- return toolRepr(row).label;
77
+ return tool.label;
78
78
  }
79
79
  function mark(row) {
80
80
  if (row.kind === "outcome")
@@ -92,7 +92,7 @@ function mark(row) {
92
92
  }
93
93
  return "│";
94
94
  }
95
- function rowText(row) {
95
+ function rowText(row, tool) {
96
96
  if (row.kind === "said" ||
97
97
  row.kind === "thought" ||
98
98
  row.kind === "note" ||
@@ -111,40 +111,62 @@ function rowText(row) {
111
111
  return { text: "", lines: 1 };
112
112
  if (row.kind !== "tool")
113
113
  return { text: "", lines: 1 };
114
- const repr = toolRepr(row);
114
+ if (row.call.kind === "other")
115
+ return { text: "", lines: 1 };
116
+ const repr = tool;
115
117
  return {
116
118
  text: repr.text,
117
- lines: 2,
119
+ lines: 1,
118
120
  ...(repr.overflow === "middle-ellipsis" ? { middle: true } : {}),
119
121
  ...(repr.suffix === undefined ? {} : { suffix: repr.suffix }),
120
122
  };
121
123
  }
122
- function eventPrefix(glyph, verb, time) {
124
+ function eventPrefix(glyph, verb, time, columns) {
123
125
  const gutter = time === undefined ? " ".repeat(TIME_WIDTH) : time.padEnd(TIME_WIDTH);
124
- return `${gutter} ${glyph} ${verb.padEnd(VERB_WIDTH)} `;
126
+ return actionCell(`${gutter} ${glyph}`, verb, columns);
127
+ }
128
+ /** The action column one row spends: six cells by default, or the name's own width when longer. */
129
+ function verbColumn(verb) {
130
+ return Math.max(VERB_WIDTH, displayColumns(verb));
131
+ }
132
+ /**
133
+ * Attach the action cell after one row prefix. A name wider than the row's
134
+ * remaining columns truncates grapheme-safely, spending every column the row
135
+ * leaves after the separator so an exact-fit name stays whole and args trim first.
136
+ */
137
+ function actionCell(head, verb, columns) {
138
+ const prefix = `${head} `;
139
+ const available = Math.max(0, columns - displayColumns(prefix) - 1);
140
+ const width = Math.min(verbColumn(verb), available);
141
+ return `${prefix}${padToDisplay(truncateDisplayText(verb, width), width)} `;
125
142
  }
126
143
  function continuationPrefix() {
127
- return eventPrefix("│", "");
144
+ return " ".repeat(TIME_WIDTH) + " │ " + " ".repeat(VERB_WIDTH) + " ";
128
145
  }
129
146
  /** Pad to a terminal-column width; raw string length is never the measuring stick. */
130
147
  function padToDisplay(text, width) {
131
148
  const remaining = width - displayColumns(text);
132
149
  return remaining > 0 ? `${text}${" ".repeat(remaining)}` : text;
133
150
  }
151
+ function timelineMarker(text) {
152
+ return `${" ".repeat(TIME_WIDTH)} ⋮ ${text}`;
153
+ }
134
154
  function plainLayout() {
135
155
  return {
136
- head: (time, glyph, verb) => eventPrefix(glyph, verb, time),
156
+ head: (time, glyph, verb, columns) => eventPrefix(glyph, verb, time, columns),
137
157
  continuation: continuationPrefix,
138
- marker: (count) => `${" ".repeat(TIME_WIDTH)} ⋮ ${count} omitted`,
158
+ marker: (count) => timelineMarker(`${count} omitted`),
139
159
  };
140
160
  }
141
- function sourceLayout(source, width) {
161
+ function sourceLayout(source, width, clock) {
142
162
  const gutter = () => `${" ".repeat(TIME_WIDTH)} ${padToDisplay(source, width())} `;
143
163
  return {
144
- head: (time, glyph, verb) => `${time === undefined ? " ".repeat(TIME_WIDTH) : time.padEnd(TIME_WIDTH)} ${padToDisplay(source, width())} ${glyph} ${verb.padEnd(VERB_WIDTH)} `,
164
+ head: (time, glyph, verb, columns) => actionCell(`${time === undefined ? " ".repeat(TIME_WIDTH) : time.padEnd(TIME_WIDTH)} ${padToDisplay(source, width())} ${glyph}`, verb, columns),
145
165
  // Continuations blank the time and source columns and align under the mark.
146
166
  continuation: () => `${" ".repeat(TIME_WIDTH)} ${" ".repeat(width())} │ ${" ".repeat(VERB_WIDTH)} `,
147
167
  marker: (count) => `${gutter()}⋮ ${count} omitted`,
168
+ clock,
169
+ singleLine: true,
148
170
  };
149
171
  }
150
172
  function quotedBody(row) {
@@ -172,14 +194,35 @@ function renderMiddleEllipsis(first, text, suffix, columns) {
172
194
  const showSuffix = suffix.length > 0 && withSuffix >= 6;
173
195
  return `${first}${truncateMiddleDisplayText(text, Math.max(0, showSuffix ? withSuffix : remaining))}${showSuffix ? suffix : ""}`;
174
196
  }
175
- function renderRow(row, context, history, first, continuation) {
176
- const value = rowText(row);
197
+ function rowBody(row, text, columns) {
198
+ if (row.kind !== "tool" || row.call.kind !== "other")
199
+ return text;
200
+ return toolContent(row, columns);
201
+ }
202
+ function renderRow(row, context, options) {
203
+ const { history, first, continuation, tool, singleLine = false, inFlightSay = false } = options;
204
+ const value = rowText(row, tool);
177
205
  const quoted = quotedBody(row);
206
+ if (singleLine) {
207
+ const openQuote = row.kind === "said" && inFlightSay;
208
+ const quoteWidth = quoted ? (openQuote ? 1 : 2) : 0;
209
+ const remaining = context.columns - displayColumns(first) - quoteWidth;
210
+ const bodyText = rowBody(row, value.text, remaining);
211
+ const text = truncateDisplayText(bodyText, Math.max(0, remaining));
212
+ if (!quoted)
213
+ return [text.length === 0 ? first.trimEnd() : `${first}${text}`];
214
+ return [`${first}"${text}${openQuote ? "" : '"'}`];
215
+ }
178
216
  const quoteWidth = quoted ? 2 : 0;
179
217
  if (value.middle === true) {
180
218
  return [renderMiddleEllipsis(first, value.text, value.suffix ?? "", context.columns - quoteWidth)];
181
219
  }
182
- const lines = renderBoundedTextBlock(value.text, {
220
+ const remaining = context.columns - quoteWidth - displayColumns(first);
221
+ // A name that already fills its row spends the width whole; its arguments trim away entirely.
222
+ if (remaining <= 0)
223
+ return [first.trimEnd()];
224
+ const bodyText = rowBody(row, value.text, remaining - displayColumns(value.suffix ?? ""));
225
+ const lines = renderBoundedTextBlock(bodyText, {
183
226
  first,
184
227
  continuation,
185
228
  columns: context.columns - quoteWidth,
@@ -199,7 +242,14 @@ function groupedEntries(entries, context, history = false, layout = plainLayout(
199
242
  const row = entry.row;
200
243
  const at = clock(row.at);
201
244
  const changed = previousClock === undefined || at !== previousClock;
202
- lines.push(...renderRow(row, context, history, layout.head(changed ? at : undefined, mark(row), label(row)), layout.continuation()));
245
+ const tool = row.kind === "tool" ? toolRepr(row) : undefined;
246
+ lines.push(...renderRow(row, context, {
247
+ history,
248
+ first: layout.head(changed ? at : undefined, mark(row), label(row, tool), context.columns),
249
+ continuation: layout.continuation(),
250
+ tool,
251
+ singleLine: layout.singleLine === true,
252
+ }));
203
253
  previousClock = at;
204
254
  }
205
255
  return lines;
@@ -307,11 +357,27 @@ function baselineOmissionCount(activity, boundary) {
307
357
  }
308
358
  return count;
309
359
  }
310
- function renderStreamRow(state, row, lines, context, layout) {
360
+ function renderStreamRow(state, row, lines, options) {
361
+ const { context, layout, inFlightSay = false } = options;
311
362
  const at = clock(row.at);
312
- const changed = state.previousClock === undefined || at !== state.previousClock;
313
- lines.push(...renderRow(row, context, false, layout.head(changed ? at : undefined, mark(row), label(row)), layout.continuation()));
314
- state.previousClock = at;
363
+ const previousClock = layout.clock?.previous ?? state.previousClock;
364
+ const changed = previousClock === undefined || at !== previousClock;
365
+ const tool = row.kind === "tool" ? toolRepr(row) : undefined;
366
+ lines.push(...renderRow(row, context, {
367
+ history: false,
368
+ first: layout.head(changed ? at : undefined, inFlightSay ? "⧖" : mark(row), label(row, tool), context.columns),
369
+ continuation: layout.continuation(),
370
+ tool,
371
+ singleLine: layout.singleLine === true,
372
+ inFlightSay,
373
+ }));
374
+ if (layout.clock !== undefined)
375
+ layout.clock.previous = at;
376
+ else
377
+ state.previousClock = at;
378
+ }
379
+ function inFlightSay(activity, row) {
380
+ return activity.snapshot.kind === "open" && row.kind === "said";
315
381
  }
316
382
  function coalesceDeferredGaps(state) {
317
383
  state.deferred = state.deferred.reduce((entries, entry) => {
@@ -340,7 +406,7 @@ function flushSafeActivityPrefix(state, lines, context, layout) {
340
406
  return;
341
407
  if (first.kind === "row") {
342
408
  state.deferred.shift();
343
- renderStreamRow(state, first.row, lines, context, layout);
409
+ renderStreamRow(state, first.row, lines, { context, layout, inFlightSay: first.inFlightSay });
344
410
  continue;
345
411
  }
346
412
  const next = state.deferred[1];
@@ -355,7 +421,7 @@ function renderCurrentTurnBoundary(state, activity, lines, context, layout) {
355
421
  if (boundary !== undefined && !state.renderedBoundaries.has(boundary.turnSequence)) {
356
422
  state.renderedBoundaries.add(boundary.turnSequence);
357
423
  if (boundary.row.kind !== "thought")
358
- renderStreamRow(state, boundary.row, lines, context, layout);
424
+ renderStreamRow(state, boundary.row, lines, { context, layout });
359
425
  }
360
426
  return boundary;
361
427
  }
@@ -383,14 +449,14 @@ function observeActivitySnapshot(state, activity, context, layout) {
383
449
  for (const row of rows) {
384
450
  if (isBoundedStreamTool(row) && state.openingTools < OPENING_TOOL_BUDGET) {
385
451
  state.openingTools += 1;
386
- renderStreamRow(state, row, lines, context, layout);
452
+ renderStreamRow(state, row, lines, { context, layout, inFlightSay: inFlightSay(activity, row) });
387
453
  continue;
388
454
  }
389
455
  if (!isBoundedStreamTool(row) && state.deferred.length === 0) {
390
- renderStreamRow(state, row, lines, context, layout);
456
+ renderStreamRow(state, row, lines, { context, layout, inFlightSay: inFlightSay(activity, row) });
391
457
  continue;
392
458
  }
393
- state.deferred.push({ kind: "row", row });
459
+ state.deferred.push({ kind: "row", row, inFlightSay: inFlightSay(activity, row) });
394
460
  if (isBoundedStreamTool(row)) {
395
461
  const pendingTools = state.deferred.filter((entry) => entry.kind === "row" && isBoundedStreamTool(entry.row)).length;
396
462
  if (pendingTools > RECENT_TOOL_BUDGET)
@@ -406,7 +472,7 @@ function flushActivityTail(state, context, layout) {
406
472
  if (entry.kind === "gap")
407
473
  lines.push(layout.marker(entry.count));
408
474
  else
409
- renderStreamRow(state, entry.row, lines, context, layout);
475
+ renderStreamRow(state, entry.row, lines, { context, layout, inFlightSay: entry.inFlightSay });
410
476
  }
411
477
  state.deferred = [];
412
478
  return lines;
@@ -492,14 +558,50 @@ function createWaitObservationState() {
492
558
  return {
493
559
  streams: new Map(),
494
560
  sources: new Map(),
561
+ tags: new Map(),
495
562
  contracts: new Map(),
496
563
  settledAt: new Map(),
497
564
  sourceWidth: 0,
565
+ clock: {},
498
566
  plural: undefined,
499
567
  headerEmitted: false,
500
568
  observed: false,
501
569
  };
502
570
  }
571
+ function shortestUniquePrefix(value, values) {
572
+ for (let length = 4; length <= value.length; length += 1) {
573
+ const tag = value.slice(0, length);
574
+ if (values.filter((candidate) => candidate.slice(0, length) === tag).length === 1)
575
+ return tag;
576
+ }
577
+ return undefined;
578
+ }
579
+ function shortestUniqueSegmentSuffix(id, ids) {
580
+ const segments = id.split("/");
581
+ for (let start = segments.length - 2; start >= 0; start -= 1) {
582
+ const tag = segments.slice(start).join("/");
583
+ if (ids.filter((candidate) => candidate.endsWith(`/${tag}`) || candidate === tag).length === 1)
584
+ return tag;
585
+ }
586
+ return undefined;
587
+ }
588
+ function waitIdentityTag(id, ids) {
589
+ const finals = ids.map((candidate) => candidate.slice(candidate.lastIndexOf("/") + 1));
590
+ const index = ids.indexOf(id);
591
+ const own = finals[index];
592
+ if (own === undefined)
593
+ return id;
594
+ return (shortestUniquePrefix(own, finals) ?? shortestUniqueSegmentSuffix(id, ids) ?? shortestUniquePrefix(id, ids) ?? id);
595
+ }
596
+ /** The selected set fixes identity tags before its first attributed row. */
597
+ function freezeWaitTags(state) {
598
+ const ids = [...state.sources.keys()];
599
+ state.tags = new Map(ids.map((id) => [id, waitIdentityTag(id, ids)]));
600
+ state.sourceWidth = Math.max(0, ...Array.from(state.tags.values(), displayColumns));
601
+ }
602
+ function sourceTag(state, id) {
603
+ return state.tags.get(id) ?? waitIdentityTag(id, [...state.sources.keys()]);
604
+ }
503
605
  /** Register one selected or newly observed source; the set freezes the column before the first row. */
504
606
  function registerWaitSource(state, id, alias, contract) {
505
607
  if (state.sources.has(id))
@@ -508,8 +610,6 @@ function registerWaitSource(state, id, alias, contract) {
508
610
  state.sources.set(id, label);
509
611
  if (contract !== undefined)
510
612
  state.contracts.set(id, contract);
511
- if (!state.headerEmitted)
512
- state.sourceWidth = Math.max(state.sourceWidth, displayColumns(label));
513
613
  }
514
614
  /**
515
615
  * The one aggregate head a plural wait prints before any activity row: the
@@ -520,7 +620,8 @@ function registerWaitSource(state, id, alias, contract) {
520
620
  function aggregateHeading(state) {
521
621
  const head = [...state.sources].map(([id, label]) => {
522
622
  const contractId = associatedContractId(state.contracts.get(id) ?? { kind: "none" });
523
- return contractId === undefined ? label : `${label} · ${contractId}`;
623
+ const named = `${sourceTag(state, id)} ${label}`;
624
+ return contractId === undefined ? named : `${named} · ${contractId}`;
524
625
  });
525
626
  return [...head, frameRule(head)];
526
627
  }
@@ -529,6 +630,8 @@ function observeWaitRound(state, round, context, now) {
529
630
  // Establish the whole round's sources before any row so widths stay aligned within it.
530
631
  for (const member of round)
531
632
  registerWaitSource(state, member.status.id, member.alias, member.contract);
633
+ if (!state.headerEmitted)
634
+ freezeWaitTags(state);
532
635
  const lines = [];
533
636
  // The observation subject opens once: an aggregate head for a plural set, the observed
534
637
  // identity frame for a single target. Every later round only appends attributed rows.
@@ -551,7 +654,7 @@ function observeWaitRound(state, round, context, now) {
551
654
  else {
552
655
  // Only a plural wait attributes its rows; a single-target stream keeps the plain row grammar.
553
656
  const stream = activityStream(context, state.plural === true
554
- ? sourceLayout(state.sources.get(status.id) ?? status.id, () => state.sourceWidth)
657
+ ? sourceLayout(sourceTag(state, status.id), () => state.sourceWidth, state.clock)
555
658
  : plainLayout());
556
659
  state.streams.set(status.id, stream);
557
660
  // Seed marks skipped retained evidence without spending this command's live tool budget.
@@ -595,15 +698,20 @@ function concludeWaitStream(state, result, startedAt, now) {
595
698
  for (const stream of state.streams.values())
596
699
  tail.push(...stream.flush());
597
700
  const end = now();
701
+ for (const observation of result.observations)
702
+ registerWaitSource(state, observation.status.id, undefined, observation.contract);
703
+ for (const member of result.unobserved)
704
+ registerWaitSource(state, member.id, undefined);
705
+ if (!state.headerEmitted)
706
+ freezeWaitTags(state);
598
707
  const order = orderedWaitIds(state, result);
599
708
  const multi = order.length > 1;
600
709
  const observationById = new Map(result.observations.map((observation) => [observation.status.id, observation]));
601
710
  const unobservedById = new Map(result.unobserved.map((member) => [member.id, member]));
602
- // A multi-target scoreboard names the frozen source label; a single target keeps the bare diagnostic.
603
- const label = (id) => (multi ? (state.sources.get(id) ?? id) : id);
711
+ // A failure fact keeps the complete identity even where the scoreboard attributes a target by its identity tag.
604
712
  const unobservedLines = order
605
713
  .filter((id) => unobservedById.has(id))
606
- .map((id) => unobservedText(label(id), unobservedById.get(id).diagnostic));
714
+ .map((id) => unobservedText(id, unobservedById.get(id).diagnostic));
607
715
  const conclusions = order.flatMap((id) => {
608
716
  const observation = observationById.get(id);
609
717
  if (observation === undefined)
@@ -612,7 +720,7 @@ function concludeWaitStream(state, result, startedAt, now) {
612
720
  const complete = defaultWaitComplete(status);
613
721
  const at = complete ? (state.settledAt.get(id) ?? end) : end;
614
722
  const { mark, verb } = conclusionMarkVerb(status, statusAnswer(observation) !== undefined);
615
- const target = multi ? ` ${padToDisplay(state.sources.get(id) ?? id, state.sourceWidth)}` : "";
723
+ const target = multi ? ` ${padToDisplay(sourceTag(state, id), state.sourceWidth)}` : "";
616
724
  return [`${clockFromMs(at)}${target} ${mark} ${verb}${conclusionClause(at, startedAt, complete, end)}`];
617
725
  });
618
726
  const answeredSingle = !multi && result.observations.length === 1 && statusAnswer(result.observations[0]) !== undefined;
@@ -643,6 +751,7 @@ export function waitObservationStream(context, options = {}) {
643
751
  for (const member of selected)
644
752
  registerWaitSource(state, member.id, member.alias, member.contract);
645
753
  state.plural ??= selected.length > 1;
754
+ freezeWaitTags(state);
646
755
  };
647
756
  const observe = (round) => observeWaitRound(state, round, context, now);
648
757
  const conclude = (result) => concludeWaitStream(state, result, startedAt, now);
@@ -763,6 +872,7 @@ function answeredBlock(observation, answer, alias, columns) {
763
872
  function snapshotCore(view, context, options) {
764
873
  const activity = snapshotActivityLines(view.status.timeline, context);
765
874
  const facts = [
875
+ ...(view.status.cwd === undefined ? [] : [`cwd ${safeText(view.status.cwd)}`]),
766
876
  ...(options.showAllowed === true ? [`allowed ${view.status.allowed.join(", ") || "none"}`] : []),
767
877
  ...(view.status.readonly?.enforcement === "none" ? [`! ${safeText(view.status.readonly.diagnostic)}`] : []),
768
878
  ...contractFacts(view.contract),
@@ -871,23 +981,19 @@ export function waitText(result, context) {
871
981
  const end = Date.now();
872
982
  const startedAt = result.startedAt ?? end;
873
983
  const order = [];
874
- const labels = new Map();
875
- const remember = (id, frozenAlias) => {
876
- if (labels.has(id))
877
- return;
878
- labels.set(id, frozenAlias ?? id);
879
- order.push(id);
984
+ const remember = (id) => {
985
+ if (!order.includes(id))
986
+ order.push(id);
880
987
  };
881
988
  for (const member of result.selection ?? [])
882
- remember(member.id, member.alias);
989
+ remember(member.id);
883
990
  for (const observation of observations)
884
991
  remember(observation.status.id);
885
992
  for (const member of unobserved)
886
993
  remember(member.id);
887
- const label = (id) => labels.get(id) ?? id;
888
994
  const observationById = new Map(observations.map((observation) => [observation.status.id, observation]));
889
995
  const unobservedById = new Map(unobserved.map((member) => [member.id, member]));
890
- const sourceWidth = order.reduce((width, id) => Math.max(width, displayColumns(label(id))), 0);
996
+ const sourceWidth = Math.max(0, ...order.map((id) => displayColumns(waitIdentityTag(id, order))));
891
997
  const blocks = [
892
998
  ...order.flatMap((id) => {
893
999
  const observation = observationById.get(id);
@@ -902,7 +1008,7 @@ export function waitText(result, context) {
902
1008
  ? [
903
1009
  order
904
1010
  .filter((id) => unobservedById.has(id))
905
- .map((id) => unobservedText(label(id), unobservedById.get(id).diagnostic))
1011
+ .map((id) => unobservedText(id, unobservedById.get(id).diagnostic))
906
1012
  .join("\n"),
907
1013
  ]
908
1014
  : []),
@@ -916,7 +1022,7 @@ export function waitText(result, context) {
916
1022
  const at = complete ? (settleMoment(status) ?? end) : end;
917
1023
  const { mark, verb } = conclusionMarkVerb(status, statusAnswer(observation) !== undefined);
918
1024
  return [
919
- `${clockFromMs(at)} ${padToDisplay(label(id), sourceWidth)} ${mark} ${verb}${conclusionClause(at, startedAt, complete, end)}`,
1025
+ `${clockFromMs(at)} ${padToDisplay(waitIdentityTag(id, order), sourceWidth)} ${mark} ${verb}${conclusionClause(at, startedAt, complete, end)}`,
920
1026
  ];
921
1027
  });
922
1028
  return rows.length > 0 ? [...blocks, rows.join("\n")].join("\n\n") : blocks.join("\n\n");
@@ -934,14 +1040,22 @@ export function historyText(command, result, context) {
934
1040
  throw new Error("history result lacks page");
935
1041
  const rows = groupedRows(result.history.rows, context, true);
936
1042
  const paging = result.history.omitted > 0
937
- ? [` ⋮ ${result.history.omitted} earlier turns · showing last ${result.history.rows.length}`]
1043
+ ? [
1044
+ timelineMarker(result.history.hasLater
1045
+ ? `${result.history.omitted} later events · showing first ${result.history.rows.length}`
1046
+ : `${result.history.omitted} earlier events · showing last ${result.history.rows.length}`),
1047
+ ]
938
1048
  : [];
939
1049
  return [...snapshotHeading(result.akuma, result.alias, result.historyResult.contract), ...paging, ...rows].join("\n");
940
1050
  }
941
1051
  export function tellText(result, context) {
942
1052
  const wake = result.result.tell.wake;
943
1053
  const target = identity(result.result.akuma, result.alias);
944
- const row = groupedRows([result.result.tell.row], context).join("\n");
1054
+ const row = groupedRows([result.result.tell.row], context, false, {
1055
+ ...plainLayout(),
1056
+ head: (time, _glyph, _verb, columns) => eventPrefix(wake.kind === "held" ? "⧗" : "⧖", "tell", time, columns),
1057
+ singleLine: true,
1058
+ }).join("\n");
945
1059
  if (wake.kind === "failed") {
946
1060
  const child = "child" in wake ? wake.child : undefined;
947
1061
  const failure = `! tell delivery failed · ${safeText(wake.diagnostic)}${child === undefined ? "" : ` · log ${child.log.path} ${child.log.from}..${child.log.to}`}`;
@@ -6,6 +6,11 @@ type FleetTimelineRow = Extract<AkumaObservation["status"]["timeline"]["entries"
6
6
  type ToolRow = Extract<ActivityRow | SnapshotRow | FleetTimelineRow, {
7
7
  kind: "tool";
8
8
  }>;
9
+ /** The bounded body of a generic tool row, including its terminal diagnostic. */
10
+ export declare function toolContent(row: ToolRow, columns: number): string;
11
+ export declare function toolLabel(row: ToolRow): string;
12
+ /** The trailing failure, duration, or message clause one settled tool row carries, otherwise the empty string. */
13
+ export declare function toolDiagnostic(row: ToolRow): string;
9
14
  export type ToolRepr = Readonly<{
10
15
  label: string;
11
16
  text: string;
@@ -1,4 +1,71 @@
1
+ import { displayColumns, truncateDisplayText } from "./terminal.js";
1
2
  import { normalizeToolCommand } from "./akuma-tool-command.js";
3
+ /** The bounded body of a generic tool row, including its terminal diagnostic. */
4
+ export function toolContent(row, columns) {
5
+ const input = row.call.kind === "other" ? row.call.input : undefined;
6
+ const diagnostic = toolDiagnostic(row);
7
+ const width = Math.max(0, columns - displayColumns(diagnostic));
8
+ let text;
9
+ if (input === undefined) {
10
+ text = "";
11
+ }
12
+ else {
13
+ const common = commonOtherText(row);
14
+ if (common !== undefined)
15
+ text = common;
16
+ else if (input.json === "{}")
17
+ text = "";
18
+ else if (input.truncated)
19
+ text = `${input.json}…`;
20
+ else {
21
+ const value = jsonObject(input.json);
22
+ text = value === undefined ? input.json : objectFieldsText(value, width);
23
+ }
24
+ }
25
+ return `${truncateDisplayText(text, width)}${diagnostic}`;
26
+ }
27
+ export function toolLabel(row) {
28
+ return toolCore(row).label;
29
+ }
30
+ /** The trailing failure, duration, or message clause one settled tool row carries, otherwise the empty string. */
31
+ export function toolDiagnostic(row) {
32
+ const suffix = result(row);
33
+ return suffix === undefined ? "" : ` — ${suffix}`;
34
+ }
35
+ function jsonObject(json) {
36
+ let value;
37
+ try {
38
+ value = JSON.parse(json);
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ return value !== null && typeof value === "object" && !Array.isArray(value)
44
+ ? value
45
+ : undefined;
46
+ }
47
+ /**
48
+ * Retain leading whole fields that fit and name the trailing omit count. When
49
+ * even that marker cannot fit, keep the first field or the object's own prefix
50
+ * with a visible ellipsis, never a silently complete-looking value.
51
+ */
52
+ function objectFieldsText(value, columns) {
53
+ const fields = Object.entries(value).map(([key, field]) => `${JSON.stringify(key)}:${JSON.stringify(field)}`);
54
+ if (fields.length === 0)
55
+ return "";
56
+ const full = `{${fields.join(",")}}`;
57
+ if (displayColumns(full) <= columns)
58
+ return full;
59
+ for (let count = fields.length - 1; count >= 1; count -= 1) {
60
+ const candidate = `{${fields.slice(0, count).join(",")}} +${fields.length - count} fields`;
61
+ if (displayColumns(candidate) <= columns)
62
+ return candidate;
63
+ }
64
+ const first = fields[0];
65
+ if (fields.length > 1 && displayColumns(first) <= columns)
66
+ return `${first}…`;
67
+ return truncateDisplayText(full, columns);
68
+ }
2
69
  function oneLine(value) {
3
70
  return value.replace(/\s+/gu, " ").trim();
4
71
  }
@@ -22,7 +89,7 @@ function result(row) {
22
89
  : row.state.status;
23
90
  const parts = [
24
91
  ...(row.call.kind === "run" && row.durationMs !== undefined ? [duration(row.durationMs)] : []),
25
- ...(row.call.kind === "fileChange" && disposition === "ok" ? [] : [disposition]),
92
+ ...(disposition === "ok" ? [] : [disposition]),
26
93
  ...(row.state.message === undefined ? [] : [oneLine(row.state.message)]),
27
94
  ];
28
95
  return parts.length === 0 ? undefined : parts.join(" · ");
@@ -52,6 +119,43 @@ function searchText(call) {
52
119
  ...(call.glob === undefined ? [] : [oneLine(call.glob)]),
53
120
  ].join(" · ");
54
121
  }
122
+ function inputObject(row) {
123
+ if (row.call.kind !== "other" || row.call.input?.truncated === true)
124
+ return undefined;
125
+ return jsonObject(row.call.input?.json ?? "");
126
+ }
127
+ function suppliedSlice(value) {
128
+ const offset = typeof value.offset_chars === "number" ? `from ${value.offset_chars}` : "";
129
+ const limit = typeof value.limit_chars === "number" ? `${value.limit_chars} chars` : "";
130
+ return [offset, limit].filter(Boolean).join(" · ");
131
+ }
132
+ function commonOtherText(row) {
133
+ const input = inputObject(row);
134
+ if (input === undefined)
135
+ return undefined;
136
+ if (row.name === "get_context_remaining")
137
+ return undefined;
138
+ if (row.name === "notes_read") {
139
+ const address = typeof input.address === "string" ? input.address : typeof input.path === "string" ? input.path : undefined;
140
+ return [address, suppliedSlice(input)]
141
+ .filter((value) => value !== undefined && value !== "")
142
+ .join(" · ");
143
+ }
144
+ if (row.name === "history_read") {
145
+ const item = typeof input.item_id === "string" ? input.item_id : undefined;
146
+ const window = typeof input.window_id === "string" ? input.window_id : undefined;
147
+ return [item, window, suppliedSlice(input)]
148
+ .filter((value) => value !== undefined && value !== "")
149
+ .join(" · ");
150
+ }
151
+ if (row.name === "history_list") {
152
+ const role = typeof input.role === "string" ? `role ${input.role}` : undefined;
153
+ const order = input.recent_first === true ? "newest first" : input.recent_first === false ? "oldest first" : undefined;
154
+ const limit = typeof input.limit === "number" ? `${input.limit} rows` : undefined;
155
+ return [role, order, limit].filter((value) => value !== undefined).join(" · ");
156
+ }
157
+ return undefined;
158
+ }
55
159
  function fileChange(call, state) {
56
160
  const first = call.changes[0];
57
161
  if (first === undefined)
@@ -69,30 +173,30 @@ function fileChange(call, state) {
69
173
  }), { added: 0, removed: 0 });
70
174
  return { label, text: `${subject} — +${totals.added} -${totals.removed}` };
71
175
  }
72
- /** Pure provider-neutral tool presentation; it performs no activity selection. */
73
- export function toolRepr(row) {
74
- let core;
176
+ /** One provider-neutral core owns every tool-kind label and non-generic body. */
177
+ function toolCore(row) {
75
178
  switch (row.call.kind) {
76
179
  case "run":
77
- core = {
180
+ return {
78
181
  label: "run",
79
182
  text: `$ ${oneLine(normalizeToolCommand(row.call.command))}`,
80
183
  overflow: "middle-ellipsis",
81
184
  };
82
- break;
83
185
  case "read":
84
- core = { label: "read", text: readText(row.call) };
85
- break;
186
+ return { label: "read", text: readText(row.call) };
86
187
  case "search":
87
- core = { label: searchLabel(row.call.scope), text: searchText(row.call) };
88
- break;
188
+ return { label: searchLabel(row.call.scope), text: searchText(row.call) };
89
189
  case "fileChange":
90
- core = fileChange(row.call, row.state);
91
- break;
190
+ return fileChange(row.call, row.state);
92
191
  case "other":
93
- core = { label: "use", text: oneLine(row.call.display || row.name) };
94
- break;
192
+ return { label: row.name, text: "" };
95
193
  }
194
+ }
195
+ /** Pure provider-neutral tool presentation; it performs no activity selection. */
196
+ export function toolRepr(row) {
197
+ const core = toolCore(row);
198
+ if (row.call.kind === "other")
199
+ return core;
96
200
  const suffix = result(row);
97
201
  if (suffix === undefined)
98
202
  return core;
@@ -1,8 +1,19 @@
1
1
  import type { AkumaInvocationResult } from "../commands/akuma-invoke.js";
2
2
  import type { ParsedCommand } from "../parse.js";
3
3
  import { type ObservedCallHead } from "./akuma-activity.js";
4
+ import type { AkumaTellWaitResult } from "../../akuma/fleet-observation.js";
5
+ import type { AkuId } from "../../akuma/identity.js";
6
+ import type { LiveStatusObservation } from "../../akuma/akuma-observe.js";
7
+ import type { TellResult } from "../../akuma/akuma.js";
4
8
  import { type TextRenderContext } from "./terminal.js";
5
- export { akumaRawAnswer } from "./akuma-activity.js";
9
+ export type TellWaitProgress = Readonly<{
10
+ admitted: (tell: TellResult, id: AkuId) => readonly string[];
11
+ observe: (observation: LiveStatusObservation) => readonly string[];
12
+ conclude: (result: AkumaTellWaitResult) => readonly string[];
13
+ }>;
14
+ export declare function tellWaitProgressStream(akuma: AkuId | undefined, alias: string | undefined, context: TextRenderContext): TellWaitProgress;
15
+ export declare function waitedTellProgress(result: AkumaTellWaitResult, alias: string | undefined, context: TextRenderContext): string;
16
+ export declare function akumaRawAnswer(result: AkumaInvocationResult): string | undefined;
6
17
  /**
7
18
  * The head a streamed observing call opens with: the identity its birth already
8
19
  * established, its Contract association, and the birth's diagnostics. The