pi-subagents 0.82.1 → 0.83.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/CHANGELOG.md +29 -0
- package/bundle/agents.js +4 -4
- package/bundle/background-work.js +1 -1
- package/bundle/child-tool-plan.js +3 -3
- package/bundle/{chunk-FXUXCX2K.js → chunk-4BW55BTL.js} +6 -6
- package/bundle/{chunk-PBK2I7WY.js → chunk-527E3D2J.js} +6 -6
- package/bundle/{chunk-46XKCPJR.js → chunk-5ESXK62X.js} +7 -1073
- package/bundle/{chunk-UTT254JE.js → chunk-6B4ITO2W.js} +8 -4
- package/bundle/{chunk-CIZQXJ5Q.js → chunk-7F5SK2U3.js} +39 -6
- package/bundle/{chunk-OYF6AFDY.js → chunk-BKFWQ642.js} +2 -2
- package/bundle/{chunk-ZIV4J4HG.js → chunk-CDI3GMNV.js} +47 -30
- package/bundle/{chunk-3JCDWF4V.js → chunk-CGXWSNPA.js} +12 -18
- package/bundle/{chunk-JYUKW2SY.js → chunk-CNLDYEH4.js} +12 -12
- package/bundle/{chunk-4SDU2BZX.js → chunk-CSSZRTKY.js} +2 -2
- package/bundle/{chunk-VSUSLDNW.js → chunk-CZTMUBUI.js} +10 -8
- package/bundle/{chunk-IVAE5Z6Q.js → chunk-HJJAVHFB.js} +28 -24
- package/bundle/{chunk-GJECHSAR.js → chunk-HLE22FSH.js} +2 -2
- package/bundle/{chunk-4Y2ROR7L.js → chunk-HXVKGS33.js} +5 -5
- package/bundle/{chunk-5LFLVSTM.js → chunk-JEZOSEWG.js} +2 -0
- package/bundle/{chunk-YKEWIB4M.js → chunk-JHZ6DZ5M.js} +1 -1
- package/bundle/{chunk-QE4AGIMG.js → chunk-LDMXJIA6.js} +457 -657
- package/bundle/{chunk-43SCHIMD.js → chunk-MBDOYZ2V.js} +1 -1
- package/bundle/{chunk-XSWOTIA3.js → chunk-MHX22USZ.js} +1 -1
- package/bundle/{chunk-6XJB7PIX.js → chunk-OOFW75PV.js} +4 -4
- package/bundle/{chunk-RMQFMMAT.js → chunk-P2NCCTZP.js} +231 -10
- package/bundle/{chunk-UCPP4GNU.js → chunk-RZJN63GU.js} +12 -5
- package/bundle/{chunk-G4GZD3OJ.js → chunk-TJQJ3MEA.js} +10 -10
- package/bundle/{chunk-5TYMSZX3.js → chunk-VTMNBPCQ.js} +2 -2
- package/bundle/{chunk-52UBALJL.js → chunk-WG2OTTAK.js} +2 -2
- package/bundle/{chunk-N2BN7BF5.js → chunk-XA3DY6UG.js} +7 -5
- package/bundle/{chunk-PDZWJCHD.js → chunk-XXKG3P2W.js} +9 -6
- package/bundle/{chunk-PLCCBLPY.js → chunk-Y3I6H6DV.js} +2 -2
- package/bundle/control-channel.js +3 -3
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +3 -3
- package/bundle/preflight.js +9 -9
- package/bundle/project-panes.js +4 -4
- package/bundle/shared-types.js +1 -1
- package/docs/configuration.md +1 -1
- package/docs/extension-api.md +19 -4
- package/docs/observability.md +1 -0
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/src/agents/agent-management.js +8 -14
- package/src/agents/agent-serializer.js +1 -2
- package/src/agents/agents.js +19 -3
- package/src/api/background-work.d.ts +9 -6
- package/src/api/background-work.js +14 -7
- package/src/extension/index.js +4 -1
- package/src/extension/public-execution.d.ts +2 -2
- package/src/extension/public-execution.js +3 -3
- package/src/extension/reply-workflow-script.js +4 -5
- package/src/extension/rpc.js +3 -1
- package/src/extension/subagent-options.js +17 -4
- package/src/runs/background/async-execution.js +4 -1
- package/src/runs/background/async-job-tracker.js +2 -1
- package/src/runs/background/auto-drain.js +2 -3
- package/src/runs/background/fleet-view.js +1 -1
- package/src/runs/background/run-child-session.js +22 -6
- package/src/runs/background/subagent-wait.d.ts +2 -1
- package/src/runs/background/subagent-wait.js +5 -2
- package/src/runs/foreground/execution.js +22 -6
- package/src/runs/foreground/subagent-executor.js +2 -5
- package/src/runs/shared/child-launch.js +5 -2
- package/src/runs/shared/child-session.d.ts +6 -0
- package/src/runs/shared/child-session.js +20 -4
- package/src/runs/shared/herdr-machine.d.ts +2 -0
- package/src/runs/shared/herdr-machine.js +2 -0
- package/src/runs/shared/herdr-placed-run.js +11 -4
- package/src/runs/shared/subagent-prompt-runtime.js +11 -17
- package/src/shared/disabled-features.d.ts +3 -1
- package/src/shared/disabled-features.js +11 -3
- package/src/shared/types.d.ts +3 -1
- package/src/shared/types.js +2 -0
- package/src/workflows/scripted-workflow.js +9 -1070
- package/workflow-script-worker.cjs +1067 -0
|
@@ -69,6 +69,8 @@ export function runChildSession(input) {
|
|
|
69
69
|
let queuedDrainHold = false;
|
|
70
70
|
let compactionStartedReceived = false;
|
|
71
71
|
let afterCompactionSettlement = false;
|
|
72
|
+
let terminalAgentEndReceived = false;
|
|
73
|
+
let compactionAfterFinalOpen = false;
|
|
72
74
|
let finalDrainTimer;
|
|
73
75
|
let finalHardFinishTimer;
|
|
74
76
|
let watchdogTailTimer;
|
|
@@ -216,7 +218,8 @@ export function runChildSession(input) {
|
|
|
216
218
|
finalDrainTimer = setTimeout(() => {
|
|
217
219
|
if (settled || promptSettled)
|
|
218
220
|
return;
|
|
219
|
-
|
|
221
|
+
// Pi runs auto-compaction after the final agent_end and only then emits agent_settled.
|
|
222
|
+
if (input.launch.capture.finalDrainHeld() || observeQueuedDrainHold() || compactionAfterFinalOpen) {
|
|
220
223
|
finalDrainTimer = undefined;
|
|
221
224
|
armFinalDrainTimer();
|
|
222
225
|
return;
|
|
@@ -231,16 +234,22 @@ export function runChildSession(input) {
|
|
|
231
234
|
return;
|
|
232
235
|
settle(undefined, true);
|
|
233
236
|
}, HARD_FINISH_MS);
|
|
234
|
-
finalHardFinishTimer.unref?.();
|
|
235
237
|
}, FINAL_STOP_GRACE_MS);
|
|
236
|
-
finalDrainTimer.unref?.();
|
|
237
238
|
}
|
|
238
|
-
const applyChildLifecycle = (action) => {
|
|
239
|
+
const applyChildLifecycle = (action, event) => {
|
|
239
240
|
if (action === "cancel-drain") {
|
|
240
|
-
|
|
241
|
+
// After a clean final stop, a non-retrying agent_end may be the child's last event
|
|
242
|
+
// (Pi may not emit agent_settled), so it re-arms the drain instead of cancelling it.
|
|
243
|
+
terminalAgentEndReceived = event?.type === "agent_end" && event.willRetry !== true && cleanTerminalAssistantStopReceived;
|
|
241
244
|
agentSettledReceived = false;
|
|
242
245
|
clearFinalDrainTimers();
|
|
243
246
|
clearWatchdogTailTimer();
|
|
247
|
+
if (terminalAgentEndReceived) {
|
|
248
|
+
startFinalDrain();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
cleanTerminalAssistantStopReceived = false;
|
|
252
|
+
compactionAfterFinalOpen = false;
|
|
244
253
|
return;
|
|
245
254
|
}
|
|
246
255
|
if (action === "start-drain")
|
|
@@ -314,11 +323,18 @@ export function runChildSession(input) {
|
|
|
314
323
|
compactionStartedReceived = true;
|
|
315
324
|
if (agentSettledReceived)
|
|
316
325
|
afterCompactionSettlement = true;
|
|
326
|
+
if (terminalAgentEndReceived)
|
|
327
|
+
compactionAfterFinalOpen = true;
|
|
317
328
|
}
|
|
318
329
|
if (event.type === "compaction_end" && event.willRetry === true) {
|
|
319
330
|
compactionStartedReceived = false;
|
|
320
331
|
afterCompactionSettlement = false;
|
|
321
332
|
}
|
|
333
|
+
if (event.type === "compaction_end" && event.willRetry !== true && compactionAfterFinalOpen) {
|
|
334
|
+
compactionAfterFinalOpen = false;
|
|
335
|
+
clearFinalDrainTimers();
|
|
336
|
+
startFinalDrain();
|
|
337
|
+
}
|
|
322
338
|
if (event.type === "turn_start" || event.type === "agent_start" || event.type === "auto_retry_start") {
|
|
323
339
|
queuedDrainHold = false;
|
|
324
340
|
}
|
|
@@ -331,7 +347,7 @@ export function runChildSession(input) {
|
|
|
331
347
|
agentSettledReceived = true;
|
|
332
348
|
afterCompactionSettlement = compactionStartedReceived;
|
|
333
349
|
}
|
|
334
|
-
applyChildLifecycle(lifecycleAction);
|
|
350
|
+
applyChildLifecycle(lifecycleAction, event);
|
|
335
351
|
if (isChildWatchdogStatusEvent(event)) {
|
|
336
352
|
if (!input.childWatchdog)
|
|
337
353
|
return;
|
|
@@ -100,7 +100,7 @@ export interface SubagentWaitDeps {
|
|
|
100
100
|
};
|
|
101
101
|
/** Injectable provider protocol surfaces for deterministic tests. */
|
|
102
102
|
backgroundWork?: {
|
|
103
|
-
snapshot(
|
|
103
|
+
snapshot(sessionIds: readonly string[], nowMs: number): BackgroundWorkSnapshot;
|
|
104
104
|
wakeChannels(): readonly string[];
|
|
105
105
|
};
|
|
106
106
|
/**
|
|
@@ -111,6 +111,7 @@ export interface SubagentWaitDeps {
|
|
|
111
111
|
*/
|
|
112
112
|
events?: WaitEventBus;
|
|
113
113
|
}
|
|
114
|
+
export declare function backgroundWorkForSession(deps: Pick<SubagentWaitDeps, "state" | "backgroundWork">, nowMs: number): BackgroundWorkSnapshot;
|
|
114
115
|
export declare function waitRunScopes(deps: Pick<SubagentWaitDeps, "asyncDirRoot" | "resultsDir" | "nestedRootRunId">): Array<{
|
|
115
116
|
asyncDirRoot: string;
|
|
116
117
|
resultsDir: string;
|
|
@@ -181,11 +181,14 @@ function hasSupervisorTool(run) {
|
|
|
181
181
|
function backgroundWorkIdentity(item) {
|
|
182
182
|
return `${item.provider}\0${item.sessionId}\0${item.id}`;
|
|
183
183
|
}
|
|
184
|
-
function backgroundWorkForSession(deps, nowMs) {
|
|
184
|
+
export function backgroundWorkForSession(deps, nowMs) {
|
|
185
185
|
const sessionId = deps.state.currentSessionId;
|
|
186
186
|
if (!sessionId)
|
|
187
187
|
throw new Error("bg_wait requires an active session identity to scope background work safely.");
|
|
188
|
-
|
|
188
|
+
// Providers may key items by the session file path or by getSessionId(); accept both.
|
|
189
|
+
const piSessionId = deps.state.supervisorOwnerSessionId;
|
|
190
|
+
const sessionIds = piSessionId ? [sessionId, piSessionId] : [sessionId];
|
|
191
|
+
return deps.backgroundWork?.snapshot(sessionIds, nowMs) ?? snapshotBackgroundWork(sessionIds, nowMs);
|
|
189
192
|
}
|
|
190
193
|
export function waitRunScopes(deps) {
|
|
191
194
|
return [
|
|
@@ -561,6 +561,8 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
561
561
|
let agentSettledReceived = false;
|
|
562
562
|
let queuedDrainHold = false;
|
|
563
563
|
let compactionStartedReceived = false;
|
|
564
|
+
let terminalAgentEndReceived = false;
|
|
565
|
+
let compactionAfterFinalOpen = false;
|
|
564
566
|
let finalDrainTimer;
|
|
565
567
|
let finalHardFinishTimer;
|
|
566
568
|
let watchdogTailTimer;
|
|
@@ -608,7 +610,8 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
608
610
|
finalDrainTimer = setTimeout(() => {
|
|
609
611
|
if (lifecycleFinished || sessionSettled)
|
|
610
612
|
return;
|
|
611
|
-
|
|
613
|
+
// Pi runs auto-compaction after the final agent_end and only then emits agent_settled.
|
|
614
|
+
if (capture.finalDrainHeld() || observeQueuedDrainHold() || compactionAfterFinalOpen) {
|
|
612
615
|
finalDrainTimer = undefined;
|
|
613
616
|
armFinalDrainTimer();
|
|
614
617
|
return;
|
|
@@ -623,9 +626,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
623
626
|
return;
|
|
624
627
|
settle(undefined, true);
|
|
625
628
|
}, HARD_FINISH_MS);
|
|
626
|
-
finalHardFinishTimer.unref?.();
|
|
627
629
|
}, FINAL_STOP_GRACE_MS);
|
|
628
|
-
finalDrainTimer.unref?.();
|
|
629
630
|
};
|
|
630
631
|
function armWatchdogTail() {
|
|
631
632
|
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || lifecycleFinished || sessionSettled)
|
|
@@ -644,12 +645,20 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
644
645
|
}, childWatchdog?.watchdogTailTimeoutMs ?? 120_000);
|
|
645
646
|
watchdogTailTimer.unref?.();
|
|
646
647
|
}
|
|
647
|
-
const applyChildLifecycle = (action) => {
|
|
648
|
+
const applyChildLifecycle = (action, event) => {
|
|
648
649
|
if (action === "cancel-drain") {
|
|
649
|
-
|
|
650
|
+
// After a clean final stop, a non-retrying agent_end may be the child's last event
|
|
651
|
+
// (Pi may not emit agent_settled), so it re-arms the drain instead of cancelling it.
|
|
652
|
+
terminalAgentEndReceived = event?.type === "agent_end" && event.willRetry !== true && cleanTerminalAssistantStopReceived;
|
|
650
653
|
agentSettledReceived = false;
|
|
651
654
|
clearFinalDrainTimers();
|
|
652
655
|
clearWatchdogTailTimer();
|
|
656
|
+
if (terminalAgentEndReceived) {
|
|
657
|
+
startFinalDrain();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
cleanTerminalAssistantStopReceived = false;
|
|
661
|
+
compactionAfterFinalOpen = false;
|
|
653
662
|
return;
|
|
654
663
|
}
|
|
655
664
|
if (action === "start-drain")
|
|
@@ -925,11 +934,18 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
925
934
|
compactionStartedReceived = true;
|
|
926
935
|
if (agentSettledReceived)
|
|
927
936
|
afterCompactionSettlement = true;
|
|
937
|
+
if (terminalAgentEndReceived)
|
|
938
|
+
compactionAfterFinalOpen = true;
|
|
928
939
|
}
|
|
929
940
|
if (evt.type === "compaction_end" && evt.willRetry === true) {
|
|
930
941
|
compactionStartedReceived = false;
|
|
931
942
|
afterCompactionSettlement = false;
|
|
932
943
|
}
|
|
944
|
+
if (evt.type === "compaction_end" && evt.willRetry !== true && compactionAfterFinalOpen) {
|
|
945
|
+
compactionAfterFinalOpen = false;
|
|
946
|
+
clearFinalDrainTimers();
|
|
947
|
+
startFinalDrain();
|
|
948
|
+
}
|
|
933
949
|
if (evt.type === "turn_start" || evt.type === "agent_start" || evt.type === "auto_retry_start") {
|
|
934
950
|
queuedDrainHold = false;
|
|
935
951
|
}
|
|
@@ -956,7 +972,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
956
972
|
agentSettledReceived = true;
|
|
957
973
|
afterCompactionSettlement = compactionStartedReceived;
|
|
958
974
|
}
|
|
959
|
-
applyChildLifecycle(lifecycleAction);
|
|
975
|
+
applyChildLifecycle(lifecycleAction, evt);
|
|
960
976
|
if (isChildWatchdogStatusEvent(evt)) {
|
|
961
977
|
if (!childWatchdog)
|
|
962
978
|
return;
|
|
@@ -17,7 +17,7 @@ import { buildDoctorReport } from "../../extension/doctor.js";
|
|
|
17
17
|
import { readSubagentGuide } from "../../extension/subagent-guide.js";
|
|
18
18
|
import { isWorkflowScriptPath, normalizePublicSubagentExecution, validateWorkflowCapacityOverrides } from "../../extension/public-execution.js";
|
|
19
19
|
import { readReplyWorkflowScript } from "../../extension/reply-workflow-script.js";
|
|
20
|
-
import { disabledFeatureNotice, disabledFeatureUseError, resolveDisabledFeatureSurface } from "../../shared/disabled-features.js";
|
|
20
|
+
import { disabledFeatureNotice, disabledFeatureSectionNotice, disabledFeatureUseError, resolveDisabledFeatureSurface } from "../../shared/disabled-features.js";
|
|
21
21
|
import { runSync } from "./execution.js";
|
|
22
22
|
import { commandAction } from "./command-action.js";
|
|
23
23
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.js";
|
|
@@ -6689,10 +6689,7 @@ export function createSubagentExecutor(deps) {
|
|
|
6689
6689
|
if (action === "guide") {
|
|
6690
6690
|
try {
|
|
6691
6691
|
const guide = readSubagentGuide(paramsWithResolvedCwd.topic);
|
|
6692
|
-
const
|
|
6693
|
-
const fullNotice = topic === "tool-reference" ? disabledFeatureNotice(disabledFeatures) : undefined;
|
|
6694
|
-
// Sections are capped at 8,000 chars, so they get a one-line pointer instead of the full list.
|
|
6695
|
-
const notice = fullNotice && section !== undefined ? "Some options and actions documented here are disabled by config in this session; guide topic tool-reference lists them." : fullNotice;
|
|
6692
|
+
const notice = paramsWithResolvedCwd.topic?.includes("/") ? disabledFeatureSectionNotice(disabledFeatures) : disabledFeatureNotice(disabledFeatures);
|
|
6696
6693
|
return {
|
|
6697
6694
|
content: [{ type: "text", text: notice ? `${notice}\n\n${guide}` : guide }],
|
|
6698
6695
|
details: { mode: "management", results: [] },
|
|
@@ -45,7 +45,7 @@ function escapeXmlAttr(value) {
|
|
|
45
45
|
function inheritedCapabilityCeiling(inherited) {
|
|
46
46
|
return inherited?.capabilityCeiling;
|
|
47
47
|
}
|
|
48
|
-
/** Environment values external child extensions read
|
|
48
|
+
/** Environment values external child extensions read in the runner, which hosts only children. */
|
|
49
49
|
function childProcessEnv(input, toolPlan) {
|
|
50
50
|
const env = {};
|
|
51
51
|
env[PI_SUBAGENT_EXTENSION_BINDINGS_ENV] = encodeExtensionBindings(input.extensionBindings);
|
|
@@ -222,7 +222,10 @@ export function buildInProcessChildLaunch(input) {
|
|
|
222
222
|
requiredExtensions: toolPlan.requiredExtensions,
|
|
223
223
|
ambientExtensions,
|
|
224
224
|
hooks: capturedHooks.hooks,
|
|
225
|
-
|
|
225
|
+
// A foreground child gets only its own bindings, so it never sees those of the session that launched it.
|
|
226
|
+
...(input.host === "runner"
|
|
227
|
+
? { processEnv: childProcessEnv(input, toolPlan) }
|
|
228
|
+
: { processEnv: { [PI_SUBAGENT_EXTENSION_BINDINGS_ENV]: encodeExtensionBindings(input.extensionBindings) }, restoreProcessEnv: true }),
|
|
226
229
|
runtime: config,
|
|
227
230
|
noSkills: !input.inheritSkills,
|
|
228
231
|
noContextFiles: !input.inheritProjectContext,
|
|
@@ -80,6 +80,12 @@ export interface ChildSessionLaunch {
|
|
|
80
80
|
* window one at a time. An undefined value removes the variable.
|
|
81
81
|
*/
|
|
82
82
|
processEnv?: Record<string, string | undefined>;
|
|
83
|
+
/**
|
|
84
|
+
* Restore the hosting process's previous values of `processEnv` once that
|
|
85
|
+
* window ends, so a child hosted in the parent's own process leaves the
|
|
86
|
+
* parent's environment as it found it.
|
|
87
|
+
*/
|
|
88
|
+
restoreProcessEnv?: boolean;
|
|
83
89
|
/** The typed runtime config the hooks were built from; informational for factories. */
|
|
84
90
|
runtime: ChildRuntimeConfig;
|
|
85
91
|
onExtensionError?: (error: ChildSessionExtensionError) => void;
|
|
@@ -153,15 +153,18 @@ function resetExtensionCacheOnReload(loader) {
|
|
|
153
153
|
loader.loaded = true;
|
|
154
154
|
return true;
|
|
155
155
|
}
|
|
156
|
+
/** Applies `values` to `process.env` and returns a function that puts back the values they replaced. */
|
|
156
157
|
function applyProcessEnv(values) {
|
|
157
158
|
if (!values)
|
|
158
|
-
return;
|
|
159
|
+
return () => { };
|
|
160
|
+
const previous = Object.fromEntries(Object.keys(values).map((name) => [name, process.env[name]]));
|
|
159
161
|
for (const [name, value] of Object.entries(values)) {
|
|
160
162
|
if (value === undefined)
|
|
161
163
|
delete process.env[name];
|
|
162
164
|
else
|
|
163
165
|
process.env[name] = value;
|
|
164
166
|
}
|
|
167
|
+
return () => applyProcessEnv(previous);
|
|
165
168
|
}
|
|
166
169
|
/**
|
|
167
170
|
* Pi's built-in MCP extension registers server tools with the configured exposure, `codemode` by
|
|
@@ -458,7 +461,11 @@ export function createDefaultChildSessionFactory(options = {}) {
|
|
|
458
461
|
await commands?.shutdown();
|
|
459
462
|
const runner = session.extensionRunner;
|
|
460
463
|
if (runner.hasHandlers("session_shutdown")) {
|
|
461
|
-
|
|
464
|
+
// Ref'd so a detached runner with nothing else pending stays alive until the bound
|
|
465
|
+
// ends and still publishes its result; cleared as soon as the handlers finish.
|
|
466
|
+
let bound;
|
|
467
|
+
await Promise.race([runner.emit({ type: "session_shutdown", reason: "quit" }), new Promise((resolve) => { bound = setTimeout(resolve, shutdownTimeoutMs); })])
|
|
468
|
+
.finally(() => clearTimeout(bound));
|
|
462
469
|
}
|
|
463
470
|
}
|
|
464
471
|
catch (error) {
|
|
@@ -469,8 +476,7 @@ export function createDefaultChildSessionFactory(options = {}) {
|
|
|
469
476
|
}
|
|
470
477
|
};
|
|
471
478
|
let inheritedProviders = new Set();
|
|
472
|
-
const
|
|
473
|
-
applyProcessEnv(launch.processEnv);
|
|
479
|
+
const openSession = async () => {
|
|
474
480
|
if (!resetExtensionCacheOnReload(loader) && (launch.ambientExtensions || launch.extensionPaths.length))
|
|
475
481
|
launch.onExtensionError?.({ extensionPath: "<loader>", event: "load", error: new Error("pi's extension cache reset is unavailable; extensions loaded into this child share module state with other sessions in this process.") });
|
|
476
482
|
await loader.reload();
|
|
@@ -559,6 +565,16 @@ export function createDefaultChildSessionFactory(options = {}) {
|
|
|
559
565
|
}
|
|
560
566
|
return session;
|
|
561
567
|
};
|
|
568
|
+
const open = async () => {
|
|
569
|
+
const restoreEnv = applyProcessEnv(launch.processEnv);
|
|
570
|
+
try {
|
|
571
|
+
return await openSession();
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
if (launch.restoreProcessEnv)
|
|
575
|
+
restoreEnv();
|
|
576
|
+
}
|
|
577
|
+
};
|
|
562
578
|
const opened = loading.catch(() => { }).then(open);
|
|
563
579
|
loading = opened;
|
|
564
580
|
const session = await opened;
|
|
@@ -42,6 +42,8 @@ export interface PreparedHerdrMachineExternalCliRun {
|
|
|
42
42
|
export declare function resolveHerdrMachinePlacement(input: ResolveHerdrMachinePlacementInput): HerdrMachinePlacement;
|
|
43
43
|
/** `local` resolves to a Rex pane on this machine; a name with a `rex` entry in user settings to a Rex pane on that SSH host; any other value to a Herdr saved machine. */
|
|
44
44
|
export declare function resolveMachinePlacement(input: ResolveHerdrMachinePlacementInput): MachinePlacement;
|
|
45
|
+
/** A placed Pi keeps its transcript on the pane's machine, so it can't start from a local one. */
|
|
46
|
+
export declare const PLACED_RESTORE_UNSUPPORTED = "Pane-native remote Pi does not support fork, resume, or revival; use fresh context.";
|
|
45
47
|
export declare function formatHerdrMachineRunnerUnsupported(input: {
|
|
46
48
|
machine?: string;
|
|
47
49
|
agentName: string;
|
|
@@ -233,6 +233,8 @@ export function resolveMachinePlacement(input) {
|
|
|
233
233
|
return { machine: { provider: "rex", id: requested, target: settings.rex.ssh, cwd: validateRemoteCwd(remoteMachineCwd(input.stepCwd, settings.cwd, requested), requested) } };
|
|
234
234
|
return resolveHerdrMachinePlacement(input);
|
|
235
235
|
}
|
|
236
|
+
/** A placed Pi keeps its transcript on the pane's machine, so it can't start from a local one. */
|
|
237
|
+
export const PLACED_RESTORE_UNSUPPORTED = "Pane-native remote Pi does not support fork, resume, or revival; use fresh context.";
|
|
236
238
|
export function formatHerdrMachineRunnerUnsupported(input) {
|
|
237
239
|
if (input.machine === undefined)
|
|
238
240
|
return undefined;
|
|
@@ -8,6 +8,7 @@ import { encodeHerdrPiFrame, HERDR_PI_MODE_ENV, HERDR_PI_PROTOCOL, HERDR_PI_RUN_
|
|
|
8
8
|
import { discoverPlacedBridgeManifest } from "./placed-run-owner.js";
|
|
9
9
|
import { RexPlacedRunOwner } from "./rex-pane-host.js";
|
|
10
10
|
import { isRexLocalMachine } from "./rex-machine.js";
|
|
11
|
+
import { PLACED_RESTORE_UNSUPPORTED } from "./herdr-machine.js";
|
|
11
12
|
/** The pane half comes from the host; the bridge half (run, package, protocol, native session, evidence cursor) is host-neutral. */
|
|
12
13
|
export function reconcileHerdrReconnect(identity, candidate, lastEvidenceCursor, host = herdrPaneHost) {
|
|
13
14
|
return host.reconcile(identity, candidate, () => { const bridge = candidate.bridge; if (bridge.runId !== identity.runId || bridge.nativeSessionId !== identity.nativeSessionId || bridge.packageVersion !== packageJson.version || bridge.protocol !== HERDR_PI_PROTOCOL)
|
|
@@ -35,16 +36,18 @@ export async function boundedHerdrReconnect(identity, lastEvidenceCursor, attemp
|
|
|
35
36
|
}
|
|
36
37
|
function safeRunId(value) { const prefix = value?.replace(/[^A-Za-z0-9_-]/gu, "_").slice(0, 48) ?? "run"; return validateHerdrPiRunId(`${prefix}_${randomBytes(8).toString("hex")}`); }
|
|
37
38
|
export function serializeHerdrPiLaunch(launch) {
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
// Every child is handed a local session file path; only one that already exists (a fork or a
|
|
40
|
+
// resumed transcript) asks the pane to restore history. A fresh child's path is never written.
|
|
41
|
+
if (launch.storage.kind === "file" && fs.existsSync(launch.storage.sessionFile))
|
|
42
|
+
throw new Error(PLACED_RESTORE_UNSUPPORTED);
|
|
40
43
|
if (launch.extensionPaths.length || launch.requiredExtensions?.length)
|
|
41
44
|
throw new Error("Pane-native remote Pi cannot transfer local extension paths; install/configure pi-subagents on the remote machine.");
|
|
42
45
|
if (launch.processEnv && Object.values(launch.processEnv).some((value) => value !== undefined && value !== "__none__"))
|
|
43
46
|
throw new Error("Pane-native remote Pi cannot transfer local bindings, MCP selections, or process environment.");
|
|
44
47
|
if (launch.runtime.nestedRoute || launch.runtime.fanoutChild || launch.runtime.depth > 1)
|
|
45
48
|
throw new Error("Nested delegation from a pane-native remote Pi is not supported.");
|
|
46
|
-
if (launch.excludeTools?.length || launch.runtime.permissions || launch.runtime.toolBudget || launch.runtime.requiredExtensions || launch.runtime.
|
|
47
|
-
throw new Error("Pane-native remote Pi cannot yet represent exclusions, permission rules, tool budgets, MCP direct tools, or required-
|
|
49
|
+
if (launch.excludeTools?.length || launch.runtime.permissions || launch.runtime.toolBudget || launch.runtime.requiredExtensions || launch.runtime.mcpDirectTools?.length)
|
|
50
|
+
throw new Error("Pane-native remote Pi cannot yet represent exclusions, permission rules, tool budgets, MCP direct tools, or required-extension contracts; remove machine placement.");
|
|
48
51
|
const resources = launch.remoteResources;
|
|
49
52
|
if (!resources || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(resources.agent) || resources.skills?.some((name) => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(name)) || resources.toolCeiling?.some((name) => !/^[A-Za-z0-9_-]{1,64}$/u.test(name)) || (resources.reads !== undefined && resources.reads !== false && resources.reads.some((read) => typeof read !== "string" || Buffer.byteLength(read) > 4096)))
|
|
50
53
|
throw new Error("Pane-native remote resources require bounded logical agent, skill, tool, and read names.");
|
|
@@ -350,6 +353,10 @@ async function startPlacedPiSession(launch, owner, policy) {
|
|
|
350
353
|
throw new Error("Remote Pi resource resolution identity mismatch.");
|
|
351
354
|
if (!Array.isArray(configured.tools) || (policy.resources.toolCeiling && configured.tools.some((tool) => !policy.resources.toolCeiling.includes(String(tool)))))
|
|
352
355
|
throw new Error("Remote Pi active-tool ceiling acknowledgement mismatch.");
|
|
356
|
+
// The remote Pi never receives the runtime contract, so required tools are checked here, against what it activated.
|
|
357
|
+
const missingRequired = (launch.runtime.requiredTools ?? []).filter((tool) => !configured.tools.includes(tool));
|
|
358
|
+
if (missingRequired.length)
|
|
359
|
+
throw new Error(`Remote Pi agent '${policy.resources.agent}' did not activate required tools: ${missingRequired.join(", ")}.`);
|
|
353
360
|
if (launch.model && configured.model !== launch.model.split(":")[0])
|
|
354
361
|
throw new Error(`Remote Pi model '${launch.model}' did not resolve to the requested provider/model.`);
|
|
355
362
|
const identity = { runId, machineId: machine.id, target: isRexLocalMachine(machine) ? machine.id : machine.target, session: owner.host.sessionOf(connection), workspaceId: owned.workspaceId, tabId: owned.tabId, paneId: owned.paneId, terminalId, agentName, nativeSessionId: manifest.nativeSessionId, cwd: machine.cwd, runtimeDir };
|
|
@@ -669,6 +669,7 @@ export default function registerSubagentPromptRuntime(pi, config, drainObservati
|
|
|
669
669
|
onRuntimeEvent("session_start", (_event, ctx) => {
|
|
670
670
|
const sessionManager = ctx?.sessionManager;
|
|
671
671
|
waitState.currentSessionId = sessionManager ? resolveCurrentSessionId(sessionManager) : null;
|
|
672
|
+
waitState.supervisorOwnerSessionId = sessionManager?.getSessionId() || null;
|
|
672
673
|
registerNativeSupervisorClientOnce();
|
|
673
674
|
});
|
|
674
675
|
onRuntimeEvent("agent_start", () => {
|
|
@@ -724,30 +725,23 @@ export default function registerSubagentPromptRuntime(pi, config, drainObservati
|
|
|
724
725
|
onRuntimeEvent("before_provider_headers", (event, ctx) => {
|
|
725
726
|
applyForkSessionAffinity(event?.headers, ctx?.model, config.parentSessionId, readInherited(ctx)?.model);
|
|
726
727
|
});
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (!fork)
|
|
733
|
-
return undefined;
|
|
734
|
-
const messages = projectForkRequest(event.messages, fork.system, fork.forkedAt, { boundary });
|
|
735
|
-
return messages === event.messages ? undefined : { messages };
|
|
736
|
-
});
|
|
737
|
-
onRuntimeEvent("context", (event, ctx) => {
|
|
728
|
+
// The strip runs on context_with_system, not context: Pi treats any change a context handler
|
|
729
|
+
// makes to the message list as a rewritten conversation and folds every mid-conversation system
|
|
730
|
+
// message into the leading one, so every later request would miss the prompt cache. The strip
|
|
731
|
+
// passes system messages through unchanged.
|
|
732
|
+
onRuntimeEvent("context_with_system", (event, ctx) => {
|
|
738
733
|
if (!event || typeof event !== "object" || !("messages" in event) || !Array.isArray(event.messages))
|
|
739
734
|
return undefined;
|
|
740
|
-
const
|
|
741
|
-
const
|
|
735
|
+
const fork = config.forkCacheKey ? readInherited(ctx) : undefined;
|
|
736
|
+
const stripped = stripParentOnlySubagentMessages(event.messages, {
|
|
742
737
|
sanitizeToolIds: !COMPOSITE_TOOL_ID_APIS.has(ctx?.model?.api ?? ""),
|
|
743
738
|
// Providers that reuse the Codex Responses wire under their own api id (e.g. cliproxyapi-codex-responses) split call_id|item_id the same way.
|
|
744
739
|
preserveBoundedCompositeToolIds: ctx?.model?.api?.endsWith("-codex-responses") === true,
|
|
745
740
|
preserveFanoutToolHistory: config.fanoutChild,
|
|
746
|
-
...(
|
|
741
|
+
...(fork ? { inheritedBefore: fork.forkedAt } : {}),
|
|
747
742
|
});
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
return { messages };
|
|
743
|
+
const messages = fork ? projectForkRequest(stripped, fork.system, fork.forkedAt, { boundary }) : stripped;
|
|
744
|
+
return messages === event.messages ? undefined : { messages };
|
|
751
745
|
});
|
|
752
746
|
// pi-intercom asks at session start for this session's intercom ID. Claiming
|
|
753
747
|
// the route there frees the session name for the readable label. An older
|
|
@@ -86,6 +86,8 @@ interface FeatureConfig {
|
|
|
86
86
|
export declare function resolveDisabledFeatureSurface(config: FeatureConfig): DisabledFeatureSurface;
|
|
87
87
|
/** Returns why a request uses a disabled feature, or undefined when every requested field is enabled. */
|
|
88
88
|
export declare function disabledFeatureUseError(request: object, surface: DisabledFeatureSurface, label?: string): string | undefined;
|
|
89
|
-
/** Lists what config disabled, for prepending to static
|
|
89
|
+
/** Lists what config disabled, for prepending to static guide docs that describe the full tool. */
|
|
90
90
|
export declare function disabledFeatureNotice(surface: DisabledFeatureSurface): string | undefined;
|
|
91
|
+
/** One-line form for guide sections, which are capped at 8,000 chars. */
|
|
92
|
+
export declare function disabledFeatureSectionNotice(surface: DisabledFeatureSurface): string | undefined;
|
|
91
93
|
export {};
|
|
@@ -99,14 +99,22 @@ export function disabledFeatureUseError(request, surface, label = "subagent") {
|
|
|
99
99
|
}
|
|
100
100
|
return undefined;
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
const WORKFLOW_SCRIPTS_REPLACEMENT = "Instead of workflow scripts, use the top-level tasks or chain fields.";
|
|
103
|
+
/** Lists what config disabled, for prepending to static guide docs that describe the full tool. */
|
|
103
104
|
export function disabledFeatureNotice(surface) {
|
|
104
105
|
if (surface.features.size === 0)
|
|
105
106
|
return undefined;
|
|
106
107
|
const lines = [...surface.features].map((feature) => {
|
|
107
108
|
const { actions, params, disabledBy } = featureSurface(feature);
|
|
108
109
|
const parts = [`options ${params.join(", ")}`, ...(actions.length > 0 ? [`actions ${actions.join(", ")}`] : [])];
|
|
109
|
-
return `- ${disabledBy}: ${parts.join("; ")}`;
|
|
110
|
+
return `- ${disabledBy}: ${parts.join("; ")}${feature === "workflow-scripts" ? `. ${WORKFLOW_SCRIPTS_REPLACEMENT}` : ""}`;
|
|
110
111
|
});
|
|
111
|
-
return `Disabled by config in this session. The
|
|
112
|
+
return `Disabled by config in this session. The guide below may still describe these, but calls that use them are rejected:\n${lines.join("\n")}`;
|
|
113
|
+
}
|
|
114
|
+
/** One-line form for guide sections, which are capped at 8,000 chars. */
|
|
115
|
+
export function disabledFeatureSectionNotice(surface) {
|
|
116
|
+
if (surface.features.size === 0)
|
|
117
|
+
return undefined;
|
|
118
|
+
const replacement = surface.features.has("workflow-scripts") ? ` ${WORKFLOW_SCRIPTS_REPLACEMENT}` : "";
|
|
119
|
+
return `Some options and actions are disabled by config in this session; guide topic tool-reference lists them.${replacement}`;
|
|
112
120
|
}
|
package/src/shared/types.d.ts
CHANGED
|
@@ -2278,7 +2278,7 @@ export interface ActiveAsyncCapacitySnapshot {
|
|
|
2278
2278
|
export interface SubagentState {
|
|
2279
2279
|
baseCwd: string;
|
|
2280
2280
|
currentSessionId: string | null;
|
|
2281
|
-
/** Exact SDK runtime session ID for supervisor ownership; never a session file path. */
|
|
2281
|
+
/** Exact SDK runtime session ID (`getSessionId()`) for supervisor ownership and background-work matching; never a session file path. */
|
|
2282
2282
|
supervisorOwnerSessionId?: string | null;
|
|
2283
2283
|
/** Session for which active status projections were restored successfully. */
|
|
2284
2284
|
statusProjectionSessionId?: string | null;
|
|
@@ -2410,6 +2410,8 @@ export declare const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
|
|
|
2410
2410
|
export declare const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
|
|
2411
2411
|
export declare const SUBAGENT_STEERING_NOTICE_EVENT = "subagent:steering-notice";
|
|
2412
2412
|
export declare const SUBAGENT_CHILD_STATUS_EVENT = "subagent:child-status";
|
|
2413
|
+
/** Payload is only `{ version: 1 }`: a hint for hosts to read `status` again, not a description of the change. */
|
|
2414
|
+
export declare const SUBAGENT_JOBS_CHANGED_EVENT = "subagent:jobs-changed";
|
|
2413
2415
|
export declare const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
|
|
2414
2416
|
export declare const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
|
|
2415
2417
|
export interface SubagentChildStatusEvent {
|
package/src/shared/types.js
CHANGED
|
@@ -19,6 +19,8 @@ export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
|
|
|
19
19
|
export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
|
|
20
20
|
export const SUBAGENT_STEERING_NOTICE_EVENT = "subagent:steering-notice";
|
|
21
21
|
export const SUBAGENT_CHILD_STATUS_EVENT = "subagent:child-status";
|
|
22
|
+
/** Payload is only `{ version: 1 }`: a hint for hosts to read `status` again, not a description of the change. */
|
|
23
|
+
export const SUBAGENT_JOBS_CHANGED_EVENT = "subagent:jobs-changed";
|
|
22
24
|
export const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
|
|
23
25
|
export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
|
|
24
26
|
export const FLEET_KEYBINDING_ACTIONS = [
|