pi-subagents-lite 1.4.3 → 1.4.5
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/package.json +1 -1
- package/src/agents/agent-runner.ts +17 -1
- package/src/index.ts +5 -1
- package/src/shell.ts +28 -0
- package/src/spawn/spawn-coordinator.ts +21 -7
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@ import { buildAgentPrompt, type PromptExtras } from "../prompt/prompts.js";
|
|
|
27
27
|
import { preloadSkills, loadSkillMeta, type SkillMeta } from "../prompt/skill-loader.js";
|
|
28
28
|
import { type EnvInfo, type RunCallbacks, type RunTunables, SHORT_ID_LENGTH } from "../types.js";
|
|
29
29
|
import type { SubagentType, SystemPromptMode } from "./types.js";
|
|
30
|
-
import { getStore } from "../shell.js";
|
|
30
|
+
import { getStore, enterSubagentSpawn, exitSubagentSpawn } from "../shell.js";
|
|
31
31
|
import { DEFAULT_GRACE_TURNS, CUSTOM_PROMPT_PATH } from "../config/config-io.js";
|
|
32
32
|
|
|
33
33
|
/** Normalize max turns. undefined or 0 = unlimited, otherwise minimum 1. */
|
|
@@ -516,6 +516,22 @@ export async function runAgent(
|
|
|
516
516
|
type: SubagentType,
|
|
517
517
|
prompt: string,
|
|
518
518
|
options: RunOptions,
|
|
519
|
+
): Promise<RunResult> {
|
|
520
|
+
// Bracket the whole subagent lifecycle so the extension factory can detect
|
|
521
|
+
// it's being re-loaded inside a subagent and avoid clobbering the parent shell.
|
|
522
|
+
enterSubagentSpawn();
|
|
523
|
+
try {
|
|
524
|
+
return await runAgentImpl(ctx, type, prompt, options);
|
|
525
|
+
} finally {
|
|
526
|
+
exitSubagentSpawn();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function runAgentImpl(
|
|
531
|
+
ctx: ExtensionContext,
|
|
532
|
+
type: SubagentType,
|
|
533
|
+
prompt: string,
|
|
534
|
+
options: RunOptions,
|
|
519
535
|
): Promise<RunResult> {
|
|
520
536
|
const store = getStore();
|
|
521
537
|
const config = getConfig(type, store.agent.loadSkillsImplicitly, store.agent.loadExtensionsImplicitly);
|
package/src/index.ts
CHANGED
|
@@ -24,11 +24,15 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
27
|
-
import { setPiInstance } from "./shell.js";
|
|
27
|
+
import { setPiInstance, isInsideSubagentSpawn } from "./shell.js";
|
|
28
28
|
import { registerTools } from "./registration.js";
|
|
29
29
|
import { setupEventListeners } from "./events.js";
|
|
30
30
|
|
|
31
31
|
export default function (pi: ExtensionAPI) {
|
|
32
|
+
// Subagents re-load this extension under their own pi/runtime. Stay inert so
|
|
33
|
+
// we never overwrite the parent-owned shell (pi, sessionCtx, manager, ...).
|
|
34
|
+
// The completion nudge relies on those still pointing at the parent session.
|
|
35
|
+
if (isInsideSubagentSpawn()) return;
|
|
32
36
|
setPiInstance(pi);
|
|
33
37
|
registerTools(pi);
|
|
34
38
|
setupEventListeners(pi);
|
package/src/shell.ts
CHANGED
|
@@ -99,3 +99,31 @@ export function setWidget(w: AgentWidget | null): void {
|
|
|
99
99
|
export function setCoordinator(c: SpawnCoordinator | null): void {
|
|
100
100
|
shell.coordinator = c;
|
|
101
101
|
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Subagent spawn context
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Nesting depth of in-flight subagent spawns.
|
|
109
|
+
*
|
|
110
|
+
* Subagents are created via runAgent(), which re-loads this extension fresh
|
|
111
|
+
* (new runtime, new pi/ctx). Without protection those re-loads clobber the
|
|
112
|
+
* parent-owned shell singletons below, so the nudge would later route to a
|
|
113
|
+
* dead subagent session instead of the parent. The factory checks this flag
|
|
114
|
+
* and stays inert while a subagent is spawning.
|
|
115
|
+
*/
|
|
116
|
+
let subagentSpawnDepth = 0;
|
|
117
|
+
|
|
118
|
+
export function enterSubagentSpawn(): void {
|
|
119
|
+
subagentSpawnDepth++;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function exitSubagentSpawn(): void {
|
|
123
|
+
if (subagentSpawnDepth > 0) subagentSpawnDepth--;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** True while a subagent is being spawned (factory/session_start run in subagent context). */
|
|
127
|
+
export function isInsideSubagentSpawn(): boolean {
|
|
128
|
+
return subagentSpawnDepth > 0;
|
|
129
|
+
}
|
|
@@ -58,6 +58,9 @@ export class SpawnCoordinator {
|
|
|
58
58
|
/** Agent IDs spawned as background — only these trigger a nudge on completion. */
|
|
59
59
|
private backgroundAgentIds = new Set<string>();
|
|
60
60
|
|
|
61
|
+
/** Captured ExtensionContext per background agent, bound to the spawning session. */
|
|
62
|
+
private backgroundContexts = new Map<string, ExtensionContext>();
|
|
63
|
+
|
|
61
64
|
/** Pending nudge agent IDs, batched within the delay window. */
|
|
62
65
|
private pendingNudges = new Set<string>();
|
|
63
66
|
|
|
@@ -106,14 +109,12 @@ export class SpawnCoordinator {
|
|
|
106
109
|
widget.ensureTimer();
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
// Track background agents
|
|
112
|
+
// Track background agents + capture ctx for fallback notification
|
|
110
113
|
if (intent.runInBackground) {
|
|
111
114
|
this.backgroundAgentIds.add(agentId);
|
|
115
|
+
this.backgroundContexts.set(agentId, ctx);
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
// pi is read from shell at nudge time, not stored here
|
|
115
|
-
// (avoids stale ctx after session replacement or reload)
|
|
116
|
-
|
|
117
118
|
const record = this.manager.getRecord(agentId)!;
|
|
118
119
|
|
|
119
120
|
if (!intent.runInBackground) {
|
|
@@ -181,6 +182,7 @@ export class SpawnCoordinator {
|
|
|
181
182
|
this.pendingNudges.clear();
|
|
182
183
|
this.liveViews.clear();
|
|
183
184
|
this.backgroundAgentIds.clear();
|
|
185
|
+
this.backgroundContexts.clear();
|
|
184
186
|
this.disposed = true;
|
|
185
187
|
}
|
|
186
188
|
|
|
@@ -242,9 +244,21 @@ export class SpawnCoordinator {
|
|
|
242
244
|
},
|
|
243
245
|
);
|
|
244
246
|
} catch (error) {
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
|
|
247
|
+
// sendMessage failed (shared runtime overwritten by subagent bindCore).
|
|
248
|
+
// Fall back to UI notification using the captured spawning-session context.
|
|
249
|
+
const spawnCtx = this.backgroundContexts.get(agentId);
|
|
250
|
+
if (spawnCtx?.ui?.notify) {
|
|
251
|
+
try {
|
|
252
|
+
spawnCtx.ui.notify(
|
|
253
|
+
`[Subagent "${record.display.type}" ${record.lifecycle.status}] Result available`,
|
|
254
|
+
"info",
|
|
255
|
+
);
|
|
256
|
+
} catch {
|
|
257
|
+
// ctx may also be stale if session was replaced
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} finally {
|
|
261
|
+
this.backgroundContexts.delete(agentId);
|
|
248
262
|
}
|
|
249
263
|
}
|
|
250
264
|
}
|