@astrosheep/keiyaku 4.5.24 → 4.5.26
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/build/src/akuma/akuma-handle.d.ts +10 -0
- package/build/src/akuma/akuma-handle.js +18 -18
- package/build/src/akuma/akuma-instance.d.ts +5 -10
- package/build/src/akuma/akuma-instance.js +55 -40
- package/build/src/akuma/akuma-observe.d.ts +40 -7
- package/build/src/akuma/akuma-observe.js +51 -16
- package/build/src/akuma/akuma-product.d.ts +1 -2
- package/build/src/akuma/akuma-product.js +8 -5
- package/build/src/akuma/akuma.d.ts +2 -0
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +48 -30
- package/build/src/akuma/call-request.d.ts +0 -1
- package/build/src/akuma/call-request.js +1 -2
- package/build/src/akuma/fleet-execution.d.ts +3 -1
- package/build/src/akuma/fleet-execution.js +30 -71
- package/build/src/akuma/fleet-observation.d.ts +13 -2
- package/build/src/akuma/fleet-observation.js +2 -1
- package/build/src/akuma/fleet-request.js +3 -2
- package/build/src/akuma/heart/timeline.d.ts +1 -1
- package/build/src/akuma/heart/timeline.js +5 -5
- package/build/src/akuma/projection-read.js +37 -7
- package/build/src/akuma/projection.d.ts +1 -0
- package/build/src/akuma/projection.js +24 -9
- package/build/src/akuma/providers/pi/index.d.ts +2 -1
- package/build/src/akuma/providers/pi/index.js +2 -1
- package/build/src/akuma/publication.js +7 -4
- package/build/src/akuma-body.js +1 -0
- package/build/src/cli/commands/akuma-invoke.d.ts +1 -0
- package/build/src/cli/commands/akuma-invoke.js +16 -3
- package/build/src/cli/invoke.js +1 -0
- package/build/src/cli/render/akuma-activity.d.ts +13 -5
- package/build/src/cli/render/akuma-activity.js +118 -112
- package/build/src/cli/render/akuma-tool.js +5 -3
- package/build/src/library/address.d.ts +1 -1
- package/build/src/library/address.js +10 -8
- package/build/src/library/akuma-creation.d.ts +3 -0
- package/build/src/library/akuma-creation.js +111 -46
- package/build/src/library/composition.d.ts +3 -1
- package/build/src/library/fleet.d.ts +7 -1
- package/build/src/library/fleet.js +35 -9
- package/build/src/library/keiyaku.d.ts +3 -1
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +3 -3
|
@@ -185,8 +185,11 @@ async function settleLaunch(allocated, owned, failure) {
|
|
|
185
185
|
throw new Error("Akuma birth settled without a seal");
|
|
186
186
|
return "sealed";
|
|
187
187
|
}
|
|
188
|
-
async function awaitAsleepBirth(paths) {
|
|
189
|
-
const leash = await acquireLeash(paths, {
|
|
188
|
+
async function awaitAsleepBirth(paths, signal) {
|
|
189
|
+
const leash = await acquireLeash(paths, {
|
|
190
|
+
deadline: performance.now() + BIRTH_TIMEOUT_MS,
|
|
191
|
+
...(signal === undefined ? {} : { signal }),
|
|
192
|
+
});
|
|
190
193
|
if (leash === null)
|
|
191
194
|
throw new Error("Forked Akuma did not finish its birth body");
|
|
192
195
|
try {
|
|
@@ -241,7 +244,7 @@ export async function launchAkuma(input) {
|
|
|
241
244
|
if (soul.id !== allocated.id)
|
|
242
245
|
throw new Error("Akuma birth returned a different identity");
|
|
243
246
|
if (input.awaitAsleep === true)
|
|
244
|
-
await awaitAsleepBirth(allocated.paths);
|
|
247
|
+
await awaitAsleepBirth(allocated.paths, input.signal);
|
|
245
248
|
owned?.release();
|
|
246
249
|
return allocated;
|
|
247
250
|
}
|
|
@@ -256,7 +259,7 @@ export async function launchAkuma(input) {
|
|
|
256
259
|
}
|
|
257
260
|
if (outcome === "born" && input.signal?.aborted && error === input.signal.reason) {
|
|
258
261
|
if (input.awaitAsleep === true)
|
|
259
|
-
await awaitAsleepBirth(allocated.paths);
|
|
262
|
+
await awaitAsleepBirth(allocated.paths, input.signal);
|
|
260
263
|
return allocated;
|
|
261
264
|
}
|
|
262
265
|
throw error;
|
package/build/src/akuma-body.js
CHANGED
|
@@ -93,6 +93,7 @@ function fleetRequestPort(world) {
|
|
|
93
93
|
schema: Schema.json(JSON.parse(input.schemaJson), (value) => value),
|
|
94
94
|
...(input.interrupt === undefined ? {} : { interrupt: input.interrupt }),
|
|
95
95
|
...(input.initiator === undefined ? {} : { initiator: input.initiator }),
|
|
96
|
+
signal: input.signal,
|
|
96
97
|
}),
|
|
97
98
|
kill: async (input) => {
|
|
98
99
|
const result = await executeKillAkuma({ path: world, ids: input.targets, signal: input.signal });
|
|
@@ -99,6 +99,7 @@ type InvokeInput = Readonly<{
|
|
|
99
99
|
environment: NodeJS.ProcessEnv;
|
|
100
100
|
readStdin(): Promise<string>;
|
|
101
101
|
execution?: ExecutionContext;
|
|
102
|
+
signal?: AbortSignal;
|
|
102
103
|
}>;
|
|
103
104
|
export declare function invokeAkuma(command: InvokedAkumaCommand, input: InvokeInput): Promise<AkumaInvocationResult>;
|
|
104
105
|
export declare function invokeAkumaStatus(path: WorldRoot, akuma: string, alias?: string, repo?: Repo): Promise<AkumaInvocationResult>;
|
|
@@ -13,6 +13,9 @@ import { executionChannel } from "../../akuma/requests.js";
|
|
|
13
13
|
import { requestForwardedFleetTellAnswer } from "../../akuma/fleet-request.js";
|
|
14
14
|
/** The window a call observes for when the caller gives no `--wait` duration. */
|
|
15
15
|
const CALL_TIMEOUT_MS = 300_000;
|
|
16
|
+
function callSignalOption(signal) {
|
|
17
|
+
return signal === undefined ? {} : { signal };
|
|
18
|
+
}
|
|
16
19
|
function integrationFailure(error) {
|
|
17
20
|
return {
|
|
18
21
|
kind: error instanceof AuthorityCorruptionError ? "authority-corruption" : "infrastructure",
|
|
@@ -39,20 +42,23 @@ function writeProgress(body) {
|
|
|
39
42
|
async function observeCallUntilComplete(command, input, born) {
|
|
40
43
|
const stream = callObservationStream(resultContext(), callObservationHead(born));
|
|
41
44
|
try {
|
|
42
|
-
const
|
|
45
|
+
const observed = await observeAkumaStatus(input.path, born.akuma, {
|
|
43
46
|
timeoutMs: command.timeoutMs ?? CALL_TIMEOUT_MS,
|
|
47
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
44
48
|
observe: (observed) => {
|
|
45
49
|
const lines = stream.observe(observed);
|
|
46
50
|
if (lines.length > 0)
|
|
47
51
|
writeProgress(lines.join("\n"));
|
|
48
52
|
},
|
|
49
53
|
});
|
|
50
|
-
const conclusion = stream.conclude({ kind: "observed", status });
|
|
54
|
+
const conclusion = stream.conclude({ kind: "observed", reason: observed.reason, status: observed.status });
|
|
51
55
|
if (conclusion.length > 0)
|
|
52
56
|
writeProgress(conclusion);
|
|
53
|
-
return { kind: "observed", status };
|
|
57
|
+
return { kind: "observed", reason: observed.reason, status: observed.status };
|
|
54
58
|
}
|
|
55
59
|
catch (error) {
|
|
60
|
+
if (input.signal?.aborted)
|
|
61
|
+
throw error;
|
|
56
62
|
const failure = integrationFailure(error);
|
|
57
63
|
const conclusion = stream.conclude({ kind: "failed", failure });
|
|
58
64
|
if (conclusion.length > 0)
|
|
@@ -111,6 +117,7 @@ async function invokeWait(command, input) {
|
|
|
111
117
|
...(input.repo === undefined ? {} : { repo: input.repo }),
|
|
112
118
|
...(command.completion === undefined ? {} : { completion: command.completion }),
|
|
113
119
|
...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
|
|
120
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
114
121
|
}, input.execution ?? localExecutionContext(), observer);
|
|
115
122
|
const frozen = {
|
|
116
123
|
startedAt,
|
|
@@ -177,11 +184,13 @@ async function invokeTell(command, input) {
|
|
|
177
184
|
schema,
|
|
178
185
|
interrupt: command.interrupt,
|
|
179
186
|
...initiator,
|
|
187
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
180
188
|
})
|
|
181
189
|
: await Akuma.select(addressed.path, addressed.id).tell(body, {
|
|
182
190
|
schema,
|
|
183
191
|
...initiator,
|
|
184
192
|
...(command.interrupt ? { interrupt: true } : {}),
|
|
193
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
185
194
|
});
|
|
186
195
|
const alias = inputAlias(command.akuma);
|
|
187
196
|
return {
|
|
@@ -201,6 +210,7 @@ async function invokeTell(command, input) {
|
|
|
201
210
|
akuma: command.akuma,
|
|
202
211
|
body,
|
|
203
212
|
...(input.repo === undefined ? {} : { repo: input.repo }),
|
|
213
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
204
214
|
});
|
|
205
215
|
const alias = inputAlias(command.akuma);
|
|
206
216
|
return {
|
|
@@ -218,6 +228,7 @@ async function invokeTell(command, input) {
|
|
|
218
228
|
akuma: command.akuma,
|
|
219
229
|
body,
|
|
220
230
|
...(input.repo === undefined ? {} : { repo: input.repo }),
|
|
231
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
221
232
|
}, input.execution ?? localExecutionContext());
|
|
222
233
|
const alias = inputAlias(command.akuma);
|
|
223
234
|
return { kind: "akuma", action: "tell", mode: "ordinary", result, body, ...(alias === undefined ? {} : { alias }) };
|
|
@@ -269,6 +280,7 @@ async function invokeKill(command, input) {
|
|
|
269
280
|
path: input.path,
|
|
270
281
|
akuma: command.akuma,
|
|
271
282
|
...(input.repo === undefined ? {} : { repo: input.repo }),
|
|
283
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
272
284
|
}, input.execution ?? localExecutionContext()),
|
|
273
285
|
...(alias === undefined ? {} : { alias }),
|
|
274
286
|
};
|
|
@@ -291,6 +303,7 @@ export async function invokeAkuma(command, input) {
|
|
|
291
303
|
...(command.alias === undefined ? {} : { alias: command.alias }),
|
|
292
304
|
...(command.allowed === undefined ? {} : { allowed: command.allowed }),
|
|
293
305
|
...(schema === undefined ? {} : { schema }),
|
|
306
|
+
...callSignalOption(input.signal),
|
|
294
307
|
};
|
|
295
308
|
const observing = schema === undefined && command.mode === "wait" && command.output === "text";
|
|
296
309
|
const born = await caller.call(observing
|
package/build/src/cli/invoke.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AkumaStatus, KillEvidence } from "../../akuma/akuma.js";
|
|
1
|
+
import type { ActivityRow, AkumaStatus, KillEvidence } from "../../akuma/akuma.js";
|
|
2
2
|
import type { CallObservation } from "../../library/akuma-creation.js";
|
|
3
3
|
import type { AkumaObservation, AkumaObservationStage, CreatedTaskObservation, DispatchAssociation } from "../../index.js";
|
|
4
4
|
import type { AkumaInvocationResult } from "../commands/akuma-invoke.js";
|
|
@@ -13,6 +13,10 @@ export declare const DEFAULT_CONTEXT: TextRenderContext;
|
|
|
13
13
|
export declare function frameRule(headLines: readonly string[]): string;
|
|
14
14
|
type FleetTimeline = AkumaObservation["status"]["timeline"];
|
|
15
15
|
type RenderedSnapshot = FleetTimeline;
|
|
16
|
+
type RenderedActivity = Readonly<{
|
|
17
|
+
snapshot: RenderedSnapshot;
|
|
18
|
+
rows: readonly ActivityRow[];
|
|
19
|
+
}>;
|
|
16
20
|
export declare function associatedIdentity(id: string, alias?: string, _contract?: DispatchAssociation): string;
|
|
17
21
|
export declare function snapshotHeading(id: string, alias: string | undefined, contract: DispatchAssociation | undefined): readonly string[];
|
|
18
22
|
/**
|
|
@@ -34,9 +38,9 @@ export declare function snapshotActivityLines(snapshot: RenderedSnapshot, contex
|
|
|
34
38
|
latest?: boolean;
|
|
35
39
|
}>): readonly string[];
|
|
36
40
|
/** One command's append-only activity view, with a baseline and a final tail flush. */
|
|
37
|
-
export type ActivityStream = ((
|
|
38
|
-
/**
|
|
39
|
-
seed: (
|
|
41
|
+
export type ActivityStream = ((activity: RenderedActivity) => readonly string[]) & Readonly<{
|
|
42
|
+
/** Establish a wait baseline without spending the command's live tool budget. */
|
|
43
|
+
seed: (activity: RenderedActivity) => readonly string[];
|
|
40
44
|
/** Emit the deferred tail exactly once before the command's conclusion. */
|
|
41
45
|
flush: () => readonly string[];
|
|
42
46
|
}>;
|
|
@@ -50,6 +54,7 @@ export type ActivityStream = ((snapshot: RenderedSnapshot) => readonly string[])
|
|
|
50
54
|
export declare function activityStream(context: TextRenderContext, layout?: RowLayout): ActivityStream;
|
|
51
55
|
/** What a wait conclusion renders over: its observed and unobserved members. */
|
|
52
56
|
export type WaitConclusionResult = Readonly<{
|
|
57
|
+
reason: "completed" | "deadline";
|
|
53
58
|
observations: readonly AkumaObservation[];
|
|
54
59
|
unobserved: readonly Readonly<{
|
|
55
60
|
id: string;
|
|
@@ -93,7 +98,10 @@ export type ObservedCallHead = Readonly<{
|
|
|
93
98
|
facts: readonly string[];
|
|
94
99
|
}>;
|
|
95
100
|
export type CallObservationStream = Readonly<{
|
|
96
|
-
observe: (
|
|
101
|
+
observe: (observation: Readonly<{
|
|
102
|
+
status: AkumaStatus;
|
|
103
|
+
rows: readonly ActivityRow[];
|
|
104
|
+
}>) => readonly string[];
|
|
97
105
|
conclude: (observation: CallObservation) => string;
|
|
98
106
|
opened: () => boolean;
|
|
99
107
|
}>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseAkumaStatus } from "../../akuma/akuma.js";
|
|
2
|
+
import { defaultWaitComplete } from "../../akuma/akuma-observe.js";
|
|
2
3
|
import { toolRepr } from "./akuma-tool.js";
|
|
3
4
|
import { displayColumns, renderBoundedTextBlock, safeText, takeDisplayColumns, truncateMiddleDisplayText, } from "./terminal.js";
|
|
4
5
|
export const DEFAULT_CONTEXT = { columns: 80, color: false };
|
|
@@ -68,7 +69,7 @@ function label(row) {
|
|
|
68
69
|
if (row.kind === "tell")
|
|
69
70
|
return row.state === "told" ? "told" : "tell";
|
|
70
71
|
if (row.kind === "outcome")
|
|
71
|
-
return row.outcome.kind === "answered" ? "
|
|
72
|
+
return row.outcome.kind === "answered" ? "answer" : "error";
|
|
72
73
|
if (row.kind === "turn")
|
|
73
74
|
return "call";
|
|
74
75
|
if (row.kind !== "tool")
|
|
@@ -91,18 +92,6 @@ function mark(row) {
|
|
|
91
92
|
}
|
|
92
93
|
return "│";
|
|
93
94
|
}
|
|
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
|
-
}
|
|
106
95
|
function rowText(row) {
|
|
107
96
|
if (row.kind === "said" ||
|
|
108
97
|
row.kind === "thought" ||
|
|
@@ -110,13 +99,13 @@ function rowText(row) {
|
|
|
110
99
|
row.kind === "call" ||
|
|
111
100
|
row.kind === "tell") {
|
|
112
101
|
return {
|
|
113
|
-
text:
|
|
102
|
+
text: row.text,
|
|
114
103
|
lines: row.kind === "said" || row.kind === "thought" ? 2 : row.kind === "tell" || row.kind === "call" ? 1 : 2,
|
|
115
104
|
};
|
|
116
105
|
}
|
|
117
106
|
if (row.kind === "outcome")
|
|
118
107
|
return row.outcome.kind === "answered"
|
|
119
|
-
? { text:
|
|
108
|
+
? { text: row.outcome.answer, lines: 3 }
|
|
120
109
|
: { text: row.outcome.diagnostic, lines: 2 };
|
|
121
110
|
if (row.kind === "turn")
|
|
122
111
|
return { text: "", lines: 1 };
|
|
@@ -235,37 +224,21 @@ function orderedSnapshotEntries(snapshot) {
|
|
|
235
224
|
entries.push({ kind: "row", row: snapshot.outcome });
|
|
236
225
|
return entries;
|
|
237
226
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
* pursuing turn and is not its boundary.
|
|
242
|
-
*/
|
|
243
|
-
function currentTurnBoundary(snapshot) {
|
|
244
|
-
if (snapshot.kind !== "open")
|
|
227
|
+
function currentTurnBoundary(activity) {
|
|
228
|
+
const snapshot = activity.snapshot;
|
|
229
|
+
if (snapshot.kind !== "open" || snapshot.openingSequence === undefined)
|
|
245
230
|
return undefined;
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
return call === undefined ? undefined : { row: call, turnSequence: snapshot.turn.turnSequence };
|
|
254
|
-
}
|
|
255
|
-
/** Move the visible current-turn boundary ahead of activity without duplicating it. */
|
|
256
|
-
function boundaryFirstSnapshotEntries(snapshot) {
|
|
257
|
-
const entries = orderedSnapshotEntries(snapshot);
|
|
258
|
-
const boundary = currentTurnBoundary(snapshot);
|
|
259
|
-
if (boundary === undefined)
|
|
260
|
-
return entries;
|
|
261
|
-
return [
|
|
262
|
-
{ kind: "row", row: boundary.row },
|
|
263
|
-
...entries.filter((entry) => entry.kind !== "row" || entry.row !== boundary.row),
|
|
264
|
-
];
|
|
231
|
+
const row = activity.rows.find((candidate) => candidate.sequence === snapshot.openingSequence);
|
|
232
|
+
if (row === undefined)
|
|
233
|
+
return undefined;
|
|
234
|
+
// The projector selects a typed opening: its call or delivered launch Tell.
|
|
235
|
+
if (row.kind !== "call" && !(row.kind === "tell" && row.state === "told"))
|
|
236
|
+
return undefined;
|
|
237
|
+
return { row, turnSequence: snapshot.turn.turnSequence };
|
|
265
238
|
}
|
|
266
239
|
/** Open status hides internal thought narration without selecting a second activity window. */
|
|
267
240
|
function visibleOpenSnapshotEntries(snapshot) {
|
|
268
|
-
return
|
|
241
|
+
return snapshot.entries.filter((entry) => entry.kind !== "row" || entry.row.kind !== "thought");
|
|
269
242
|
}
|
|
270
243
|
/** Adjacent omitted spans are one continuous unknown portion of the retained timeline. */
|
|
271
244
|
function coalesceAdjacentGaps(entries) {
|
|
@@ -294,25 +267,45 @@ export function snapshotActivityLines(snapshot, context, selection = {}) {
|
|
|
294
267
|
const latest = entries.filter((entry) => entry.kind === "row").at(-1);
|
|
295
268
|
return latest === undefined ? [] : groupedEntries([latest], context);
|
|
296
269
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
270
|
+
function isSettledStreamRow(row) {
|
|
271
|
+
if (row.kind === "turn" || row.kind === "outcome")
|
|
272
|
+
return false;
|
|
273
|
+
if (row.kind === "tell")
|
|
274
|
+
return row.state === "told";
|
|
275
|
+
return row.kind !== "tool" || (row.state !== "active" && row.state !== "unsettled");
|
|
276
|
+
}
|
|
277
|
+
/** A pending Tell or active tool retains its sequence when it later becomes eligible. */
|
|
278
|
+
function isMutableStreamRow(row) {
|
|
279
|
+
return (row.kind === "tell" && row.state === "pending") || (row.kind === "tool" && row.state === "active");
|
|
280
|
+
}
|
|
281
|
+
function settledRows(activity) {
|
|
282
|
+
return activity.rows.filter(isSettledStreamRow);
|
|
283
|
+
}
|
|
284
|
+
function isProtectedStreamRow(row) {
|
|
285
|
+
return row.kind === "said" || (row.kind === "tool" && row.call.kind === "fileChange");
|
|
286
|
+
}
|
|
287
|
+
function isBoundedStreamTool(row) {
|
|
288
|
+
return row.kind === "tool" && !isProtectedStreamRow(row);
|
|
289
|
+
}
|
|
290
|
+
function rememberMutableRows(state, activity) {
|
|
291
|
+
for (const row of activity.rows)
|
|
292
|
+
if (isMutableStreamRow(row))
|
|
293
|
+
state.mutableSequences.add(row.sequence);
|
|
294
|
+
}
|
|
295
|
+
/** A seeded wait counts its skipped settled companion evidence after its typed opening. */
|
|
296
|
+
function baselineOmissionCount(activity, boundary) {
|
|
297
|
+
let afterBoundary = false;
|
|
298
|
+
let count = 0;
|
|
299
|
+
for (const row of activity.rows) {
|
|
300
|
+
if (!afterBoundary) {
|
|
301
|
+
if (row.sequence === boundary.row.sequence)
|
|
302
|
+
afterBoundary = true;
|
|
303
|
+
continue;
|
|
311
304
|
}
|
|
305
|
+
if (isSettledStreamRow(row))
|
|
306
|
+
count += 1;
|
|
312
307
|
}
|
|
313
|
-
|
|
314
|
-
function settledRows(snapshot) {
|
|
315
|
-
return orderedSnapshotEntries(settledTimeline(snapshot)).flatMap((entry) => entry.kind === "row" ? [entry.row] : []);
|
|
308
|
+
return count;
|
|
316
309
|
}
|
|
317
310
|
function renderStreamRow(state, row, lines, context, layout) {
|
|
318
311
|
const at = clock(row.at);
|
|
@@ -333,7 +326,7 @@ function coalesceDeferredGaps(state) {
|
|
|
333
326
|
}, []);
|
|
334
327
|
}
|
|
335
328
|
function omitOldestDeferredTool(state) {
|
|
336
|
-
const index = state.deferred.findIndex((entry) => entry.kind === "row" && entry.row
|
|
329
|
+
const index = state.deferred.findIndex((entry) => entry.kind === "row" && isBoundedStreamTool(entry.row));
|
|
337
330
|
if (index === -1)
|
|
338
331
|
throw new Error("activity tail lost its pending tool");
|
|
339
332
|
state.deferred[index] = { kind: "gap", count: 1 };
|
|
@@ -343,7 +336,7 @@ function omitOldestDeferredTool(state) {
|
|
|
343
336
|
function flushSafeActivityPrefix(state, lines, context, layout) {
|
|
344
337
|
for (;;) {
|
|
345
338
|
const first = state.deferred[0];
|
|
346
|
-
if (first === undefined || (first.kind === "row" && first.row
|
|
339
|
+
if (first === undefined || (first.kind === "row" && isBoundedStreamTool(first.row)))
|
|
347
340
|
return;
|
|
348
341
|
if (first.kind === "row") {
|
|
349
342
|
state.deferred.shift();
|
|
@@ -351,44 +344,55 @@ function flushSafeActivityPrefix(state, lines, context, layout) {
|
|
|
351
344
|
continue;
|
|
352
345
|
}
|
|
353
346
|
const next = state.deferred[1];
|
|
354
|
-
if (next === undefined || (next.kind === "row" && next.row
|
|
347
|
+
if (next === undefined || (next.kind === "row" && isBoundedStreamTool(next.row)))
|
|
355
348
|
return;
|
|
356
349
|
state.deferred.shift();
|
|
357
350
|
lines.push(layout.marker(first.count));
|
|
358
351
|
}
|
|
359
352
|
}
|
|
360
|
-
function renderCurrentTurnBoundary(state,
|
|
361
|
-
const boundary = currentTurnBoundary(
|
|
353
|
+
function renderCurrentTurnBoundary(state, activity, lines, context, layout) {
|
|
354
|
+
const boundary = currentTurnBoundary(activity);
|
|
362
355
|
if (boundary !== undefined && !state.renderedBoundaries.has(boundary.turnSequence)) {
|
|
363
|
-
renderStreamRow(state, boundary.row, lines, context, layout);
|
|
364
356
|
state.renderedBoundaries.add(boundary.turnSequence);
|
|
357
|
+
if (boundary.row.kind !== "thought")
|
|
358
|
+
renderStreamRow(state, boundary.row, lines, context, layout);
|
|
365
359
|
}
|
|
366
360
|
return boundary;
|
|
367
361
|
}
|
|
368
|
-
function observeActivitySnapshot(state,
|
|
362
|
+
function observeActivitySnapshot(state, activity, context, layout) {
|
|
369
363
|
const lines = [];
|
|
370
|
-
const boundary =
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
.
|
|
376
|
-
|
|
364
|
+
const boundary = currentTurnBoundary(activity);
|
|
365
|
+
rememberMutableRows(state, activity);
|
|
366
|
+
// A row newly selected as this Turn's typed opening must still respect the
|
|
367
|
+
// settled cursor, even if its earlier pending form was remembered as mutable.
|
|
368
|
+
if (boundary !== undefined)
|
|
369
|
+
state.mutableSequences.delete(boundary.row.sequence);
|
|
370
|
+
const observedRows = settledRows(activity)
|
|
371
|
+
.filter((row) => boundary === undefined ||
|
|
372
|
+
!state.renderedBoundaries.has(boundary.turnSequence) ||
|
|
373
|
+
row.sequence !== boundary.row.sequence)
|
|
374
|
+
.filter((row) => state.mutableSequences.has(row.sequence) ||
|
|
375
|
+
state.newestSettledSequence === undefined ||
|
|
376
|
+
row.sequence > state.newestSettledSequence);
|
|
377
|
+
if (observedRows.length === 0)
|
|
377
378
|
return lines;
|
|
378
|
-
|
|
379
|
+
for (const row of observedRows)
|
|
380
|
+
state.mutableSequences.delete(row.sequence);
|
|
381
|
+
state.newestSettledSequence = observedRows.reduce((newest, row) => Math.max(newest, row.sequence), state.newestSettledSequence ?? observedRows[0].sequence);
|
|
382
|
+
const rows = observedRows.filter((row) => row.kind !== "thought");
|
|
379
383
|
for (const row of rows) {
|
|
380
|
-
if (row
|
|
384
|
+
if (isBoundedStreamTool(row) && state.openingTools < OPENING_TOOL_BUDGET) {
|
|
381
385
|
state.openingTools += 1;
|
|
382
386
|
renderStreamRow(state, row, lines, context, layout);
|
|
383
387
|
continue;
|
|
384
388
|
}
|
|
385
|
-
if (row
|
|
389
|
+
if (!isBoundedStreamTool(row) && state.deferred.length === 0) {
|
|
386
390
|
renderStreamRow(state, row, lines, context, layout);
|
|
387
391
|
continue;
|
|
388
392
|
}
|
|
389
393
|
state.deferred.push({ kind: "row", row });
|
|
390
|
-
if (row
|
|
391
|
-
const pendingTools = state.deferred.filter((entry) => entry.kind === "row" && entry.row
|
|
394
|
+
if (isBoundedStreamTool(row)) {
|
|
395
|
+
const pendingTools = state.deferred.filter((entry) => entry.kind === "row" && isBoundedStreamTool(entry.row)).length;
|
|
392
396
|
if (pendingTools > RECENT_TOOL_BUDGET)
|
|
393
397
|
omitOldestDeferredTool(state);
|
|
394
398
|
}
|
|
@@ -416,31 +420,45 @@ function flushActivityTail(state, context, layout) {
|
|
|
416
420
|
*/
|
|
417
421
|
export function activityStream(context, layout = plainLayout()) {
|
|
418
422
|
const state = {
|
|
419
|
-
|
|
423
|
+
newestSettledSequence: undefined,
|
|
424
|
+
mutableSequences: new Set(),
|
|
420
425
|
previousClock: undefined,
|
|
421
426
|
renderedBoundaries: new Set(),
|
|
422
427
|
openingTools: 0,
|
|
423
428
|
deferred: [],
|
|
424
429
|
};
|
|
425
|
-
const seed = (
|
|
430
|
+
const seed = (activity) => {
|
|
426
431
|
const lines = [];
|
|
427
|
-
|
|
428
|
-
const
|
|
432
|
+
const boundary = currentTurnBoundary(activity);
|
|
433
|
+
const unseenBoundary = boundary !== undefined && !state.renderedBoundaries.has(boundary.turnSequence);
|
|
434
|
+
if (unseenBoundary) {
|
|
435
|
+
renderCurrentTurnBoundary(state, activity, lines, context, layout);
|
|
436
|
+
const omitted = baselineOmissionCount(activity, boundary);
|
|
437
|
+
if (omitted > 0)
|
|
438
|
+
lines.push(layout.marker(omitted));
|
|
439
|
+
}
|
|
440
|
+
rememberMutableRows(state, activity);
|
|
441
|
+
const rows = settledRows(activity);
|
|
429
442
|
if (rows.length > 0)
|
|
430
|
-
state.
|
|
443
|
+
state.newestSettledSequence = rows.reduce((newest, row) => Math.max(newest, row.sequence), rows[0].sequence);
|
|
431
444
|
return lines;
|
|
432
445
|
};
|
|
433
|
-
const observe = (
|
|
446
|
+
const observe = (activity) => observeActivitySnapshot(state, activity, context, layout);
|
|
434
447
|
const flush = () => flushActivityTail(state, context, layout);
|
|
435
448
|
return Object.assign(observe, { seed, flush });
|
|
436
449
|
}
|
|
437
450
|
function conclusionMarkVerb(status, answered) {
|
|
438
|
-
if (status
|
|
439
|
-
return { mark: "
|
|
451
|
+
if (failedOutcomeDiagnostic(status) !== undefined)
|
|
452
|
+
return { mark: "!", verb: "failed" };
|
|
440
453
|
if (answered)
|
|
441
454
|
return { mark: "✓", verb: "answered" };
|
|
455
|
+
if (status.life === "running")
|
|
456
|
+
return { mark: "●", verb: "still running" };
|
|
457
|
+
if (!defaultWaitComplete(status)) {
|
|
458
|
+
return { mark: "⧗", verb: "pending tell" };
|
|
459
|
+
}
|
|
442
460
|
if (status.life === "asleep")
|
|
443
|
-
return { mark: "
|
|
461
|
+
return { mark: "✓", verb: "completed" };
|
|
444
462
|
if (status.life === "killed")
|
|
445
463
|
return { mark: "×", verb: "killed" };
|
|
446
464
|
if (status.life === "hung")
|
|
@@ -525,10 +543,10 @@ function observeWaitRound(state, round, context, now) {
|
|
|
525
543
|
lines.push(...snapshotHeading(sole.status.id, sole.alias, sole.contract));
|
|
526
544
|
}
|
|
527
545
|
}
|
|
528
|
-
for (const { status } of round) {
|
|
546
|
+
for (const { status, rows } of round) {
|
|
529
547
|
const known = state.streams.get(status.id);
|
|
530
548
|
if (known !== undefined) {
|
|
531
|
-
lines.push(...known(status.timeline));
|
|
549
|
+
lines.push(...known({ snapshot: status.timeline, rows }));
|
|
532
550
|
}
|
|
533
551
|
else {
|
|
534
552
|
// Only a plural wait attributes its rows; a single-target stream keeps the plain row grammar.
|
|
@@ -536,10 +554,10 @@ function observeWaitRound(state, round, context, now) {
|
|
|
536
554
|
? sourceLayout(state.sources.get(status.id) ?? status.id, () => state.sourceWidth)
|
|
537
555
|
: plainLayout());
|
|
538
556
|
state.streams.set(status.id, stream);
|
|
539
|
-
//
|
|
540
|
-
lines.push(...stream.seed(status.timeline));
|
|
557
|
+
// Seed marks skipped retained evidence without spending this command's live tool budget.
|
|
558
|
+
lines.push(...stream.seed({ snapshot: status.timeline, rows }));
|
|
541
559
|
}
|
|
542
|
-
if (!state.settledAt.has(status.id) &&
|
|
560
|
+
if (!state.settledAt.has(status.id) && defaultWaitComplete(status))
|
|
543
561
|
state.settledAt.set(status.id, settleMoment(status) ?? now());
|
|
544
562
|
}
|
|
545
563
|
return lines;
|
|
@@ -574,13 +592,6 @@ function orderedWaitIds(state, result) {
|
|
|
574
592
|
}
|
|
575
593
|
function concludeWaitStream(state, result, startedAt, now) {
|
|
576
594
|
const tail = [];
|
|
577
|
-
// The returned observation can be newer than the final callback. Advance every known stream
|
|
578
|
-
// before flushing it, while still flushing members that became unobserved at the end.
|
|
579
|
-
for (const observation of result.observations) {
|
|
580
|
-
const stream = state.streams.get(observation.status.id);
|
|
581
|
-
if (stream !== undefined)
|
|
582
|
-
tail.push(...stream(observation.status.timeline));
|
|
583
|
-
}
|
|
584
595
|
for (const stream of state.streams.values())
|
|
585
596
|
tail.push(...stream.flush());
|
|
586
597
|
const end = now();
|
|
@@ -598,7 +609,7 @@ function concludeWaitStream(state, result, startedAt, now) {
|
|
|
598
609
|
if (observation === undefined)
|
|
599
610
|
return [];
|
|
600
611
|
const status = observation.status;
|
|
601
|
-
const complete =
|
|
612
|
+
const complete = defaultWaitComplete(status);
|
|
602
613
|
const at = complete ? (state.settledAt.get(id) ?? end) : end;
|
|
603
614
|
const { mark, verb } = conclusionMarkVerb(status, statusAnswer(observation) !== undefined);
|
|
604
615
|
const target = multi ? ` ${padToDisplay(state.sources.get(id) ?? id, state.sourceWidth)}` : "";
|
|
@@ -659,17 +670,15 @@ export function callObservationStream(context, head, options = {}) {
|
|
|
659
670
|
opened = true;
|
|
660
671
|
lines.push(...snapshotHeading(head.id, head.alias, head.contract), ...head.facts);
|
|
661
672
|
};
|
|
662
|
-
const observe = (
|
|
673
|
+
const observe = (observation) => {
|
|
663
674
|
const lines = [];
|
|
664
675
|
open(lines);
|
|
665
|
-
lines.push(...stream(status.timeline));
|
|
676
|
+
lines.push(...stream({ snapshot: observation.status.timeline, rows: observation.rows }));
|
|
666
677
|
return lines;
|
|
667
678
|
};
|
|
668
679
|
const conclude = (observation) => {
|
|
669
680
|
const lines = [];
|
|
670
681
|
open(lines);
|
|
671
|
-
if (observation.kind === "observed")
|
|
672
|
-
lines.push(...stream(observation.status.timeline));
|
|
673
682
|
lines.push(...stream.flush());
|
|
674
683
|
if (observation.kind === "failed") {
|
|
675
684
|
lines.push(`! error ${safeText(observation.failure.diagnostic)}`);
|
|
@@ -680,7 +689,7 @@ export function callObservationStream(context, head, options = {}) {
|
|
|
680
689
|
const end = now();
|
|
681
690
|
const status = observation.status;
|
|
682
691
|
const answered = statusAnswer({ status }) !== undefined;
|
|
683
|
-
const complete =
|
|
692
|
+
const complete = defaultWaitComplete(status);
|
|
684
693
|
const at = complete ? (settleMoment(status) ?? end) : end;
|
|
685
694
|
const { mark, verb } = conclusionMarkVerb(status, answered);
|
|
686
695
|
lines.push(`${clockFromMs(at)} ${mark} ${verb}${conclusionClause(at, startedAt, complete, end)}`);
|
|
@@ -798,7 +807,7 @@ export function mutationObservationStageText(id, observation, context, options =
|
|
|
798
807
|
return mutationSnapshotText(observation, context, options);
|
|
799
808
|
}
|
|
800
809
|
export function statusAnswer(view) {
|
|
801
|
-
if (!
|
|
810
|
+
if (!defaultWaitComplete(view.status))
|
|
802
811
|
return undefined;
|
|
803
812
|
if (view.status.life !== "asleep")
|
|
804
813
|
return undefined;
|
|
@@ -809,10 +818,6 @@ export function statusAnswer(view) {
|
|
|
809
818
|
return undefined;
|
|
810
819
|
return timeline.outcome.outcome.answer;
|
|
811
820
|
}
|
|
812
|
-
function waitComplete(status) {
|
|
813
|
-
return (status.life !== "running" &&
|
|
814
|
-
!status.timeline.entries.some((entry) => entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending"));
|
|
815
|
-
}
|
|
816
821
|
function answerCallFailed(result) {
|
|
817
822
|
return result.dispatch.kind === "failed" || result.alias.kind === "failed" || result.readonly?.enforcement === "none";
|
|
818
823
|
}
|
|
@@ -835,7 +840,8 @@ export function akumaRawAnswer(result) {
|
|
|
835
840
|
const single = total === 1 ? result.result.observations[0] : undefined;
|
|
836
841
|
return single === undefined ? "" : (statusAnswer(single) ?? "");
|
|
837
842
|
}
|
|
838
|
-
|
|
843
|
+
const total = result.result.observations.length + result.result.unobserved.length;
|
|
844
|
+
if (total === 1)
|
|
839
845
|
return statusAnswer(result.result.observations[0]);
|
|
840
846
|
}
|
|
841
847
|
if (result.action === "history" && result.mode === "exact" && result.historyResult.kind === "exact") {
|
|
@@ -906,7 +912,7 @@ export function waitText(result, context) {
|
|
|
906
912
|
if (observation === undefined)
|
|
907
913
|
return [];
|
|
908
914
|
const status = observation.status;
|
|
909
|
-
const complete =
|
|
915
|
+
const complete = defaultWaitComplete(status);
|
|
910
916
|
const at = complete ? (settleMoment(status) ?? end) : end;
|
|
911
917
|
const { mark, verb } = conclusionMarkVerb(status, statusAnswer(observation) !== undefined);
|
|
912
918
|
return [
|
|
@@ -52,15 +52,17 @@ function searchText(call) {
|
|
|
52
52
|
...(call.glob === undefined ? [] : [oneLine(call.glob)]),
|
|
53
53
|
].join(" · ");
|
|
54
54
|
}
|
|
55
|
-
function fileChange(call) {
|
|
55
|
+
function fileChange(call, state) {
|
|
56
56
|
const first = call.changes[0];
|
|
57
57
|
if (first === undefined)
|
|
58
58
|
return { label: "edit", text: "files" };
|
|
59
59
|
const label = call.changes.length === 1 ? (first.op === "add" ? "write" : first.op === "delete" ? "delete" : "edit") : "edit";
|
|
60
60
|
const subject = call.changes.length === 1 ? oneLine(first.path) : `${call.changes.length} files · ${oneLine(first.path)} ...`;
|
|
61
61
|
const complete = call.changes.every((change) => change.diffstat !== undefined);
|
|
62
|
-
if (!complete)
|
|
62
|
+
if (!complete && (state === "active" || state === "unsettled"))
|
|
63
63
|
return { label, text: subject };
|
|
64
|
+
if (!complete)
|
|
65
|
+
return { label, text: `${subject} — +? -?` };
|
|
64
66
|
const totals = call.changes.reduce((sum, change) => ({
|
|
65
67
|
added: sum.added + change.diffstat.added,
|
|
66
68
|
removed: sum.removed + change.diffstat.removed,
|
|
@@ -85,7 +87,7 @@ export function toolRepr(row) {
|
|
|
85
87
|
core = { label: searchLabel(row.call.scope), text: searchText(row.call) };
|
|
86
88
|
break;
|
|
87
89
|
case "fileChange":
|
|
88
|
-
core = fileChange(row.call);
|
|
90
|
+
core = fileChange(row.call, row.state);
|
|
89
91
|
break;
|
|
90
92
|
case "other":
|
|
91
93
|
core = { label: "use", text: oneLine(row.call.display || row.name) };
|