@llblab/pi-actors 0.43.0 → 0.44.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 +16 -10
- package/CHANGELOG.md +425 -536
- package/README.md +13 -11
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +2 -1
- package/dist/lib/async-runs.js +24 -34
- package/dist/lib/automatic-review-runtime.d.ts +1 -1
- package/dist/lib/automatic-review-runtime.js +5 -5
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +38 -4
- package/dist/lib/control-projection.d.ts +20 -0
- package/dist/lib/control-projection.js +66 -0
- package/dist/lib/control.d.ts +3 -0
- package/dist/lib/control.js +27 -14
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/file-state.d.ts +4 -1
- package/dist/lib/file-state.js +118 -44
- package/dist/lib/inspector-overlay.d.ts +2 -0
- package/dist/lib/inspector-overlay.js +124 -69
- package/dist/lib/limits.d.ts +15 -3
- package/dist/lib/limits.js +15 -3
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-control.js +6 -2
- package/dist/lib/review-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -5
- package/dist/lib/run-evidence-policy.d.ts +95 -0
- package/dist/lib/run-evidence-policy.js +177 -0
- package/dist/lib/run-ui-runtime.js +2 -0
- package/dist/lib/runs-control-delivery.d.ts +8 -1
- package/dist/lib/runs-control-delivery.js +38 -15
- package/dist/lib/runs-controls.d.ts +8 -4
- package/dist/lib/runs-controls.js +189 -50
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +26 -2
- package/dist/lib/runs-trace.js +411 -18
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- package/dist/lib/runtime-triage.d.ts +29 -0
- package/dist/lib/runtime-triage.js +60 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.js +91 -18
- package/dist/lib/tools-message.d.ts +1 -2
- package/dist/lib/tools-message.js +6 -6
- package/dist/lib/tools-response.d.ts +0 -1
- package/dist/lib/tools-response.js +0 -9
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +1 -1
- package/dist/lib/trace-projection.js +107 -41
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +40 -90
- package/dist/scripts/music-player.mjs +48 -142
- package/dist/scripts/release-gates.mjs +56 -3
- package/dist/scripts/validate-recipe.mjs +5 -4
- package/dist/skills/actors/SKILL.md +17 -11
- package/dist/skills/swarm/SKILL.md +2 -4
- package/docs/README.md +1 -4
- package/docs/actor-inspector.md +6 -5
- package/docs/async-runs.md +11 -9
- package/docs/command-templates.md +6 -116
- package/docs/recipe-library.md +4 -6
- package/docs/releasing.md +28 -0
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +2 -2
- package/index.ts +1 -1
- package/lib/async-runs.ts +26 -50
- package/lib/automatic-review-runtime.ts +7 -7
- package/lib/command-templates.ts +44 -4
- package/lib/control-projection.ts +105 -0
- package/lib/control.ts +33 -18
- package/lib/draft-sleep.ts +3 -3
- package/lib/file-state.ts +91 -63
- package/lib/inspector-overlay.ts +108 -61
- package/lib/limits.ts +15 -3
- package/lib/observability.ts +55 -57
- package/lib/prompts.ts +1 -1
- package/lib/recipe-control.ts +9 -2
- package/lib/review-control.ts +4 -5
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-control-delivery.ts +45 -17
- package/lib/runs-controls.ts +180 -102
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +499 -20
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-triage.ts +106 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +94 -20
- package/lib/tools-message.ts +7 -8
- package/lib/tools-response.ts +0 -12
- package/lib/tools.ts +4 -4
- package/lib/trace-projection.ts +156 -71
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +40 -90
- package/scripts/music-player.mjs +48 -142
- package/scripts/release-gates.mjs +56 -3
- package/scripts/validate-recipe.mjs +5 -4
- package/skills/actors/SKILL.md +17 -11
- package/skills/swarm/SKILL.md +2 -4
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -44
- package/docs/actors-deep-reference.md +0 -108
- package/docs/component-recipes.md +0 -45
- package/docs/task-first-recipes.md +0 -261
- package/lib/runtime-notifier.ts +0 -211
package/dist/lib/file-state.js
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
* Zones: file persistence, atomic writes, runtime state support
|
|
4
4
|
* Owns generic durable JSON file writes shared by registry config and async run state.
|
|
5
5
|
*/
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync
|
|
8
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
import { basename, dirname, join, parse, resolve } from "node:path";
|
|
10
|
-
const FILE_MUTATION_LOCK_TIMEOUT_MS =
|
|
11
|
+
const FILE_MUTATION_LOCK_TIMEOUT_MS = process.platform === "win32" ? 30000 : 15000;
|
|
11
12
|
const FILE_MUTATION_LOCK_STALE_MS = 30000;
|
|
13
|
+
const FILE_MUTATION_LOCK_RECLAIM_POLL_MS = 100;
|
|
14
|
+
const FILE_MUTATION_LOCK_REMOVAL_GRACE_MS = 250;
|
|
15
|
+
const FILE_MUTATION_LOCK_MAX_WAIT_MS = 50;
|
|
12
16
|
const FILE_MUTATION_LOCK_ROOT = join(tmpdir(), "pi-actors-file-locks");
|
|
13
17
|
function canonicalMutationPath(path) {
|
|
14
18
|
const absolute = resolve(path);
|
|
@@ -42,6 +46,20 @@ export function mutationLockPath(path) {
|
|
|
42
46
|
.digest("hex");
|
|
43
47
|
return join(FILE_MUTATION_LOCK_ROOT, `${key}.lock`);
|
|
44
48
|
}
|
|
49
|
+
function isZombieProcess(pid) {
|
|
50
|
+
if (process.platform === "linux")
|
|
51
|
+
try {
|
|
52
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
53
|
+
return stat.slice(stat.lastIndexOf(")") + 2).startsWith("Z ");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (process.platform !== "darwin")
|
|
59
|
+
return false;
|
|
60
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "stat="], { encoding: "utf8" });
|
|
61
|
+
return result.status === 0 && result.stdout.trim().startsWith("Z");
|
|
62
|
+
}
|
|
45
63
|
function lockOwnerStatus(lockPath) {
|
|
46
64
|
try {
|
|
47
65
|
const owner = JSON.parse(readFileSync(join(lockPath, "owner.json"), "utf8"));
|
|
@@ -50,6 +68,9 @@ function lockOwnerStatus(lockPath) {
|
|
|
50
68
|
return "unknown";
|
|
51
69
|
try {
|
|
52
70
|
process.kill(pid, 0);
|
|
71
|
+
if (Date.now() - statSync(lockPath).mtimeMs > FILE_MUTATION_LOCK_REMOVAL_GRACE_MS &&
|
|
72
|
+
isZombieProcess(pid))
|
|
73
|
+
return "dead";
|
|
53
74
|
return "alive";
|
|
54
75
|
}
|
|
55
76
|
catch (error) {
|
|
@@ -71,17 +92,59 @@ function readLockToken(lockPath) {
|
|
|
71
92
|
return undefined;
|
|
72
93
|
}
|
|
73
94
|
}
|
|
95
|
+
function prepareLockBoundary(lockPath, token, onBeforePublish) {
|
|
96
|
+
const pendingPath = `${lockPath}.${process.pid}.${token}.pending`;
|
|
97
|
+
try {
|
|
98
|
+
mkdirSync(pendingPath);
|
|
99
|
+
writeFileSync(join(pendingPath, "owner.json"), `${JSON.stringify({ pid: process.pid, token, acquired_at: new Date().toISOString() })}\n`, "utf8");
|
|
100
|
+
onBeforePublish?.();
|
|
101
|
+
return pendingPath;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
try {
|
|
105
|
+
rmSync(pendingPath, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function removeLockBoundary(lockPath, token) {
|
|
112
|
+
if (readLockToken(lockPath) !== token)
|
|
113
|
+
return false;
|
|
114
|
+
if (process.platform === "win32")
|
|
115
|
+
try {
|
|
116
|
+
rmSync(lockPath, { recursive: true, force: true, maxRetries: 20, retryDelay: 5 });
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const removingPath = `${lockPath}.${process.pid}.${randomUUID()}.removing`;
|
|
123
|
+
try {
|
|
124
|
+
renameSync(lockPath, removingPath);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
rmSync(removingPath, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
74
135
|
function tryReclaimRemovalBoundary(reclaimPath) {
|
|
75
136
|
if (!existsSync(reclaimPath))
|
|
76
137
|
return;
|
|
77
138
|
try {
|
|
78
|
-
const inspectedToken = readLockToken(reclaimPath);
|
|
79
139
|
const age = Date.now() - statSync(reclaimPath).mtimeMs;
|
|
140
|
+
if (age <= FILE_MUTATION_LOCK_REMOVAL_GRACE_MS)
|
|
141
|
+
return;
|
|
142
|
+
const inspectedToken = readLockToken(reclaimPath);
|
|
80
143
|
const ownerStatus = lockOwnerStatus(reclaimPath);
|
|
81
144
|
if ((ownerStatus === "dead" ||
|
|
82
145
|
(ownerStatus === "unknown" && age > FILE_MUTATION_LOCK_STALE_MS)) &&
|
|
83
146
|
readLockToken(reclaimPath) === inspectedToken) {
|
|
84
|
-
|
|
147
|
+
removeLockBoundary(reclaimPath, inspectedToken);
|
|
85
148
|
}
|
|
86
149
|
}
|
|
87
150
|
catch {
|
|
@@ -91,28 +154,28 @@ function tryReclaimRemovalBoundary(reclaimPath) {
|
|
|
91
154
|
function withRemovalBoundary(lockPath, action) {
|
|
92
155
|
const reclaimPath = `${lockPath}.reclaim`;
|
|
93
156
|
const token = randomUUID();
|
|
157
|
+
const pendingPath = prepareLockBoundary(reclaimPath, token);
|
|
94
158
|
try {
|
|
95
|
-
mkdirSync(reclaimPath);
|
|
96
159
|
try {
|
|
97
|
-
|
|
160
|
+
renameSync(pendingPath, reclaimPath);
|
|
98
161
|
}
|
|
99
|
-
catch
|
|
100
|
-
|
|
101
|
-
|
|
162
|
+
catch {
|
|
163
|
+
tryReclaimRemovalBoundary(reclaimPath);
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
action();
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
removeLockBoundary(reclaimPath, token);
|
|
102
172
|
}
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
tryReclaimRemovalBoundary(reclaimPath);
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
action();
|
|
110
|
-
return true;
|
|
111
173
|
}
|
|
112
174
|
finally {
|
|
113
|
-
|
|
114
|
-
rmSync(
|
|
175
|
+
try {
|
|
176
|
+
rmSync(pendingPath, { recursive: true, force: true });
|
|
115
177
|
}
|
|
178
|
+
catch { }
|
|
116
179
|
}
|
|
117
180
|
}
|
|
118
181
|
function tryReclaimMutationLock(lockPath, options) {
|
|
@@ -129,8 +192,7 @@ function tryReclaimMutationLock(lockPath, options) {
|
|
|
129
192
|
(ownerStatus === "unknown" && age > FILE_MUTATION_LOCK_STALE_MS)) &&
|
|
130
193
|
readLockToken(lockPath) === inspectedToken) {
|
|
131
194
|
options.onBeforeReclaimRemove?.();
|
|
132
|
-
|
|
133
|
-
reclaimed = true;
|
|
195
|
+
reclaimed = removeLockBoundary(lockPath, inspectedToken);
|
|
134
196
|
}
|
|
135
197
|
})) {
|
|
136
198
|
return false;
|
|
@@ -142,32 +204,45 @@ export function acquireFileMutationLock(path, options = {}) {
|
|
|
142
204
|
const lockPath = mutationLockPath(path);
|
|
143
205
|
const deadline = Date.now() + FILE_MUTATION_LOCK_TIMEOUT_MS;
|
|
144
206
|
const token = randomUUID();
|
|
207
|
+
const pendingPath = prepareLockBoundary(lockPath, token, options.onBeforeLockPublish);
|
|
145
208
|
let contentionReported = false;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
209
|
+
let nextReclaimAt = 0;
|
|
210
|
+
let waitMs = 10;
|
|
211
|
+
try {
|
|
212
|
+
for (;;) {
|
|
149
213
|
try {
|
|
150
|
-
|
|
214
|
+
renameSync(pendingPath, lockPath);
|
|
215
|
+
break;
|
|
151
216
|
}
|
|
152
217
|
catch (error) {
|
|
153
|
-
|
|
154
|
-
|
|
218
|
+
if (!contentionReported) {
|
|
219
|
+
contentionReported = true;
|
|
220
|
+
options.onContention?.();
|
|
221
|
+
}
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
if (now >= nextReclaimAt) {
|
|
224
|
+
nextReclaimAt = now + FILE_MUTATION_LOCK_RECLAIM_POLL_MS;
|
|
225
|
+
if (tryReclaimMutationLock(lockPath, options)) {
|
|
226
|
+
waitMs = 10;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (now >= deadline) {
|
|
231
|
+
throw new Error(`Timed out waiting for file mutation lock: ${canonicalMutationPath(path)}`, {
|
|
232
|
+
cause: error,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const jitter = Math.floor(Math.random() * Math.max(1, Math.floor(waitMs / 2)));
|
|
236
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, waitMs + jitter);
|
|
237
|
+
waitMs = Math.min(FILE_MUTATION_LOCK_MAX_WAIT_MS, waitMs + 5);
|
|
155
238
|
}
|
|
156
|
-
break;
|
|
157
239
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
tryReclaimMutationLock(lockPath, options);
|
|
164
|
-
if (Date.now() >= deadline) {
|
|
165
|
-
throw new Error(`Timed out waiting for file mutation lock: ${canonicalMutationPath(path)}`, {
|
|
166
|
-
cause: error,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
try {
|
|
243
|
+
rmSync(pendingPath, { recursive: true, force: true });
|
|
170
244
|
}
|
|
245
|
+
catch { }
|
|
171
246
|
}
|
|
172
247
|
let released = false;
|
|
173
248
|
return () => {
|
|
@@ -177,9 +252,7 @@ export function acquireFileMutationLock(path, options = {}) {
|
|
|
177
252
|
const deadline = Date.now() + FILE_MUTATION_LOCK_TIMEOUT_MS;
|
|
178
253
|
let removalContentionReported = false;
|
|
179
254
|
while (!withRemovalBoundary(lockPath, () => {
|
|
180
|
-
|
|
181
|
-
rmSync(lockPath, { recursive: true, force: true });
|
|
182
|
-
}
|
|
255
|
+
removeLockBoundary(lockPath, token);
|
|
183
256
|
})) {
|
|
184
257
|
if (!removalContentionReported) {
|
|
185
258
|
removalContentionReported = true;
|
|
@@ -200,11 +273,12 @@ export function withFileMutationLock(path, mutate, options = {}) {
|
|
|
200
273
|
release();
|
|
201
274
|
}
|
|
202
275
|
}
|
|
203
|
-
export function writeTextAtomic(path, content) {
|
|
276
|
+
export function writeTextAtomic(path, content, options = {}) {
|
|
204
277
|
mkdirSync(dirname(path), { recursive: true });
|
|
205
278
|
const tempPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
206
279
|
try {
|
|
207
280
|
writeFileSync(tempPath, content, "utf8");
|
|
281
|
+
options.onBeforeReplace?.();
|
|
208
282
|
renameSync(tempPath, path);
|
|
209
283
|
}
|
|
210
284
|
catch (error) {
|
|
@@ -41,6 +41,7 @@ export declare class ActorInspectorOverlay {
|
|
|
41
41
|
private killDialogChoice;
|
|
42
42
|
private rowIndex;
|
|
43
43
|
private runCache?;
|
|
44
|
+
private selectedTraceId?;
|
|
44
45
|
private runIndex;
|
|
45
46
|
private selectorIndex;
|
|
46
47
|
private selectorMode?;
|
|
@@ -81,6 +82,7 @@ export declare class ActorInspectorOverlay {
|
|
|
81
82
|
private readRunMeta;
|
|
82
83
|
private documentProjection;
|
|
83
84
|
private inlineDocumentValue;
|
|
85
|
+
private labeledDocumentLines;
|
|
84
86
|
private labeledScalarLines;
|
|
85
87
|
private document;
|
|
86
88
|
private wrapDocument;
|
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
* Zones: owned actor selection, Recipe/Trace/Control tabs, safe Run Kill confirmation
|
|
4
4
|
* Owns manual kernel navigation; evidence parsing and lifecycle mutation stay in ports.
|
|
5
5
|
*/
|
|
6
|
-
import { readFileSync } from "node:fs";
|
|
6
|
+
import { readFileSync, statSync } from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
9
9
|
import * as ActorInspector from "./inspector.js";
|
|
10
|
+
import * as ControlProjection from "./control-projection.js";
|
|
10
11
|
import * as RunsControls from "./runs-controls.js";
|
|
11
12
|
import * as RunControlDelivery from "./runs-control-delivery.js";
|
|
13
|
+
import * as RunsTrace from "./runs-trace.js";
|
|
14
|
+
import * as Limits from "./limits.js";
|
|
15
|
+
import { computeRunControlCapacity } from "./run-evidence-policy.js";
|
|
16
|
+
import * as RuntimeTriage from "./runtime-triage.js";
|
|
12
17
|
import * as TraceProjection from "./trace-projection.js";
|
|
13
18
|
const TABS = ["recipe", "trace", "control"];
|
|
14
19
|
const TRACE_SOURCES = [
|
|
@@ -39,6 +44,7 @@ export class ActorInspectorOverlay {
|
|
|
39
44
|
killDialogChoice = "cancel";
|
|
40
45
|
rowIndex = 0;
|
|
41
46
|
runCache;
|
|
47
|
+
selectedTraceId;
|
|
42
48
|
runIndex = 0;
|
|
43
49
|
selectorIndex = 0;
|
|
44
50
|
selectorMode;
|
|
@@ -56,9 +62,7 @@ export class ActorInspectorOverlay {
|
|
|
56
62
|
this.theme = options.theme;
|
|
57
63
|
this.tui = options.tui;
|
|
58
64
|
this.refreshTimer = setInterval(() => {
|
|
59
|
-
this.
|
|
60
|
-
if (this.focus !== "list" && this.focus !== "detail")
|
|
61
|
-
this.traceCache = undefined;
|
|
65
|
+
this.invalidate();
|
|
62
66
|
this.tui.requestRender();
|
|
63
67
|
}, 1_000);
|
|
64
68
|
this.refreshTimer.unref?.();
|
|
@@ -66,7 +70,10 @@ export class ActorInspectorOverlay {
|
|
|
66
70
|
dispose() {
|
|
67
71
|
clearInterval(this.refreshTimer);
|
|
68
72
|
}
|
|
69
|
-
invalidate() {
|
|
73
|
+
invalidate() {
|
|
74
|
+
this.runCache = undefined;
|
|
75
|
+
this.traceCache = undefined;
|
|
76
|
+
}
|
|
70
77
|
handleInput(data) {
|
|
71
78
|
const runs = this.runs();
|
|
72
79
|
this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
|
|
@@ -90,11 +97,14 @@ export class ActorInspectorOverlay {
|
|
|
90
97
|
this.focus = this.tab === "trace" ? "tabs" : "runs";
|
|
91
98
|
}
|
|
92
99
|
else if (this.focus === "detail") {
|
|
100
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
93
101
|
this.traceDetail = undefined;
|
|
94
102
|
this.detailScroll = 0;
|
|
95
103
|
this.focus = "list";
|
|
96
104
|
}
|
|
97
105
|
else if (this.focus === "document" || this.focus === "list") {
|
|
106
|
+
if (this.focus === "list")
|
|
107
|
+
this.selectedTraceId = undefined;
|
|
98
108
|
this.focus = "tabs";
|
|
99
109
|
}
|
|
100
110
|
else {
|
|
@@ -146,9 +156,12 @@ export class ActorInspectorOverlay {
|
|
|
146
156
|
this.focus = "runs";
|
|
147
157
|
}
|
|
148
158
|
else if (matchesKey(data, "down")) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
const items = this.tab === "trace" ? this.traceItems(runs[this.runIndex]) : [];
|
|
160
|
+
this.focus = items.length > 0 ? "list" : "document";
|
|
161
|
+
if (this.focus === "list") {
|
|
162
|
+
this.rowIndex = 0;
|
|
163
|
+
this.selectedTraceId = items[0]?.id;
|
|
164
|
+
}
|
|
152
165
|
}
|
|
153
166
|
else if (matchesKey(data, "return")) {
|
|
154
167
|
if (this.tab === "trace")
|
|
@@ -158,12 +171,17 @@ export class ActorInspectorOverlay {
|
|
|
158
171
|
}
|
|
159
172
|
}
|
|
160
173
|
else if (this.focus === "list") {
|
|
161
|
-
const
|
|
162
|
-
|
|
174
|
+
const items = this.traceItems(runs[this.runIndex]);
|
|
175
|
+
const count = items.length;
|
|
176
|
+
if (matchesKey(data, "left")) {
|
|
177
|
+
this.selectedTraceId = undefined;
|
|
163
178
|
this.focus = "tabs";
|
|
179
|
+
}
|
|
164
180
|
else if (matchesKey(data, "up")) {
|
|
165
|
-
if (this.rowIndex === 0)
|
|
181
|
+
if (this.rowIndex === 0) {
|
|
182
|
+
this.selectedTraceId = undefined;
|
|
166
183
|
this.focus = "tabs";
|
|
184
|
+
}
|
|
167
185
|
else
|
|
168
186
|
this.rowIndex -= 1;
|
|
169
187
|
}
|
|
@@ -177,13 +195,15 @@ export class ActorInspectorOverlay {
|
|
|
177
195
|
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + this.contentViewportRows());
|
|
178
196
|
}
|
|
179
197
|
else if (matchesKey(data, "return") || matchesKey(data, "right")) {
|
|
180
|
-
const item =
|
|
198
|
+
const item = items[this.rowIndex];
|
|
181
199
|
if (item) {
|
|
182
200
|
this.traceDetail = item;
|
|
183
201
|
this.detailScroll = 0;
|
|
184
202
|
this.focus = "detail";
|
|
185
203
|
}
|
|
186
204
|
}
|
|
205
|
+
if (this.focus === "list")
|
|
206
|
+
this.selectedTraceId = items[this.rowIndex]?.id;
|
|
187
207
|
}
|
|
188
208
|
if (data.toLowerCase() === "f" && this.tab === "trace") {
|
|
189
209
|
this.cycleTraceSource(runs[this.runIndex]);
|
|
@@ -239,6 +259,7 @@ export class ActorInspectorOverlay {
|
|
|
239
259
|
this.documentScroll = 0;
|
|
240
260
|
this.detailScroll = 0;
|
|
241
261
|
this.rowIndex = 0;
|
|
262
|
+
this.selectedTraceId = undefined;
|
|
242
263
|
this.traceDetail = undefined;
|
|
243
264
|
}
|
|
244
265
|
cycleRun(runs, direction) {
|
|
@@ -260,6 +281,7 @@ export class ActorInspectorOverlay {
|
|
|
260
281
|
this[key] += this.contentViewportRows();
|
|
261
282
|
}
|
|
262
283
|
else if (matchesKey(data, "left") && target === "detail") {
|
|
284
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
263
285
|
this.traceDetail = undefined;
|
|
264
286
|
this.detailScroll = 0;
|
|
265
287
|
this.focus = "list";
|
|
@@ -361,7 +383,10 @@ export class ActorInspectorOverlay {
|
|
|
361
383
|
if (!confirmation || !this.killRun)
|
|
362
384
|
return;
|
|
363
385
|
try {
|
|
364
|
-
|
|
386
|
+
const result = this.killRun(confirmation.run, confirmation.runInstanceId);
|
|
387
|
+
this.feedback = result.ok ? undefined : result;
|
|
388
|
+
if (result.ok)
|
|
389
|
+
this.invalidate();
|
|
365
390
|
}
|
|
366
391
|
catch (error) {
|
|
367
392
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -386,8 +411,7 @@ export class ActorInspectorOverlay {
|
|
|
386
411
|
`${this.theme.fg("muted", "Run:")} ${this.theme.fg("accent", `run:${confirmation.run}`)}`,
|
|
387
412
|
`${this.theme.fg("muted", "Current status:")} ${this.theme.fg("warning", confirmation.status)}`,
|
|
388
413
|
"",
|
|
389
|
-
this.theme.fg("text", "This
|
|
390
|
-
this.theme.fg("error", "The action is destructive and cannot be undone."),
|
|
414
|
+
this.theme.fg("text", "This invokes generation-fenced runtime kill."),
|
|
391
415
|
"",
|
|
392
416
|
`${cancel} ${kill}`,
|
|
393
417
|
"",
|
|
@@ -478,33 +502,45 @@ export class ActorInspectorOverlay {
|
|
|
478
502
|
}
|
|
479
503
|
renderTraceList(run) {
|
|
480
504
|
const items = this.traceItems(run);
|
|
505
|
+
const summary = RunsTrace.summarizeRunTraceJournal(RunsTrace.readRunTraceJournal(path.join(this.stateRoot, run.run)));
|
|
506
|
+
const banner = summary.history_complete ? [] : [this.theme.fg("warning", ` ! Trace history incomplete: ${summary.compacted ? `${summary.compactions_total} compaction(s), ` : ""}${summary.dropped_event_count_exact ? summary.dropped_events : "unknown"} dropped event(s), ${summary.dropped_bytes} bytes`)];
|
|
481
507
|
if (items.length === 0)
|
|
482
|
-
return [this.theme.fg("muted", " No Trace evidence")];
|
|
508
|
+
return [...banner, this.theme.fg("muted", " No Trace evidence")];
|
|
509
|
+
if (this.selectedTraceId) {
|
|
510
|
+
const selectedIndex = items.findIndex((item) => item.id === this.selectedTraceId);
|
|
511
|
+
if (selectedIndex >= 0)
|
|
512
|
+
this.rowIndex = selectedIndex;
|
|
513
|
+
else
|
|
514
|
+
this.selectedTraceId = undefined;
|
|
515
|
+
}
|
|
483
516
|
this.rowIndex = Math.min(this.rowIndex, items.length - 1);
|
|
517
|
+
if (this.focus === "list" && !this.selectedTraceId) {
|
|
518
|
+
this.selectedTraceId = items[this.rowIndex]?.id;
|
|
519
|
+
}
|
|
484
520
|
const viewportRows = this.contentViewportRows();
|
|
485
521
|
const maxStart = Math.max(0, items.length - viewportRows);
|
|
486
522
|
const start = Math.max(0, Math.min(this.rowIndex - Math.floor(viewportRows / 2), maxStart));
|
|
487
523
|
this.contentStripeIndices = items
|
|
488
524
|
.slice(start, start + viewportRows)
|
|
489
525
|
.map((_item, offset) => start + offset);
|
|
490
|
-
return items.slice(start, start + viewportRows).map((item, offset) => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
526
|
+
return [...banner, ...items.slice(start, start + viewportRows - banner.length).map((item, offset) => {
|
|
527
|
+
const index = start + offset;
|
|
528
|
+
const detail = item.detail && typeof item.detail === "object"
|
|
529
|
+
? item.detail
|
|
530
|
+
: {};
|
|
531
|
+
const marker = item.level === "error"
|
|
532
|
+
? this.theme.fg("error", "!")
|
|
533
|
+
: detail.attention
|
|
534
|
+
? this.theme.fg("warning", "A")
|
|
535
|
+
: " ";
|
|
536
|
+
const prefix = this.focus === "list" && index === this.rowIndex
|
|
537
|
+
? this.theme.fg("accent", " ▶ ")
|
|
538
|
+
: " ";
|
|
539
|
+
const row = `${prefix}${this.theme.fg("text", `#${items.length - index}`)} ${marker} ${this.theme.fg("muted", item.source)}/${this.theme.fg(item.level === "error" ? "error" : "accent", item.kind)} ${this.theme.fg("text", item.summary)}`;
|
|
540
|
+
return this.focus === "list" && index === this.rowIndex
|
|
541
|
+
? this.theme.fg("accent", row)
|
|
542
|
+
: row;
|
|
543
|
+
})];
|
|
508
544
|
}
|
|
509
545
|
renderTraceDetail(width) {
|
|
510
546
|
if (!this.traceDetail)
|
|
@@ -520,13 +556,29 @@ export class ActorInspectorOverlay {
|
|
|
520
556
|
? RunControlDelivery.readRunControlEndpoint(stateDir, run.runInstanceId)
|
|
521
557
|
: undefined;
|
|
522
558
|
const meta = this.readRunMeta(stateDir);
|
|
559
|
+
const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
560
|
+
const capacity = computeRunControlCapacity(journal.records);
|
|
561
|
+
const stalePending = journal.records.reduce((count, control) => count + Number(Boolean(RuntimeTriage.classifyRuntimeControl({ run: run.run,
|
|
562
|
+
runInstanceId: run.runInstanceId, status: run.status }, control, Date.now()).stale)), 0);
|
|
523
563
|
return {
|
|
524
564
|
status: run.status,
|
|
525
565
|
run_instance_id: run.runInstanceId,
|
|
566
|
+
pending: capacity.pending, pending_limit: capacity.limit,
|
|
567
|
+
available: capacity.available, backpressured: capacity.backpressured,
|
|
568
|
+
journal_bytes: (() => { try {
|
|
569
|
+
return statSync(RunsControls.runControlsFile(stateDir)).size;
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
return 0;
|
|
573
|
+
} })(),
|
|
574
|
+
journal_limit: Limits.RUN_CONTROL_JOURNAL_MAX_BYTES,
|
|
575
|
+
stale_pending: stalePending,
|
|
576
|
+
diagnostics: journal.diagnostics.map((item) => item.line === undefined
|
|
577
|
+
? { reason: "unreadable_control_journal" } : { line: item.line, reason: "invalid_control_json" }),
|
|
526
578
|
actor_actions: Array.isArray(meta.control) ? meta.control : [],
|
|
527
579
|
runtime_actions: run.status === "running" ? ["kill"] : ["archive", "prune"],
|
|
528
580
|
endpoint,
|
|
529
|
-
recent_controls:
|
|
581
|
+
recent_controls: journal.records.slice(-20).reverse().map((control) => ControlProjection.projectRunControl(control)),
|
|
530
582
|
};
|
|
531
583
|
}
|
|
532
584
|
allTraceItems(run) {
|
|
@@ -567,14 +619,7 @@ export class ActorInspectorOverlay {
|
|
|
567
619
|
}
|
|
568
620
|
documentProjection(value, width) {
|
|
569
621
|
const sections = value && typeof value === "object" && !Array.isArray(value)
|
|
570
|
-
? Object.entries(value).map(([key, item]) =>
|
|
571
|
-
const inline = this.inlineDocumentValue(item);
|
|
572
|
-
return inline !== undefined
|
|
573
|
-
? [`${key}: ${inline}`]
|
|
574
|
-
: item && typeof item === "object"
|
|
575
|
-
? [`${key}:`, ...this.document(item, 1)]
|
|
576
|
-
: this.labeledScalarLines(key, item);
|
|
577
|
-
})
|
|
622
|
+
? Object.entries(value).map(([key, item]) => this.labeledDocumentLines(key, item))
|
|
578
623
|
: [this.document(value)];
|
|
579
624
|
const lines = [];
|
|
580
625
|
const stripes = [];
|
|
@@ -596,19 +641,22 @@ export class ActorInspectorOverlay {
|
|
|
596
641
|
return undefined;
|
|
597
642
|
}
|
|
598
643
|
if (value && typeof value === "object") {
|
|
599
|
-
|
|
600
|
-
if (entries.length === 0)
|
|
644
|
+
if (Object.keys(value).length === 0)
|
|
601
645
|
return "{}";
|
|
602
|
-
if (entries.length <= 3 &&
|
|
603
|
-
entries.every(([, item]) => (item === null || typeof item !== "object") &&
|
|
604
|
-
!(typeof item === "string" && /[\r\n]/u.test(item)))) {
|
|
605
|
-
return `{ ${entries
|
|
606
|
-
.map(([key, item]) => `${key}: ${String(item ?? "none")}`)
|
|
607
|
-
.join(", ")} }`;
|
|
608
|
-
}
|
|
609
646
|
}
|
|
610
647
|
return undefined;
|
|
611
648
|
}
|
|
649
|
+
labeledDocumentLines(key, value, depth = 0) {
|
|
650
|
+
const inline = this.inlineDocumentValue(value);
|
|
651
|
+
if (inline !== undefined)
|
|
652
|
+
return [`${" ".repeat(depth)}${key}: ${inline}`];
|
|
653
|
+
if (value && typeof value === "object") {
|
|
654
|
+
const indent = " ".repeat(depth);
|
|
655
|
+
const nested = this.document(value, depth);
|
|
656
|
+
return [`${indent}${key}: ${nested[0].slice(indent.length)}`, ...nested.slice(1)];
|
|
657
|
+
}
|
|
658
|
+
return this.labeledScalarLines(key, value, depth);
|
|
659
|
+
}
|
|
612
660
|
labeledScalarLines(key, value, depth = 0) {
|
|
613
661
|
const indent = " ".repeat(depth);
|
|
614
662
|
const [first = "", ...rest] = String(value ?? "none").split(/\r?\n/u);
|
|
@@ -624,25 +672,32 @@ export class ActorInspectorOverlay {
|
|
|
624
672
|
if (inline !== undefined)
|
|
625
673
|
return [`${" ".repeat(depth)}${inline}`];
|
|
626
674
|
if (Array.isArray(value)) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
675
|
+
const indent = " ".repeat(depth);
|
|
676
|
+
return [
|
|
677
|
+
`${indent}[`,
|
|
678
|
+
...value.flatMap((item, index) => {
|
|
679
|
+
const marker = `${indent} - #${index + 1}`;
|
|
680
|
+
const itemInline = this.inlineDocumentValue(item);
|
|
681
|
+
return itemInline !== undefined
|
|
682
|
+
? [`${marker}: ${itemInline}`]
|
|
683
|
+
: [marker, ...this.document(item, depth + 2)];
|
|
684
|
+
}),
|
|
685
|
+
`${indent}]`,
|
|
686
|
+
];
|
|
634
687
|
}
|
|
635
688
|
if (typeof value === "object") {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
689
|
+
const indent = " ".repeat(depth);
|
|
690
|
+
const entries = Object.entries(value);
|
|
691
|
+
return [
|
|
692
|
+
`${indent}{`,
|
|
693
|
+
...entries.flatMap(([key, item], index) => {
|
|
694
|
+
const lines = this.labeledDocumentLines(key, item, depth + 1);
|
|
695
|
+
if (index < entries.length - 1)
|
|
696
|
+
lines[lines.length - 1] += ",";
|
|
697
|
+
return lines;
|
|
698
|
+
}),
|
|
699
|
+
`${indent}}`,
|
|
700
|
+
];
|
|
646
701
|
}
|
|
647
702
|
return String(value)
|
|
648
703
|
.split(/\r?\n/u)
|
package/dist/lib/limits.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared output and
|
|
3
|
-
* Zones: output governance, Trace previews,
|
|
2
|
+
* Shared output, preview, and Control envelope limits.
|
|
3
|
+
* Zones: output governance, Trace previews, Inspect defaults, Control portability
|
|
4
4
|
*/
|
|
5
5
|
export declare const DEFAULT_INSPECT_LINES = 40;
|
|
6
6
|
export declare const TOOL_OUTPUT_MAX_BYTES: number;
|
|
7
7
|
export declare const TOOL_OUTPUT_MAX_LINES = 2000;
|
|
8
8
|
export declare const COMPACT_PREVIEW_CHARS = 160;
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const CONTROL_ACTION_MAX_LENGTH = 64;
|
|
10
|
+
export declare const CONTROL_INPUT_MAX_BYTES = 380;
|
|
11
|
+
export declare const CONTROL_WIRE_MAX_BYTES = 512;
|
|
10
12
|
export declare const INSPECTOR_BODY_PREVIEW_CHARS = 320;
|
|
11
13
|
export declare const DOCTOR_ACTION_PREVIEW_CHARS = 72;
|
|
12
14
|
export declare const SESSION_EVIDENCE_MAX_TURNS = 100;
|
|
@@ -14,3 +16,13 @@ export declare const SESSION_EVIDENCE_TEXT_CHARS = 4000;
|
|
|
14
16
|
export declare const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
|
|
15
17
|
export declare const TRACE_EVENT_MAX_BYTES: number;
|
|
16
18
|
export declare const TRACE_EVENT_MAX_READ = 200;
|
|
19
|
+
export declare const TRACE_JOURNAL_MAX_EVENTS = 2048;
|
|
20
|
+
export declare const TRACE_JOURNAL_TARGET_EVENTS = 1536;
|
|
21
|
+
export declare const TRACE_JOURNAL_MAX_BYTES: number;
|
|
22
|
+
export declare const TRACE_JOURNAL_TARGET_BYTES: number;
|
|
23
|
+
export declare const RUN_CONTROL_PENDING_LIMIT = 64;
|
|
24
|
+
export declare const RUN_CONTROL_TERMINAL_LIMIT = 128;
|
|
25
|
+
export declare const RUN_CONTROL_ERROR_MAX_BYTES: number;
|
|
26
|
+
export declare const RUN_CONTROL_JOURNAL_MAX_BYTES: number;
|
|
27
|
+
export declare const RUN_RETENTION_MAX_RECORDS = 256;
|
|
28
|
+
export declare const RUN_RETENTION_MAX_BYTES: number;
|