@llblab/pi-actors 0.54.0 → 0.56.0
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/AGENTS.md +110 -20
- package/BACKLOG.md +207 -4
- package/CHANGELOG.md +17 -0
- package/README.md +33 -3
- package/dist/index.js +2 -1
- package/dist/lib/async-runs.d.ts +37 -0
- package/dist/lib/async-runs.js +131 -62
- package/dist/lib/automatic-review-runtime.d.ts +2 -0
- package/dist/lib/automatic-review-runtime.js +3 -2
- package/dist/lib/command-templates.d.ts +37 -0
- package/dist/lib/command-templates.js +263 -129
- package/dist/lib/execution-files.d.ts +32 -0
- package/dist/lib/execution-files.js +128 -0
- package/dist/lib/execution-output.js +5 -4
- package/dist/lib/execution-sessions.d.ts +19 -1
- package/dist/lib/execution-sessions.js +62 -26
- package/dist/lib/execution.d.ts +4 -1
- package/dist/lib/execution.js +6 -1
- package/dist/lib/extension-runtime.d.ts +10 -2
- package/dist/lib/extension-runtime.js +95 -14
- package/dist/lib/file-state.js +21 -2
- package/dist/lib/inspector-command.js +2 -2
- package/dist/lib/inspector-overlay.d.ts +25 -7
- package/dist/lib/inspector-overlay.js +562 -262
- package/dist/lib/inspector.d.ts +20 -10
- package/dist/lib/inspector.js +80 -91
- package/dist/lib/limits.d.ts +1 -0
- package/dist/lib/limits.js +1 -0
- package/dist/lib/observability.d.ts +15 -1
- package/dist/lib/observability.js +216 -99
- package/dist/lib/pi.d.ts +9 -3
- package/dist/lib/pi.js +80 -12
- package/dist/lib/recipe-projection.d.ts +18 -0
- package/dist/lib/recipe-projection.js +220 -0
- package/dist/lib/recipes-context.d.ts +2 -0
- package/dist/lib/recipes-context.js +30 -6
- package/dist/lib/registry.js +270 -36
- package/dist/lib/run-delivery.d.ts +213 -5
- package/dist/lib/run-delivery.js +1909 -40
- package/dist/lib/run-supervision.d.ts +107 -0
- package/dist/lib/run-supervision.js +412 -0
- package/dist/lib/run-ui-runtime.d.ts +10 -2
- package/dist/lib/run-ui-runtime.js +347 -124
- package/dist/lib/runs-artifacts.d.ts +26 -1
- package/dist/lib/runs-artifacts.js +68 -2
- package/dist/lib/runs-context-exports.d.ts +451 -0
- package/dist/lib/runs-context-exports.js +2315 -0
- package/dist/lib/runs-context-index.d.ts +97 -0
- package/dist/lib/runs-context-index.js +368 -0
- package/dist/lib/runs-control-delivery.js +46 -7
- package/dist/lib/runs-control.d.ts +78 -2
- package/dist/lib/runs-control.js +334 -2
- package/dist/lib/runs-controls.js +4 -1
- package/dist/lib/runs-descendant-launches.d.ts +80 -0
- package/dist/lib/runs-descendant-launches.js +287 -0
- package/dist/lib/runs-execution-evidence.d.ts +110 -0
- package/dist/lib/runs-execution-evidence.js +282 -0
- package/dist/lib/runs-index.d.ts +66 -1
- package/dist/lib/runs-index.js +349 -13
- package/dist/lib/runs-native-launches.d.ts +73 -0
- package/dist/lib/runs-native-launches.js +227 -0
- package/dist/lib/runs-outcomes.d.ts +141 -0
- package/dist/lib/runs-outcomes.js +292 -0
- package/dist/lib/runs-ownership.d.ts +10 -1
- package/dist/lib/runs-ownership.js +36 -4
- package/dist/lib/runs-process-windows-mi.d.ts +46 -0
- package/dist/lib/runs-process-windows-mi.js +114 -0
- package/dist/lib/runs-process.d.ts +34 -1
- package/dist/lib/runs-process.js +158 -26
- package/dist/lib/runs-results.d.ts +116 -0
- package/dist/lib/runs-results.js +518 -0
- package/dist/lib/runs-retention.d.ts +15 -2
- package/dist/lib/runs-retention.js +148 -9
- package/dist/lib/runs-session-exports.d.ts +95 -0
- package/dist/lib/runs-session-exports.js +470 -0
- package/dist/lib/runs-snapshot.d.ts +58 -0
- package/dist/lib/runs-snapshot.js +92 -0
- package/dist/lib/runs-start.d.ts +2 -0
- package/dist/lib/runs-start.js +44 -3
- package/dist/lib/runs-status.d.ts +15 -1
- package/dist/lib/runs-status.js +337 -13
- package/dist/lib/runs-supervision.d.ts +105 -0
- package/dist/lib/runs-supervision.js +501 -0
- package/dist/lib/runs-trace.d.ts +1 -0
- package/dist/lib/runs-trace.js +14 -3
- package/dist/lib/runtime.js +22 -3
- package/dist/lib/session-evidence.d.ts +37 -0
- package/dist/lib/session-evidence.js +59 -3
- package/dist/lib/state-readers.d.ts +4 -1
- package/dist/lib/state-readers.js +14 -8
- package/dist/lib/temp.d.ts +2 -4
- package/dist/lib/temp.js +4 -56
- package/dist/lib/tools-access.d.ts +3 -0
- package/dist/lib/tools-access.js +13 -0
- package/dist/lib/tools-inspect.d.ts +1 -0
- package/dist/lib/tools-inspect.js +110 -43
- package/dist/lib/tools-local.d.ts +3 -0
- package/dist/lib/tools-local.js +18 -6
- package/dist/lib/tools-message.d.ts +4 -1
- package/dist/lib/tools-message.js +126 -31
- package/dist/lib/tools-response.d.ts +31 -3
- package/dist/lib/tools-response.js +311 -47
- package/dist/lib/tools-spawn.d.ts +3 -0
- package/dist/lib/tools-spawn.js +29 -7
- package/dist/lib/trace-projection.d.ts +20 -0
- package/dist/lib/trace-projection.js +192 -43
- package/dist/scripts/async-runner.mjs +272 -194
- package/dist/scripts/conformance.mjs +14 -2
- package/dist/skills/actors/SKILL.md +47 -3
- package/dist/skills/actors/references/diagnostics.md +40 -0
- package/dist/skills/actors/references/runs.md +56 -2
- package/docs/README.md +9 -1
- package/docs/actor-inspector.md +84 -15
- package/docs/agent-experience.md +216 -0
- package/docs/async-runs.md +21 -3
- package/docs/composition-transport.md +103 -0
- package/docs/coordinator-delivery.md +226 -24
- package/docs/inspection.md +38 -2
- package/docs/output-projection-map.md +130 -0
- package/docs/releasing.md +19 -2
- package/docs/run-supervision.md +1622 -0
- package/docs/template-recipes.md +4 -0
- package/docs/windows-process-observation.md +248 -0
- package/index.ts +2 -1
- package/lib/async-runs.ts +164 -73
- package/lib/automatic-review-runtime.ts +4 -2
- package/lib/command-templates.ts +287 -138
- package/lib/execution-files.ts +118 -0
- package/lib/execution-output.ts +4 -4
- package/lib/execution-sessions.ts +57 -27
- package/lib/execution.ts +8 -0
- package/lib/extension-runtime.ts +106 -12
- package/lib/file-state.ts +16 -2
- package/lib/inspector-command.ts +2 -2
- package/lib/inspector-overlay.ts +524 -261
- package/lib/inspector.ts +101 -130
- package/lib/limits.ts +1 -0
- package/lib/observability.ts +235 -116
- package/lib/pi.ts +89 -17
- package/lib/recipe-projection.ts +232 -0
- package/lib/recipes-context.ts +27 -6
- package/lib/registry.ts +266 -36
- package/lib/run-delivery.ts +1737 -49
- package/lib/run-supervision.ts +411 -0
- package/lib/run-ui-runtime.ts +293 -126
- package/lib/runs-artifacts.ts +73 -1
- package/lib/runs-context-exports.ts +1871 -0
- package/lib/runs-context-index.ts +372 -0
- package/lib/runs-control-delivery.ts +36 -18
- package/lib/runs-control.ts +297 -1
- package/lib/runs-controls.ts +3 -1
- package/lib/runs-descendant-launches.ts +338 -0
- package/lib/runs-execution-evidence.ts +302 -0
- package/lib/runs-index.ts +345 -17
- package/lib/runs-native-launches.ts +270 -0
- package/lib/runs-outcomes.ts +377 -0
- package/lib/runs-ownership.ts +37 -4
- package/lib/runs-process-windows-mi.ts +113 -0
- package/lib/runs-process.ts +210 -34
- package/lib/runs-results.ts +601 -0
- package/lib/runs-retention.ts +134 -10
- package/lib/runs-session-exports.ts +406 -0
- package/lib/runs-snapshot.ts +143 -0
- package/lib/runs-start.ts +44 -2
- package/lib/runs-status.ts +252 -10
- package/lib/runs-supervision.ts +394 -0
- package/lib/runs-trace.ts +14 -3
- package/lib/runtime.ts +22 -3
- package/lib/session-evidence.ts +76 -4
- package/lib/state-readers.ts +16 -7
- package/lib/temp.ts +3 -56
- package/lib/tools-access.ts +13 -0
- package/lib/tools-inspect.ts +110 -51
- package/lib/tools-local.ts +37 -24
- package/lib/tools-message.ts +120 -37
- package/lib/tools-response.ts +273 -50
- package/lib/tools-spawn.ts +31 -10
- package/lib/trace-projection.ts +165 -42
- package/package.json +10 -6
- package/scripts/async-runner.mjs +272 -194
- package/scripts/conformance.mjs +14 -2
- package/skills/actors/SKILL.md +47 -3
- package/skills/actors/references/diagnostics.md +40 -0
- package/skills/actors/references/runs.md +56 -2
- package/banner.jpg +0 -0
- package/dist/lib/inspector-actions.d.ts +0 -16
- package/dist/lib/inspector-actions.js +0 -57
- package/lib/inspector-actions.ts +0 -79
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
|
|
10
|
+
import { observeExecutionFileReference } from "./execution-files.ts";
|
|
10
11
|
import * as SessionEvidence from "./session-evidence.ts";
|
|
12
|
+
import * as Limits from "./limits.ts";
|
|
11
13
|
import { readJsonFileResilient } from "./state-readers.ts";
|
|
12
14
|
|
|
13
15
|
export interface ExecutionTurnItem extends SessionEvidence.SessionEvidenceTurn {
|
|
@@ -21,33 +23,48 @@ export interface ExecutionTurnItem extends SessionEvidence.SessionEvidenceTurn {
|
|
|
21
23
|
stage?: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const sessionsRoot = path.resolve(stateDir, "sessions");
|
|
30
|
-
const resolved = path.resolve(stateDir, sessionFile);
|
|
31
|
-
if (!resolved.startsWith(`${sessionsRoot}${path.sep}`)) return undefined;
|
|
32
|
-
try {
|
|
33
|
-
const canonicalRoot = fs.realpathSync(sessionsRoot);
|
|
34
|
-
const canonicalFile = fs.realpathSync(resolved);
|
|
35
|
-
return canonicalFile.startsWith(`${canonicalRoot}${path.sep}`)
|
|
36
|
-
? canonicalFile
|
|
37
|
-
: undefined;
|
|
38
|
-
} catch {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
26
|
+
export const EXECUTION_CHRONOLOGY = Symbol("execution-session-chronology");
|
|
27
|
+
export interface ExecutionChronologyEntry extends SessionEvidence.SessionChronologyEntry {
|
|
28
|
+
commandId: string;
|
|
29
|
+
sessionFile: string;
|
|
30
|
+
sessionOrdinal: number;
|
|
41
31
|
}
|
|
32
|
+
export type ExecutionChronology = Readonly<{
|
|
33
|
+
basis: "recorded_execution_metadata";
|
|
34
|
+
entries: readonly Readonly<ExecutionChronologyEntry>[];
|
|
35
|
+
} & ({ state: "available" } | { state: "unavailable"; reason: SessionEvidence.SessionChronologyReason | "source_changed" | "namespace_unavailable" })>;
|
|
36
|
+
export type ExecutionTurnCollection = ExecutionTurnItem[] & { readonly [EXECUTION_CHRONOLOGY]?: ExecutionChronology };
|
|
42
37
|
|
|
43
|
-
export function readExecutionTurns(stateDir: string):
|
|
44
|
-
const
|
|
38
|
+
export function readExecutionTurns(stateDir: string): ExecutionTurnCollection {
|
|
39
|
+
const read = readJsonFileResilient<Record<string, unknown>>(
|
|
45
40
|
path.join(stateDir, "execution.json"),
|
|
46
41
|
{},
|
|
47
|
-
)
|
|
42
|
+
);
|
|
43
|
+
const execution = read.value;
|
|
44
|
+
const basis = "recorded_execution_metadata" as const;
|
|
45
|
+
let reason: Extract<ExecutionChronology, { state: "unavailable" }>["reason"] | undefined = read.diagnostics.length ? "source_unavailable" : undefined;
|
|
46
|
+
const metadata: Readonly<ExecutionChronologyEntry>[] = [];
|
|
47
|
+
let metadataBytes = Buffer.byteLength(JSON.stringify({ state: "available", basis, entries: [] }));
|
|
48
|
+
const refuse = (next: NonNullable<typeof reason>): void => { reason ??= next; metadata.length = 0; };
|
|
49
|
+
const collect = (commandId: string, sessionFile: string, frame: SessionEvidence.SessionChronology | undefined): void => {
|
|
50
|
+
if (reason) return;
|
|
51
|
+
if (!frame || frame.state !== "available") { refuse(frame?.state === "unavailable" ? frame.reason : "source_unavailable"); return; }
|
|
52
|
+
if (SessionEvidence.redactSessionEvidenceValue(commandId) !== commandId || SessionEvidence.redactSessionEvidenceValue(sessionFile) !== sessionFile) {
|
|
53
|
+
refuse("namespace_unavailable"); return;
|
|
54
|
+
}
|
|
55
|
+
for (const item of frame.entries) {
|
|
56
|
+
const entry = Object.freeze({ ...item, commandId, sessionFile, sessionOrdinal: item.ordinal, ordinal: metadata.length + 1 });
|
|
57
|
+
metadataBytes += Buffer.byteLength(JSON.stringify(entry)) + Number(metadata.length > 0);
|
|
58
|
+
if (metadataBytes > Limits.SESSION_EVIDENCE_MAX_BYTES) { refuse("metadata_too_large"); return; }
|
|
59
|
+
metadata.push(entry);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
48
62
|
const commands = Array.isArray(execution.commands)
|
|
49
63
|
? (execution.commands as Array<Record<string, unknown>>)
|
|
50
64
|
: [];
|
|
65
|
+
if ((execution.commands !== undefined && !Array.isArray(execution.commands)) || commands.some((command) =>
|
|
66
|
+
!command || typeof command !== "object" || Array.isArray(command) ||
|
|
67
|
+
(command.session_files !== undefined && (!Array.isArray(command.session_files) || command.session_files.some((file) => typeof file !== "string"))))) refuse("source_unavailable");
|
|
51
68
|
const recordedFiles = new Set(
|
|
52
69
|
commands.flatMap((command) =>
|
|
53
70
|
(Array.isArray(command.session_files) ? command.session_files : []).filter(
|
|
@@ -57,8 +74,13 @@ export function readExecutionTurns(stateDir: string): ExecutionTurnItem[] {
|
|
|
57
74
|
);
|
|
58
75
|
let discoveredCommands: Array<Record<string, unknown>> = [];
|
|
59
76
|
try {
|
|
60
|
-
const
|
|
61
|
-
|
|
77
|
+
const state = path.resolve(stateDir);
|
|
78
|
+
const sessionsDir = path.join(state, "sessions");
|
|
79
|
+
const root = fs.lstatSync(state);
|
|
80
|
+
const rootObserved = root.isDirectory() && !root.isSymbolicLink();
|
|
81
|
+
const namespace = fs.lstatSync(sessionsDir, { throwIfNoEntry: false });
|
|
82
|
+
if (!rootObserved || (namespace && (!namespace.isDirectory() || namespace.isSymbolicLink()))) refuse("source_unavailable");
|
|
83
|
+
discoveredCommands = rootObserved && namespace?.isDirectory() && !namespace.isSymbolicLink() ? fs
|
|
62
84
|
.readdirSync(sessionsDir, { withFileTypes: true })
|
|
63
85
|
.filter((entry) => entry.isDirectory())
|
|
64
86
|
.flatMap((entry) => {
|
|
@@ -72,18 +94,22 @@ export function readExecutionTurns(stateDir: string): ExecutionTurnItem[] {
|
|
|
72
94
|
return files.length > 0
|
|
73
95
|
? [{ id: entry.name, session_files: files, stage: "subagent" }]
|
|
74
96
|
: [];
|
|
75
|
-
});
|
|
97
|
+
}) : [];
|
|
76
98
|
} catch {
|
|
99
|
+
refuse("source_unavailable");
|
|
77
100
|
discoveredCommands = [];
|
|
78
101
|
}
|
|
79
|
-
|
|
102
|
+
const turns: ExecutionTurnCollection = [...commands, ...discoveredCommands]
|
|
80
103
|
.flatMap((command) =>
|
|
81
104
|
(Array.isArray(command.session_files) ? command.session_files : [])
|
|
82
105
|
.filter((file): file is string => typeof file === "string")
|
|
83
106
|
.flatMap((file) => {
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
const session = SessionEvidence.readSessionEvidence(
|
|
107
|
+
const reference = observeExecutionFileReference(stateDir, { kind: "session", path: file });
|
|
108
|
+
if (reference.availability !== "available") { refuse("source_unavailable"); return []; }
|
|
109
|
+
const session = SessionEvidence.readSessionEvidence(reference.local_path);
|
|
110
|
+
if (!reference.unchanged()) { refuse("source_changed"); return []; }
|
|
111
|
+
if (typeof command.id !== "string" || !command.id) refuse("namespace_unavailable");
|
|
112
|
+
else collect(command.id, file, session[SessionEvidence.SESSION_EVIDENCE_CHRONOLOGY]);
|
|
87
113
|
return session.turns.map((turn) => ({
|
|
88
114
|
...turn,
|
|
89
115
|
commandId: String(command.id ?? "unknown"),
|
|
@@ -108,4 +134,8 @@ export function readExecutionTurns(stateDir: string): ExecutionTurnItem[] {
|
|
|
108
134
|
}),
|
|
109
135
|
)
|
|
110
136
|
.map((turn, index) => ({ ...turn, index: index + 1 }));
|
|
137
|
+
Object.defineProperty(turns, EXECUTION_CHRONOLOGY, { value: Object.freeze(reason
|
|
138
|
+
? { state: "unavailable", basis, reason, entries: Object.freeze([]) }
|
|
139
|
+
: { state: "available", basis, entries: Object.freeze(metadata) }) });
|
|
140
|
+
return turns;
|
|
111
141
|
}
|
package/lib/execution.ts
CHANGED
|
@@ -1084,6 +1084,8 @@ export async function executeRegisteredTool(
|
|
|
1084
1084
|
exec: RegisteredToolExec,
|
|
1085
1085
|
cwd: string,
|
|
1086
1086
|
signal?: AbortSignal,
|
|
1087
|
+
kernel?: { readonly onPrimaryCommandSelected?: (result: Readonly<ToolExecResult>) => void | Promise<void>;
|
|
1088
|
+
readonly onFinalResponseSelected?: (text: string, upstreamTruncated: boolean) => void | Promise<void> },
|
|
1087
1089
|
): Promise<RegisteredToolExecutionResult> {
|
|
1088
1090
|
const executed = await executeTemplateSteps(
|
|
1089
1091
|
cfg,
|
|
@@ -1126,6 +1128,12 @@ export async function executeRegisteredTool(
|
|
|
1126
1128
|
},
|
|
1127
1129
|
});
|
|
1128
1130
|
}
|
|
1131
|
+
// Only the plain successful leaf is admitted initially; compositions/acceptance policies are separate contracts.
|
|
1132
|
+
if (typeof cfg.template === "string" && executed.commands.length === 1 && !executed.failures.length) {
|
|
1133
|
+
await kernel?.onPrimaryCommandSelected?.(result);
|
|
1134
|
+
}
|
|
1135
|
+
await kernel?.onFinalResponseSelected?.(result.stdout, result.stdoutTruncated === true || executed.branches.some(branch =>
|
|
1136
|
+
branch.status !== "done" && (branch.stderrTruncated === true || branch.stderrBytes > Buffer.byteLength(branch.stderr ?? ""))));
|
|
1129
1137
|
const formatted = formatOutput(cfg.name, "stdout", result.stdout);
|
|
1130
1138
|
const templateWarnings = CommandTemplates.getCommandTemplateWarnings(
|
|
1131
1139
|
createTemplateConfig(cfg),
|
package/lib/extension-runtime.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Owns low-level Pi lifecycle effects and tool wrapping without owning event registration.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
7
9
|
import * as AutomaticReviewRuntime from "./automatic-review-runtime.ts";
|
|
8
10
|
import * as CommandTemplates from "./command-templates.ts";
|
|
9
11
|
import * as Paths from "./paths.ts";
|
|
@@ -14,8 +16,16 @@ import * as RecipesReferences from "./recipes-references.ts";
|
|
|
14
16
|
import * as RunUiRuntime from "./run-ui-runtime.ts";
|
|
15
17
|
import * as Runtime from "./runtime.ts";
|
|
16
18
|
import * as Temp from "./temp.ts";
|
|
19
|
+
import * as RunRetention from "./runs-retention.ts";
|
|
17
20
|
import * as Tools from "./tools.ts";
|
|
18
21
|
import * as ToolsResponse from "./tools-response.ts";
|
|
22
|
+
import {
|
|
23
|
+
createRunSupervisionOwner,
|
|
24
|
+
type RunSupervisionOwner,
|
|
25
|
+
type RunSupervisionOwnerSnapshotReading,
|
|
26
|
+
} from "./runs-supervision.ts";
|
|
27
|
+
import type { RunSupervisionControllerPorts } from "./run-supervision.ts";
|
|
28
|
+
import type { AsyncRunStartOptions } from "./async-runs.ts";
|
|
19
29
|
|
|
20
30
|
export interface ActorExtensionRuntime {
|
|
21
31
|
beforeAgentStart(
|
|
@@ -26,23 +36,62 @@ export interface ActorExtensionRuntime {
|
|
|
26
36
|
discoverResources(metaUrl: string): { skillPaths: string[] } | undefined;
|
|
27
37
|
getRunOwnerId(ctx: Pi.ExtensionContext): string;
|
|
28
38
|
onAgentSettled(ctx: Pi.ExtensionContext): void;
|
|
39
|
+
onTurnEnd(event: Pi.TurnEndEvent, ctx: Pi.ExtensionContext): Pi.TurnEndEventResult | undefined;
|
|
29
40
|
onContext(messages: unknown[], ctx: Pi.ExtensionContext): unknown[];
|
|
30
41
|
onSessionShutdown(reason: string, ctx: Pi.ExtensionContext): void;
|
|
31
|
-
onSessionStart(ctx: Pi.ExtensionContext): Promise<void>;
|
|
42
|
+
onSessionStart(ctx: Pi.ExtensionContext, event?: Pi.SessionStartEvent): Promise<void>;
|
|
32
43
|
registerCoreTools(): void;
|
|
44
|
+
/** Private kernel observation only; no public tool route or reconstructed origin. */
|
|
45
|
+
observeRunSnapshot(expected: unknown, capturedAt: unknown, stateDir?: string): Readonly<RunSupervisionOwnerSnapshotReading>;
|
|
46
|
+
getRunAdmissionObserver(ctx: Pi.ExtensionContext): AsyncRunStartOptions["onSupervisionClockCaptured"];
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
export function createActorExtensionRuntime(
|
|
36
50
|
pi: Pi.ExtensionAPI,
|
|
51
|
+
supervisionPorts?: Omit<RunSupervisionControllerPorts, "readCurrent">,
|
|
52
|
+
completionStreamSessionAdmission?: RunUiRuntime.RunUiRuntimeDeps["completionStreamSessionAdmission"],
|
|
53
|
+
captureCompletionBatch?: RunUiRuntime.RunUiRuntimeDeps["captureCompletionBatch"],
|
|
37
54
|
): ActorExtensionRuntime {
|
|
38
55
|
let activeRunContext: Pi.ExtensionContext | undefined;
|
|
39
56
|
let activeRunOwnerId: string | undefined;
|
|
40
|
-
|
|
57
|
+
let sessionStartTicket: object | undefined;
|
|
58
|
+
type ParentSession = {
|
|
59
|
+
ownerId: string;
|
|
60
|
+
sessionManager: Pi.ExtensionContext["sessionManager"];
|
|
61
|
+
shutdownAdmitted: boolean;
|
|
62
|
+
};
|
|
63
|
+
let parentSession: ParentSession | undefined;
|
|
64
|
+
const parentSessionsByContext = new WeakMap<Pi.ExtensionContext, ParentSession>();
|
|
65
|
+
const seenParentManagers = new WeakSet<Pi.ExtensionContext["sessionManager"]>();
|
|
41
66
|
const recipeResolutionContextsBySession = new Map<
|
|
42
67
|
string,
|
|
43
68
|
RecipeResolution.RecipeResolutionContext
|
|
44
69
|
>();
|
|
45
70
|
const getRunOwnerId = Pi.getSessionId;
|
|
71
|
+
let supervision: {
|
|
72
|
+
context: Pi.ExtensionContext;
|
|
73
|
+
ownerId: string;
|
|
74
|
+
owner: Readonly<RunSupervisionOwner>;
|
|
75
|
+
ready: boolean;
|
|
76
|
+
sessionManager: Pi.ExtensionContext["sessionManager"];
|
|
77
|
+
} | undefined;
|
|
78
|
+
const closeSupervision = (): void => {
|
|
79
|
+
const scope = supervision;
|
|
80
|
+
supervision = undefined;
|
|
81
|
+
scope?.owner.close();
|
|
82
|
+
};
|
|
83
|
+
const getRunAdmissionObserver = (ctx: Pi.ExtensionContext): AsyncRunStartOptions["onSupervisionClockCaptured"] => {
|
|
84
|
+
if (!supervisionPorts) return undefined;
|
|
85
|
+
const scope = supervision;
|
|
86
|
+
const sessionManager = ctx.sessionManager;
|
|
87
|
+
const ownerId = sessionManager.getSessionId();
|
|
88
|
+
return capture => {
|
|
89
|
+
if (!scope || supervision !== scope || !scope.ready || scope.sessionManager !== sessionManager || scope.ownerId !== ownerId || capture.identity.ownerId !== ownerId) {
|
|
90
|
+
throw new Error("Run supervision coordinator admission is unavailable or changed.");
|
|
91
|
+
}
|
|
92
|
+
scope.owner.onSupervisionClockCaptured(capture);
|
|
93
|
+
};
|
|
94
|
+
};
|
|
46
95
|
const getRecipeResolutionContext = (ctx: Pi.ExtensionContext) => {
|
|
47
96
|
const sessionId = getRunOwnerId(ctx);
|
|
48
97
|
const resolutionContext = recipeResolutionContextsBySession.get(sessionId);
|
|
@@ -57,6 +106,7 @@ export function createActorExtensionRuntime(
|
|
|
57
106
|
getActiveContext: () => activeRunContext,
|
|
58
107
|
getRunOwnerId,
|
|
59
108
|
getThinkingLevel: () => pi.getThinkingLevel(),
|
|
109
|
+
getRunAdmissionObserver,
|
|
60
110
|
});
|
|
61
111
|
let recipeReload: Runtime.RecipeToolReloadWatcher | undefined;
|
|
62
112
|
let runUiRuntime: RunUiRuntime.RunUiRuntime;
|
|
@@ -64,12 +114,16 @@ export function createActorExtensionRuntime(
|
|
|
64
114
|
const ownerId = activeRunOwnerId;
|
|
65
115
|
activeRunContext = undefined;
|
|
66
116
|
activeRunOwnerId = undefined;
|
|
117
|
+
sessionStartTicket = undefined;
|
|
67
118
|
runUiRuntime?.close();
|
|
68
119
|
automaticReview.close();
|
|
69
120
|
recipeReload?.close();
|
|
70
121
|
if (ownerId) recipeResolutionContextsBySession.delete(ownerId);
|
|
71
122
|
};
|
|
123
|
+
RunUiRuntime.registerRunCompletionRenderer(pi);
|
|
72
124
|
runUiRuntime = RunUiRuntime.createRunUiRuntime({
|
|
125
|
+
completionStreamSessionAdmission,
|
|
126
|
+
captureCompletionBatch,
|
|
73
127
|
getActiveContext: () => activeRunContext,
|
|
74
128
|
onCallbackError: closeActiveSessionRuntimes,
|
|
75
129
|
onRunEvent: automaticReview.schedule,
|
|
@@ -93,6 +147,7 @@ export function createActorExtensionRuntime(
|
|
|
93
147
|
ctx as Pi.ExtensionContext,
|
|
94
148
|
),
|
|
95
149
|
getThinkingLevel: () => pi.getThinkingLevel(),
|
|
150
|
+
onSupervisionClockCaptured: getRunAdmissionObserver(ctx as Pi.ExtensionContext),
|
|
96
151
|
};
|
|
97
152
|
}
|
|
98
153
|
try {
|
|
@@ -142,22 +197,55 @@ export function createActorExtensionRuntime(
|
|
|
142
197
|
return skillPaths.length > 0 ? { skillPaths } : undefined;
|
|
143
198
|
},
|
|
144
199
|
getRunOwnerId,
|
|
200
|
+
getRunAdmissionObserver,
|
|
201
|
+
observeRunSnapshot(expected, capturedAt, stateDir) {
|
|
202
|
+
return supervision?.owner.observeSnapshot(expected, capturedAt, stateDir) ?? Object.freeze({ state: "unavailable", reason: "origin_not_retained" });
|
|
203
|
+
},
|
|
145
204
|
onAgentSettled(ctx) {
|
|
205
|
+
if ((completionStreamSessionAdmission || captureCompletionBatch) && !runUiRuntime.isCurrentSessionContext(ctx)) return;
|
|
146
206
|
if (!activeRunOwnerId || getRunOwnerId(ctx) !== activeRunOwnerId) return;
|
|
147
207
|
if (!runUiRuntime.flushCompletionBatch(ctx)) automaticReview.schedule();
|
|
148
208
|
},
|
|
209
|
+
onTurnEnd(event, ctx) {
|
|
210
|
+
return runUiRuntime.onTurnEnd(event, ctx);
|
|
211
|
+
},
|
|
149
212
|
onContext(messages, ctx) {
|
|
150
213
|
return runUiRuntime.projectContext(messages, ctx);
|
|
151
214
|
},
|
|
152
215
|
onSessionShutdown(reason, ctx) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
216
|
+
const scope = parentSession;
|
|
217
|
+
if (!scope?.shutdownAdmitted) return;
|
|
218
|
+
const known = parentSessionsByContext.get(ctx);
|
|
219
|
+
if (known && known !== scope) return;
|
|
220
|
+
if (!known) {
|
|
221
|
+
try {
|
|
222
|
+
if (ctx.sessionManager !== scope.sessionManager || scope.sessionManager.getSessionId() !== scope.ownerId) return;
|
|
223
|
+
} catch { return; /* Unknown invalidated wrappers cannot identify this lifetime. */ }
|
|
224
|
+
}
|
|
225
|
+
if (parentSession !== scope) return;
|
|
226
|
+
// Consume before cleanup/teardown: stale or reentrant callbacks cannot stop a replacement.
|
|
227
|
+
scope.shutdownAdmitted = false;
|
|
228
|
+
parentSessionsByContext.set(ctx, scope);
|
|
229
|
+
closeSupervision();
|
|
230
|
+
if (parentSession !== scope) return;
|
|
231
|
+
closeActiveSessionRuntimes();
|
|
232
|
+
if (parentSession !== scope) return;
|
|
233
|
+
recipeResolutionContextsBySession.delete(scope.ownerId);
|
|
234
|
+
runUiRuntime.shutdown(reason, scope.ownerId, ctx);
|
|
158
235
|
},
|
|
159
|
-
async onSessionStart(ctx) {
|
|
160
|
-
const
|
|
236
|
+
async onSessionStart(ctx, event) {
|
|
237
|
+
const ticket = {};
|
|
238
|
+
sessionStartTicket = ticket;
|
|
239
|
+
parentSession = undefined;
|
|
240
|
+
const sessionManager = ctx.sessionManager;
|
|
241
|
+
const sessionId = sessionManager.getSessionId();
|
|
242
|
+
if (sessionStartTicket !== ticket) return;
|
|
243
|
+
const scopeBinding: ParentSession = { ownerId: sessionId, sessionManager,
|
|
244
|
+
shutdownAdmitted: !seenParentManagers.has(sessionManager) && !parentSessionsByContext.has(ctx) };
|
|
245
|
+
seenParentManagers.add(sessionManager);
|
|
246
|
+
if (!parentSessionsByContext.has(ctx)) parentSessionsByContext.set(ctx, scopeBinding);
|
|
247
|
+
parentSession = scopeBinding;
|
|
248
|
+
closeSupervision();
|
|
161
249
|
recipeResolutionContextsBySession.set(
|
|
162
250
|
sessionId,
|
|
163
251
|
RecipeResolution.createEmptyRecipeResolutionContext(sessionId, ctx.cwd),
|
|
@@ -165,14 +253,20 @@ export function createActorExtensionRuntime(
|
|
|
165
253
|
ctx.ui.setWidget("zz-pi-actors-comms", undefined);
|
|
166
254
|
activeRunContext = ctx;
|
|
167
255
|
activeRunOwnerId = sessionId;
|
|
168
|
-
runOwnerIdsByContext.set(ctx, sessionId);
|
|
169
256
|
runUiRuntime.close();
|
|
170
257
|
automaticReview.close();
|
|
171
258
|
recipeReload.close();
|
|
259
|
+
const scope = supervisionPorts ? {
|
|
260
|
+
context: ctx, ownerId: sessionId, owner: createRunSupervisionOwner(supervisionPorts),
|
|
261
|
+
ready: false, sessionManager,
|
|
262
|
+
} : undefined;
|
|
263
|
+
supervision = scope;
|
|
172
264
|
await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
|
|
173
|
-
|
|
265
|
+
await RunRetention.cleanupStaleRunEntries(join(Paths.EXTENSION_RUNTIME_PATHS.tempDir, "runs"));
|
|
266
|
+
if (sessionStartTicket !== ticket || activeRunContext !== ctx || activeRunOwnerId !== sessionId || supervision !== scope) return;
|
|
267
|
+
if (scope) scope.ready = true;
|
|
174
268
|
automaticReview.start(ctx);
|
|
175
|
-
runUiRuntime.start(ctx, sessionId);
|
|
269
|
+
runUiRuntime.start(ctx, sessionId, event);
|
|
176
270
|
recipeReload.watch(ctx);
|
|
177
271
|
},
|
|
178
272
|
registerCoreTools() {
|
package/lib/file-state.ts
CHANGED
|
@@ -260,10 +260,19 @@ export function withFileMutationLock<T>(
|
|
|
260
260
|
options: FileMutationLockOptions = {},
|
|
261
261
|
): T {
|
|
262
262
|
const release = acquireFileMutationLock(path, options);
|
|
263
|
+
let mutationFailed = false, mutationFailure: unknown;
|
|
263
264
|
try {
|
|
264
265
|
return mutate();
|
|
266
|
+
} catch (error) {
|
|
267
|
+
mutationFailed = true; mutationFailure = error;
|
|
268
|
+
throw error;
|
|
265
269
|
} finally {
|
|
266
|
-
release();
|
|
270
|
+
try { release(); }
|
|
271
|
+
catch (releaseError) {
|
|
272
|
+
if (mutationFailed) throw new AggregateError([mutationFailure, releaseError],
|
|
273
|
+
"File mutation callback and lock release failed; effects may already exist.", { cause: mutationFailure });
|
|
274
|
+
throw releaseError;
|
|
275
|
+
}
|
|
267
276
|
}
|
|
268
277
|
}
|
|
269
278
|
|
|
@@ -273,7 +282,12 @@ function replaceFileWithRetry(source: string, target: string): void {
|
|
|
273
282
|
renameSync(source, target);
|
|
274
283
|
return;
|
|
275
284
|
} catch (error) {
|
|
276
|
-
|
|
285
|
+
let code: unknown;
|
|
286
|
+
try { code = (error as NodeJS.ErrnoException | null | undefined)?.code; }
|
|
287
|
+
catch (observationError) {
|
|
288
|
+
throw new AggregateError([error, observationError],
|
|
289
|
+
"File replacement failed and retry-code inspection also failed; effects may already exist.", { cause: error });
|
|
290
|
+
}
|
|
277
291
|
if ((code !== "EPERM" && code !== "EBUSY") || attempt >= 19) throw error;
|
|
278
292
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
|
|
279
293
|
}
|
package/lib/inspector-command.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as AsyncRuns from "./async-runs.ts";
|
|
8
|
-
import * as
|
|
8
|
+
import * as Inspector from "./inspector.ts";
|
|
9
9
|
import * as InspectorOverlay from "./inspector-overlay.ts";
|
|
10
10
|
import * as Paths from "./paths.ts";
|
|
11
11
|
import type * as Pi from "./pi.ts";
|
|
@@ -23,7 +23,7 @@ export function registerActorInspectorCommand(
|
|
|
23
23
|
new InspectorOverlay.ActorInspectorOverlay({
|
|
24
24
|
done,
|
|
25
25
|
killRun: (run, runInstanceId) =>
|
|
26
|
-
|
|
26
|
+
Inspector.killOwnedRunFromInspector(
|
|
27
27
|
getRunOwnerId(ctx),
|
|
28
28
|
run,
|
|
29
29
|
Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
|