@astrosheep/keiyaku 4.5.19 → 4.5.21
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.
- package/README.md +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +14 -7
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +53 -20
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -4
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
- package/build/src/akuma/akuma-handle.d.ts +5 -1
- package/build/src/akuma/akuma-handle.js +4 -1
- package/build/src/akuma/akuma-instance.d.ts +7 -2
- package/build/src/akuma/akuma-instance.js +14 -7
- package/build/src/akuma/akuma-observe.d.ts +11 -0
- package/build/src/akuma/akuma-observe.js +23 -0
- package/build/src/akuma/akuma-product.d.ts +2 -0
- package/build/src/akuma/akuma-product.js +6 -0
- package/build/src/akuma/akuma.d.ts +20 -1
- package/build/src/akuma/akuma.js +2 -0
- package/build/src/akuma/allowed.d.ts +20 -0
- package/build/src/akuma/allowed.js +11 -0
- package/build/src/akuma/archetype.js +3 -0
- package/build/src/akuma/body.d.ts +25 -5
- package/build/src/akuma/body.js +70 -31
- package/build/src/akuma/call-request.d.ts +5 -0
- package/build/src/akuma/call-request.js +14 -4
- package/build/src/akuma/fleet-execution.d.ts +28 -0
- package/build/src/akuma/fleet-execution.js +36 -4
- package/build/src/akuma/fleet-observation.d.ts +54 -0
- package/build/src/akuma/fleet-request.d.ts +10 -0
- package/build/src/akuma/fleet-request.js +13 -3
- package/build/src/akuma/heart/facts.d.ts +2 -0
- package/build/src/akuma/heart/index.d.ts +1 -0
- package/build/src/akuma/heart/index.js +4 -1
- package/build/src/akuma/heart/rows.d.ts +2 -0
- package/build/src/akuma/heart/rows.js +7 -5
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +3 -1
- package/build/src/akuma/heart/tells.js +6 -5
- package/build/src/akuma/heart/timeline.js +1 -1
- package/build/src/akuma/provider-recipe.d.ts +1 -0
- package/build/src/akuma/provider-recipe.js +5 -0
- package/build/src/akuma/provider.d.ts +1 -1
- package/build/src/akuma/provider.js +1 -1
- package/build/src/akuma/providers/acp/core.js +3 -6
- package/build/src/akuma/providers/acp/index.js +3 -0
- package/build/src/akuma/providers/claude/index.js +10 -10
- package/build/src/akuma/providers/codex-app-server/index.js +12 -11
- package/build/src/akuma/providers/execution-environment.d.ts +6 -0
- package/build/src/akuma/providers/execution-environment.js +24 -0
- package/build/src/akuma/providers/grok-build/index.js +3 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +32 -23
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
- package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
- package/build/src/akuma/providers/pi/events.js +7 -2
- package/build/src/akuma/providers/pi/index.js +5 -2
- package/build/src/akuma/request-observation.d.ts +45 -0
- package/build/src/akuma/request-observation.js +162 -0
- package/build/src/akuma/request-rendezvous.d.ts +6 -3
- package/build/src/akuma/request-rendezvous.js +80 -14
- package/build/src/akuma/request-serve.js +27 -4
- package/build/src/akuma/request-wire.d.ts +5 -0
- package/build/src/akuma/request-wire.js +2 -0
- package/build/src/akuma/turn-drive.d.ts +1 -0
- package/build/src/akuma/turn-drive.js +2 -0
- package/build/src/akuma-body.js +5 -0
- package/build/src/body/amend.js +6 -4
- package/build/src/body/decode.d.ts +3 -1
- package/build/src/body/decode.js +53 -17
- package/build/src/body/region.d.ts +9 -1
- package/build/src/body/region.js +87 -12
- package/build/src/body/render.js +1 -1
- package/build/src/body/verification.d.ts +3 -1
- package/build/src/body/verification.js +4 -1
- package/build/src/cli/accepted.js +19 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +3 -1
- package/build/src/cli/commands/akuma-invoke.js +134 -14
- package/build/src/cli/commands/akuma.js +24 -12
- package/build/src/cli/commands/contract-help.d.ts +15 -15
- package/build/src/cli/commands/contract-help.js +45 -19
- package/build/src/cli/commands/contract-invoke.d.ts +3 -0
- package/build/src/cli/commands/contract-invoke.js +25 -11
- package/build/src/cli/commands/contract.js +6 -4
- package/build/src/cli/commands/install.d.ts +1 -1
- package/build/src/cli/commands/install.js +1 -1
- package/build/src/cli/commands/status-set.js +15 -24
- package/build/src/cli/commands/task.js +18 -18
- package/build/src/cli/invoke.d.ts +3 -0
- package/build/src/cli/invoke.js +58 -23
- package/build/src/cli/main.js +5 -0
- package/build/src/cli/parse.d.ts +3 -0
- package/build/src/cli/parse.js +26 -4
- package/build/src/cli/render/akuma-activity.d.ts +100 -2
- package/build/src/cli/render/akuma-activity.js +485 -40
- package/build/src/cli/render/akuma.d.ts +10 -0
- package/build/src/cli/render/akuma.js +31 -21
- package/build/src/cli/render/audit.js +1 -1
- package/build/src/cli/render/board.js +1 -1
- package/build/src/cli/render/catalog.js +16 -13
- package/build/src/cli/render/contract-history.js +39 -10
- package/build/src/cli/render/contract-observation.js +10 -6
- package/build/src/cli/render/contract.js +109 -106
- package/build/src/cli/render/execution-progress.d.ts +26 -0
- package/build/src/cli/render/execution-progress.js +178 -0
- package/build/src/cli/render/kanshi-akuma.js +27 -39
- package/build/src/cli/render/kanshi.js +33 -26
- package/build/src/cli/render/nuke.js +1 -1
- package/build/src/cli/render/receipt.d.ts +1 -1
- package/build/src/cli/render/receipt.js +6 -6
- package/build/src/cli/render/refusal.js +10 -7
- package/build/src/cli/render/region.js +1 -1
- package/build/src/cli/render/settings.js +12 -2
- package/build/src/cli/render/status-line.d.ts +30 -0
- package/build/src/cli/render/status-line.js +58 -0
- package/build/src/cli/render/status-set.js +1 -1
- package/build/src/cli/render/task.d.ts +2 -0
- package/build/src/cli/render/task.js +11 -7
- package/build/src/cli/render/terminal.d.ts +0 -1
- package/build/src/cli/render/terminal.js +47 -15
- package/build/src/cli/result.d.ts +1 -0
- package/build/src/cli/runtime.d.ts +2 -0
- package/build/src/cli/runtime.js +36 -4
- package/build/src/cli/usage.js +2 -2
- package/build/src/cli/version.d.ts +1 -0
- package/build/src/cli/version.js +25 -0
- package/build/src/git/hooks.d.ts +13 -1
- package/build/src/git/hooks.js +2 -1
- package/build/src/git/workspace.d.ts +1 -1
- package/build/src/git/workspace.js +1 -1
- package/build/src/identity/mint.d.ts +13 -0
- package/build/src/identity/mint.js +26 -0
- package/build/src/identity/normalize.d.ts +4 -0
- package/build/src/identity/normalize.js +30 -0
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/kanshi/read.js +17 -4
- package/build/src/kanshi/report.d.ts +1 -0
- package/build/src/library/akuma-creation.d.ts +2 -0
- package/build/src/library/akuma-creation.js +34 -18
- package/build/src/library/audit.d.ts +2 -0
- package/build/src/library/audit.js +1 -0
- package/build/src/library/bind.js +18 -16
- package/build/src/library/composition.d.ts +1 -0
- package/build/src/library/contract-bind.d.ts +3 -1
- package/build/src/library/contract-bind.js +5 -3
- package/build/src/library/contract-execution.d.ts +2 -0
- package/build/src/library/contract-execution.js +15 -1
- package/build/src/library/contract-forwarding.d.ts +4 -0
- package/build/src/library/contract-forwarding.js +3 -0
- package/build/src/library/contract-handle.d.ts +7 -0
- package/build/src/library/contract-handle.js +25 -0
- package/build/src/library/contract-operations.d.ts +7 -2
- package/build/src/library/contract-operations.js +21 -1
- package/build/src/library/contract-types.d.ts +1 -0
- package/build/src/library/execution-result.d.ts +2 -2
- package/build/src/library/execution.d.ts +8 -0
- package/build/src/library/execution.js +102 -0
- package/build/src/library/fleet.d.ts +4 -1
- package/build/src/library/fleet.js +32 -8
- package/build/src/library/keiyaku.d.ts +2 -0
- package/build/src/library/mutation.js +15 -7
- package/build/src/plugin/akuma-signals.d.ts +6 -0
- package/build/src/plugin/akuma-signals.js +9 -1
- package/build/src/plugin/public.d.ts +6 -2
- package/build/src/plugin/runtime.d.ts +1 -3
- package/build/src/plugin/runtime.js +95 -103
- package/build/src/protocol/amend.js +4 -3
- package/build/src/protocol/attempt.d.ts +2 -0
- package/build/src/protocol/bind.js +41 -14
- package/build/src/protocol/completion.d.ts +3 -0
- package/build/src/protocol/completion.js +35 -11
- package/build/src/protocol/deliver.js +165 -92
- package/build/src/protocol/execution-observation.d.ts +181 -0
- package/build/src/protocol/execution-observation.js +45 -0
- package/build/src/protocol/intent.d.ts +18 -22
- package/build/src/protocol/intent.js +63 -41
- package/build/src/protocol/operations.d.ts +2 -0
- package/build/src/protocol/placement.js +5 -1
- package/build/src/protocol/progress.d.ts +5 -1
- package/build/src/protocol/progress.js +10 -1
- package/build/src/protocol/read/status.d.ts +1 -0
- package/build/src/protocol/read/status.js +10 -2
- package/build/src/protocol/reintegrate.d.ts +1 -1
- package/build/src/protocol/reintegrate.js +70 -43
- package/build/src/protocol/result-codec.js +27 -11
- package/build/src/protocol/review.js +53 -30
- package/build/src/protocol/run.d.ts +83 -36
- package/build/src/protocol/run.js +90 -75
- package/build/src/runtime/proc/run.d.ts +16 -4
- package/build/src/runtime/proc/run.js +64 -22
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settings.js +5 -14
- package/build/src/task/document.d.ts +5 -1
- package/build/src/task/document.js +6 -1
- package/build/src/verification/execution.d.ts +10 -2
- package/build/src/verification/execution.js +143 -42
- package/build/src/verification/observation.d.ts +41 -0
- package/build/src/verification/observation.js +29 -0
- package/package.json +3 -1
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import { parseAkumaStatus } from "../../akuma/akuma.js";
|
|
2
2
|
import { toolRepr } from "./akuma-tool.js";
|
|
3
|
-
import { displayColumns, renderBoundedTextBlock, safeText, truncateMiddleDisplayText, } from "./terminal.js";
|
|
3
|
+
import { displayColumns, renderBoundedTextBlock, safeText, takeDisplayColumns, truncateMiddleDisplayText, } from "./terminal.js";
|
|
4
4
|
export const DEFAULT_CONTEXT = { columns: 80, color: false };
|
|
5
|
-
const SNAPSHOT_RULER = "────────────────";
|
|
6
5
|
const TIME_WIDTH = 5;
|
|
7
6
|
const VERB_WIDTH = 6;
|
|
7
|
+
/**
|
|
8
|
+
* The one blessed ruler: a run of U+2500 exactly as wide as the frame head's
|
|
9
|
+
* widest line, marking the boundary between an observation frame and its content.
|
|
10
|
+
*/
|
|
11
|
+
export function frameRule(headLines) {
|
|
12
|
+
const width = headLines.reduce((widest, line) => Math.max(widest, displayColumns(line)), 0);
|
|
13
|
+
return "─".repeat(width);
|
|
14
|
+
}
|
|
15
|
+
/** Tool rows one focused activity view keeps at its opening and final end; the surplus folds in place. */
|
|
16
|
+
const OPENING_TOOL_BUDGET = 3;
|
|
17
|
+
const RECENT_TOOL_BUDGET = 2;
|
|
8
18
|
function identity(id, alias) {
|
|
9
19
|
return `${id}${alias === undefined ? "" : ` (${alias})`}`;
|
|
10
20
|
}
|
|
11
21
|
function associatedContractId(contract) {
|
|
12
22
|
return contract.kind === "associated" ? contract.contractId : undefined;
|
|
13
23
|
}
|
|
14
|
-
export function associatedIdentity(id, alias,
|
|
15
|
-
|
|
16
|
-
return `${identity(id, alias)}${contractId === undefined ? "" : ` [${contractId}]`}`;
|
|
24
|
+
export function associatedIdentity(id, alias, _contract) {
|
|
25
|
+
return identity(id, alias);
|
|
17
26
|
}
|
|
18
27
|
export function snapshotHeading(id, alias, contract) {
|
|
19
28
|
const contractId = contract === undefined ? undefined : associatedContractId(contract);
|
|
20
|
-
|
|
29
|
+
const head = [identity(id, alias), ...(contractId === undefined ? [] : [`└─ ${contractId}`])];
|
|
30
|
+
return [...head, frameRule(head)];
|
|
21
31
|
}
|
|
22
32
|
function answeredHeading(id, alias) {
|
|
23
|
-
|
|
33
|
+
const heading = `✓ came back ${identity(id, alias)}`;
|
|
34
|
+
return [heading, frameRule([heading])];
|
|
24
35
|
}
|
|
25
36
|
function contractFacts(contract) {
|
|
26
37
|
return contract.kind === "failed" ? [`! contract failed ${safeText(contract.diagnostic)}`] : [];
|
|
@@ -30,12 +41,14 @@ function unobservedText(id, diagnostic) {
|
|
|
30
41
|
}
|
|
31
42
|
function lifeLabel(life) {
|
|
32
43
|
if (life === "running")
|
|
33
|
-
return "●
|
|
44
|
+
return "● running";
|
|
34
45
|
if (life === "asleep")
|
|
35
46
|
return "✓ came back";
|
|
36
47
|
if (life === "killed")
|
|
37
|
-
return "
|
|
38
|
-
|
|
48
|
+
return "× killed";
|
|
49
|
+
if (life === "hung")
|
|
50
|
+
return "? hung";
|
|
51
|
+
return "! stranded";
|
|
39
52
|
}
|
|
40
53
|
function clock(at) {
|
|
41
54
|
const date = new Date(at);
|
|
@@ -78,6 +91,18 @@ function mark(row) {
|
|
|
78
91
|
}
|
|
79
92
|
return "│";
|
|
80
93
|
}
|
|
94
|
+
/** Strip Markdown decoration from preview prose without rewriting the words. */
|
|
95
|
+
function undecorated(text) {
|
|
96
|
+
return (text
|
|
97
|
+
.replace(/^ {0,3}#{1,6}[ \t]+/gmu, "")
|
|
98
|
+
.replace(/^ {0,3}(?:[-*+]|\d+\.)[ \t]+/gmu, "")
|
|
99
|
+
.replace(/\*\*([^*\n]+?)\*\*/gu, "$1")
|
|
100
|
+
// A single pair of asterisks is emphasis only when it does not sit inside a word, path, or glob.
|
|
101
|
+
.replace(/(?<![\w*/\\])\*([^\s*/\\](?:[^*/\\\n]*[^\s*/\\])?)\*(?![\w*/\\])/gu, "$1")
|
|
102
|
+
.replace(/`([^`\n]+?)`/gu, "$1")
|
|
103
|
+
.replace(/\s+/gu, " ")
|
|
104
|
+
.trim());
|
|
105
|
+
}
|
|
81
106
|
function rowText(row) {
|
|
82
107
|
if (row.kind === "said" ||
|
|
83
108
|
row.kind === "thought" ||
|
|
@@ -85,13 +110,13 @@ function rowText(row) {
|
|
|
85
110
|
row.kind === "call" ||
|
|
86
111
|
row.kind === "tell") {
|
|
87
112
|
return {
|
|
88
|
-
text: row.text,
|
|
113
|
+
text: undecorated(row.text),
|
|
89
114
|
lines: row.kind === "said" || row.kind === "thought" ? 2 : row.kind === "tell" || row.kind === "call" ? 1 : 2,
|
|
90
115
|
};
|
|
91
116
|
}
|
|
92
117
|
if (row.kind === "outcome")
|
|
93
118
|
return row.outcome.kind === "answered"
|
|
94
|
-
? { text: row.outcome.answer, lines: 3 }
|
|
119
|
+
? { text: undecorated(row.outcome.answer), lines: 3 }
|
|
95
120
|
: { text: row.outcome.diagnostic, lines: 2 };
|
|
96
121
|
if (row.kind === "turn")
|
|
97
122
|
return { text: "", lines: 1 };
|
|
@@ -112,19 +137,41 @@ function eventPrefix(glyph, verb, time) {
|
|
|
112
137
|
function continuationPrefix() {
|
|
113
138
|
return eventPrefix("│", "");
|
|
114
139
|
}
|
|
140
|
+
/** Pad to a terminal-column width; raw string length is never the measuring stick. */
|
|
141
|
+
function padToDisplay(text, width) {
|
|
142
|
+
const remaining = width - displayColumns(text);
|
|
143
|
+
return remaining > 0 ? `${text}${" ".repeat(remaining)}` : text;
|
|
144
|
+
}
|
|
145
|
+
function plainLayout() {
|
|
146
|
+
return {
|
|
147
|
+
head: (time, glyph, verb) => eventPrefix(glyph, verb, time),
|
|
148
|
+
continuation: continuationPrefix,
|
|
149
|
+
marker: (count) => `${" ".repeat(TIME_WIDTH)} ⋮ ${count} omitted`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function sourceLayout(source, width) {
|
|
153
|
+
const gutter = () => `${" ".repeat(TIME_WIDTH)} ${padToDisplay(source, width())} `;
|
|
154
|
+
return {
|
|
155
|
+
head: (time, glyph, verb) => `${time === undefined ? " ".repeat(TIME_WIDTH) : time.padEnd(TIME_WIDTH)} ${padToDisplay(source, width())} ${glyph} ${verb.padEnd(VERB_WIDTH)} `,
|
|
156
|
+
// Continuations blank the time and source columns and align under the mark.
|
|
157
|
+
continuation: () => `${" ".repeat(TIME_WIDTH)} ${" ".repeat(width())} │ ${" ".repeat(VERB_WIDTH)} `,
|
|
158
|
+
marker: (count) => `${gutter()}⋮ ${count} omitted`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
115
161
|
function quotedBody(row) {
|
|
116
162
|
return (row.kind === "said" ||
|
|
117
163
|
row.kind === "thought" ||
|
|
118
164
|
row.kind === "tell" ||
|
|
119
165
|
(row.kind === "outcome" && row.outcome.kind === "answered"));
|
|
120
166
|
}
|
|
167
|
+
/** Quote every rendered body line, slicing each line's prefix by its display width. */
|
|
121
168
|
function quoteLines(lines, prefix) {
|
|
122
|
-
const prefixWidth = prefix
|
|
169
|
+
const prefixWidth = displayColumns(prefix);
|
|
123
170
|
return lines.map((line) => {
|
|
124
|
-
const body = line
|
|
171
|
+
const { text: head, rest: body } = takeDisplayColumns(line, prefixWidth);
|
|
125
172
|
if (body.length === 0)
|
|
126
173
|
return line;
|
|
127
|
-
return `${
|
|
174
|
+
return `${head}“${body}”`;
|
|
128
175
|
});
|
|
129
176
|
}
|
|
130
177
|
function renderMiddleEllipsis(first, text, suffix, columns) {
|
|
@@ -136,7 +183,7 @@ function renderMiddleEllipsis(first, text, suffix, columns) {
|
|
|
136
183
|
const showSuffix = suffix.length > 0 && withSuffix >= 6;
|
|
137
184
|
return `${first}${truncateMiddleDisplayText(text, Math.max(0, showSuffix ? withSuffix : remaining))}${showSuffix ? suffix : ""}`;
|
|
138
185
|
}
|
|
139
|
-
function renderRow(row, context, history, first) {
|
|
186
|
+
function renderRow(row, context, history, first, continuation) {
|
|
140
187
|
const value = rowText(row);
|
|
141
188
|
const quoted = quotedBody(row);
|
|
142
189
|
const quoteWidth = quoted ? 2 : 0;
|
|
@@ -145,37 +192,419 @@ function renderRow(row, context, history, first) {
|
|
|
145
192
|
}
|
|
146
193
|
const lines = renderBoundedTextBlock(value.text, {
|
|
147
194
|
first,
|
|
148
|
-
continuation
|
|
195
|
+
continuation,
|
|
149
196
|
columns: context.columns - quoteWidth,
|
|
150
197
|
lines: history ? Number.MAX_SAFE_INTEGER : value.lines,
|
|
151
198
|
...("truncated" in row && row.truncated === true ? { truncated: true } : {}),
|
|
152
199
|
});
|
|
153
200
|
return quoted ? quoteLines(lines, first) : lines;
|
|
154
201
|
}
|
|
155
|
-
function groupedEntries(entries, context, history = false) {
|
|
202
|
+
function groupedEntries(entries, context, history = false, layout = plainLayout()) {
|
|
156
203
|
const lines = [];
|
|
157
204
|
let previousClock;
|
|
158
205
|
for (const entry of entries) {
|
|
159
206
|
if (entry.kind === "gap") {
|
|
160
|
-
lines.push(
|
|
207
|
+
lines.push(layout.marker(entry.count));
|
|
161
208
|
continue;
|
|
162
209
|
}
|
|
163
210
|
const row = entry.row;
|
|
164
211
|
const at = clock(row.at);
|
|
165
212
|
const changed = previousClock === undefined || at !== previousClock;
|
|
166
|
-
lines.push(...renderRow(row, context, history,
|
|
213
|
+
lines.push(...renderRow(row, context, history, layout.head(changed ? at : undefined, mark(row), label(row)), layout.continuation()));
|
|
167
214
|
previousClock = at;
|
|
168
215
|
}
|
|
169
216
|
return lines;
|
|
170
217
|
}
|
|
171
|
-
function groupedRows(rows, context, history = false) {
|
|
172
|
-
return groupedEntries(rows.filter((row) => row.kind !== "turn").map((row) => ({ kind: "row", row })), context, history);
|
|
218
|
+
function groupedRows(rows, context, history = false, layout = plainLayout()) {
|
|
219
|
+
return groupedEntries(rows.filter((row) => row.kind !== "turn").map((row) => ({ kind: "row", row })), context, history, layout);
|
|
220
|
+
}
|
|
221
|
+
/** Ordered entry stream of one retained snapshot; an idle outcome keeps its sequence position. */
|
|
222
|
+
function orderedSnapshotEntries(snapshot) {
|
|
223
|
+
if (snapshot.kind !== "idle" || snapshot.outcome === undefined)
|
|
224
|
+
return snapshot.entries;
|
|
225
|
+
const entries = [];
|
|
226
|
+
let outcomeInserted = false;
|
|
227
|
+
for (const entry of snapshot.entries) {
|
|
228
|
+
if (!outcomeInserted && entry.kind === "row" && entry.row.sequence > snapshot.outcome.sequence) {
|
|
229
|
+
entries.push({ kind: "row", row: snapshot.outcome });
|
|
230
|
+
outcomeInserted = true;
|
|
231
|
+
}
|
|
232
|
+
entries.push(entry);
|
|
233
|
+
}
|
|
234
|
+
if (!outcomeInserted)
|
|
235
|
+
entries.push({ kind: "row", row: snapshot.outcome });
|
|
236
|
+
return entries;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Focus one complete snapshot without making its provider-side gaps mean more
|
|
240
|
+
* than they do. Thought rows disappear; known tool rows use the same opening
|
|
241
|
+
* and recent budgets as live activity, while every other row keeps its place.
|
|
242
|
+
*/
|
|
243
|
+
function focusedSnapshotEntries(entries) {
|
|
244
|
+
const tools = entries.filter((entry) => entry.kind === "row" && entry.row.kind === "tool");
|
|
245
|
+
const focused = [];
|
|
246
|
+
let toolIndex = 0;
|
|
247
|
+
let omittedTools = 0;
|
|
248
|
+
const flushOmittedTools = () => {
|
|
249
|
+
if (omittedTools > 0)
|
|
250
|
+
focused.push({ kind: "gap", count: omittedTools });
|
|
251
|
+
omittedTools = 0;
|
|
252
|
+
};
|
|
253
|
+
for (const entry of entries) {
|
|
254
|
+
if (entry.kind === "row" && entry.row.kind === "thought")
|
|
255
|
+
continue;
|
|
256
|
+
if (entry.kind !== "row" || entry.row.kind !== "tool") {
|
|
257
|
+
flushOmittedTools();
|
|
258
|
+
focused.push(entry);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const keep = toolIndex < OPENING_TOOL_BUDGET || toolIndex >= tools.length - RECENT_TOOL_BUDGET;
|
|
262
|
+
toolIndex += 1;
|
|
263
|
+
if (keep)
|
|
264
|
+
flushOmittedTools();
|
|
265
|
+
else
|
|
266
|
+
omittedTools += 1;
|
|
267
|
+
if (keep)
|
|
268
|
+
focused.push(entry);
|
|
269
|
+
}
|
|
270
|
+
flushOmittedTools();
|
|
271
|
+
return focused;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Shared snapshot activity rendering. Full snapshots focus known tool evidence;
|
|
275
|
+
* `latest` preserves compact callers' established newest-entry selection.
|
|
276
|
+
*/
|
|
277
|
+
export function snapshotActivityLines(snapshot, context, selection = {}) {
|
|
278
|
+
const entries = orderedSnapshotEntries(snapshot);
|
|
279
|
+
if (selection.latest !== true)
|
|
280
|
+
return groupedEntries(focusedSnapshotEntries(entries), context);
|
|
281
|
+
const latest = entries.filter((entry) => entry.kind === "row").at(-1);
|
|
282
|
+
return latest === undefined ? [] : groupedEntries([latest], context);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* One status' timeline without its conclusion: the newest open entry is still
|
|
286
|
+
* moving, and an idle outcome is the command's final result rather than a
|
|
287
|
+
* settled row.
|
|
288
|
+
*/
|
|
289
|
+
function settledTimeline(snapshot) {
|
|
290
|
+
switch (snapshot.kind) {
|
|
291
|
+
case "unborn":
|
|
292
|
+
return snapshot;
|
|
293
|
+
case "open":
|
|
294
|
+
return { ...snapshot, entries: snapshot.entries.slice(0, -1) };
|
|
295
|
+
case "idle": {
|
|
296
|
+
const { outcome: _outcome, ...rest } = snapshot;
|
|
297
|
+
return { ...rest, entries: snapshot.entries };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function settledRows(snapshot) {
|
|
302
|
+
return orderedSnapshotEntries(settledTimeline(snapshot)).flatMap((entry) => entry.kind === "row" ? [entry.row] : []);
|
|
303
|
+
}
|
|
304
|
+
function renderStreamRow(state, row, lines, context, layout) {
|
|
305
|
+
const at = clock(row.at);
|
|
306
|
+
const changed = state.previousClock === undefined || at !== state.previousClock;
|
|
307
|
+
lines.push(...renderRow(row, context, false, layout.head(changed ? at : undefined, mark(row), label(row)), layout.continuation()));
|
|
308
|
+
state.previousClock = at;
|
|
309
|
+
}
|
|
310
|
+
function coalesceDeferredGaps(state) {
|
|
311
|
+
state.deferred = state.deferred.reduce((entries, entry) => {
|
|
312
|
+
const previous = entries.at(-1);
|
|
313
|
+
if (entry.kind === "gap" && previous?.kind === "gap") {
|
|
314
|
+
entries[entries.length - 1] = { kind: "gap", count: previous.count + entry.count };
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
entries.push(entry);
|
|
318
|
+
}
|
|
319
|
+
return entries;
|
|
320
|
+
}, []);
|
|
321
|
+
}
|
|
322
|
+
function omitOldestDeferredTool(state) {
|
|
323
|
+
const index = state.deferred.findIndex((entry) => entry.kind === "row" && entry.row.kind === "tool");
|
|
324
|
+
if (index === -1)
|
|
325
|
+
throw new Error("activity tail lost its pending tool");
|
|
326
|
+
state.deferred[index] = { kind: "gap", count: 1 };
|
|
327
|
+
coalesceDeferredGaps(state);
|
|
328
|
+
}
|
|
329
|
+
/** Emit only a prefix whose omission runs can no longer join an unresolved tail tool. */
|
|
330
|
+
function flushSafeActivityPrefix(state, lines, context, layout) {
|
|
331
|
+
for (;;) {
|
|
332
|
+
const first = state.deferred[0];
|
|
333
|
+
if (first === undefined || (first.kind === "row" && first.row.kind === "tool"))
|
|
334
|
+
return;
|
|
335
|
+
if (first.kind === "row") {
|
|
336
|
+
state.deferred.shift();
|
|
337
|
+
renderStreamRow(state, first.row, lines, context, layout);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const next = state.deferred[1];
|
|
341
|
+
if (next === undefined || (next.kind === "row" && next.row.kind === "tool"))
|
|
342
|
+
return;
|
|
343
|
+
state.deferred.shift();
|
|
344
|
+
lines.push(layout.marker(first.count));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function observeActivitySnapshot(state, snapshot, context, layout) {
|
|
348
|
+
const rows = settledRows(snapshot)
|
|
349
|
+
.filter((row) => state.newestSequence === undefined || row.sequence > state.newestSequence)
|
|
350
|
+
// Thoughts remain retained activity, but are ineligible for default live progress.
|
|
351
|
+
.filter((row) => row.kind !== "thought");
|
|
352
|
+
if (rows.length === 0)
|
|
353
|
+
return [];
|
|
354
|
+
state.newestSequence = rows.reduce((newest, row) => Math.max(newest, row.sequence), state.newestSequence ?? rows[0].sequence);
|
|
355
|
+
const lines = [];
|
|
356
|
+
for (const row of rows) {
|
|
357
|
+
if (row.kind === "tool" && state.openingTools < OPENING_TOOL_BUDGET) {
|
|
358
|
+
state.openingTools += 1;
|
|
359
|
+
renderStreamRow(state, row, lines, context, layout);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (row.kind !== "tool" && state.deferred.length === 0) {
|
|
363
|
+
renderStreamRow(state, row, lines, context, layout);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
state.deferred.push({ kind: "row", row });
|
|
367
|
+
if (row.kind === "tool") {
|
|
368
|
+
const pendingTools = state.deferred.filter((entry) => entry.kind === "row" && entry.row.kind === "tool").length;
|
|
369
|
+
if (pendingTools > RECENT_TOOL_BUDGET)
|
|
370
|
+
omitOldestDeferredTool(state);
|
|
371
|
+
}
|
|
372
|
+
flushSafeActivityPrefix(state, lines, context, layout);
|
|
373
|
+
}
|
|
374
|
+
return lines;
|
|
375
|
+
}
|
|
376
|
+
function flushActivityTail(state, context, layout) {
|
|
377
|
+
const lines = [];
|
|
378
|
+
for (const entry of state.deferred) {
|
|
379
|
+
if (entry.kind === "gap")
|
|
380
|
+
lines.push(layout.marker(entry.count));
|
|
381
|
+
else
|
|
382
|
+
renderStreamRow(state, entry.row, lines, context, layout);
|
|
383
|
+
}
|
|
384
|
+
state.deferred = [];
|
|
385
|
+
return lines;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Append-only live view over one command's successive settled snapshots. The
|
|
389
|
+
* first three tools stream immediately; later tools wait in a two-row tail
|
|
390
|
+
* until the command ends. As a newer tool displaces an older tail candidate,
|
|
391
|
+
* its body becomes an in-place omission count. Narrative waits only while a
|
|
392
|
+
* preceding tail tool still decides its position.
|
|
393
|
+
*/
|
|
394
|
+
export function activityStream(context, layout = plainLayout()) {
|
|
395
|
+
const state = {
|
|
396
|
+
newestSequence: undefined,
|
|
397
|
+
previousClock: undefined,
|
|
398
|
+
openingTools: 0,
|
|
399
|
+
deferred: [],
|
|
400
|
+
};
|
|
401
|
+
const seed = (snapshot) => {
|
|
402
|
+
const rows = settledRows(snapshot);
|
|
403
|
+
if (rows.length > 0)
|
|
404
|
+
state.newestSequence = rows.reduce((newest, row) => Math.max(newest, row.sequence), rows[0].sequence);
|
|
405
|
+
};
|
|
406
|
+
const observe = (snapshot) => observeActivitySnapshot(state, snapshot, context, layout);
|
|
407
|
+
const flush = () => flushActivityTail(state, context, layout);
|
|
408
|
+
return Object.assign(observe, { seed, flush });
|
|
409
|
+
}
|
|
410
|
+
function conclusionMarkVerb(status, answered) {
|
|
411
|
+
if (status.life === "running")
|
|
412
|
+
return { mark: "●", verb: "still running", waited: true };
|
|
413
|
+
if (answered)
|
|
414
|
+
return { mark: "✓", verb: "answered", waited: false };
|
|
415
|
+
if (status.life === "asleep")
|
|
416
|
+
return { mark: "!", verb: "failed", waited: false };
|
|
417
|
+
if (status.life === "killed")
|
|
418
|
+
return { mark: "×", verb: "killed", waited: false };
|
|
419
|
+
if (status.life === "hung")
|
|
420
|
+
return { mark: "?", verb: "hung", waited: false };
|
|
421
|
+
if (status.life === "untidy")
|
|
422
|
+
return { mark: "!", verb: "untidy", waited: false };
|
|
423
|
+
return { mark: "!", verb: "stranded", waited: false };
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The durable moment an Akuma settled, read from the outcome row its own timeline
|
|
427
|
+
* retained. Absent for an ending that leaves no outcome row, so the caller falls
|
|
428
|
+
* back to the observation that noticed it.
|
|
429
|
+
*/
|
|
430
|
+
function settleMoment(status) {
|
|
431
|
+
const outcome = status.timeline.kind === "idle" ? status.timeline.outcome : undefined;
|
|
432
|
+
if (outcome === undefined)
|
|
433
|
+
return undefined;
|
|
434
|
+
const at = new Date(outcome.at).getTime();
|
|
435
|
+
return Number.isFinite(at) ? at : undefined;
|
|
436
|
+
}
|
|
437
|
+
function clockFromMs(at) {
|
|
438
|
+
return clock(new Date(at).toISOString());
|
|
439
|
+
}
|
|
440
|
+
function durationText(durationMs) {
|
|
441
|
+
const seconds = Math.max(0, Math.round(durationMs / 1000));
|
|
442
|
+
if (seconds < 60)
|
|
443
|
+
return `${seconds}s`;
|
|
444
|
+
return `${Math.floor(seconds / 60)}m${String(seconds % 60)}s`;
|
|
445
|
+
}
|
|
446
|
+
function createWaitObservationState() {
|
|
447
|
+
return {
|
|
448
|
+
streams: new Map(),
|
|
449
|
+
attributed: new Map(),
|
|
450
|
+
sources: new Map(),
|
|
451
|
+
settledAt: new Map(),
|
|
452
|
+
sourceWidth: 0,
|
|
453
|
+
opened: false,
|
|
454
|
+
observed: false,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function registerWaitSource(state, id, alias) {
|
|
458
|
+
if (state.sources.has(id))
|
|
459
|
+
return;
|
|
460
|
+
const label = alias ?? id;
|
|
461
|
+
state.sources.set(id, label);
|
|
462
|
+
state.sourceWidth = Math.max(state.sourceWidth, displayColumns(label));
|
|
463
|
+
}
|
|
464
|
+
function observeWaitRound(state, round, context, now) {
|
|
465
|
+
state.observed = true;
|
|
466
|
+
// Establish the whole round's sources before any row so widths stay aligned within it.
|
|
467
|
+
for (const member of round)
|
|
468
|
+
registerWaitSource(state, member.status.id, member.alias);
|
|
469
|
+
const lines = [];
|
|
470
|
+
for (const { status, alias, contract } of round) {
|
|
471
|
+
const known = state.streams.get(status.id);
|
|
472
|
+
if (known === undefined) {
|
|
473
|
+
if (state.opened)
|
|
474
|
+
lines.push("");
|
|
475
|
+
lines.push(...snapshotHeading(status.id, alias, contract));
|
|
476
|
+
state.opened = true;
|
|
477
|
+
state.attributed.set(status.id, alias);
|
|
478
|
+
// Only a plural wait attributes its rows; a single-target stream keeps the plain row grammar.
|
|
479
|
+
const stream = activityStream(context, state.sources.size > 1
|
|
480
|
+
? sourceLayout(state.sources.get(status.id) ?? status.id, () => state.sourceWidth)
|
|
481
|
+
: plainLayout());
|
|
482
|
+
state.streams.set(status.id, stream);
|
|
483
|
+
// A wait starts at the current settled frontier: its backlog is neither evidence nor budget.
|
|
484
|
+
stream.seed(status.timeline);
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
lines.push(...known(status.timeline));
|
|
488
|
+
}
|
|
489
|
+
if (!state.settledAt.has(status.id) && waitComplete(status))
|
|
490
|
+
state.settledAt.set(status.id, settleMoment(status) ?? now());
|
|
491
|
+
}
|
|
492
|
+
return lines;
|
|
493
|
+
}
|
|
494
|
+
function concludeWaitStream(state, result, startedAt, now) {
|
|
495
|
+
const tail = [];
|
|
496
|
+
// The returned observation can be newer than the final callback. Advance every known stream
|
|
497
|
+
// before flushing it, while still flushing members that became unobserved at the end.
|
|
498
|
+
for (const observation of result.observations) {
|
|
499
|
+
const stream = state.streams.get(observation.status.id);
|
|
500
|
+
if (stream !== undefined)
|
|
501
|
+
tail.push(...stream(observation.status.timeline));
|
|
502
|
+
}
|
|
503
|
+
for (const stream of state.streams.values())
|
|
504
|
+
tail.push(...stream.flush());
|
|
505
|
+
const end = now();
|
|
506
|
+
const total = result.observations.length + result.unobserved.length;
|
|
507
|
+
const multi = total > 1;
|
|
508
|
+
const conclusions = result.observations.map((observation) => {
|
|
509
|
+
const status = observation.status;
|
|
510
|
+
const answered = statusAnswer(observation) !== undefined;
|
|
511
|
+
const complete = waitComplete(status);
|
|
512
|
+
const at = complete ? (state.settledAt.get(status.id) ?? end) : end;
|
|
513
|
+
const durationMs = Math.max(0, at - startedAt);
|
|
514
|
+
const { mark, verb, waited } = conclusionMarkVerb(status, answered);
|
|
515
|
+
const target = multi ? ` ${padToDisplay(state.sources.get(status.id) ?? status.id, state.sourceWidth)}` : "";
|
|
516
|
+
return `${clockFromMs(at)}${target} ${mark} ${verb} — ${waited ? "waited " : ""}${durationText(durationMs)}`;
|
|
517
|
+
});
|
|
518
|
+
const unobservedLines = result.unobserved.map((member) => unobservedText(member.id, member.diagnostic));
|
|
519
|
+
const answeredSingle = !multi && result.observations.length === 1 && statusAnswer(result.observations[0]) !== undefined;
|
|
520
|
+
const blocks = [
|
|
521
|
+
...(unobservedLines.length > 0 ? [unobservedLines.join("\n")] : []),
|
|
522
|
+
...(conclusions.length > 0 ? [(multi ? [""] : []).concat(conclusions).join("\n")] : []),
|
|
523
|
+
];
|
|
524
|
+
const body = [...tail, ...blocks].join("\n");
|
|
525
|
+
// One blank line keeps the bare stdout answer visually separate from the stream.
|
|
526
|
+
return answeredSingle ? `${body}\n\n` : body;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Live view over a wait's successive observation rounds, one append-only
|
|
530
|
+
* stream per selected Akuma. Each Akuma's stream opens with its identity frame
|
|
531
|
+
* — the identity and Contract association the observed facts carry — before any
|
|
532
|
+
* row; an already settled Akuma prints that frame and never replays backlog
|
|
533
|
+
* rows. `conclude` renders the closing rows once the wait ends: one row per
|
|
534
|
+
* observed Akuma in `<clock> <mark> <verb> — <duration>` grammar, with a target
|
|
535
|
+
* named for a multi-target scoreboard and no activity replay.
|
|
536
|
+
*/
|
|
537
|
+
export function waitObservationStream(context, options = {}) {
|
|
538
|
+
const now = options.now ?? (() => Date.now());
|
|
539
|
+
const startedAt = now();
|
|
540
|
+
const state = createWaitObservationState();
|
|
541
|
+
const select = (selected) => {
|
|
542
|
+
for (const member of selected)
|
|
543
|
+
registerWaitSource(state, member.id, member.alias);
|
|
544
|
+
};
|
|
545
|
+
const observe = (round) => observeWaitRound(state, round, context, now);
|
|
546
|
+
const conclude = (result) => concludeWaitStream(state, result, startedAt, now);
|
|
547
|
+
return { select, observe, conclude, streamed: () => state.observed };
|
|
548
|
+
}
|
|
549
|
+
function failedOutcomeDiagnostic(status) {
|
|
550
|
+
const outcome = status.timeline.kind === "idle" ? status.timeline.outcome : undefined;
|
|
551
|
+
return outcome !== undefined && outcome.outcome.kind === "failed" ? outcome.outcome.diagnostic : undefined;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Live view over one observing call: its identity frame and birth diagnostics
|
|
555
|
+
* open the stream once, settled rows follow as they arrive, and the return
|
|
556
|
+
* appends one conclusion in single-target wait grammar. The stream never
|
|
557
|
+
* replays its own activity as a final snapshot, and it never carries the
|
|
558
|
+
* birth receipt's cwd row.
|
|
559
|
+
*/
|
|
560
|
+
export function callObservationStream(context, head, options = {}) {
|
|
561
|
+
const now = options.now ?? (() => Date.now());
|
|
562
|
+
const startedAt = now();
|
|
563
|
+
const stream = activityStream(context);
|
|
564
|
+
let opened = false;
|
|
565
|
+
const open = (lines) => {
|
|
566
|
+
if (opened)
|
|
567
|
+
return;
|
|
568
|
+
opened = true;
|
|
569
|
+
lines.push(...snapshotHeading(head.id, head.alias, head.contract), ...head.facts);
|
|
570
|
+
};
|
|
571
|
+
const observe = (status) => {
|
|
572
|
+
const lines = [];
|
|
573
|
+
open(lines);
|
|
574
|
+
lines.push(...stream(status.timeline));
|
|
575
|
+
return lines;
|
|
576
|
+
};
|
|
577
|
+
const conclude = (observation) => {
|
|
578
|
+
const lines = [];
|
|
579
|
+
open(lines);
|
|
580
|
+
if (observation.kind === "observed")
|
|
581
|
+
lines.push(...stream(observation.status.timeline));
|
|
582
|
+
lines.push(...stream.flush());
|
|
583
|
+
if (observation.kind === "failed") {
|
|
584
|
+
lines.push(`! error ${safeText(observation.failure.diagnostic)}`);
|
|
585
|
+
return lines.join("\n");
|
|
586
|
+
}
|
|
587
|
+
if (observation.kind !== "observed")
|
|
588
|
+
return lines.join("\n");
|
|
589
|
+
const end = now();
|
|
590
|
+
const status = observation.status;
|
|
591
|
+
const answered = statusAnswer({ status }) !== undefined;
|
|
592
|
+
const at = waitComplete(status) ? (settleMoment(status) ?? end) : end;
|
|
593
|
+
const durationMs = Math.max(0, at - startedAt);
|
|
594
|
+
const { mark, verb, waited } = conclusionMarkVerb(status, answered);
|
|
595
|
+
lines.push(`${clockFromMs(at)} ${mark} ${verb} — ${waited ? "waited " : ""}${durationText(durationMs)}`);
|
|
596
|
+
const failure = failedOutcomeDiagnostic(status);
|
|
597
|
+
if (failure !== undefined)
|
|
598
|
+
lines.push(`! error ${safeText(failure)}`);
|
|
599
|
+
return lines.join("\n");
|
|
600
|
+
};
|
|
601
|
+
return { observe, conclude, opened: () => opened };
|
|
173
602
|
}
|
|
174
603
|
function taskDispositionMark(disposition) {
|
|
175
604
|
if (disposition === "done")
|
|
176
605
|
return "✓";
|
|
177
606
|
if (disposition === "drop")
|
|
178
|
-
return "
|
|
607
|
+
return "×";
|
|
179
608
|
if (disposition === "on_hold")
|
|
180
609
|
return "⧗";
|
|
181
610
|
if (disposition === "in_progress")
|
|
@@ -186,6 +615,8 @@ function changeStat(change) {
|
|
|
186
615
|
return change.diffstat === undefined ? "+? -?" : `+${change.diffstat.added} -${change.diffstat.removed}`;
|
|
187
616
|
}
|
|
188
617
|
function renderReportedChangeLines(snapshot) {
|
|
618
|
+
if (snapshot.reportedChanges.length === 0 && snapshot.reportedChangesOmitted === 0)
|
|
619
|
+
return [];
|
|
189
620
|
const width = snapshot.reportedChanges.reduce((max, change) => Math.max(max, changeStat(change).length), 0);
|
|
190
621
|
return [
|
|
191
622
|
`changes ${snapshot.reportedChanges.length + snapshot.reportedChangesOmitted}`,
|
|
@@ -211,6 +642,8 @@ function renderTaskContextLines(created, columns) {
|
|
|
211
642
|
return [];
|
|
212
643
|
if (created.kind === "failed")
|
|
213
644
|
return [`! tasks failed ${safeText(created.diagnostic)}`];
|
|
645
|
+
if (created.rows.length === 0)
|
|
646
|
+
return [];
|
|
214
647
|
return [`tasks ${created.rows.length}`, ...created.rows.flatMap((row) => renderTaskRow(row, columns))];
|
|
215
648
|
}
|
|
216
649
|
function answerContextLines(observation, columns) {
|
|
@@ -220,21 +653,17 @@ function answerContextLines(observation, columns) {
|
|
|
220
653
|
];
|
|
221
654
|
}
|
|
222
655
|
function answeredBlock(observation, answer, alias, columns) {
|
|
656
|
+
const frame = answeredHeading(observation.status.id, alias).join("\n");
|
|
223
657
|
const context = answerContextLines(observation, columns).join("\n");
|
|
224
658
|
if (context.length === 0)
|
|
225
|
-
return `${
|
|
659
|
+
return `${frame}\n${answer}`;
|
|
226
660
|
const separator = answer.endsWith("\n") ? "\n" : "\n\n";
|
|
227
|
-
return `${
|
|
661
|
+
return `${frame}\n${answer}${separator}${context}`;
|
|
228
662
|
}
|
|
229
663
|
function snapshotCore(view, context, options) {
|
|
230
|
-
const
|
|
231
|
-
const activity = snapshot.kind === "idle" && snapshot.outcome !== undefined
|
|
232
|
-
? groupedRows([
|
|
233
|
-
...snapshot.entries.filter((entry) => entry.kind === "row").map((entry) => entry.row),
|
|
234
|
-
snapshot.outcome,
|
|
235
|
-
].sort((left, right) => left.sequence - right.sequence), context)
|
|
236
|
-
: groupedEntries(snapshot.entries, context);
|
|
664
|
+
const activity = snapshotActivityLines(view.status.timeline, context);
|
|
237
665
|
const facts = [
|
|
666
|
+
...(options.showAllowed === true ? [`allowed ${view.status.allowed.join(", ") || "none"}`] : []),
|
|
238
667
|
...(view.status.readonly?.enforcement === "none" ? [`! ${safeText(view.status.readonly.diagnostic)}`] : []),
|
|
239
668
|
...contractFacts(view.contract),
|
|
240
669
|
...(options.facts ?? []),
|
|
@@ -298,12 +727,26 @@ function answerCallFailed(result) {
|
|
|
298
727
|
}
|
|
299
728
|
export function akumaRawAnswer(result) {
|
|
300
729
|
if (result.action === "call") {
|
|
730
|
+
if (result.streamed === true) {
|
|
731
|
+
// A streamed call's stdout is its answer or nothing; diagnostics belong to stderr.
|
|
732
|
+
if (answerCallFailed(result.result) || result.result.observation.kind !== "observed")
|
|
733
|
+
return "";
|
|
734
|
+
return statusAnswer({ status: parseAkumaStatus(result.result.observation.status) }) ?? "";
|
|
735
|
+
}
|
|
301
736
|
if (answerCallFailed(result.result) || result.result.observation.kind !== "observed")
|
|
302
737
|
return undefined;
|
|
303
738
|
return statusAnswer({ status: parseAkumaStatus(result.result.observation.status) });
|
|
304
739
|
}
|
|
305
|
-
if (result.action === "wait"
|
|
306
|
-
|
|
740
|
+
if (result.action === "wait") {
|
|
741
|
+
if (result.streamed === true) {
|
|
742
|
+
// Plural waits leave stdout empty so one answer can never be mistaken for the whole result.
|
|
743
|
+
const total = result.result.observations.length + result.result.unobserved.length;
|
|
744
|
+
const single = total === 1 ? result.result.observations[0] : undefined;
|
|
745
|
+
return single === undefined ? "" : (statusAnswer(single) ?? "");
|
|
746
|
+
}
|
|
747
|
+
if (result.result.observations.length === 1)
|
|
748
|
+
return statusAnswer(result.result.observations[0]);
|
|
749
|
+
}
|
|
307
750
|
if (result.action === "history" && result.mode === "exact" && result.historyResult.kind === "exact") {
|
|
308
751
|
return result.historyResult.outcome.outcome.kind === "answered"
|
|
309
752
|
? result.historyResult.outcome.outcome.answer
|
|
@@ -339,10 +782,11 @@ export function historyText(command, result, context) {
|
|
|
339
782
|
return result.mode === "last" ? result.answer : "no answer retained";
|
|
340
783
|
if (result.mode !== "page")
|
|
341
784
|
throw new Error("history result lacks page");
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
785
|
+
const rows = groupedRows(result.history.rows, context, true);
|
|
786
|
+
const paging = result.history.omitted > 0
|
|
787
|
+
? [` ⋮ ${result.history.omitted} earlier turns · showing last ${result.history.rows.length}`]
|
|
788
|
+
: [];
|
|
789
|
+
return [...snapshotHeading(result.akuma, result.alias, result.historyResult.contract), ...paging, ...rows].join("\n");
|
|
346
790
|
}
|
|
347
791
|
export function tellText(result, context) {
|
|
348
792
|
const wake = result.result.tell.wake;
|
|
@@ -350,7 +794,8 @@ export function tellText(result, context) {
|
|
|
350
794
|
const row = groupedRows([result.result.tell.row], context).join("\n");
|
|
351
795
|
if (wake.kind === "failed") {
|
|
352
796
|
const child = "child" in wake ? wake.child : undefined;
|
|
353
|
-
|
|
797
|
+
const failure = `! tell delivery failed · ${safeText(wake.diagnostic)}${child === undefined ? "" : ` · log ${child.log.path} ${child.log.from}..${child.log.to}`}`;
|
|
798
|
+
return `${target}\n${row}\n${renderBoundedTextBlock(failure, { first: "", continuation: " ", columns: context.columns, lines: Number.MAX_SAFE_INTEGER }).join("\n")}`;
|
|
354
799
|
}
|
|
355
|
-
return `${target}\n${
|
|
800
|
+
return `${target}\n${row}`;
|
|
356
801
|
}
|