@nklisch/pi-enhanced 0.2.0 → 0.2.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/CHANGELOG.md +12 -0
- package/node_modules/@nklisch/pi-background-tasks/CHANGELOG.md +7 -0
- package/node_modules/@nklisch/pi-background-tasks/extensions/background-tasks.test.ts +105 -0
- package/node_modules/@nklisch/pi-background-tasks/extensions/background-tasks.ts +131 -44
- package/node_modules/@nklisch/pi-background-tasks/package.json +1 -1
- package/node_modules/@nklisch/pi-background-tasks/skills/background-tasks/SKILL.md +6 -7
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-clearance/package.json +1 -1
- package/node_modules/@nklisch/pi-clearance/src/config/config-command-writer.ts +34 -17
- package/node_modules/@nklisch/pi-clearance/src/index.ts +34 -3
- package/node_modules/@nklisch/pi-clearance/src/packs/package-registration.ts +63 -36
- package/node_modules/@nklisch/pi-clearance/src/runtime/allow-request-message.ts +40 -1
- package/node_modules/@nklisch/pi-clearance/src/runtime/command-registry.ts +22 -2
- package/node_modules/@nklisch/pi-clearance/src/runtime/command-transforms.ts +50 -29
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/native-ui.ts +233 -49
- package/node_modules/@nklisch/pi-clearance/src/runtime/policy-cache.ts +19 -5
- package/node_modules/@nklisch/pi-conveniences/extensions/context-window-footer.ts +48 -13
- package/node_modules/@nklisch/pi-conveniences/package.json +1 -1
- package/node_modules/@nklisch/pi-fff-compat/extensions/fff-compat-search.ts +49 -42
- package/node_modules/@nklisch/pi-fff-compat/extensions/finder-lifecycle.ts +92 -0
- package/node_modules/@nklisch/pi-fff-compat/package.json +2 -2
- package/node_modules/@nklisch/pi-model-modes/package.json +1 -1
- package/node_modules/@nklisch/pi-model-modes/src/command-message.ts +30 -0
- package/node_modules/@nklisch/pi-model-modes/src/commands.ts +31 -15
- package/node_modules/@nklisch/pi-model-modes/src/style-command.ts +25 -14
- package/node_modules/@nklisch/pi-plugins/README.md +2 -2
- package/node_modules/@nklisch/pi-plugins/dist/application/subagent-hook-runtime.js +4 -1
- package/node_modules/@nklisch/pi-plugins/dist/application/subagent-hook-runtime.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/composition/background-update-coordinator.js +12 -3
- package/node_modules/@nklisch/pi-plugins/dist/composition/background-update-coordinator.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/process/command-runner.d.ts +1 -0
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/process/command-runner.js +19 -11
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/process/command-runner.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/pi/hooks/pi-hook-decision-adapter.js +36 -7
- package/node_modules/@nklisch/pi-plugins/dist/pi/hooks/pi-hook-decision-adapter.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/pi/manager/plugin-manager-component.js +14 -3
- package/node_modules/@nklisch/pi-plugins/dist/pi/manager/plugin-manager-component.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/pi/manager/plugin-manager-session.js +153 -54
- package/node_modules/@nklisch/pi-plugins/dist/pi/manager/plugin-manager-session.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager-lifecycle.js +35 -8
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager-lifecycle.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-package.js +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-runtime.js +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/subagents/pi-subagents-package.js +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +7 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +22 -17
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/debug.ts +18 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/child-lifecycle.ts +22 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/concurrency-limiter.ts +32 -8
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +8 -7
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/lifecycle-interceptor.ts +25 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/run-listeners.ts +6 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +31 -16
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +70 -32
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +55 -50
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/workspace-bracket.ts +6 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/record-observer.ts +37 -34
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +15 -14
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +31 -26
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +51 -26
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +100 -46
- package/node_modules/@nklisch/pi-plugins/package.json +3 -3
- package/package.json +4 -4
- package/test/verify-bundle.mjs +8 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
import type { Model } from "@earendil-works/pi-ai";
|
|
11
11
|
import type { AgentSessionEvent, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { debugLog } from "#src/debug";
|
|
12
|
+
import { debugLog, runDetached, runSafely } from "#src/debug";
|
|
13
13
|
import type { CreateSubagentSessionParams } from "#src/lifecycle/create-subagent-session";
|
|
14
14
|
import {
|
|
15
15
|
type LifecycleInterceptorRegistry,
|
|
@@ -264,30 +264,26 @@ export class Subagent {
|
|
|
264
264
|
*/
|
|
265
265
|
async run(): Promise<void> {
|
|
266
266
|
this.markRunning(Date.now());
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
267
|
+
try {
|
|
268
|
+
// Observer callbacks are extension-owned sinks, not part of the agent's
|
|
269
|
+
// work. A stale UI/context callback must not leave a background record
|
|
270
|
+
// running with a slot held and no terminal notification.
|
|
271
|
+
runSafely("subagent onStarted observer", () => this.execution.observer?.onStarted?.(this));
|
|
272
|
+
this.listeners.wireSignal(this.execution.signal, () => this.abort());
|
|
273
|
+
|
|
274
|
+
// Guard the await so the no-provider path stays synchronous, preserving
|
|
275
|
+
// the original run() timing: the factory is called in the same turn as
|
|
276
|
+
// spawn() when no workspace provider is registered.
|
|
277
|
+
let cwd: string | undefined;
|
|
278
|
+
if (this.workspaceBracket.hasProvider()) {
|
|
276
279
|
cwd = await this.workspaceBracket.prepare({
|
|
277
280
|
agentId: this.id,
|
|
278
281
|
agentType: this.type,
|
|
279
282
|
baseCwd: this.execution.baseCwd,
|
|
280
283
|
invocation: this.invocation,
|
|
281
284
|
});
|
|
282
|
-
} catch (err) {
|
|
283
|
-
this.markError(err);
|
|
284
|
-
this.listeners.release();
|
|
285
|
-
this.execution.observer?.onRunFinished?.(this);
|
|
286
|
-
return;
|
|
287
285
|
}
|
|
288
|
-
}
|
|
289
286
|
|
|
290
|
-
try {
|
|
291
287
|
this.subagentSession = await this.execution.createSubagentSession({
|
|
292
288
|
snapshot: this.execution.snapshot,
|
|
293
289
|
type: this.type,
|
|
@@ -296,27 +292,24 @@ export class Subagent {
|
|
|
296
292
|
model: this.execution.model,
|
|
297
293
|
thinkingLevel: this.execution.thinkingLevel,
|
|
298
294
|
});
|
|
299
|
-
} catch (err) {
|
|
300
|
-
// The factory disposed its own session on a post-creation failure.
|
|
301
|
-
this.failRun(err);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
295
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
296
|
+
// The SDK session is authoritative after creation: it has applied its
|
|
297
|
+
// defaults and model-capability clamp. Keep the record as the one source
|
|
298
|
+
// consumed by every operator-facing status surface.
|
|
299
|
+
this._modelLabel = formatModelLabel(this.subagentSession.model ?? this.execution.model ?? this.execution.snapshot.model);
|
|
300
|
+
this._effectiveThinkingLevel = this.subagentSession.thinkingLevel ?? this._effectiveThinkingLevel;
|
|
301
|
+
|
|
302
|
+
this.flushPendingSteers();
|
|
303
|
+
this.listeners.attachObserver(subscribeSubagentObserver(this.subagentSession, this.state, {
|
|
304
|
+
onCompact: (info) => runSafely(
|
|
305
|
+
"subagent onCompacted observer",
|
|
306
|
+
() => this.execution.observer?.onCompacted?.(this, info),
|
|
307
|
+
),
|
|
308
|
+
}));
|
|
309
|
+
runSafely("subagent onSessionCreated observer", () => this.execution.observer?.onSessionCreated?.(this));
|
|
310
|
+
|
|
311
|
+
const runConfig = this.execution.getRunConfig?.();
|
|
312
|
+
const lifecycle = this.createTurnLifecycle("initial");
|
|
320
313
|
const result = await this.subagentSession.runTurnLoop(this.execution.prompt, {
|
|
321
314
|
maxTurns: this.execution.maxTurns,
|
|
322
315
|
defaultMaxTurns: runConfig?.defaultMaxTurns,
|
|
@@ -326,6 +319,8 @@ export class Subagent {
|
|
|
326
319
|
});
|
|
327
320
|
this.completeRun(result);
|
|
328
321
|
} catch (err) {
|
|
322
|
+
// One outer failure path guarantees terminal state, listener release,
|
|
323
|
+
// workspace cleanup, and the manager's completion funnel.
|
|
329
324
|
this.failRun(err);
|
|
330
325
|
}
|
|
331
326
|
}
|
|
@@ -389,12 +384,15 @@ export class Subagent {
|
|
|
389
384
|
const executionSignal = signal
|
|
390
385
|
? AbortSignal.any([this.abortController.signal, signal])
|
|
391
386
|
: this.abortController.signal;
|
|
392
|
-
this.listeners.attachObserver(subscribeSubagentObserver(subagentSession, this.state, {
|
|
393
|
-
onCompact: (info) => this.execution.observer?.onCompacted?.(this, info),
|
|
394
|
-
}));
|
|
395
|
-
|
|
396
|
-
const lifecycle = this.createTurnLifecycle("resume", executionSignal);
|
|
397
387
|
try {
|
|
388
|
+
this.listeners.attachObserver(subscribeSubagentObserver(subagentSession, this.state, {
|
|
389
|
+
onCompact: (info) => runSafely(
|
|
390
|
+
"subagent onCompacted observer",
|
|
391
|
+
() => this.execution.observer?.onCompacted?.(this, info),
|
|
392
|
+
),
|
|
393
|
+
}));
|
|
394
|
+
|
|
395
|
+
const lifecycle = this.createTurnLifecycle("resume", executionSignal);
|
|
398
396
|
if (lifecycle) {
|
|
399
397
|
const result = await subagentSession.resumeLifecycleTurnLoop(
|
|
400
398
|
prompt,
|
|
@@ -415,7 +413,7 @@ export class Subagent {
|
|
|
415
413
|
this.markError(err);
|
|
416
414
|
} finally {
|
|
417
415
|
this.listeners.release();
|
|
418
|
-
this.execution.observer?.onResumedFinished?.(this);
|
|
416
|
+
runSafely("subagent onResumedFinished observer", () => this.execution.observer?.onResumedFinished?.(this));
|
|
419
417
|
}
|
|
420
418
|
}
|
|
421
419
|
|
|
@@ -527,7 +525,7 @@ export class Subagent {
|
|
|
527
525
|
/** Stop a queued agent through the same terminal observer funnel as a run. */
|
|
528
526
|
stopQueued(): void {
|
|
529
527
|
this.state.stopQueued();
|
|
530
|
-
this.execution.observer?.onRunFinished?.(this);
|
|
528
|
+
runSafely("subagent onRunFinished observer", () => this.execution.observer?.onRunFinished?.(this));
|
|
531
529
|
}
|
|
532
530
|
|
|
533
531
|
/**
|
|
@@ -557,7 +555,7 @@ export class Subagent {
|
|
|
557
555
|
*/
|
|
558
556
|
private flushPendingSteers(): void {
|
|
559
557
|
for (const msg of this._pendingSteers) {
|
|
560
|
-
this.subagentSession?.steer(msg)
|
|
558
|
+
runDetached("subagent buffered steer", () => this.subagentSession?.steer(msg));
|
|
561
559
|
}
|
|
562
560
|
this._pendingSteers = [];
|
|
563
561
|
}
|
|
@@ -580,16 +578,23 @@ export class Subagent {
|
|
|
580
578
|
: result.steered
|
|
581
579
|
? "steered"
|
|
582
580
|
: "completed";
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
this.workspaceBracket.dispose({ status: finalStatus, description: this.description });
|
|
581
|
+
let finalResult = result.responseText;
|
|
582
|
+
try {
|
|
583
|
+
finalResult += this.workspaceBracket.dispose({ status: finalStatus, description: this.description });
|
|
584
|
+
} catch (error) {
|
|
585
|
+
// Workspace teardown belongs to the run's terminal boundary. If it
|
|
586
|
+
// fails, report an error once rather than re-entering disposal from the
|
|
587
|
+
// outer catch path.
|
|
588
|
+
this.failRun(error);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
586
591
|
|
|
587
592
|
if (result.failure) this.markFailed(result.failure, finalResult);
|
|
588
593
|
else if (result.aborted) this.markAborted(finalResult);
|
|
589
594
|
else if (result.steered) this.markSteered(finalResult);
|
|
590
595
|
else this.markCompleted(finalResult);
|
|
591
596
|
|
|
592
|
-
this.execution.observer?.onRunFinished?.(this);
|
|
597
|
+
runSafely("subagent onRunFinished observer", () => this.execution.observer?.onRunFinished?.(this));
|
|
593
598
|
}
|
|
594
599
|
|
|
595
600
|
/** Dispose the wrapped session, firing the `disposed` lifecycle event. */
|
|
@@ -616,7 +621,7 @@ export class Subagent {
|
|
|
616
621
|
this.workspaceBracket.dispose({ status: "error", description: this.description });
|
|
617
622
|
} catch (cleanupErr) { debugLog("workspace dispose on agent error", cleanupErr); }
|
|
618
623
|
|
|
619
|
-
this.execution.observer?.onRunFinished?.(this);
|
|
624
|
+
runSafely("subagent onRunFinished observer", () => this.execution.observer?.onRunFinished?.(this));
|
|
620
625
|
}
|
|
621
626
|
}
|
|
622
627
|
|
|
@@ -53,7 +53,11 @@ export class WorkspaceBracket {
|
|
|
53
53
|
* disposal is desired (e.g. failRun).
|
|
54
54
|
*/
|
|
55
55
|
dispose(outcome: WorkspaceDisposeOutcome): string {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const prepared = this.prepared;
|
|
57
|
+
// Clear the owned resource before calling user code. If disposal throws,
|
|
58
|
+
// an outer failure path must not invoke the provider a second time.
|
|
59
|
+
this.prepared = undefined;
|
|
60
|
+
if (!prepared) return "";
|
|
61
|
+
return prepared.dispose(outcome)?.resultAddendum ?? "";
|
|
58
62
|
}
|
|
59
63
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* the caller forwards itself to its own lifecycle observer via onCompact.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { runSafely } from "#src/debug";
|
|
10
11
|
import type { SubagentState } from "#src/lifecycle/subagent-state";
|
|
11
12
|
import type { CompactionInfo, SubscribableSession } from "#src/types";
|
|
12
13
|
|
|
@@ -34,45 +35,47 @@ export function subscribeSubagentObserver(
|
|
|
34
35
|
options?: SubagentObserverOptions,
|
|
35
36
|
): () => void {
|
|
36
37
|
return session.subscribe((event) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
runSafely("subagent session observer", () => {
|
|
39
|
+
if (event.type === "tool_execution_start") {
|
|
40
|
+
state.addActiveTool(event.toolName);
|
|
41
|
+
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
if (event.type === "tool_execution_end") {
|
|
44
|
+
state.removeActiveTool(event.toolName);
|
|
45
|
+
state.incrementToolUses();
|
|
46
|
+
}
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (event.type === "message_start") {
|
|
49
|
+
state.resetResponseText();
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
if (
|
|
53
|
+
event.type === "message_update" &&
|
|
54
|
+
event.assistantMessageEvent.type === "text_delta"
|
|
55
|
+
) {
|
|
56
|
+
state.appendResponseText(event.assistantMessageEvent.delta);
|
|
57
|
+
}
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
if (event.type === "turn_end") {
|
|
60
|
+
state.incrementTurnCount();
|
|
61
|
+
}
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
64
|
+
const u = event.message.usage;
|
|
65
|
+
state.addUsage({
|
|
66
|
+
input: u.input,
|
|
67
|
+
output: u.output,
|
|
68
|
+
cacheWrite: u.cacheWrite,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
if (event.type === "compaction_end" && !event.aborted && event.result) {
|
|
73
|
+
state.incrementCompactions();
|
|
74
|
+
options?.onCompact?.({
|
|
75
|
+
reason: event.reason,
|
|
76
|
+
tokensBefore: event.result.tokensBefore,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
77
80
|
});
|
|
78
81
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runSafely } from "#src/debug";
|
|
1
2
|
import type { SubagentManagerObserver } from "#src/lifecycle/subagent-manager";
|
|
2
3
|
import { buildEventData, type NotificationSystem } from "#src/observation/notification";
|
|
3
4
|
import type { CompactionInfo, Subagent } from "#src/types";
|
|
@@ -35,11 +36,11 @@ export class SubagentEventsObserver implements SubagentManagerObserver {
|
|
|
35
36
|
|
|
36
37
|
onSubagentStarted(record: Subagent): void {
|
|
37
38
|
// Emit started event when agent transitions to running (including from queue).
|
|
38
|
-
this.emit("subagents:started", {
|
|
39
|
+
runSafely("subagent event emission started", () => this.emit("subagents:started", {
|
|
39
40
|
id: record.id,
|
|
40
41
|
type: record.type,
|
|
41
42
|
description: record.description,
|
|
42
|
-
});
|
|
43
|
+
}));
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
onSubagentCompleted(record: Subagent): void {
|
|
@@ -47,13 +48,13 @@ export class SubagentEventsObserver implements SubagentManagerObserver {
|
|
|
47
48
|
const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
|
|
48
49
|
const eventData = buildEventData(record);
|
|
49
50
|
if (isError) {
|
|
50
|
-
this.emit("subagents:failed", eventData);
|
|
51
|
+
runSafely("subagent event emission failed", () => this.emit("subagents:failed", eventData));
|
|
51
52
|
} else {
|
|
52
|
-
this.emit("subagents:completed", eventData);
|
|
53
|
+
runSafely("subagent event emission completed", () => this.emit("subagents:completed", eventData));
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// Persist final record for cross-extension history reconstruction.
|
|
56
|
-
this.appendEntry("subagents:record", {
|
|
57
|
+
runSafely("subagent record append", () => this.appendEntry("subagents:record", {
|
|
57
58
|
id: record.id,
|
|
58
59
|
type: record.type,
|
|
59
60
|
description: record.description,
|
|
@@ -62,16 +63,16 @@ export class SubagentEventsObserver implements SubagentManagerObserver {
|
|
|
62
63
|
error: record.error,
|
|
63
64
|
startedAt: record.startedAt,
|
|
64
65
|
completedAt: record.completedAt,
|
|
65
|
-
});
|
|
66
|
+
}));
|
|
66
67
|
|
|
67
68
|
// The manager decides whether to nudge (it owns the consumed-result state).
|
|
68
|
-
this.notifications.sendCompletion(record);
|
|
69
|
+
runSafely("subagent completion notification", () => this.notifications.sendCompletion(record));
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
onSubagentResumed(record: Subagent): void {
|
|
72
73
|
const eventData = buildEventData(record);
|
|
73
|
-
this.emit("subagents:resumed", eventData);
|
|
74
|
-
this.appendEntry("subagents:record", {
|
|
74
|
+
runSafely("subagent event emission resumed", () => this.emit("subagents:resumed", eventData));
|
|
75
|
+
runSafely("subagent record append resumed", () => this.appendEntry("subagents:record", {
|
|
75
76
|
id: record.id,
|
|
76
77
|
type: record.type,
|
|
77
78
|
description: record.description,
|
|
@@ -80,28 +81,28 @@ export class SubagentEventsObserver implements SubagentManagerObserver {
|
|
|
80
81
|
error: record.error,
|
|
81
82
|
startedAt: record.startedAt,
|
|
82
83
|
completedAt: record.completedAt,
|
|
83
|
-
});
|
|
84
|
+
}));
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
onSubagentCompacted(record: Subagent, info: CompactionInfo): void {
|
|
87
88
|
// Emit compacted event when agent's session compacts (preserves count on record).
|
|
88
|
-
this.emit("subagents:compacted", {
|
|
89
|
+
runSafely("subagent event emission compacted", () => this.emit("subagents:compacted", {
|
|
89
90
|
id: record.id,
|
|
90
91
|
type: record.type,
|
|
91
92
|
description: record.description,
|
|
92
93
|
reason: info.reason,
|
|
93
94
|
tokensBefore: info.tokensBefore,
|
|
94
95
|
compactionCount: record.compactionCount,
|
|
95
|
-
});
|
|
96
|
+
}));
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
onSubagentCreated(record: Subagent): void {
|
|
99
100
|
// Emit created event for background agents (before limiter admission).
|
|
100
|
-
this.emit("subagents:created", {
|
|
101
|
+
runSafely("subagent event emission created", () => this.emit("subagents:created", {
|
|
101
102
|
id: record.id,
|
|
102
103
|
type: record.type,
|
|
103
104
|
description: record.description,
|
|
104
105
|
isBackground: true,
|
|
105
|
-
});
|
|
106
|
+
}));
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { runSafely } from "#src/debug";
|
|
2
3
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
3
4
|
import type { AgentSpawnConfig } from "#src/lifecycle/subagent-manager";
|
|
4
5
|
import {
|
|
@@ -50,27 +51,29 @@ export async function runForeground(
|
|
|
50
51
|
|
|
51
52
|
let recordRef: Subagent | undefined;
|
|
52
53
|
|
|
53
|
-
const streamUpdate = () => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
54
|
+
const streamUpdate = (): void => {
|
|
55
|
+
runSafely("foreground progress update", () => {
|
|
56
|
+
const toolUses = recordRef?.toolUses ?? 0;
|
|
57
|
+
const details: AgentDetails = {
|
|
58
|
+
...presentation.detailBase,
|
|
59
|
+
toolUses,
|
|
60
|
+
tokens: recordRef ? formatLifetimeTokens(recordRef) : "",
|
|
61
|
+
// Read activity off the record; fall back to safe defaults before onSessionCreated fires
|
|
62
|
+
turnCount: recordRef?.turnCount ?? 1,
|
|
63
|
+
maxTurns: recordRef?.maxTurns ?? execution.effectiveMaxTurns,
|
|
64
|
+
durationMs: Date.now() - startedAt,
|
|
65
|
+
status: "running",
|
|
66
|
+
activity: describeActivity(
|
|
67
|
+
recordRef?.activeTools ?? new Map(),
|
|
68
|
+
recordRef?.responseText ?? "",
|
|
69
|
+
),
|
|
70
|
+
spinnerFrame: spinnerFrame % SPINNER.length,
|
|
71
|
+
};
|
|
72
|
+
onUpdate?.({
|
|
73
|
+
content: [{ type: "text", text: `${toolUses} tool uses...` }],
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Pi SDK ToolCallUpdate details type is not exported
|
|
75
|
+
details: details as any,
|
|
76
|
+
});
|
|
74
77
|
});
|
|
75
78
|
};
|
|
76
79
|
|
|
@@ -81,10 +84,10 @@ export async function runForeground(
|
|
|
81
84
|
streamUpdate();
|
|
82
85
|
}, 500);
|
|
83
86
|
|
|
84
|
-
streamUpdate();
|
|
85
|
-
|
|
86
87
|
let record: Subagent;
|
|
87
88
|
try {
|
|
89
|
+
streamUpdate();
|
|
90
|
+
|
|
88
91
|
record = await manager.spawnAndWait(
|
|
89
92
|
params.snapshot,
|
|
90
93
|
identity.subagentType,
|
|
@@ -107,12 +110,14 @@ export async function runForeground(
|
|
|
107
110
|
},
|
|
108
111
|
);
|
|
109
112
|
} catch (err) {
|
|
110
|
-
|
|
113
|
+
// Only the spawn boundary converts to an error-text result. Post-spawn
|
|
114
|
+
// formatting is internal code; its defects stay thrown tool errors so Pi
|
|
115
|
+
// reports isError rather than a success-shaped result.
|
|
111
116
|
return textResult(err instanceof Error ? err.message : String(err));
|
|
117
|
+
} finally {
|
|
118
|
+
clearInterval(spinnerInterval);
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
clearInterval(spinnerInterval);
|
|
115
|
-
|
|
116
121
|
// Foreground delivery returns the complete outcome inline, so retention can
|
|
117
122
|
// use the shorter consumed-session window.
|
|
118
123
|
record.markConsumed();
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { AgentTypeRegistry } from "#src/config/agent-types";
|
|
10
|
+
import { debugLog, runSafely } from "#src/debug";
|
|
10
11
|
import type { Subagent } from "#src/lifecycle/subagent";
|
|
11
12
|
import type { SubagentManagerObserver } from "#src/lifecycle/subagent-manager";
|
|
12
13
|
import type { CompactionInfo } from "#src/types";
|
|
@@ -121,7 +122,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
121
122
|
// Lifecycle events can arrive before the first tool gives us a UI context.
|
|
122
123
|
// Refresh when that context becomes available, but let update() decide
|
|
123
124
|
// whether any active record actually needs an animation timer.
|
|
124
|
-
if (contextChanged && this.uiCtx && this.backgroundAgents.size > 0) this.
|
|
125
|
+
if (contextChanged && this.uiCtx && this.backgroundAgents.size > 0) this.refresh();
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
/**
|
|
@@ -141,7 +142,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
141
142
|
this.finishedTurnAge.set(id, nextAge);
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
|
-
this.
|
|
145
|
+
this.refresh();
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
// ---- SubagentManagerObserver: react to lifecycle, self-drive the timer ----
|
|
@@ -164,7 +165,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
164
165
|
onSubagentCompleted(record: Subagent) {
|
|
165
166
|
if (!this.trackBackground(record)) return;
|
|
166
167
|
this.finishedTurnAge.set(record.id, 0);
|
|
167
|
-
this.
|
|
168
|
+
this.refresh();
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
/** A resumed subagent started — ensure the update loop is live and render. */
|
|
@@ -178,38 +179,38 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
178
179
|
onSubagentResumed(record: Subagent) {
|
|
179
180
|
if (!this.trackBackground(record)) return;
|
|
180
181
|
this.finishedTurnAge.set(record.id, 0);
|
|
181
|
-
this.
|
|
182
|
+
this.refresh();
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
/** A subagent's session compacted — render to refresh the compaction count. */
|
|
185
186
|
onSubagentCompacted(record: Subagent, _info: CompactionInfo) {
|
|
186
|
-
if (this.trackBackground(record)) this.
|
|
187
|
+
if (this.trackBackground(record)) this.refresh();
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
/** Remove terminal state when the manager clears a parent session. */
|
|
190
191
|
onSubagentCleared(record: Subagent) {
|
|
191
192
|
if (!this.backgroundAgents.delete(record.id)) return;
|
|
192
193
|
this.finishedTurnAge.delete(record.id);
|
|
193
|
-
this.
|
|
194
|
+
this.refresh();
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/** Refresh immediately; update() owns the timer lifecycle. */
|
|
197
198
|
private startLoop() {
|
|
198
199
|
if (!this.uiCtx) return;
|
|
199
|
-
this.
|
|
200
|
+
this.refresh();
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
/** Ensure the widget update timer is running while active records animate. */
|
|
203
204
|
private ensureTimer() {
|
|
204
205
|
this.widgetInterval ??= setInterval(
|
|
205
|
-
() => this.
|
|
206
|
+
() => this.refresh(),
|
|
206
207
|
AgentWidget.STATUS_REFRESH_INTERVAL_MS,
|
|
207
208
|
);
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
/** Stop animated refreshes while leaving any static finished widget intact. */
|
|
211
212
|
private stopTimer() {
|
|
212
|
-
if (this.widgetInterval) {
|
|
213
|
+
if (this.widgetInterval !== undefined) {
|
|
213
214
|
clearInterval(this.widgetInterval);
|
|
214
215
|
this.widgetInterval = undefined;
|
|
215
216
|
}
|
|
@@ -285,12 +286,12 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
285
286
|
*/
|
|
286
287
|
private clearWidget(): void {
|
|
287
288
|
if (this.widgetRegistered) {
|
|
288
|
-
this.uiCtx!.setWidget("agents", undefined);
|
|
289
|
+
runSafely("agent widget clear widget", () => this.uiCtx!.setWidget("agents", undefined));
|
|
289
290
|
this.widgetRegistered = false;
|
|
290
291
|
this.tui = undefined;
|
|
291
292
|
}
|
|
292
293
|
if (this.lastStatusText !== undefined) {
|
|
293
|
-
this.uiCtx!.setStatus("subagents", undefined);
|
|
294
|
+
runSafely("agent widget clear status", () => this.uiCtx!.setStatus("subagents", undefined));
|
|
294
295
|
this.lastStatusText = undefined;
|
|
295
296
|
}
|
|
296
297
|
this.stopTimer();
|
|
@@ -334,21 +335,45 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
334
335
|
// Register widget callback once; subsequent updates use requestRender()
|
|
335
336
|
// which re-invokes render() without replacing the component (avoids layout thrashing).
|
|
336
337
|
if (!this.widgetRegistered) {
|
|
337
|
-
|
|
338
|
-
this.tui
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
338
|
+
try {
|
|
339
|
+
this.uiCtx!.setWidget("agents", (tui, theme) => {
|
|
340
|
+
this.tui = tui;
|
|
341
|
+
return {
|
|
342
|
+
// TUI render callbacks run after the lifecycle callback that
|
|
343
|
+
// registered them; contain SDK/theme failures at this boundary too.
|
|
344
|
+
render: () => {
|
|
345
|
+
try {
|
|
346
|
+
return this.renderWidget(tui, theme);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
debugLog("agent widget render", error);
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
invalidate: () => {
|
|
353
|
+
// Theme changed — force re-registration so factory captures fresh theme.
|
|
354
|
+
this.widgetRegistered = false;
|
|
355
|
+
this.tui = undefined;
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}, { placement: "aboveEditor" });
|
|
359
|
+
this.widgetRegistered = true;
|
|
360
|
+
} catch (error) {
|
|
361
|
+
debugLog("agent widget register", error);
|
|
362
|
+
this.stopTimer();
|
|
363
|
+
}
|
|
349
364
|
} else {
|
|
350
365
|
// Widget already registered — just request a re-render of existing components.
|
|
351
|
-
this.tui?.requestRender();
|
|
366
|
+
runSafely("agent widget request render", () => this.tui?.requestRender());
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Contain UI refresh failures and stop a timer that can no longer make progress. */
|
|
371
|
+
private refresh(): void {
|
|
372
|
+
try {
|
|
373
|
+
this.update();
|
|
374
|
+
} catch (error) {
|
|
375
|
+
debugLog("agent widget update", error);
|
|
376
|
+
this.stopTimer();
|
|
352
377
|
}
|
|
353
378
|
}
|
|
354
379
|
|
|
@@ -356,8 +381,8 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
356
381
|
dispose() {
|
|
357
382
|
this.stopTimer();
|
|
358
383
|
if (this.uiCtx) {
|
|
359
|
-
this.uiCtx
|
|
360
|
-
this.uiCtx
|
|
384
|
+
runSafely("agent widget dispose widget", () => this.uiCtx!.setWidget("agents", undefined));
|
|
385
|
+
runSafely("agent widget dispose status", () => this.uiCtx!.setStatus("subagents", undefined));
|
|
361
386
|
}
|
|
362
387
|
this.widgetRegistered = false;
|
|
363
388
|
this.tui = undefined;
|