@llblab/pi-actors 0.52.1 → 0.53.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 +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/lib/async-runs.d.ts +3 -0
- package/dist/lib/async-runs.js +14 -1
- package/dist/lib/command-templates.js +45 -3
- package/dist/lib/extension-runtime.js +1 -1
- package/dist/lib/observability.d.ts +16 -3
- package/dist/lib/observability.js +92 -7
- package/dist/lib/pi.d.ts +0 -1
- package/dist/lib/pi.js +15 -24
- package/dist/lib/run-delivery-lineage.d.ts +17 -0
- package/dist/lib/run-delivery-lineage.js +44 -0
- package/dist/lib/run-delivery.d.ts +4 -0
- package/dist/lib/run-delivery.js +102 -4
- package/dist/lib/run-ui-runtime.js +58 -39
- package/dist/lib/runtime.js +14 -6
- package/dist/skills/actors/SKILL.md +1 -1
- package/docs/async-runs.md +1 -1
- package/docs/coordinator-delivery.md +18 -23
- package/lib/async-runs.ts +18 -1
- package/lib/command-templates.ts +41 -3
- package/lib/extension-runtime.ts +1 -1
- package/lib/observability.ts +119 -5
- package/lib/pi.ts +15 -28
- package/lib/run-delivery-lineage.ts +68 -0
- package/lib/run-delivery.ts +120 -4
- package/lib/run-ui-runtime.ts +69 -44
- package/lib/runtime.ts +17 -6
- package/package.json +1 -1
- package/skills/actors/SKILL.md +1 -1
package/lib/run-ui-runtime.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as Observability from "./observability.ts";
|
|
|
10
10
|
import * as Paths from "./paths.ts";
|
|
11
11
|
import * as Pi from "./pi.ts";
|
|
12
12
|
import * as RunDelivery from "./run-delivery.ts";
|
|
13
|
+
import * as RunDeliveryLineage from "./run-delivery-lineage.ts";
|
|
13
14
|
|
|
14
15
|
export interface RunUiRuntime {
|
|
15
16
|
close(): void;
|
|
@@ -43,12 +44,11 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
43
44
|
let animationInterval: NodeJS.Timeout | undefined;
|
|
44
45
|
let deliveryTimeout: NodeJS.Timeout | undefined;
|
|
45
46
|
let notifyTimeout: NodeJS.Timeout | undefined;
|
|
46
|
-
let recoverQueuedBatch = false;
|
|
47
47
|
let recoverQueuedSteers = false;
|
|
48
48
|
let running = false;
|
|
49
49
|
let lastWatcherDiagnosticId = 0;
|
|
50
50
|
const observation = Observability.createRunUiObservationState();
|
|
51
|
-
const
|
|
51
|
+
const deliveryObservation = Observability.createRunUiObservationState();
|
|
52
52
|
const retirementAttempts = new Set<string>();
|
|
53
53
|
const steerDiagnostics = new Set<string>();
|
|
54
54
|
|
|
@@ -70,9 +70,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
70
70
|
notifyTimeout = undefined;
|
|
71
71
|
if (deliveryTimeout) clearTimeout(deliveryTimeout);
|
|
72
72
|
deliveryTimeout = undefined;
|
|
73
|
-
recoverQueuedBatch = false;
|
|
74
73
|
recoverQueuedSteers = false;
|
|
75
|
-
deliveryRecoveryDiagnostics.clear();
|
|
76
74
|
steerDiagnostics.clear();
|
|
77
75
|
if (animationInterval) clearInterval(animationInterval);
|
|
78
76
|
animationInterval = undefined;
|
|
@@ -228,12 +226,14 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
228
226
|
};
|
|
229
227
|
const admitCompletionTransitions = (
|
|
230
228
|
ownerId: string,
|
|
231
|
-
|
|
229
|
+
snapshot: Observability.RunUiSnapshot,
|
|
232
230
|
): boolean => {
|
|
233
231
|
const existing = journal(ownerId).completion_batch;
|
|
234
232
|
if (existing) return true;
|
|
235
|
-
const members = Observability.collectRunCompletionBatchMembers(
|
|
236
|
-
.
|
|
233
|
+
const members = Observability.collectRunCompletionBatchMembers(
|
|
234
|
+
snapshot.transitions,
|
|
235
|
+
snapshot.summary.runs,
|
|
236
|
+
).slice(0, Limits.RUN_DELIVERY_BATCH_MAX_MEMBERS);
|
|
237
237
|
if (members.length === 0) return false;
|
|
238
238
|
RunDelivery.admitRunCompletionBatch({
|
|
239
239
|
members,
|
|
@@ -244,6 +244,10 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
244
244
|
};
|
|
245
245
|
const isIdle = (ctx: Pi.ExtensionContext): boolean =>
|
|
246
246
|
typeof ctx.isIdle !== "function" || ctx.isIdle();
|
|
247
|
+
const isDeliveryRoot = (ownerId: string): boolean => {
|
|
248
|
+
const inheritedOwner = RunDeliveryLineage.getProcessDeliveryOwnerId();
|
|
249
|
+
return inheritedOwner === undefined || inheritedOwner === ownerId;
|
|
250
|
+
};
|
|
247
251
|
let flushCompletionBatch = (_ctx: Pi.ExtensionContext): boolean => false;
|
|
248
252
|
const flushSteers = (ctx: Pi.ExtensionContext, ownerId: string): boolean => {
|
|
249
253
|
const recovering = recoverQueuedSteers;
|
|
@@ -334,11 +338,25 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
334
338
|
if (!notify) return false;
|
|
335
339
|
const sink = Pi.createNotificationSink(deps.pi, ctx);
|
|
336
340
|
retireCandidateRuns(ctx, snapshot.summary);
|
|
337
|
-
const
|
|
338
|
-
Observability.
|
|
339
|
-
|
|
341
|
+
const deliverySnapshot = isDeliveryRoot(ownerId)
|
|
342
|
+
? Observability.readRunDeliverySnapshot(deliveryObservation, ownerId)
|
|
343
|
+
: undefined;
|
|
344
|
+
const hasCompletionCandidates = deliverySnapshot
|
|
345
|
+
? Observability.collectRunCompletionBatchMembers(
|
|
346
|
+
deliverySnapshot.transitions,
|
|
347
|
+
deliverySnapshot.summary.runs,
|
|
348
|
+
).length > 0
|
|
349
|
+
: false;
|
|
350
|
+
const hasCompletionBatch = isDeliveryRoot(ownerId) &&
|
|
351
|
+
Boolean(journal(ownerId).completion_batch);
|
|
340
352
|
admitSteerEvents(ctx, ownerId, snapshot.attentionEvents);
|
|
341
353
|
Observability.pruneRunUiObservationState(observation, snapshot);
|
|
354
|
+
if (deliverySnapshot) {
|
|
355
|
+
Observability.pruneRunUiObservationState(
|
|
356
|
+
deliveryObservation,
|
|
357
|
+
deliverySnapshot,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
342
360
|
if (!terminalOnly) {
|
|
343
361
|
Observability.deliverRunAttentionNotifications(
|
|
344
362
|
snapshot.attentionEvents.filter((event) =>
|
|
@@ -403,54 +421,48 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
403
421
|
});
|
|
404
422
|
|
|
405
423
|
flushCompletionBatch = (ctx: Pi.ExtensionContext): boolean => {
|
|
406
|
-
if (!running || activeContext !== ctx || deps.getActiveContext() !== ctx) return false;
|
|
407
424
|
const ownerId = activeOwnerId;
|
|
408
|
-
if (!ownerId) return false;
|
|
425
|
+
if (!running || !ownerId) return false;
|
|
426
|
+
try {
|
|
427
|
+
if (Pi.getSessionId(ctx) !== ownerId) return false;
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
409
431
|
if (flushSteers(ctx, ownerId)) return true;
|
|
432
|
+
if (!isDeliveryRoot(ownerId)) return false;
|
|
410
433
|
let batch = journal(ownerId).completion_batch;
|
|
411
434
|
if (!batch) {
|
|
412
|
-
const snapshot = Observability.
|
|
413
|
-
|
|
414
|
-
|
|
435
|
+
const snapshot = Observability.readRunDeliverySnapshot(
|
|
436
|
+
deliveryObservation,
|
|
437
|
+
ownerId,
|
|
438
|
+
);
|
|
439
|
+
admitCompletionTransitions(ownerId, snapshot);
|
|
440
|
+
Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
|
|
415
441
|
batch = journal(ownerId).completion_batch;
|
|
416
442
|
}
|
|
417
443
|
if (!batch) return false;
|
|
418
444
|
if (batch.phase === "presented") {
|
|
419
445
|
finishPresentedBatch(ownerId, batch);
|
|
420
|
-
const snapshot = Observability.
|
|
421
|
-
|
|
422
|
-
|
|
446
|
+
const snapshot = Observability.readRunDeliverySnapshot(
|
|
447
|
+
deliveryObservation,
|
|
448
|
+
ownerId,
|
|
449
|
+
);
|
|
450
|
+
admitCompletionTransitions(ownerId, snapshot);
|
|
451
|
+
Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
|
|
423
452
|
batch = journal(ownerId).completion_batch;
|
|
424
453
|
if (!batch) return false;
|
|
425
454
|
}
|
|
426
455
|
if (!isIdle(ctx)) return true;
|
|
427
456
|
const content = RunDelivery.formatRunCompletionBatchMessage(batch);
|
|
428
457
|
if (batch.phase === "queued") {
|
|
429
|
-
if (!
|
|
430
|
-
recoverQueuedBatch = false;
|
|
431
|
-
const evidence = Pi.inspectRunCompletionBatchSessionEvidence(
|
|
432
|
-
ctx,
|
|
433
|
-
batch.batch_id,
|
|
434
|
-
content,
|
|
435
|
-
);
|
|
436
|
-
if (evidence.status === "present") return true;
|
|
437
|
-
if (evidence.status !== "absent") {
|
|
438
|
-
const diagnosticKey = `${batch.batch_id}:${evidence.status}:${evidence.reason ?? ""}`;
|
|
439
|
-
if (!deliveryRecoveryDiagnostics.has(diagnosticKey)) {
|
|
440
|
-
deliveryRecoveryDiagnostics.add(diagnosticKey);
|
|
441
|
-
ctx.ui.notify(
|
|
442
|
-
`Actor completion recovery is ${evidence.status}: ${evidence.reason ?? "conflicting session evidence"}. Batch ${batch.batch_id} remains queued.`,
|
|
443
|
-
"warning",
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
return true;
|
|
447
|
-
}
|
|
448
|
-
if (!RunDelivery.resetRunCompletionBatchPending({
|
|
458
|
+
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
449
459
|
batchId: batch.batch_id,
|
|
450
460
|
ownerId,
|
|
451
461
|
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
452
462
|
})) return true;
|
|
453
|
-
|
|
463
|
+
const accepted = journal(ownerId).completion_batch;
|
|
464
|
+
if (accepted) finishPresentedBatch(ownerId, accepted);
|
|
465
|
+
return flushCompletionBatch(ctx);
|
|
454
466
|
}
|
|
455
467
|
if (!isIdle(ctx)) return true;
|
|
456
468
|
try {
|
|
@@ -471,7 +483,19 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
471
483
|
})) {
|
|
472
484
|
throw new Error("Completion batch changed before queue acknowledgment");
|
|
473
485
|
}
|
|
474
|
-
|
|
486
|
+
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
487
|
+
batchId: batch.batch_id,
|
|
488
|
+
ownerId,
|
|
489
|
+
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
490
|
+
})) {
|
|
491
|
+
throw new Error("Completion batch changed before durable acceptance");
|
|
492
|
+
}
|
|
493
|
+
const accepted = journal(ownerId).completion_batch;
|
|
494
|
+
if (accepted) finishPresentedBatch(ownerId, accepted);
|
|
495
|
+
ctx.ui.notify(
|
|
496
|
+
`Actor completions ready: ${batch.members.length}`,
|
|
497
|
+
"info",
|
|
498
|
+
);
|
|
475
499
|
return true;
|
|
476
500
|
};
|
|
477
501
|
|
|
@@ -479,11 +503,13 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
479
503
|
close,
|
|
480
504
|
flushCompletionBatch,
|
|
481
505
|
projectContext(messages, ctx) {
|
|
482
|
-
|
|
506
|
+
const ownerId = activeOwnerId;
|
|
507
|
+
if (!running || !ownerId) return messages;
|
|
508
|
+
try {
|
|
509
|
+
if (Pi.getSessionId(ctx) !== ownerId) return messages;
|
|
510
|
+
} catch {
|
|
483
511
|
return messages;
|
|
484
512
|
}
|
|
485
|
-
const ownerId = activeOwnerId;
|
|
486
|
-
if (!ownerId) return messages;
|
|
487
513
|
const steerContext = Pi.dedupeRunSteerContext(messages);
|
|
488
514
|
let contextMessages = steerContext.messages;
|
|
489
515
|
for (const steer of journal(ownerId).steers) {
|
|
@@ -560,7 +586,6 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
560
586
|
close();
|
|
561
587
|
activeContext = ctx;
|
|
562
588
|
activeOwnerId = ownerId;
|
|
563
|
-
recoverQueuedBatch = true;
|
|
564
589
|
recoverQueuedSteers = true;
|
|
565
590
|
running = true;
|
|
566
591
|
try {
|
package/lib/runtime.ts
CHANGED
|
@@ -325,6 +325,7 @@ export function createRecipeToolReloadWatcher(
|
|
|
325
325
|
let rootWatcher: FSWatcher | undefined;
|
|
326
326
|
let parentWatcher: FSWatcher | undefined;
|
|
327
327
|
let failureNotified = false;
|
|
328
|
+
let notifyAfterReload = false;
|
|
328
329
|
const setWatchStatus = (watchStatus: RecipeRegistryStatus["watch_status"]): void =>
|
|
329
330
|
runtime.setWatchStatus?.(watchStatus);
|
|
330
331
|
const close = (): void => {
|
|
@@ -336,6 +337,7 @@ export function createRecipeToolReloadWatcher(
|
|
|
336
337
|
closingParent?.close();
|
|
337
338
|
if (reloadTimeout) clearTimeout(reloadTimeout);
|
|
338
339
|
reloadTimeout = undefined;
|
|
340
|
+
notifyAfterReload = false;
|
|
339
341
|
setWatchStatus("closed");
|
|
340
342
|
};
|
|
341
343
|
const reportCallbackError = (error: unknown): void => {
|
|
@@ -358,14 +360,21 @@ export function createRecipeToolReloadWatcher(
|
|
|
358
360
|
reportCallbackError(error);
|
|
359
361
|
}
|
|
360
362
|
};
|
|
361
|
-
const scheduleReload = (
|
|
363
|
+
const scheduleReload = (
|
|
364
|
+
ctx: RuntimeContext,
|
|
365
|
+
notifyActiveChange = false,
|
|
366
|
+
): void => {
|
|
362
367
|
failureNotified = false;
|
|
368
|
+
notifyAfterReload ||= notifyActiveChange;
|
|
363
369
|
if (reloadTimeout) clearTimeout(reloadTimeout);
|
|
364
370
|
reloadTimeout = setTimeout(() => {
|
|
365
371
|
reloadTimeout = undefined;
|
|
366
372
|
try {
|
|
367
373
|
runtime.loadTools(ctx, deps.getResolutionContext?.());
|
|
368
|
-
|
|
374
|
+
if (notifyAfterReload) {
|
|
375
|
+
ctx.ui.notify("Recipe tools refreshed from ~/.pi/agent/recipes", "info");
|
|
376
|
+
}
|
|
377
|
+
notifyAfterReload = false;
|
|
369
378
|
} catch (error) {
|
|
370
379
|
notifyFailure(ctx);
|
|
371
380
|
reportCallbackError(error);
|
|
@@ -394,7 +403,7 @@ export function createRecipeToolReloadWatcher(
|
|
|
394
403
|
parentWatcher = undefined;
|
|
395
404
|
watcher.close();
|
|
396
405
|
watchRoot(ctx, recipeRoot);
|
|
397
|
-
scheduleReload(ctx);
|
|
406
|
+
scheduleReload(ctx, true);
|
|
398
407
|
});
|
|
399
408
|
parentWatcher = watcher;
|
|
400
409
|
setWatchStatus("watching_parent");
|
|
@@ -415,16 +424,18 @@ export function createRecipeToolReloadWatcher(
|
|
|
415
424
|
return;
|
|
416
425
|
}
|
|
417
426
|
try {
|
|
418
|
-
const watcher = watchPath(recipeRoot, () => {
|
|
427
|
+
const watcher = watchPath(recipeRoot, (_event, changedFile) => {
|
|
419
428
|
if (rootWatcher !== watcher) return;
|
|
420
429
|
if (!pathExists(recipeRoot)) {
|
|
421
430
|
rootWatcher = undefined;
|
|
422
431
|
watcher.close();
|
|
423
|
-
scheduleReload(ctx);
|
|
432
|
+
scheduleReload(ctx, true);
|
|
424
433
|
watchParent(ctx, recipeRoot);
|
|
425
434
|
return;
|
|
426
435
|
}
|
|
427
|
-
|
|
436
|
+
const relativeChange = changedFile ? String(changedFile) : "";
|
|
437
|
+
const firstSegment = relativeChange.split(/[\\/]/u)[0];
|
|
438
|
+
scheduleReload(ctx, Boolean(relativeChange) && firstSegment !== "drafts");
|
|
428
439
|
});
|
|
429
440
|
rootWatcher = watcher;
|
|
430
441
|
setWatchStatus("watching_root");
|
package/package.json
CHANGED
package/skills/actors/SKILL.md
CHANGED
|
@@ -107,7 +107,7 @@ Run = Recipe + Trace + Control
|
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
1. Spawn with the exact logical Recipe identity and caller-owned values.
|
|
110
|
-
2. Retain the returned `run:<id>` and normally wait for
|
|
110
|
+
2. Retain the returned `run:<id>` and normally wait for the root coordinator's settled completion batch instead of polling. Nested actor completions accumulate under their containing top-level Run and arrive in that one tree-compressed batch; descendant sessions do not need notification options.
|
|
111
111
|
3. Inspect `view=trace` when retained observations or attention matter.
|
|
112
112
|
4. Inspect `view=control` before diagnosing service readiness, stale work, or saturation.
|
|
113
113
|
5. Send `message` only for an action declared and consumed by that controlled Recipe.
|