@llblab/pi-actors 0.42.0 → 0.42.2
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 +2 -2
- package/BACKLOG.md +1 -5
- package/CHANGELOG.md +15 -0
- package/README.md +1 -1
- package/dist/lib/async-runs.d.ts +12 -0
- package/dist/lib/async-runs.js +53 -12
- package/dist/lib/command-templates.js +1 -1
- package/dist/lib/inspector-overlay.d.ts +5 -1
- package/dist/lib/inspector-overlay.js +104 -36
- package/dist/lib/inspector.js +1 -1
- package/dist/lib/observability.d.ts +12 -1
- package/dist/lib/observability.js +159 -80
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/runs-control.d.ts +2 -0
- package/dist/lib/runs-control.js +14 -1
- package/dist/lib/runs-ownership.js +17 -3
- package/dist/lib/runs-process.js +4 -3
- package/dist/lib/runs-start.js +1 -0
- package/dist/lib/runs-status.js +3 -0
- package/dist/lib/tools-inspect.js +2 -1
- package/dist/lib/tools-local.js +17 -2
- package/dist/lib/tools-spawn.js +10 -1
- package/dist/scripts/async-runner.mjs +24 -24
- package/dist/scripts/build-dist.mjs +6 -1
- package/dist/scripts/conformance.mjs +6 -1
- package/dist/scripts/recipe-utils.mjs +3 -3
- package/dist/skills/actors/SKILL.md +3 -3
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +3 -3
- package/docs/async-runs.md +10 -4
- package/docs/recipe-library.md +1 -1
- package/docs/tool-registry.md +2 -0
- package/lib/async-runs.ts +72 -12
- package/lib/command-templates.ts +1 -1
- package/lib/inspector-overlay.ts +129 -36
- package/lib/inspector.ts +1 -1
- package/lib/observability.ts +194 -76
- package/lib/prompts.ts +1 -1
- package/lib/runs-control.ts +20 -1
- package/lib/runs-ownership.ts +22 -3
- package/lib/runs-process.ts +4 -3
- package/lib/runs-start.ts +1 -0
- package/lib/runs-status.ts +5 -0
- package/lib/tools-inspect.ts +2 -1
- package/lib/tools-local.ts +21 -2
- package/lib/tools-spawn.ts +14 -1
- package/package.json +4 -3
- package/scripts/async-runner.mjs +24 -24
- package/scripts/build-dist.mjs +6 -1
- package/scripts/conformance.mjs +6 -1
- package/scripts/recipe-utils.mjs +3 -3
- package/skills/actors/SKILL.md +3 -3
- package/skills/swarm/SKILL.md +1 -1
package/AGENTS.md
CHANGED
|
@@ -108,7 +108,7 @@ Pi host
|
|
|
108
108
|
- Preserve node controls: `when`, positive `timeout`, `delay`, bounded `retry`, `failure`, and `recover` cleanup.
|
|
109
109
|
- Persist every async command's complete byte-exact stdout/stderr under command- and retry-specific run-state paths while keeping returned tails bounded and pipeline stdin complete.
|
|
110
110
|
- Keep async run state under `~/.pi/agent/tmp/pi-actors/runs` with injected `{run_id}` and `{state_dir}` values.
|
|
111
|
-
- Preserve event-driven observability with bounded reconciliation: file watchers accelerate durable retrying terminal follow-up notifications, while a conservative terminal-only interval recovers missed watcher activity, rearms degraded watchers, and never replays outbox traffic. Queue coordinator
|
|
111
|
+
- Preserve event-driven observability with bounded reconciliation: file watchers accelerate durable retrying terminal follow-up notifications, while a conservative terminal-only interval recovers missed watcher activity, rearms degraded watchers, and never replays outbox traffic. Queue compact terminal coordinator notices through Pi follow-up delivery rather than steering so current work finishes before async results arrive and host follow-up batching policy can combine concurrent completions; LLM content contains only run id, status, one base path, and relative artifact names, while semantic output/error/correlation stays in non-LLM details and run state. Terminal delivery is at-least-once across the unavoidable send/handled-marker crash window; watch-triggered and periodic delivery share one live-runtime in-flight guard.
|
|
112
112
|
- When a deferred actor result gates the next step, wait for its terminal follow-up. Do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs; inspect early only on operator request, meaningful actor event, or diagnosis of an overdue/stuck run.
|
|
113
113
|
- Do not restore busy-polling examples, duplicate terminal notifications, or duplicate notifications for handled `cancel`, `kill`, or control-stop actions.
|
|
114
114
|
|
|
@@ -142,7 +142,7 @@ Pi host
|
|
|
142
142
|
- Keep tail truncation, full-output temp files, failure formatting, and centralized limits intact.
|
|
143
143
|
- Published docs must not include machine-local absolute paths.
|
|
144
144
|
- Any view scanning run directories must apply coordinator/session ownership filters before exposing summaries or previews.
|
|
145
|
-
- Actor Inspector remains evidence-first; its `Kill` action is available only for a focused owned running run, requires in-overlay confirmation, revalidates exact ownership/status at action time, routes through canonical `control.kill`, and renders bounded success/failure feedback without direct process signaling.
|
|
145
|
+
- Actor Inspector remains evidence-first; its `Kill` action is available only for a focused owned running run, requires in-overlay confirmation, revalidates exact ownership/status at action time, routes through canonical `control.kill`, and renders bounded success/failure feedback without direct process signaling. Keep active key hints in the bottom border as one blue-key/border-accent-description rail joined by border-accent `─`, never as a dedicated body row or bullet-separated footer.
|
|
146
146
|
- Direct branch messages are active inbox queues; guard branch-local append/status rewrites with the branch inbox lock and keep claim/handled/failed transitions tested.
|
|
147
147
|
- Run-state launch and destructive retention require the runtime ownership marker bound to the canonical directory and run id; reject non-run directories, missing/mismatched markers, and symlink aliases rather than trusting `run.json`.
|
|
148
148
|
- Runner lifecycle and destructive process controls require the persisted cross-platform process identity proof (start time, command, and cwd where available), revalidated at authorization and immediately before signaling; dead, mismatched, or unsupported proofs stay distinct and fail closed rather than degrading to pid liveness. On Unix, only process-group `ESRCH` followed by another matching identity proof permits exact-pid fallback. Keep the residual post-read OS PID/PGID reuse window explicit because Node lacks one portable identity-stable group handle.
|
package/BACKLOG.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
# Project Backlog
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
### Windows filesystem fencing verification
|
|
6
|
-
|
|
7
|
-
- [ ] Add Windows CI and native NTFS regressions for consolidation root identity, directory junction/reparse-point substitution, lifecycle locks, and recovery without requiring privileged symbolic-link creation. Keep bigint device/inode capture, document filesystems that report weak or zero file identity, and evaluate a native handle-relative mutation layer only if real Windows evidence justifies expanding beyond Node’s portable process-crash and trusted-state-tree contract.
|
|
3
|
+
No open items.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.42.2: Inspector Key Rail and Terminal Follow-up Context
|
|
6
|
+
|
|
7
|
+
- `[Inspector Key Rail]` Moved every Inspector hotkey hint onto the bottom border, replacing the dedicated two-row footer with a border-connected rail. Blue key labels remain, while descriptions and `─` connectors use the border accent instead of bullet separators; the main viewport cap rises from 16 to 24 rows. Impact: the Inspector gains two content rows without losing keyboard discoverability, and the kill confirmation dialog now shares the same visual grammar.
|
|
8
|
+
- `[Terminal Follow-up Context]` Kept terminal delivery on Pi's `followUp` queue but reduced LLM content to run id, status, one base path, and relative artifact names. Bounded semantic stdout/error payloads, correlation, and adapter transport metadata remain available only in non-LLM details and run state; the launcher writes initial progress before spawning its runner, and runners write terminal progress and review evidence before the `result.json` completion marker. Impact: completed background actors no longer inject raw output or unsolicited workflow prompts into coordinator context, and result readers never see an incomplete terminal state.
|
|
9
|
+
|
|
10
|
+
## 0.42.1: Terminal Delivery and Cross-platform Validation
|
|
11
|
+
|
|
12
|
+
- `[Terminal Delivery]` Added one bounded semantic terminal result with durable launch/tool-call correlation and bounded adapter-provided transport context. Explicit advertised semantic envelopes win; successful accepted reviews that advertise `review.completed` deterministically synthesize it when absent, while failed runs include their terminal error. Watcher and reconciliation delivery share the existing live in-flight dedupe guard, failed sends persist bounded retry evidence without writing the handled marker, and status exposes the latest failure. Impact: coordinators and Telegram-style chat/thread adapters can retain the exact launch/result relationship across detached completion instead of receiving only run-file metadata.
|
|
13
|
+
- `[Inspector Kill Dialog]` Replaced the inline kill prompt with a dedicated responsive confirmation overlay showing the exact run and current status, explicit destructive copy, Cancel-first focus, arrow/Tab selection, Enter activation, and direct Y/N/Escape behavior. Exact owner, generation, and running status still revalidate through canonical control after confirmation, and bounded feedback returns to the Inspector. Impact: destructive actor termination now has an unmistakable, keyboard-predictable safety boundary.
|
|
14
|
+
- `[Cross-platform CI]` Added identical release-validation matrix jobs for Ubuntu, macOS, and Windows with fail-fast disabled, bounded timeouts, pinned Node setup, npm caching, and `npm ci`; dependency audit now runs once in a separate Ubuntu job. Added invariants that keep audit outside platform-neutral validation. Impact: the package has one baseline release path across all supported host filesystems and shells.
|
|
15
|
+
- `[macOS Run Ownership]` Recognized the exact platform-owned `/var` to `/private/var` temporary-root alias without weakening nested-parent or leaf-symlink rejection. Impact: run-state ownership remains fail-closed for caller-controlled aliases while macOS temporary directories pass canonical ownership checks.
|
|
16
|
+
- `[Portable Build and Cleanup]` Invoked local JavaScript entrypoints through Node instead of relying on shell-specific executable resolution, recognized both path separators when resolving run state, converted file URLs with the platform API, made path assertions native, serialized release suites that exercise shared watchers, and cancelled live fixtures before removing their state. Impact: Windows paths and builds remain shell-free while macOS and Windows cleanup no longer races active worker writes.
|
|
17
|
+
- `[Windows Filesystem Fencing]` Added native-only NTFS regressions for bigint consolidation-root identity, unprivileged directory-junction alias locking, lifecycle locking, draft-root reparse substitution, and trusted-root recovery replacement. Documented weak/zero device-inode filesystems and retained the portable realpath/root-identity/CAS contract without preemptively introducing a native handle-relative mutation layer. Impact: the Windows runner now owns concrete evidence for the deeper filesystem boundary rather than inheriting Unix symlink assumptions.
|
|
18
|
+
- `[Cross-platform Validation Evidence]` Completed the release-validation matrix on Ubuntu, macOS, and Windows in GitHub Actions run `30322093465`, including native NTFS bigint root identity, directory-junction mutation/lifecycle locking, draft-root junction substitution, and trusted-root replacement recovery regressions. The failures found while establishing the matrix were portable path, command, process-tree, temporary-root alias, line-ending, and fixture-lifecycle gaps; no residual trusted-state-tree substitution window was reproduced, so a native handle-relative mutation layer is not warranted. Impact: the release boundary now has direct platform evidence for the portable process-crash and trusted-state-tree contract.
|
|
19
|
+
|
|
5
20
|
## 0.42.0: Automatic Recipe Evolution and Recipe-first Inspector
|
|
6
21
|
|
|
7
22
|
- `Recipe-first Inspector`: The Inspector now opens on a bounded, redacted `Recipe` document for the selected owned run, showing authored identity, resolved launch template and values, composition, policy, mailbox, and artifacts without following mutable external recipe paths. `Messages` and `Turns` remain adjacent tabs; Turns use one scrollable detail level without duplicated recipe context, and documents/timelines/details support arrows plus PageUp/PageDown. Nested values render as compact indented text with label-first, separator-aware wrapping and consistent lowercase hints. Recipe documents retain ↑-at-top/← navigation to tabs, and ← now also returns a scrolled Messages or Turns list directly to its tabs without resetting the selected row. Turns retain one cached evidence snapshot while list/detail navigation remains active, eliminating repeated full session-file parsing on every arrow key while preserving periodic refresh when focus returns to higher-level controls.
|
package/README.md
CHANGED
|
@@ -293,7 +293,7 @@ Packaged recipes are building blocks. Use `spawn file=<recipe>` for maintained p
|
|
|
293
293
|
| A useful output that should survive context compression | Artifacts |
|
|
294
294
|
| A repeated local workflow | Recipe/tool memory |
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
Terminal completion queues a minimal follow-up with run id, status, one base path, and relative artifact names only. Bounded semantic output, launch/tool-call correlation, and optional transport context remain in non-LLM follow-up details and run state, so adapters retain the exact launch/result relationship without injecting actor output into coordinator context. Inspect the run before deciding whether a successful pattern deserves recipe persistence; never auto-save without the operator's confirmation.
|
|
297
297
|
|
|
298
298
|
## Platform support
|
|
299
299
|
|
package/dist/lib/async-runs.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface AsyncRunControlEndpoint {
|
|
|
18
18
|
path: string;
|
|
19
19
|
type: "fifo" | "mailbox" | "named-pipe";
|
|
20
20
|
}
|
|
21
|
+
export declare function normalizeRunTransportContext(value: unknown): Record<string, string | number | boolean> | undefined;
|
|
21
22
|
export interface AsyncRunStartParams {
|
|
22
23
|
async?: boolean;
|
|
23
24
|
control?: AsyncRunControlEndpoint;
|
|
@@ -26,6 +27,10 @@ export interface AsyncRunStartParams {
|
|
|
26
27
|
lifecycleHooks?: {
|
|
27
28
|
onLockContention?(): void;
|
|
28
29
|
};
|
|
30
|
+
launch_correlation?: {
|
|
31
|
+
correlation_id?: string;
|
|
32
|
+
tool_call_id?: string;
|
|
33
|
+
};
|
|
29
34
|
name?: string;
|
|
30
35
|
ownerId?: string;
|
|
31
36
|
run_id?: string;
|
|
@@ -50,6 +55,7 @@ export interface AsyncRunStartParams {
|
|
|
50
55
|
retry?: number | string;
|
|
51
56
|
failure?: CommandTemplateFailureScope;
|
|
52
57
|
recover?: CommandTemplateValue;
|
|
58
|
+
transport_context?: Record<string, unknown>;
|
|
53
59
|
repeat?: number;
|
|
54
60
|
values?: Record<string, unknown>;
|
|
55
61
|
policy_values?: Record<string, unknown>;
|
|
@@ -62,6 +68,10 @@ export interface AsyncRunMeta {
|
|
|
62
68
|
createdAt: string;
|
|
63
69
|
cwd: string;
|
|
64
70
|
launch_source?: AsyncRunLaunchSource;
|
|
71
|
+
launch_correlation?: {
|
|
72
|
+
correlation_id?: string;
|
|
73
|
+
tool_call_id?: string;
|
|
74
|
+
};
|
|
65
75
|
ownerId?: string;
|
|
66
76
|
pid: number;
|
|
67
77
|
recipe?: string;
|
|
@@ -81,6 +91,7 @@ export interface AsyncRunMeta {
|
|
|
81
91
|
process_identity?: RunProcessIdentity;
|
|
82
92
|
recipe_context_records?: RecipesReferences.TemplateRecipeContextRecord[];
|
|
83
93
|
retire_when?: "children_terminal";
|
|
94
|
+
transport_context?: Record<string, unknown>;
|
|
84
95
|
}
|
|
85
96
|
export { safeRunId } from "./runs-identity.ts";
|
|
86
97
|
export { resolveArtifactManifest } from "./runs-artifacts.ts";
|
|
@@ -136,6 +147,7 @@ export interface RunControlExpectation {
|
|
|
136
147
|
runInstanceId?: string;
|
|
137
148
|
}
|
|
138
149
|
export declare function markRunTerminalNotificationHandled(stateDir: string, status: string): void;
|
|
150
|
+
export declare function recordRunTerminalDeliveryFailure(stateDir: string, status: string, error: unknown): void;
|
|
139
151
|
export declare function cancelRun(runOrDir: string, expected?: RunControlExpectation): Record<string, unknown>;
|
|
140
152
|
export declare function archiveRun(runOrDir: string): Record<string, unknown>;
|
|
141
153
|
export declare function pruneRun(runOrDir: string, options?: {
|
package/dist/lib/async-runs.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { closeSync, existsSync, mkdirSync, openSync, readdirSync, statSync, writeFileSync, } from "node:fs";
|
|
8
|
-
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
8
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { writeJsonAtomic } from "./file-state.js";
|
|
11
11
|
import { CURRENT_MODEL_VALUE_KEY, CURRENT_THINKING_VALUE_KEY, describeCurrentPolicyProvenance, } from "./model-context.js";
|
|
@@ -27,6 +27,27 @@ import { deliverRunMessage, } from "./runs-messages.js";
|
|
|
27
27
|
import { buildRunStatus, tailFile, tailLines, } from "./runs-status.js";
|
|
28
28
|
import { readJsonFileResilient } from "./state-readers.js";
|
|
29
29
|
const RUNNER_IDENTITY_GRACE_MS = 5000;
|
|
30
|
+
export function normalizeRunTransportContext(value) {
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
32
|
+
return undefined;
|
|
33
|
+
const normalized = {};
|
|
34
|
+
for (const [key, item] of Object.entries(value).slice(0, 16)) {
|
|
35
|
+
const safeKey = key.trim().slice(0, 64);
|
|
36
|
+
if (!safeKey)
|
|
37
|
+
continue;
|
|
38
|
+
if (typeof item === "string") {
|
|
39
|
+
normalized[safeKey] = item.trim().slice(0, 256);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (typeof item === "number" && Number.isFinite(item)) {
|
|
43
|
+
normalized[safeKey] = item;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (typeof item === "boolean")
|
|
47
|
+
normalized[safeKey] = item;
|
|
48
|
+
}
|
|
49
|
+
return Object.keys(normalized).length ? normalized : undefined;
|
|
50
|
+
}
|
|
30
51
|
const DEFAULT_STATE_ROOT = Paths.getRunStateRoot();
|
|
31
52
|
const DEFAULT_RECIPE_ROOT = Paths.getRecipeRoot();
|
|
32
53
|
function packageRoot() {
|
|
@@ -91,7 +112,8 @@ function resolveRecipeFile(file) {
|
|
|
91
112
|
function isMutableUsageRecipeFile(file) {
|
|
92
113
|
const userRoot = resolve(DEFAULT_RECIPE_ROOT);
|
|
93
114
|
const resolved = resolve(file);
|
|
94
|
-
|
|
115
|
+
const relation = relative(userRoot, resolved);
|
|
116
|
+
return relation !== "" && !relation.startsWith("..") && !isAbsolute(relation);
|
|
95
117
|
}
|
|
96
118
|
function readRecipeFile(file) {
|
|
97
119
|
const path = resolveRecipeFile(file);
|
|
@@ -260,6 +282,7 @@ export function startRun(params, cwd) {
|
|
|
260
282
|
...(startParams.defaults || {}),
|
|
261
283
|
...values,
|
|
262
284
|
};
|
|
285
|
+
const transportContext = normalizeRunTransportContext(startParams.transport_context);
|
|
263
286
|
const artifacts = resolveArtifactPaths(startParams.artifacts, outputValues);
|
|
264
287
|
const meta = {
|
|
265
288
|
argv: [process.execPath, ...argv],
|
|
@@ -268,6 +291,8 @@ export function startRun(params, cwd) {
|
|
|
268
291
|
...(startParams.launch_source
|
|
269
292
|
? { launch_source: startParams.launch_source }
|
|
270
293
|
: {}),
|
|
294
|
+
...(startParams.launch_correlation
|
|
295
|
+
? { launch_correlation: startParams.launch_correlation } : {}),
|
|
271
296
|
...(startParams.ownerId ? { ownerId: startParams.ownerId } : {}),
|
|
272
297
|
pid: 0,
|
|
273
298
|
...(recipe ? { recipe } : {}),
|
|
@@ -292,8 +317,17 @@ export function startRun(params, cwd) {
|
|
|
292
317
|
...(startParams.retire_when === "children_terminal"
|
|
293
318
|
? { retire_when: "children_terminal" }
|
|
294
319
|
: {}),
|
|
320
|
+
...(transportContext
|
|
321
|
+
? { transport_context: transportContext } : {}),
|
|
295
322
|
};
|
|
296
323
|
writeJsonAtomic(join(stateDir, "run.json"), meta);
|
|
324
|
+
writeJsonAtomic(join(stateDir, "progress.json"), {
|
|
325
|
+
completed: 0,
|
|
326
|
+
failures: [],
|
|
327
|
+
model_policy: modelPolicy,
|
|
328
|
+
phase: "starting",
|
|
329
|
+
updatedAt: new Date().toISOString(),
|
|
330
|
+
});
|
|
297
331
|
const child = spawn(process.execPath, argv, {
|
|
298
332
|
cwd,
|
|
299
333
|
detached: true,
|
|
@@ -306,13 +340,6 @@ export function startRun(params, cwd) {
|
|
|
306
340
|
if (processIdentity)
|
|
307
341
|
meta.process_identity = processIdentity;
|
|
308
342
|
writeJsonAtomic(join(stateDir, "run.json"), meta);
|
|
309
|
-
writeJsonAtomic(join(stateDir, "progress.json"), {
|
|
310
|
-
completed: 0,
|
|
311
|
-
failures: [],
|
|
312
|
-
model_policy: modelPolicy,
|
|
313
|
-
phase: "starting",
|
|
314
|
-
updatedAt: new Date().toISOString(),
|
|
315
|
-
});
|
|
316
343
|
writeFileSync(join(stateDir, "events.jsonl"), `${JSON.stringify({ event: "run.start", run, run_instance_id: meta.run_instance_id, pid: meta.pid, ts: new Date().toISOString() })}\n`, { flag: "a" });
|
|
317
344
|
child.unref();
|
|
318
345
|
return meta;
|
|
@@ -323,7 +350,7 @@ export function startRun(params, cwd) {
|
|
|
323
350
|
}
|
|
324
351
|
export { parseRunOutboxEventLine } from "./runs-outbox.js";
|
|
325
352
|
function resolveRunStateDir(runOrDir) {
|
|
326
|
-
return resolve(
|
|
353
|
+
return resolve(/[\\/]/u.test(runOrDir)
|
|
327
354
|
? runOrDir
|
|
328
355
|
: join(DEFAULT_STATE_ROOT, safeRunId(runOrDir)));
|
|
329
356
|
}
|
|
@@ -515,11 +542,11 @@ function finalizeInterruptedReviewEvidence(stateDir, phase, signal) {
|
|
|
515
542
|
return {
|
|
516
543
|
attempt: index + 1,
|
|
517
544
|
stdout: {
|
|
518
|
-
path: relative(stateDir, stdoutFile),
|
|
545
|
+
path: relative(stateDir, stdoutFile).replaceAll("\\", "/"),
|
|
519
546
|
bytes: existsSync(stdoutFile) ? statSync(stdoutFile).size : 0,
|
|
520
547
|
},
|
|
521
548
|
stderr: {
|
|
522
|
-
path: relative(stateDir, stderrFile),
|
|
549
|
+
path: relative(stateDir, stderrFile).replaceAll("\\", "/"),
|
|
523
550
|
bytes: existsSync(stderrFile) ? statSync(stderrFile).size : 0,
|
|
524
551
|
},
|
|
525
552
|
};
|
|
@@ -606,6 +633,20 @@ export function markRunTerminalNotificationHandled(stateDir, status) {
|
|
|
606
633
|
status,
|
|
607
634
|
});
|
|
608
635
|
}
|
|
636
|
+
export function recordRunTerminalDeliveryFailure(stateDir, status, error) {
|
|
637
|
+
const path = join(stateDir, "terminal-delivery-failure.json");
|
|
638
|
+
const previous = readJson(path);
|
|
639
|
+
const message = (error instanceof Error ? error.message : String(error))
|
|
640
|
+
.replaceAll(/\s+/g, " ")
|
|
641
|
+
.trim()
|
|
642
|
+
.slice(0, 500);
|
|
643
|
+
writeJsonAtomic(path, {
|
|
644
|
+
attempts: Math.max(0, Number(previous?.attempts ?? 0)) + 1,
|
|
645
|
+
error: message || "unknown delivery failure",
|
|
646
|
+
status,
|
|
647
|
+
ts: new Date().toISOString(),
|
|
648
|
+
});
|
|
649
|
+
}
|
|
609
650
|
export function cancelRun(runOrDir, expected = {}) {
|
|
610
651
|
const result = stopRun(runOrDir, "SIGTERM", "run.cancel", expected);
|
|
611
652
|
return Object.hasOwn(result, "stopped")
|
|
@@ -344,7 +344,7 @@ export function splitCommandTemplate(input) {
|
|
|
344
344
|
let active = false;
|
|
345
345
|
for (const char of input) {
|
|
346
346
|
if (escaped) {
|
|
347
|
-
current += char
|
|
347
|
+
current += /[\s'"\\]/u.test(char) ? char : `\\${char}`;
|
|
348
348
|
escaped = false;
|
|
349
349
|
active = true;
|
|
350
350
|
continue;
|
|
@@ -40,6 +40,7 @@ export declare class ActorInspectorOverlay {
|
|
|
40
40
|
private detailTurn?;
|
|
41
41
|
private feedback?;
|
|
42
42
|
private killConfirmation?;
|
|
43
|
+
private killDialogChoice;
|
|
43
44
|
private readonly readKeys;
|
|
44
45
|
private focus;
|
|
45
46
|
private filterControlIndex;
|
|
@@ -62,8 +63,9 @@ export declare class ActorInspectorOverlay {
|
|
|
62
63
|
private selectRun;
|
|
63
64
|
private cycleRun;
|
|
64
65
|
private requestKill;
|
|
66
|
+
private cancelKill;
|
|
65
67
|
private confirmKill;
|
|
66
|
-
private
|
|
68
|
+
private renderKillDialog;
|
|
67
69
|
private listItemCount;
|
|
68
70
|
private selectorAnchor;
|
|
69
71
|
private renderKeyHints;
|
|
@@ -102,11 +104,13 @@ export declare class ActorInspectorOverlay {
|
|
|
102
104
|
private openDetail;
|
|
103
105
|
private backDetail;
|
|
104
106
|
private closeDetail;
|
|
107
|
+
private footerBorder;
|
|
105
108
|
private border;
|
|
106
109
|
private stripeBackground;
|
|
107
110
|
private stripedRow;
|
|
108
111
|
private row;
|
|
109
112
|
private takeVisiblePrefix;
|
|
110
113
|
private dropVisiblePrefix;
|
|
114
|
+
private center;
|
|
111
115
|
private fit;
|
|
112
116
|
}
|
|
@@ -33,6 +33,7 @@ export class ActorInspectorOverlay {
|
|
|
33
33
|
detailTurn;
|
|
34
34
|
feedback;
|
|
35
35
|
killConfirmation;
|
|
36
|
+
killDialogChoice = "cancel";
|
|
36
37
|
readKeys = new Set();
|
|
37
38
|
focus = "tabs";
|
|
38
39
|
filterControlIndex = 0;
|
|
@@ -67,13 +68,25 @@ export class ActorInspectorOverlay {
|
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
70
|
if (this.killConfirmation) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.
|
|
71
|
+
const key = data.toLowerCase();
|
|
72
|
+
if (matchesKey(data, "escape") || key === "n") {
|
|
73
|
+
this.cancelKill();
|
|
73
74
|
}
|
|
74
|
-
else if (matchesKey(data, "
|
|
75
|
+
else if (matchesKey(data, "left") ||
|
|
76
|
+
matchesKey(data, "right") ||
|
|
77
|
+
matchesKey(data, "tab")) {
|
|
78
|
+
this.killDialogChoice =
|
|
79
|
+
this.killDialogChoice === "cancel" ? "kill" : "cancel";
|
|
80
|
+
}
|
|
81
|
+
else if (key === "y") {
|
|
75
82
|
this.confirmKill();
|
|
76
83
|
}
|
|
84
|
+
else if (matchesKey(data, "return")) {
|
|
85
|
+
if (this.killDialogChoice === "kill")
|
|
86
|
+
this.confirmKill();
|
|
87
|
+
else
|
|
88
|
+
this.cancelKill();
|
|
89
|
+
}
|
|
77
90
|
this.tui.requestRender();
|
|
78
91
|
return;
|
|
79
92
|
}
|
|
@@ -200,6 +213,8 @@ export class ActorInspectorOverlay {
|
|
|
200
213
|
this.ensureSelectedRun(this.runs());
|
|
201
214
|
const safeWidth = Math.max(24, width);
|
|
202
215
|
const innerWidth = Math.max(1, safeWidth - 2);
|
|
216
|
+
if (this.killConfirmation)
|
|
217
|
+
return this.renderKillDialog(safeWidth, innerWidth);
|
|
203
218
|
const lines = [];
|
|
204
219
|
lines.push(this.border("╭", " Actor Inspector ", "╮", innerWidth));
|
|
205
220
|
lines.push(this.row(this.renderRunControl(), innerWidth));
|
|
@@ -224,18 +239,16 @@ export class ActorInspectorOverlay {
|
|
|
224
239
|
if (selectorTop) {
|
|
225
240
|
const leadingBorder = "─".repeat(selectorAnchor);
|
|
226
241
|
const remainingBorder = "─".repeat(Math.max(0, innerWidth - visibleWidth(selectorTop) - selectorAnchor));
|
|
227
|
-
lines.push(`${this.theme.fg("
|
|
242
|
+
lines.push(`${this.theme.fg("borderAccent", `├${leadingBorder}`)}${selectorTop}${this.theme.fg("borderAccent", `${remainingBorder}┤`)}`);
|
|
228
243
|
}
|
|
229
244
|
else
|
|
230
245
|
lines.push(this.border("├", "", "┤", innerWidth));
|
|
231
246
|
this.contentStripeIndices = [];
|
|
232
|
-
const baseContent = this.
|
|
233
|
-
? this.
|
|
234
|
-
: this.
|
|
235
|
-
? this.
|
|
236
|
-
: this.
|
|
237
|
-
? this.renderTimeline(innerWidth)
|
|
238
|
-
: this.renderRunSelector(innerWidth);
|
|
247
|
+
const baseContent = this.detailOpen
|
|
248
|
+
? this.renderDetail(innerWidth)
|
|
249
|
+
: this.selectedRun
|
|
250
|
+
? this.renderTimeline(innerWidth)
|
|
251
|
+
: this.renderRunSelector(innerWidth);
|
|
239
252
|
const content = this.feedback && !this.killConfirmation
|
|
240
253
|
? [
|
|
241
254
|
this.theme.fg(this.feedback.ok
|
|
@@ -267,9 +280,7 @@ export class ActorInspectorOverlay {
|
|
|
267
280
|
const preservedBase = this.stripeBackground(this.fit(this.dropVisiblePrefix(base, popupWidth + popupAnchor), baseWidth), stripeIndex);
|
|
268
281
|
lines.push(this.row(`${leadingBase}${visiblePopup}${preservedBase}`, innerWidth, true));
|
|
269
282
|
}
|
|
270
|
-
lines.push(this.
|
|
271
|
-
lines.push(this.row(this.renderKeyHints(), innerWidth));
|
|
272
|
-
lines.push(this.border("╰", "", "╯", innerWidth));
|
|
283
|
+
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
273
284
|
return lines;
|
|
274
285
|
}
|
|
275
286
|
invalidate() { }
|
|
@@ -285,7 +296,7 @@ export class ActorInspectorOverlay {
|
|
|
285
296
|
}
|
|
286
297
|
contentViewportRows() {
|
|
287
298
|
const overlayRows = Math.floor(this.tui.terminal.rows * 0.94);
|
|
288
|
-
return Math.max(4, Math.min(
|
|
299
|
+
return Math.max(4, Math.min(24, overlayRows - 5));
|
|
289
300
|
}
|
|
290
301
|
selectRun(run, index) {
|
|
291
302
|
this.selectedRun = run;
|
|
@@ -337,11 +348,19 @@ export class ActorInspectorOverlay {
|
|
|
337
348
|
this.killConfirmation = {
|
|
338
349
|
run: run.run,
|
|
339
350
|
runInstanceId: run.runInstanceId,
|
|
351
|
+
status: run.status,
|
|
340
352
|
};
|
|
353
|
+
this.killDialogChoice = "cancel";
|
|
354
|
+
}
|
|
355
|
+
cancelKill() {
|
|
356
|
+
this.feedback = { ok: false, message: "Kill cancelled." };
|
|
357
|
+
this.killConfirmation = undefined;
|
|
358
|
+
this.killDialogChoice = "cancel";
|
|
341
359
|
}
|
|
342
360
|
confirmKill() {
|
|
343
361
|
const confirmation = this.killConfirmation;
|
|
344
362
|
this.killConfirmation = undefined;
|
|
363
|
+
this.killDialogChoice = "cancel";
|
|
345
364
|
if (!confirmation || !this.killRun) {
|
|
346
365
|
this.feedback = { ok: false, message: "Kill unavailable in this Inspector." };
|
|
347
366
|
return;
|
|
@@ -357,11 +376,41 @@ export class ActorInspectorOverlay {
|
|
|
357
376
|
};
|
|
358
377
|
}
|
|
359
378
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
379
|
+
renderKillDialog(_width, innerWidth) {
|
|
380
|
+
const confirmation = this.killConfirmation;
|
|
381
|
+
const totalRows = this.contentViewportRows() + 5;
|
|
382
|
+
const cancel = this.killDialogChoice === "cancel"
|
|
383
|
+
? this.theme.bg("selectedBg", this.theme.fg("accent", " Cancel "))
|
|
384
|
+
: this.theme.fg("muted", " Cancel ");
|
|
385
|
+
const kill = this.killDialogChoice === "kill"
|
|
386
|
+
? this.theme.bg("selectedBg", this.theme.fg("error", " Kill actor "))
|
|
387
|
+
: this.theme.fg("error", " Kill actor ");
|
|
388
|
+
const body = [
|
|
389
|
+
"",
|
|
390
|
+
this.theme.fg("error", "Kill this running actor?"),
|
|
391
|
+
"",
|
|
392
|
+
`${this.theme.fg("muted", "Run:")} ${this.theme.fg("accent", `run:${confirmation.run}`)}`,
|
|
393
|
+
`${this.theme.fg("muted", "Current status:")} ${this.theme.fg("warning", confirmation.status)}`,
|
|
394
|
+
"",
|
|
395
|
+
this.theme.fg("text", "This sends canonical control.kill."),
|
|
396
|
+
this.theme.fg("error", "The action is destructive and cannot be undone."),
|
|
397
|
+
"",
|
|
398
|
+
`${cancel} ${kill}`,
|
|
399
|
+
"",
|
|
364
400
|
];
|
|
401
|
+
const lines = [this.border("╭", " Confirm Actor Kill ", "╮", innerWidth)];
|
|
402
|
+
const availableRows = Math.max(1, totalRows - 2);
|
|
403
|
+
const topPadding = Math.max(0, Math.floor((availableRows - body.length) / 2));
|
|
404
|
+
const dialogRows = [
|
|
405
|
+
...Array.from({ length: topPadding }, () => ""),
|
|
406
|
+
...body,
|
|
407
|
+
].slice(0, availableRows);
|
|
408
|
+
while (dialogRows.length < availableRows)
|
|
409
|
+
dialogRows.push("");
|
|
410
|
+
for (const line of dialogRows)
|
|
411
|
+
lines.push(this.row(this.center(line, innerWidth), innerWidth));
|
|
412
|
+
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
413
|
+
return lines;
|
|
365
414
|
}
|
|
366
415
|
listItemCount() {
|
|
367
416
|
if (this.tab === "recipe")
|
|
@@ -384,27 +433,34 @@ export class ActorInspectorOverlay {
|
|
|
384
433
|
return Math.max(0, tabs.indexOf(label) - 2);
|
|
385
434
|
}
|
|
386
435
|
renderKeyHints() {
|
|
387
|
-
const hint = (keys, description) => `${this.theme.fg("accent", keys)}${this.theme.fg("
|
|
436
|
+
const hint = (keys, description) => `${this.theme.fg("accent", keys)}${this.theme.fg("borderAccent", ` ${description}`)}`;
|
|
437
|
+
const divider = this.theme.fg("borderAccent", " ─ ");
|
|
438
|
+
const hints = (...items) => items.map(([keys, description]) => hint(keys, description)).join(divider);
|
|
388
439
|
if (this.killConfirmation)
|
|
389
|
-
return
|
|
440
|
+
return hints(["←→/tab", "choose"], ["enter/y", "confirm"], ["esc/n", "cancel"]);
|
|
390
441
|
if (this.focus === "select")
|
|
391
442
|
return this.menuLevel === "value"
|
|
392
|
-
?
|
|
393
|
-
:
|
|
443
|
+
? hints(["↑↓", "option"], ["enter", "apply"], ["←/esc", "back"])
|
|
444
|
+
: hints(["↑↓", "option"], ["→/enter", "open"], ["←/esc", "back"]);
|
|
394
445
|
if (this.focus === "recipe")
|
|
395
|
-
return
|
|
446
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["↑ at top", "tabs"], ["←/esc", "tabs"]);
|
|
396
447
|
if (this.focus === "detail")
|
|
397
|
-
return
|
|
448
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
|
|
398
449
|
if (this.focus === "list")
|
|
399
|
-
return
|
|
450
|
+
return hints(["↑↓/pgup/pgdn", "row"], ["→/enter", "open"], ["←", "tabs"], ["esc", "close"]);
|
|
400
451
|
if (this.focus === "runs") {
|
|
452
|
+
const items = [
|
|
453
|
+
["←→", "run"],
|
|
454
|
+
["↓", "tabs"],
|
|
455
|
+
["enter", "list"],
|
|
456
|
+
];
|
|
401
457
|
const run = this.runs().find((item) => item.run === this.selectedRun);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return
|
|
458
|
+
if (this.killRun && run?.status === "running" && run.runInstanceId)
|
|
459
|
+
items.push(["k", "kill"]);
|
|
460
|
+
items.push(["esc", "close"]);
|
|
461
|
+
return hints(...items);
|
|
406
462
|
}
|
|
407
|
-
return
|
|
463
|
+
return hints(["←→", "navigate"], ["↑↓", "change row"], ["enter", "select"], ["esc", "close"]);
|
|
408
464
|
}
|
|
409
465
|
renderRunControl() {
|
|
410
466
|
const runs = this.runs();
|
|
@@ -962,7 +1018,7 @@ export class ActorInspectorOverlay {
|
|
|
962
1018
|
const hiddenAbove = start > 0;
|
|
963
1019
|
const hiddenBelow = start + visibleOptions.length < options.length;
|
|
964
1020
|
const contentWidth = Math.max(4, ...options.map((option) => visibleWidth(option) + 4));
|
|
965
|
-
const border = (left, right, marker = "") => this.theme.fg("
|
|
1021
|
+
const border = (left, right, marker = "") => this.theme.fg("borderAccent", `${omitLeftBorder ? "" : left}${marker}${"─".repeat(Math.max(0, contentWidth - visibleWidth(marker)))}${omitRightBorder ? "" : right}`);
|
|
966
1022
|
return [
|
|
967
1023
|
border("╭", "╮", hiddenAbove ? "↑" : ""),
|
|
968
1024
|
...visibleOptions.map((option, localIndex) => {
|
|
@@ -975,7 +1031,7 @@ export class ActorInspectorOverlay {
|
|
|
975
1031
|
const styled = focused || index === parentIndex
|
|
976
1032
|
? this.theme.bg("selectedBg", colored)
|
|
977
1033
|
: colored;
|
|
978
|
-
return `${omitLeftBorder ? "" : this.theme.fg("
|
|
1034
|
+
return `${omitLeftBorder ? "" : this.theme.fg("borderAccent", "│")}${styled}${omitRightBorder ? "" : this.theme.fg("borderAccent", "│")}`;
|
|
979
1035
|
}),
|
|
980
1036
|
border("╰", "╯", hiddenBelow ? "↓" : ""),
|
|
981
1037
|
];
|
|
@@ -1017,10 +1073,17 @@ export class ActorInspectorOverlay {
|
|
|
1017
1073
|
this.detailScroll = 0;
|
|
1018
1074
|
this.focus = "list";
|
|
1019
1075
|
}
|
|
1076
|
+
footerBorder(hints, width) {
|
|
1077
|
+
const prefix = " ";
|
|
1078
|
+
const suffix = " ─";
|
|
1079
|
+
const content = truncateToWidth(hints, Math.max(0, width - visibleWidth(prefix) - visibleWidth(suffix)), "");
|
|
1080
|
+
const fill = "─".repeat(Math.max(0, width - visibleWidth(prefix) - visibleWidth(suffix) - visibleWidth(content)));
|
|
1081
|
+
return `${this.theme.fg("borderAccent", `╰${prefix}`)}${content}${this.theme.fg("borderAccent", `${suffix}${fill}╯`)}`;
|
|
1082
|
+
}
|
|
1020
1083
|
border(left, title, right, width) {
|
|
1021
1084
|
const titleText = truncateToWidth(title, width, "");
|
|
1022
1085
|
const fill = "─".repeat(Math.max(0, width - visibleWidth(titleText)));
|
|
1023
|
-
return this.theme.fg("
|
|
1086
|
+
return this.theme.fg("borderAccent", `${left}${titleText}${fill}${right}`);
|
|
1024
1087
|
}
|
|
1025
1088
|
stripeBackground(content, index) {
|
|
1026
1089
|
if (index % 2 === 0)
|
|
@@ -1035,7 +1098,7 @@ export class ActorInspectorOverlay {
|
|
|
1035
1098
|
}
|
|
1036
1099
|
row(content, width, fitted = false) {
|
|
1037
1100
|
const body = fitted ? content : this.fit(content, width);
|
|
1038
|
-
return `${this.theme.fg("
|
|
1101
|
+
return `${this.theme.fg("borderAccent", "│")}${body}${this.theme.fg("borderAccent", "│")}`;
|
|
1039
1102
|
}
|
|
1040
1103
|
takeVisiblePrefix(content, width) {
|
|
1041
1104
|
const plain = stripVTControlCharacters(content);
|
|
@@ -1062,6 +1125,11 @@ export class ActorInspectorOverlay {
|
|
|
1062
1125
|
}
|
|
1063
1126
|
return plain.slice(index);
|
|
1064
1127
|
}
|
|
1128
|
+
center(content, width) {
|
|
1129
|
+
const contentWidth = visibleWidth(content);
|
|
1130
|
+
const leading = Math.max(0, Math.floor((width - contentWidth) / 2));
|
|
1131
|
+
return `${" ".repeat(leading)}${content}`;
|
|
1132
|
+
}
|
|
1065
1133
|
fit(content, width) {
|
|
1066
1134
|
// A Component render entry must remain exactly one terminal row. Persisted
|
|
1067
1135
|
// prompts, messages, and tool results may contain line breaks or tabs;
|
package/dist/lib/inspector.js
CHANGED
|
@@ -126,7 +126,7 @@ export function readActorInspectorTurns(stateDir) {
|
|
|
126
126
|
withFileTypes: true,
|
|
127
127
|
})
|
|
128
128
|
.filter((file) => file.isFile() && file.name.endsWith(".jsonl"))
|
|
129
|
-
.map((file) => path.join("sessions", entry.name, file.name))
|
|
129
|
+
.map((file) => path.posix.join("sessions", entry.name, file.name))
|
|
130
130
|
.filter((file) => !recordedFiles.has(file));
|
|
131
131
|
return files.length > 0
|
|
132
132
|
? [{ id: entry.name, session_files: files, stage: "subagent" }]
|
|
@@ -15,6 +15,7 @@ export interface RunObservation {
|
|
|
15
15
|
failures?: number;
|
|
16
16
|
ownerId?: string;
|
|
17
17
|
artifacts?: Record<string, string>;
|
|
18
|
+
launchCorrelation?: Record<string, string>;
|
|
18
19
|
launchSource?: AsyncRuns.AsyncRunLaunchSource;
|
|
19
20
|
modelPolicy?: Record<string, unknown>;
|
|
20
21
|
notificationPolicy?: "normal" | "silent";
|
|
@@ -22,6 +23,7 @@ export interface RunObservation {
|
|
|
22
23
|
terminalHandled?: boolean;
|
|
23
24
|
retireWhen?: string;
|
|
24
25
|
run: string;
|
|
26
|
+
semanticResult?: RunTerminalSemanticResult;
|
|
25
27
|
tool?: string;
|
|
26
28
|
stateDir?: string;
|
|
27
29
|
status: RunObservedStatus;
|
|
@@ -131,12 +133,22 @@ export interface RunTransition {
|
|
|
131
133
|
run: string;
|
|
132
134
|
stateDir?: string;
|
|
133
135
|
artifacts?: Record<string, string>;
|
|
136
|
+
launchCorrelation?: Record<string, string>;
|
|
134
137
|
launchSource?: AsyncRuns.AsyncRunLaunchSource;
|
|
135
138
|
modelPolicy?: Record<string, unknown>;
|
|
136
139
|
recipeFile?: string;
|
|
137
140
|
terminalHandled?: boolean;
|
|
138
141
|
to: RunObservedStatus;
|
|
139
142
|
tool?: string;
|
|
143
|
+
semanticResult?: RunTerminalSemanticResult;
|
|
144
|
+
}
|
|
145
|
+
export interface RunTerminalSemanticResult {
|
|
146
|
+
body?: string;
|
|
147
|
+
correlationId?: string;
|
|
148
|
+
metadata: Record<string, unknown>;
|
|
149
|
+
summary: string;
|
|
150
|
+
synthesized: boolean;
|
|
151
|
+
type: string;
|
|
140
152
|
}
|
|
141
153
|
export interface RunOutboxEvent {
|
|
142
154
|
body?: unknown;
|
|
@@ -169,5 +181,4 @@ export declare function formatRunOutboxMessage(event: RunOutboxEvent): string;
|
|
|
169
181
|
export declare function getRunTransitionNotificationType(transition: RunTransition): RunTransitionNotificationType;
|
|
170
182
|
export declare function shouldNotifyRunTransition(transition: RunTransition): boolean;
|
|
171
183
|
export declare function shouldSendRunTransitionFollowUp(transition: RunTransition): boolean;
|
|
172
|
-
export declare function shouldSuggestRecipePersistence(transition: RunTransition): boolean;
|
|
173
184
|
export declare function formatRunTransitionMessage(transition: RunTransition): string;
|