@llblab/pi-kit 0.6.0 → 0.7.1
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/CHANGELOG.md +8 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
- package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
- package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
- package/node_modules/@llblab/pi-state-flow/README.md +203 -107
- package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
- package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
- package/node_modules/@llblab/pi-state-flow/package.json +8 -2
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
- package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
- package/node_modules/@llblab/pi-telegram/README.md +13 -9
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
- package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
- package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
- package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
- package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
- package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
- package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
- package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
- package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
- package/node_modules/@llblab/skills/package.json +2 -3
- package/package.json +6 -5
- /package/node_modules/@llblab/pi-telegram/lib/{logs.ts → logging.ts} +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns portable command-template parsing, expansion, risk checks, retries, timeouts, and direct execution.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
7
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
8
8
|
import { accessSync, appendFileSync, constants, mkdirSync, mkdtempSync, statSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { homedir, tmpdir } from "node:os";
|
|
10
10
|
import { basename, delimiter, extname, isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
@@ -1010,12 +1010,50 @@ function execCommandTemplateOnce(
|
|
|
1010
1010
|
let settled = false;
|
|
1011
1011
|
let timeoutId: NodeJS.Timeout | undefined;
|
|
1012
1012
|
let killTimeoutId: NodeJS.Timeout | undefined;
|
|
1013
|
+
const signalProcessTree = (signal: NodeJS.Signals): void => {
|
|
1014
|
+
if (proc.pid === undefined) return;
|
|
1015
|
+
if (process.platform === "win32") {
|
|
1016
|
+
spawnSync("taskkill", [
|
|
1017
|
+
"/PID",
|
|
1018
|
+
String(proc.pid),
|
|
1019
|
+
"/T",
|
|
1020
|
+
...(signal === "SIGKILL" ? ["/F"] : []),
|
|
1021
|
+
]);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
const snapshot = spawnSync("ps", ["-eo", "pid=,ppid="], { encoding: "utf8" });
|
|
1025
|
+
const children = new Map<number, number[]>();
|
|
1026
|
+
for (const line of snapshot.stdout?.split(/\r?\n/u) ?? []) {
|
|
1027
|
+
const [pidText, parentText] = line.trim().split(/\s+/u);
|
|
1028
|
+
const pid = Number(pidText);
|
|
1029
|
+
const parent = Number(parentText);
|
|
1030
|
+
if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(parent)) continue;
|
|
1031
|
+
const siblings = children.get(parent) ?? [];
|
|
1032
|
+
siblings.push(pid);
|
|
1033
|
+
children.set(parent, siblings);
|
|
1034
|
+
}
|
|
1035
|
+
const descendants: number[] = [];
|
|
1036
|
+
const collect = (parent: number): void => {
|
|
1037
|
+
for (const child of children.get(parent) ?? []) {
|
|
1038
|
+
collect(child);
|
|
1039
|
+
descendants.push(child);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
collect(proc.pid);
|
|
1043
|
+
for (const pid of [...descendants, proc.pid]) {
|
|
1044
|
+
try {
|
|
1045
|
+
process.kill(pid, signal);
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
if ((error as NodeJS.ErrnoException).code !== "ESRCH") throw error;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1013
1051
|
const killProcess = (): void => {
|
|
1014
1052
|
if (killed) return;
|
|
1015
1053
|
killed = true;
|
|
1016
|
-
|
|
1054
|
+
signalProcessTree("SIGTERM");
|
|
1017
1055
|
killTimeoutId = setTimeout(() => {
|
|
1018
|
-
if (!settled)
|
|
1056
|
+
if (!settled) signalProcessTree("SIGKILL");
|
|
1019
1057
|
}, options.killGrace ?? 5000);
|
|
1020
1058
|
};
|
|
1021
1059
|
const settle = (code: number): void => {
|
|
@@ -143,7 +143,7 @@ export function createActorExtensionRuntime(
|
|
|
143
143
|
},
|
|
144
144
|
getRunOwnerId,
|
|
145
145
|
onAgentSettled(ctx) {
|
|
146
|
-
if (
|
|
146
|
+
if (!activeRunOwnerId || getRunOwnerId(ctx) !== activeRunOwnerId) return;
|
|
147
147
|
if (!runUiRuntime.flushCompletionBatch(ctx)) automaticReview.schedule();
|
|
148
148
|
},
|
|
149
149
|
onContext(messages, ctx) {
|
|
@@ -43,6 +43,12 @@ export interface RunObservation {
|
|
|
43
43
|
activeSubagents?: number;
|
|
44
44
|
completed?: number;
|
|
45
45
|
descendantSubagents?: number;
|
|
46
|
+
deliveryOwnerId?: string;
|
|
47
|
+
deliveryParent?: {
|
|
48
|
+
run: string;
|
|
49
|
+
run_instance_id: string;
|
|
50
|
+
state_dir: string;
|
|
51
|
+
};
|
|
46
52
|
failures?: number;
|
|
47
53
|
ownerId?: string;
|
|
48
54
|
artifacts?: Record<string, string>;
|
|
@@ -136,6 +142,21 @@ export function readRunUiSnapshot(
|
|
|
136
142
|
};
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
/** Observe every Run generation routed to one root coordinator. */
|
|
146
|
+
export function readRunDeliverySnapshot(
|
|
147
|
+
state: RunUiObservationState,
|
|
148
|
+
deliveryOwnerId: string,
|
|
149
|
+
stateRoot?: string,
|
|
150
|
+
): RunUiSnapshot {
|
|
151
|
+
const summary = summarizeRuns(stateRoot, undefined, deliveryOwnerId);
|
|
152
|
+
return {
|
|
153
|
+
attentionEvents: [],
|
|
154
|
+
status: undefined,
|
|
155
|
+
summary,
|
|
156
|
+
transitions: detectRunTransitions(state.observed, summary),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
139
160
|
export function pruneRunUiObservationState(
|
|
140
161
|
state: RunUiObservationState,
|
|
141
162
|
snapshot: Pick<RunUiSnapshot, "summary" | "transitions">,
|
|
@@ -383,6 +404,11 @@ export interface RunRetirementExecutorOptions {
|
|
|
383
404
|
}
|
|
384
405
|
|
|
385
406
|
export interface RunTransition {
|
|
407
|
+
deliveryParent?: {
|
|
408
|
+
run: string;
|
|
409
|
+
run_instance_id: string;
|
|
410
|
+
state_dir: string;
|
|
411
|
+
};
|
|
386
412
|
from: RunObservedStatus;
|
|
387
413
|
run: string;
|
|
388
414
|
runInstanceId?: string;
|
|
@@ -584,6 +610,23 @@ function observeRun(stateDir: string): RunObservation | undefined {
|
|
|
584
610
|
failures: Array.isArray(progress.failures)
|
|
585
611
|
? progress.failures.length
|
|
586
612
|
: undefined,
|
|
613
|
+
...(typeof status.delivery_owner_id === "string"
|
|
614
|
+
? { deliveryOwnerId: status.delivery_owner_id }
|
|
615
|
+
: {}),
|
|
616
|
+
...(status.delivery_parent &&
|
|
617
|
+
typeof status.delivery_parent === "object" &&
|
|
618
|
+
!Array.isArray(status.delivery_parent) &&
|
|
619
|
+
typeof (status.delivery_parent as Record<string, unknown>).run === "string" &&
|
|
620
|
+
typeof (status.delivery_parent as Record<string, unknown>).run_instance_id === "string" &&
|
|
621
|
+
typeof (status.delivery_parent as Record<string, unknown>).state_dir === "string"
|
|
622
|
+
? {
|
|
623
|
+
deliveryParent: status.delivery_parent as {
|
|
624
|
+
run: string;
|
|
625
|
+
run_instance_id: string;
|
|
626
|
+
state_dir: string;
|
|
627
|
+
},
|
|
628
|
+
}
|
|
629
|
+
: {}),
|
|
587
630
|
...(typeof status.ownerId === "string"
|
|
588
631
|
? { ownerId: status.ownerId }
|
|
589
632
|
: {}),
|
|
@@ -630,6 +673,7 @@ function observeRun(stateDir: string): RunObservation | undefined {
|
|
|
630
673
|
export function summarizeRuns(
|
|
631
674
|
stateRoot = Paths.getRunStateRoot(),
|
|
632
675
|
ownerId?: string,
|
|
676
|
+
deliveryOwnerId?: string,
|
|
633
677
|
): RunSummary {
|
|
634
678
|
if (!existsSync(stateRoot)) {
|
|
635
679
|
return {
|
|
@@ -650,7 +694,10 @@ export function summarizeRuns(
|
|
|
650
694
|
)
|
|
651
695
|
.map((stateDir) => observeRun(stateDir))
|
|
652
696
|
.filter((run): run is RunObservation => Boolean(run))
|
|
653
|
-
.filter((run) =>
|
|
697
|
+
.filter((run) => deliveryOwnerId !== undefined
|
|
698
|
+
? run.deliveryOwnerId === deliveryOwnerId ||
|
|
699
|
+
(run.deliveryOwnerId === undefined && run.ownerId === deliveryOwnerId)
|
|
700
|
+
: ownerId === undefined || run.ownerId === ownerId)
|
|
654
701
|
.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""));
|
|
655
702
|
const processSubagentsByRun = countRunningSubagentsByRun(stateRoot, ownerId);
|
|
656
703
|
const runsWithDescendants = runs.map((run) => {
|
|
@@ -1005,6 +1052,7 @@ export function detectRunTransitions(
|
|
|
1005
1052
|
transitions.push({
|
|
1006
1053
|
from: old ?? "running",
|
|
1007
1054
|
run: run.run,
|
|
1055
|
+
...(run.deliveryParent ? { deliveryParent: run.deliveryParent } : {}),
|
|
1008
1056
|
...(run.stateDir ? { stateDir: run.stateDir } : {}),
|
|
1009
1057
|
...(run.artifacts ? { artifacts: run.artifacts } : {}),
|
|
1010
1058
|
...(run.launchCorrelation ? { launchCorrelation: run.launchCorrelation } : {}),
|
|
@@ -1282,11 +1330,57 @@ export function shouldNotifyRunTransition(transition: RunTransition): boolean {
|
|
|
1282
1330
|
);
|
|
1283
1331
|
}
|
|
1284
1332
|
|
|
1285
|
-
|
|
1333
|
+
function deliveryGenerationIdentity(input: {
|
|
1334
|
+
runInstanceId?: string;
|
|
1335
|
+
stateDir?: string;
|
|
1336
|
+
}): string | undefined {
|
|
1337
|
+
return input.stateDir && input.runInstanceId
|
|
1338
|
+
? `${input.stateDir}\0${input.runInstanceId}`
|
|
1339
|
+
: undefined;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function deliveryTreePosition(
|
|
1343
|
+
input: {
|
|
1344
|
+
deliveryParent?: RunObservation["deliveryParent"];
|
|
1345
|
+
runInstanceId?: string;
|
|
1346
|
+
stateDir?: string;
|
|
1347
|
+
},
|
|
1348
|
+
runsByIdentity: Map<string, RunObservation>,
|
|
1349
|
+
): { depth: number; rootIdentity: string } | undefined {
|
|
1350
|
+
const ownIdentity = deliveryGenerationIdentity(input);
|
|
1351
|
+
if (!ownIdentity) return undefined;
|
|
1352
|
+
let parent = input.deliveryParent;
|
|
1353
|
+
let depth = 0;
|
|
1354
|
+
let rootIdentity = ownIdentity;
|
|
1355
|
+
const visited = new Set([ownIdentity]);
|
|
1356
|
+
while (parent) {
|
|
1357
|
+
const identity = `${parent.state_dir}\0${parent.run_instance_id}`;
|
|
1358
|
+
if (visited.has(identity)) return undefined;
|
|
1359
|
+
visited.add(identity);
|
|
1360
|
+
const ancestor = runsByIdentity.get(identity);
|
|
1361
|
+
if (!ancestor || ancestor.notificationPolicy === "silent") return undefined;
|
|
1362
|
+
depth += 1;
|
|
1363
|
+
rootIdentity = identity;
|
|
1364
|
+
parent = ancestor.deliveryParent;
|
|
1365
|
+
}
|
|
1366
|
+
return { depth, rootIdentity };
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/** Build exact immutable members only after each containing root Run is terminal. */
|
|
1286
1370
|
export function collectRunCompletionBatchMembers(
|
|
1287
1371
|
transitions: RunTransition[],
|
|
1372
|
+
deliveryRuns: RunObservation[] = [],
|
|
1288
1373
|
): RunCompletionBatchMember[] {
|
|
1289
|
-
|
|
1374
|
+
const runsByIdentity = new Map(deliveryRuns.flatMap((run) => {
|
|
1375
|
+
const identity = deliveryGenerationIdentity(run);
|
|
1376
|
+
return identity ? [[identity, run] as const] : [];
|
|
1377
|
+
}));
|
|
1378
|
+
const incompleteRoots = new Set(deliveryRuns.flatMap((run) => {
|
|
1379
|
+
if (TERMINAL.has(run.status)) return [];
|
|
1380
|
+
const position = deliveryTreePosition(run, runsByIdentity);
|
|
1381
|
+
return position ? [position.rootIdentity] : [];
|
|
1382
|
+
}));
|
|
1383
|
+
const admitted = transitions.flatMap((transition) => {
|
|
1290
1384
|
if (
|
|
1291
1385
|
!shouldNotifyRunTransition(transition) ||
|
|
1292
1386
|
!transition.stateDir ||
|
|
@@ -1294,6 +1388,9 @@ export function collectRunCompletionBatchMembers(
|
|
|
1294
1388
|
!transition.terminalAt ||
|
|
1295
1389
|
Number.isNaN(Date.parse(transition.terminalAt))
|
|
1296
1390
|
) return [];
|
|
1391
|
+
const position = deliveryTreePosition(transition, runsByIdentity);
|
|
1392
|
+
if (!position || incompleteRoots.has(position.rootIdentity)) return [];
|
|
1393
|
+
const { depth } = position;
|
|
1297
1394
|
const artifactEntries = Object.entries(transition.artifacts ?? {})
|
|
1298
1395
|
.filter((entry): entry is [string, string] =>
|
|
1299
1396
|
typeof entry[1] === "string" && Boolean(entry[1]))
|
|
@@ -1304,6 +1401,20 @@ export function collectRunCompletionBatchMembers(
|
|
|
1304
1401
|
...(artifactEntries.length > 0
|
|
1305
1402
|
? { artifacts: Object.fromEntries(artifactEntries) }
|
|
1306
1403
|
: {}),
|
|
1404
|
+
...(transition.semanticResult?.body?.trim()
|
|
1405
|
+
? {
|
|
1406
|
+
output: transition.semanticResult.body.trim().length > 4_000
|
|
1407
|
+
? `${transition.semanticResult.body.trim().slice(0, 3_999)}…`
|
|
1408
|
+
: transition.semanticResult.body.trim(),
|
|
1409
|
+
}
|
|
1410
|
+
: {}),
|
|
1411
|
+
...(transition.deliveryParent
|
|
1412
|
+
? {
|
|
1413
|
+
parent_run: transition.deliveryParent.run,
|
|
1414
|
+
parent_run_instance_id: transition.deliveryParent.run_instance_id,
|
|
1415
|
+
parent_state_dir: transition.deliveryParent.state_dir,
|
|
1416
|
+
}
|
|
1417
|
+
: {}),
|
|
1307
1418
|
run: transition.run,
|
|
1308
1419
|
run_instance_id: transition.runInstanceId,
|
|
1309
1420
|
state_dir: transition.stateDir,
|
|
@@ -1312,13 +1423,16 @@ export function collectRunCompletionBatchMembers(
|
|
|
1312
1423
|
? `${rawSummary.slice(0, 999)}…`
|
|
1313
1424
|
: rawSummary,
|
|
1314
1425
|
terminal_at: transition.terminalAt,
|
|
1426
|
+
tree_depth: depth,
|
|
1315
1427
|
}];
|
|
1316
|
-
})
|
|
1428
|
+
});
|
|
1429
|
+
return admitted.sort((left, right) =>
|
|
1430
|
+
left.tree_depth - right.tree_depth ||
|
|
1317
1431
|
left.terminal_at.localeCompare(right.terminal_at) ||
|
|
1318
1432
|
left.run.localeCompare(right.run) ||
|
|
1319
1433
|
left.run_instance_id.localeCompare(right.run_instance_id) ||
|
|
1320
1434
|
left.state_dir.localeCompare(right.state_dir)
|
|
1321
|
-
);
|
|
1435
|
+
).map(({ tree_depth: _depth, ...member }) => member);
|
|
1322
1436
|
}
|
|
1323
1437
|
|
|
1324
1438
|
const TERMINAL_FOLLOW_UP_ARTIFACT_LIMIT = 4;
|
|
@@ -52,7 +52,7 @@ export function sendRunCompletionBatch(
|
|
|
52
52
|
pi.sendMessage({
|
|
53
53
|
customType: RUN_COMPLETION_BATCH_CUSTOM_TYPE,
|
|
54
54
|
content,
|
|
55
|
-
display:
|
|
55
|
+
display: true,
|
|
56
56
|
details: {
|
|
57
57
|
pi_actors_delivery: {
|
|
58
58
|
batch_id: batchId,
|
|
@@ -96,17 +96,20 @@ function completionBatchMessage(
|
|
|
96
96
|
typeof record.content !== "string"
|
|
97
97
|
) return undefined;
|
|
98
98
|
const details = record.details;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const envelope = delivery
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
envelope.batch_id
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
const delivery = details && typeof details === "object" && !Array.isArray(details)
|
|
100
|
+
? (details as Record<string, unknown>).pi_actors_delivery
|
|
101
|
+
: undefined;
|
|
102
|
+
const envelope = delivery && typeof delivery === "object" && !Array.isArray(delivery)
|
|
103
|
+
? delivery as Record<string, unknown>
|
|
104
|
+
: undefined;
|
|
105
|
+
const detailedId = envelope?.kind === "completion_batch" &&
|
|
106
|
+
typeof envelope.batch_id === "string"
|
|
107
|
+
? envelope.batch_id
|
|
108
|
+
: undefined;
|
|
109
|
+
const contentId = /^Batch: `([^`]{1,128})`$/mu.exec(record.content)?.[1];
|
|
110
|
+
const batchId = detailedId ?? contentId;
|
|
111
|
+
if (!batchId || batchId.length > 128) return undefined;
|
|
112
|
+
return { batchId, content: record.content };
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
/** Collapse exact retry duplicates and remove conflicting delivery envelopes. */
|
|
@@ -233,22 +236,6 @@ export function inspectRunSteerSessionEvidence(
|
|
|
233
236
|
});
|
|
234
237
|
}
|
|
235
238
|
|
|
236
|
-
export function inspectRunCompletionBatchSessionEvidence(
|
|
237
|
-
ctx: ExtensionContext,
|
|
238
|
-
batchId: string,
|
|
239
|
-
content: string,
|
|
240
|
-
): SessionEvidence.ActiveSessionEntryEvidence {
|
|
241
|
-
return SessionEvidence.inspectBoundedActiveSessionEntries({
|
|
242
|
-
getEntry: (id) => ctx.sessionManager.getEntry(id),
|
|
243
|
-
leaf: ctx.sessionManager.getLeafEntry(),
|
|
244
|
-
match: (entry) => {
|
|
245
|
-
const batch = completionBatchMessage(entry);
|
|
246
|
-
if (!batch || batch.batchId !== batchId) return undefined;
|
|
247
|
-
return batch.content === content ? "present" : "conflict";
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
239
|
export function registerToolDefinitions(
|
|
253
240
|
pi: ExtensionAPI,
|
|
254
241
|
definitions: Iterable<unknown>,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run delivery lineage.
|
|
3
|
+
* Zones: inherited coordinator root, parent Run generation, delivery-tree filtering
|
|
4
|
+
* Owns durable completion ancestry; excludes delivery persistence and Pi scheduling.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface RunDeliveryParent {
|
|
8
|
+
run: string;
|
|
9
|
+
run_instance_id: string;
|
|
10
|
+
state_dir: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RunDeliveryLineage {
|
|
14
|
+
delivery_owner_id: string;
|
|
15
|
+
delivery_parent?: RunDeliveryParent;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DELIVERY_OWNER_ENV = "PI_ACTORS_DELIVERY_OWNER_ID";
|
|
19
|
+
const PARENT_RUN_ENV = "PI_ACTORS_DELIVERY_PARENT_RUN";
|
|
20
|
+
const PARENT_INSTANCE_ENV = "PI_ACTORS_DELIVERY_PARENT_INSTANCE";
|
|
21
|
+
const PARENT_STATE_DIR_ENV = "PI_ACTORS_DELIVERY_PARENT_STATE_DIR";
|
|
22
|
+
|
|
23
|
+
function nonEmpty(value: string | undefined): string | undefined {
|
|
24
|
+
const text = value?.trim();
|
|
25
|
+
return text || undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function inheritedRunDeliveryLineage(
|
|
29
|
+
ownerId: string | undefined,
|
|
30
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
31
|
+
): RunDeliveryLineage | undefined {
|
|
32
|
+
const localOwner = nonEmpty(ownerId);
|
|
33
|
+
if (!localOwner) return undefined;
|
|
34
|
+
const deliveryOwner = nonEmpty(env[DELIVERY_OWNER_ENV]) ?? localOwner;
|
|
35
|
+
const run = nonEmpty(env[PARENT_RUN_ENV]);
|
|
36
|
+
const runInstanceId = nonEmpty(env[PARENT_INSTANCE_ENV]);
|
|
37
|
+
const stateDir = nonEmpty(env[PARENT_STATE_DIR_ENV]);
|
|
38
|
+
const completeParent = run && runInstanceId && stateDir
|
|
39
|
+
? { run, run_instance_id: runInstanceId, state_dir: stateDir }
|
|
40
|
+
: undefined;
|
|
41
|
+
if ((run || runInstanceId || stateDir) && !completeParent) {
|
|
42
|
+
throw new Error("Inherited Run delivery parent lineage is incomplete");
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
delivery_owner_id: deliveryOwner,
|
|
46
|
+
...(completeParent ? { delivery_parent: completeParent } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function runDeliveryChildEnv(
|
|
51
|
+
lineage: RunDeliveryLineage,
|
|
52
|
+
parent: RunDeliveryParent,
|
|
53
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
54
|
+
): NodeJS.ProcessEnv {
|
|
55
|
+
return {
|
|
56
|
+
...env,
|
|
57
|
+
[DELIVERY_OWNER_ENV]: lineage.delivery_owner_id,
|
|
58
|
+
[PARENT_RUN_ENV]: parent.run,
|
|
59
|
+
[PARENT_INSTANCE_ENV]: parent.run_instance_id,
|
|
60
|
+
[PARENT_STATE_DIR_ENV]: parent.state_dir,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getProcessDeliveryOwnerId(
|
|
65
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
66
|
+
): string | undefined {
|
|
67
|
+
return nonEmpty(env[DELIVERY_OWNER_ENV]);
|
|
68
|
+
}
|
|
@@ -16,6 +16,10 @@ export type RunDeliveryPhase = "pending" | "queued" | "presented";
|
|
|
16
16
|
|
|
17
17
|
export interface RunCompletionBatchMember {
|
|
18
18
|
artifacts?: Record<string, string>;
|
|
19
|
+
parent_run?: string;
|
|
20
|
+
parent_run_instance_id?: string;
|
|
21
|
+
parent_state_dir?: string;
|
|
22
|
+
output?: string;
|
|
19
23
|
run: string;
|
|
20
24
|
run_instance_id: string;
|
|
21
25
|
state_dir: string;
|
|
@@ -109,6 +113,10 @@ export interface RunSteerTransitionInput {
|
|
|
109
113
|
const RUN_DELIVERY_SCHEMA = "run-delivery-v1";
|
|
110
114
|
const MEMBER_FIELDS = new Set([
|
|
111
115
|
"artifacts",
|
|
116
|
+
"parent_run",
|
|
117
|
+
"parent_run_instance_id",
|
|
118
|
+
"parent_state_dir",
|
|
119
|
+
"output",
|
|
112
120
|
"run",
|
|
113
121
|
"run_instance_id",
|
|
114
122
|
"state_dir",
|
|
@@ -216,8 +224,40 @@ function parseMember(value: unknown): RunCompletionBatchMember {
|
|
|
216
224
|
const record = asRecord(value, "Completion batch member");
|
|
217
225
|
assertExactFields(record, MEMBER_FIELDS, "Completion batch member");
|
|
218
226
|
const parsedArtifacts = artifacts(record.artifacts);
|
|
227
|
+
const parentRun = record.parent_run === undefined
|
|
228
|
+
? undefined
|
|
229
|
+
: boundedString(record.parent_run, "Completion batch member parent_run", 120);
|
|
230
|
+
const parentRunInstanceId = record.parent_run_instance_id === undefined
|
|
231
|
+
? undefined
|
|
232
|
+
: boundedString(
|
|
233
|
+
record.parent_run_instance_id,
|
|
234
|
+
"Completion batch member parent_run_instance_id",
|
|
235
|
+
256,
|
|
236
|
+
);
|
|
237
|
+
const parentStateDir = record.parent_state_dir === undefined
|
|
238
|
+
? undefined
|
|
239
|
+
: boundedString(
|
|
240
|
+
record.parent_state_dir,
|
|
241
|
+
"Completion batch member parent_state_dir",
|
|
242
|
+
4_096,
|
|
243
|
+
);
|
|
244
|
+
if (Boolean(parentRun) !== Boolean(parentRunInstanceId) ||
|
|
245
|
+
Boolean(parentRun) !== Boolean(parentStateDir)) {
|
|
246
|
+
throw new Error("Completion batch member parent lineage is incomplete");
|
|
247
|
+
}
|
|
248
|
+
const output = record.output === undefined
|
|
249
|
+
? undefined
|
|
250
|
+
: boundedString(record.output, "Completion batch member output", 4_000);
|
|
219
251
|
return {
|
|
220
252
|
...(parsedArtifacts ? { artifacts: parsedArtifacts } : {}),
|
|
253
|
+
...(output ? { output } : {}),
|
|
254
|
+
...(parentRun
|
|
255
|
+
? {
|
|
256
|
+
parent_run: parentRun,
|
|
257
|
+
parent_run_instance_id: parentRunInstanceId!,
|
|
258
|
+
parent_state_dir: parentStateDir!,
|
|
259
|
+
}
|
|
260
|
+
: {}),
|
|
221
261
|
run: boundedString(record.run, "Completion batch member run", 120),
|
|
222
262
|
run_instance_id: boundedString(
|
|
223
263
|
record.run_instance_id,
|
|
@@ -740,7 +780,75 @@ function compactModelText(value: string, limit: number): string {
|
|
|
740
780
|
return compact.length > limit ? `${compact.slice(0, limit - 1)}…` : compact;
|
|
741
781
|
}
|
|
742
782
|
|
|
743
|
-
function
|
|
783
|
+
function memberIdentity(member: RunCompletionBatchMember): string {
|
|
784
|
+
return `${member.state_dir}\0${member.run_instance_id}`;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function completionDepth(
|
|
788
|
+
member: RunCompletionBatchMember,
|
|
789
|
+
byIdentity: Map<string, RunCompletionBatchMember>,
|
|
790
|
+
): number {
|
|
791
|
+
const visited = new Set<string>();
|
|
792
|
+
let current = member;
|
|
793
|
+
let depth = 0;
|
|
794
|
+
while (current.parent_state_dir && current.parent_run_instance_id) {
|
|
795
|
+
const identity = `${current.parent_state_dir}\0${current.parent_run_instance_id}`;
|
|
796
|
+
if (visited.has(identity)) break;
|
|
797
|
+
visited.add(identity);
|
|
798
|
+
const parent = byIdentity.get(identity);
|
|
799
|
+
if (!parent) break;
|
|
800
|
+
depth += 1;
|
|
801
|
+
current = parent;
|
|
802
|
+
}
|
|
803
|
+
return depth;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function orderCompletionForest(
|
|
807
|
+
members: RunCompletionBatchMember[],
|
|
808
|
+
): RunCompletionBatchMember[] {
|
|
809
|
+
const byIdentity = new Map(members.map((member) => [memberIdentity(member), member]));
|
|
810
|
+
const children = new Map<string, RunCompletionBatchMember[]>();
|
|
811
|
+
const roots: RunCompletionBatchMember[] = [];
|
|
812
|
+
for (const member of members) {
|
|
813
|
+
const parentIdentity = member.parent_state_dir && member.parent_run_instance_id
|
|
814
|
+
? `${member.parent_state_dir}\0${member.parent_run_instance_id}`
|
|
815
|
+
: undefined;
|
|
816
|
+
if (!parentIdentity || !byIdentity.has(parentIdentity)) {
|
|
817
|
+
roots.push(member);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
const siblings = children.get(parentIdentity) ?? [];
|
|
821
|
+
siblings.push(member);
|
|
822
|
+
children.set(parentIdentity, siblings);
|
|
823
|
+
}
|
|
824
|
+
const ordered: RunCompletionBatchMember[] = [];
|
|
825
|
+
const visited = new Set<string>();
|
|
826
|
+
const visit = (member: RunCompletionBatchMember): void => {
|
|
827
|
+
const identity = memberIdentity(member);
|
|
828
|
+
if (visited.has(identity)) return;
|
|
829
|
+
visited.add(identity);
|
|
830
|
+
ordered.push(member);
|
|
831
|
+
for (const child of children.get(identity) ?? []) visit(child);
|
|
832
|
+
};
|
|
833
|
+
for (const root of roots) visit(root);
|
|
834
|
+
for (const member of members) visit(member);
|
|
835
|
+
return ordered;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function formatModelOutput(value: string, indent: string): string {
|
|
839
|
+
return value
|
|
840
|
+
.replaceAll("`", "'")
|
|
841
|
+
.split(/\r?\n/u)
|
|
842
|
+
.map((line) => line.trimEnd())
|
|
843
|
+
.filter((line, index, lines) => line || (index > 0 && index < lines.length - 1))
|
|
844
|
+
.map((line) => `${indent} ${line}`)
|
|
845
|
+
.join("\n");
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function formatCompletionMember(
|
|
849
|
+
member: RunCompletionBatchMember,
|
|
850
|
+
byIdentity: Map<string, RunCompletionBatchMember>,
|
|
851
|
+
): string {
|
|
744
852
|
const summary = compactModelText(member.summary, 200);
|
|
745
853
|
const artifactEntries = Object.entries(member.artifacts ?? {}).slice(0, 4);
|
|
746
854
|
const artifactText = artifactEntries.length === 0
|
|
@@ -748,7 +856,11 @@ function formatCompletionMember(member: RunCompletionBatchMember): string {
|
|
|
748
856
|
: `; artifacts: ${artifactEntries.map(([name, path]) =>
|
|
749
857
|
`${compactModelText(name, 120)}=\`${compactModelText(path, 320)}\``
|
|
750
858
|
).join(", ")}`;
|
|
751
|
-
|
|
859
|
+
const indent = " ".repeat(Math.min(completionDepth(member, byIdentity), 8));
|
|
860
|
+
const output = member.output
|
|
861
|
+
? `\n${indent} Output:\n${formatModelOutput(member.output, indent)}`
|
|
862
|
+
: "";
|
|
863
|
+
return `${indent}- \`${compactModelText(member.run, 120)}\` — \`${member.status}\`: ${summary}${artifactText}${output}`;
|
|
752
864
|
}
|
|
753
865
|
|
|
754
866
|
function formatStatusCounts(members: RunCompletionBatchMember[]): string {
|
|
@@ -772,9 +884,13 @@ export function formatRunCompletionBatchMessage(batch: RunCompletionBatch): stri
|
|
|
772
884
|
`Window: \`${terminalTimes[0]}\` → \`${terminalTimes.at(-1)}\``,
|
|
773
885
|
`Statuses: \`${formatStatusCounts(safe.members)}\``,
|
|
774
886
|
];
|
|
775
|
-
const
|
|
887
|
+
const byIdentity = new Map(safe.members.map((member) => [
|
|
888
|
+
memberIdentity(member),
|
|
889
|
+
member,
|
|
890
|
+
]));
|
|
891
|
+
const candidateRows = orderCompletionForest(safe.members)
|
|
776
892
|
.slice(0, Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS)
|
|
777
|
-
.map(formatCompletionMember);
|
|
893
|
+
.map((member) => formatCompletionMember(member, byIdentity));
|
|
778
894
|
const rows: string[] = [];
|
|
779
895
|
for (const row of candidateRows) {
|
|
780
896
|
const omitted = safe.members.length - rows.length - 1;
|