pi-subagents 0.43.0 → 0.45.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 +37 -0
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +13 -1
- package/src/extension/config.ts +28 -7
- package/src/extension/index.ts +3 -1
- package/src/extension/tool-description.ts +2 -2
- package/src/inspectors/herdr/actions.ts +1 -1
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/missions/lifecycle.ts +1 -1
- package/src/missions/workflow-state.ts +188 -15
- package/src/runs/background/async-execution.ts +12 -3
- package/src/runs/background/async-status.ts +6 -1
- package/src/runs/background/result-watcher.ts +5 -0
- package/src/runs/background/run-id-resolver.ts +71 -0
- package/src/runs/background/run-status.ts +3 -2
- package/src/runs/background/subagent-runner.ts +73 -53
- package/src/runs/background/subagent-wait.ts +12 -2
- package/src/runs/background/wait-completions.ts +112 -0
- package/src/runs/foreground/chain-clarify.ts +7 -3
- package/src/runs/foreground/execution.ts +25 -19
- package/src/runs/foreground/subagent-executor.ts +159 -31
- package/src/runs/shared/subagent-prompt-runtime.ts +6 -3
- package/src/shared/settings.ts +26 -9
- package/src/shared/types.ts +40 -0
- package/src/workflows/scripted-workflow.ts +6 -2
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
getStepAgents,
|
|
37
37
|
isParallelStep,
|
|
38
38
|
isDynamicParallelStep,
|
|
39
|
+
resolveChainPath,
|
|
39
40
|
resolveStepBehavior,
|
|
40
41
|
suppressProgressForReadOnlyTask,
|
|
41
42
|
taskDisallowsFileUpdates,
|
|
@@ -91,11 +92,12 @@ import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
|
91
92
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
92
93
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
93
94
|
import { attachMissionToLaunchResult, prepareMissionLaunch, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
95
|
+
import { updateMission } from "../../missions/store.ts";
|
|
94
96
|
import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
95
97
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
96
98
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
97
99
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
98
|
-
import { runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
100
|
+
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
99
101
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
100
102
|
import {
|
|
101
103
|
cleanupWorktrees,
|
|
@@ -255,6 +257,8 @@ export interface SubagentParamsLike {
|
|
|
255
257
|
focus?: boolean;
|
|
256
258
|
skill?: string | string[] | boolean;
|
|
257
259
|
output?: string | boolean;
|
|
260
|
+
/** Internal-only; not part of the public tool schema. Wired for single-run reads (chain steps use their own field). */
|
|
261
|
+
reads?: string[] | false;
|
|
258
262
|
outputMode?: "inline" | "file-only";
|
|
259
263
|
outputSchema?: JsonSchemaObject;
|
|
260
264
|
agentScope?: unknown;
|
|
@@ -798,6 +802,20 @@ function getAsyncInterruptTarget(
|
|
|
798
802
|
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
799
803
|
}
|
|
800
804
|
|
|
805
|
+
function isStaleExtensionContextError(error: unknown): boolean {
|
|
806
|
+
if (!(error instanceof Error)) return false;
|
|
807
|
+
return /extension ctx is stale|stale after session replacement or reload/i.test(error.message);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function emitAdvisoryControlEvent(pi: ExtensionAPI, channel: string, payload: unknown): void {
|
|
811
|
+
try {
|
|
812
|
+
pi.events.emit(channel, payload);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (isStaleExtensionContextError(error)) return;
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
801
819
|
function emitControlNotification(input: {
|
|
802
820
|
pi: ExtensionAPI;
|
|
803
821
|
controlConfig: ResolvedControlConfig;
|
|
@@ -815,10 +833,10 @@ function emitControlNotification(input: {
|
|
|
815
833
|
noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
|
|
816
834
|
};
|
|
817
835
|
if (input.controlConfig.notifyChannels.includes("event")) {
|
|
818
|
-
input.pi
|
|
836
|
+
emitAdvisoryControlEvent(input.pi, SUBAGENT_CONTROL_EVENT, payload);
|
|
819
837
|
}
|
|
820
838
|
if (input.event.type !== "active_long_running" && input.controlConfig.notifyChannels.includes("intercom") && input.intercomBridge.active && input.intercomBridge.orchestratorTarget) {
|
|
821
|
-
input.pi
|
|
839
|
+
emitAdvisoryControlEvent(input.pi, SUBAGENT_CONTROL_INTERCOM_EVENT, {
|
|
822
840
|
...payload,
|
|
823
841
|
to: input.intercomBridge.orchestratorTarget,
|
|
824
842
|
message: formatControlIntercomMessage(input.event, childIntercomTarget),
|
|
@@ -1865,6 +1883,19 @@ function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
|
1865
1883
|
return "single";
|
|
1866
1884
|
}
|
|
1867
1885
|
|
|
1886
|
+
function formatStatusTargetLabel(params: Pick<SubagentParamsLike, "dir" | "index" | "view">, targetRunId: string | undefined): string {
|
|
1887
|
+
let target: string;
|
|
1888
|
+
if (targetRunId) {
|
|
1889
|
+
target = `run ${targetRunId}`;
|
|
1890
|
+
} else if (params.dir) {
|
|
1891
|
+
target = `dir ${params.dir}`;
|
|
1892
|
+
} else {
|
|
1893
|
+
target = params.view === "transcript" ? "active run" : "active runs";
|
|
1894
|
+
}
|
|
1895
|
+
if (params.view !== "transcript") return `Status target: ${target}`;
|
|
1896
|
+
return `Transcript target: ${target}${params.index !== undefined ? ` · child ${params.index}` : ""}`;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1868
1899
|
interface AgentDefaultContextPolicy {
|
|
1869
1900
|
params: SubagentParamsLike;
|
|
1870
1901
|
contextForAgent(agentName: string): ContextMode;
|
|
@@ -2534,6 +2565,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2534
2565
|
skills,
|
|
2535
2566
|
output: effectiveOutput,
|
|
2536
2567
|
outputMode: effectiveOutputMode,
|
|
2568
|
+
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
2537
2569
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2538
2570
|
modelOverride,
|
|
2539
2571
|
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
@@ -3323,10 +3355,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3323
3355
|
);
|
|
3324
3356
|
if (errorResult) return errorResult;
|
|
3325
3357
|
|
|
3326
|
-
let
|
|
3358
|
+
let worktreeCleanupHandled = false;
|
|
3359
|
+
let pendingHandoff: Details["parallelHandoff"];
|
|
3327
3360
|
try {
|
|
3328
3361
|
if (worktreeSetup) {
|
|
3329
|
-
writePendingParallelHandoff({
|
|
3362
|
+
pendingHandoff = writePendingParallelHandoff({
|
|
3330
3363
|
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3331
3364
|
runId,
|
|
3332
3365
|
mode: "parallel",
|
|
@@ -3429,10 +3462,13 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3429
3462
|
updateForegroundNestedProjection(foregroundControl);
|
|
3430
3463
|
attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
|
|
3431
3464
|
}
|
|
3465
|
+
const detached = results.find((result) => result.detached);
|
|
3432
3466
|
let handoff: ReturnType<typeof finalizeParallelWorktreeHandoff> | undefined;
|
|
3433
3467
|
if (worktreeSetup) {
|
|
3434
|
-
|
|
3435
|
-
handoff =
|
|
3468
|
+
worktreeCleanupHandled = true;
|
|
3469
|
+
handoff = detached
|
|
3470
|
+
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3471
|
+
: finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
|
|
3436
3472
|
}
|
|
3437
3473
|
const interrupted = results.find((result) => result.interrupted);
|
|
3438
3474
|
const totalCost = sumResultsCost(results);
|
|
@@ -3455,11 +3491,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3455
3491
|
details,
|
|
3456
3492
|
};
|
|
3457
3493
|
}
|
|
3458
|
-
const detachedIndex = results.findIndex((result) => result.detached);
|
|
3459
|
-
const detached = detachedIndex >= 0 ? results[detachedIndex] : undefined;
|
|
3460
3494
|
if (detached) {
|
|
3495
|
+
const handoffSuffix = handoff?.suffix ? `\n\n${handoff.suffix}` : "";
|
|
3461
3496
|
return {
|
|
3462
|
-
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached
|
|
3497
|
+
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.${handoffSuffix}` }],
|
|
3463
3498
|
details,
|
|
3464
3499
|
};
|
|
3465
3500
|
}
|
|
@@ -3508,7 +3543,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3508
3543
|
details,
|
|
3509
3544
|
};
|
|
3510
3545
|
} finally {
|
|
3511
|
-
if (worktreeSetup && !
|
|
3546
|
+
if (worktreeSetup && !worktreeCleanupHandled) cleanupWorktrees(worktreeSetup);
|
|
3512
3547
|
}
|
|
3513
3548
|
}
|
|
3514
3549
|
|
|
@@ -3559,6 +3594,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3559
3594
|
data.modelScope === undefined ? {} : { scope: data.modelScope },
|
|
3560
3595
|
);
|
|
3561
3596
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
3597
|
+
let readsOverride: string[] | false | undefined = params.reads;
|
|
3562
3598
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
3563
3599
|
let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
|
|
3564
3600
|
const effectiveOutputMode = params.outputMode ?? "inline";
|
|
@@ -3596,6 +3632,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3596
3632
|
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
3597
3633
|
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
3598
3634
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
3635
|
+
if (override?.reads !== undefined) readsOverride = override.reads;
|
|
3599
3636
|
|
|
3600
3637
|
if (result.runInBackground) {
|
|
3601
3638
|
if (!isAsyncAvailable()) {
|
|
@@ -3635,6 +3672,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3635
3672
|
skills: skillOverride === false ? [] : skillOverride,
|
|
3636
3673
|
output: effectiveOutput,
|
|
3637
3674
|
outputMode: effectiveOutputMode,
|
|
3675
|
+
...(readsOverride !== undefined ? { reads: readsOverride } : {}),
|
|
3638
3676
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
3639
3677
|
modelOverride,
|
|
3640
3678
|
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
@@ -3671,6 +3709,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3671
3709
|
const structuredRuntime = params.outputSchema
|
|
3672
3710
|
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined)
|
|
3673
3711
|
: undefined;
|
|
3712
|
+
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
3713
|
+
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
3714
|
+
const reads = readsOverride !== undefined ? readsOverride : agentConfig.defaultReads ?? false;
|
|
3715
|
+
const readsInstruction = Array.isArray(reads) && reads.length > 0
|
|
3716
|
+
? `[Read from: ${reads.map((f) => resolveChainPath(f, effectiveCwd)).join(", ")}]\n\n`
|
|
3717
|
+
: "";
|
|
3718
|
+
task = readsInstruction + task;
|
|
3674
3719
|
task = injectSingleOutputInstruction(task, outputPath, agentConfig);
|
|
3675
3720
|
|
|
3676
3721
|
let effectiveSkills: string[] | undefined;
|
|
@@ -3895,6 +3940,8 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
3895
3940
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
3896
3941
|
? result.details.results[0].finalOutput
|
|
3897
3942
|
: receiptOutput;
|
|
3943
|
+
const detached = result.details.results.some((child) => child.detached);
|
|
3944
|
+
const ok = result.isError !== true && !detached;
|
|
3898
3945
|
const artifactPaths = new Set<string>();
|
|
3899
3946
|
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
3900
3947
|
if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
|
|
@@ -3906,10 +3953,10 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
3906
3953
|
const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
|
|
3907
3954
|
return {
|
|
3908
3955
|
key,
|
|
3909
|
-
ok
|
|
3956
|
+
ok,
|
|
3910
3957
|
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
3911
3958
|
output,
|
|
3912
|
-
...(
|
|
3959
|
+
...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
3913
3960
|
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
3914
3961
|
artifactPaths: [...artifactPaths],
|
|
3915
3962
|
results: result.details.results,
|
|
@@ -3983,6 +4030,7 @@ function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsL
|
|
|
3983
4030
|
acceptance,
|
|
3984
4031
|
agentContract,
|
|
3985
4032
|
toolBudget,
|
|
4033
|
+
reads,
|
|
3986
4034
|
...runParams
|
|
3987
4035
|
} = params;
|
|
3988
4036
|
return {
|
|
@@ -3994,6 +4042,7 @@ function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsL
|
|
|
3994
4042
|
...(model !== undefined ? { model } : {}),
|
|
3995
4043
|
...(skill !== undefined ? { skill } : {}),
|
|
3996
4044
|
...(output !== undefined ? { output } : {}),
|
|
4045
|
+
...(reads !== undefined ? { reads } : {}),
|
|
3997
4046
|
...(outputMode !== undefined ? { outputMode } : {}),
|
|
3998
4047
|
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
3999
4048
|
...(acceptance !== undefined ? { acceptance } : {}),
|
|
@@ -4123,11 +4172,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4123
4172
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4124
4173
|
const chatProgress = chatProgressResult.projection!;
|
|
4125
4174
|
const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
|
|
4175
|
+
const autoMission = !explicitMission;
|
|
4176
|
+
const workflowPreview = autoMission ? previewSimpleWorkflowRun(requestParams.workflowScript) : undefined;
|
|
4177
|
+
const previewTask = workflowPreview?.task?.trim() || undefined;
|
|
4178
|
+
const previewAgent = workflowPreview?.agent?.trim() || undefined;
|
|
4179
|
+
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4180
|
+
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4181
|
+
const derivedObjective = previewTask || (previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview);
|
|
4126
4182
|
let missionBinding: MissionLaunchBinding | undefined;
|
|
4127
4183
|
let missionWarning: string | undefined;
|
|
4128
4184
|
try {
|
|
4129
4185
|
missionBinding = prepareMissionLaunch({
|
|
4130
|
-
params: requestParams,
|
|
4186
|
+
params: autoMission ? { ...requestParams, task: derivedObjective } : requestParams,
|
|
4131
4187
|
projectRoot: workflowCwd,
|
|
4132
4188
|
...(deps.config.missions ? { config: deps.config.missions } : {}),
|
|
4133
4189
|
ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
|
|
@@ -4136,6 +4192,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4136
4192
|
if (explicitMission) return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4137
4193
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4138
4194
|
}
|
|
4195
|
+
let shouldPatchMissionObjective = autoMission && previewTask === undefined && missionBinding !== undefined;
|
|
4196
|
+
const patchMissionObjective = (task: unknown): void => {
|
|
4197
|
+
if (!shouldPatchMissionObjective || !missionBinding || typeof task !== "string" || !task.trim()) return;
|
|
4198
|
+
shouldPatchMissionObjective = false;
|
|
4199
|
+
const objective = task.trim();
|
|
4200
|
+
const firstLine = objective.split(/\r?\n/, 1)[0]?.trim() || objective;
|
|
4201
|
+
const title = firstLine.length > 100 ? `${firstLine.slice(0, 97)}...` : firstLine;
|
|
4202
|
+
try {
|
|
4203
|
+
updateMission(missionBinding.location, missionBinding.missionId, { title, objective });
|
|
4204
|
+
} catch (error) {
|
|
4205
|
+
console.warn(`[pi-subagents] Failed to update automatic mission objective: ${error instanceof Error ? error.message : String(error)}`);
|
|
4206
|
+
}
|
|
4207
|
+
};
|
|
4208
|
+
const detachWorkflowChildMissions = autoMission || missionBinding !== undefined || requestParams.mission === false;
|
|
4139
4209
|
const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
|
|
4140
4210
|
const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
4141
4211
|
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
@@ -4149,7 +4219,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4149
4219
|
}
|
|
4150
4220
|
};
|
|
4151
4221
|
if (requestParams.async !== false) {
|
|
4152
|
-
const
|
|
4222
|
+
const toolCallId = _id;
|
|
4223
|
+
const workflowRunId = randomUUID();
|
|
4153
4224
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4154
4225
|
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4155
4226
|
const statusPath = path.join(asyncDir, "status.json");
|
|
@@ -4163,13 +4234,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4163
4234
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4164
4235
|
let status: AsyncStatus = {
|
|
4165
4236
|
runId: workflowRunId,
|
|
4237
|
+
toolCallId,
|
|
4166
4238
|
sessionId: currentSessionId ?? undefined,
|
|
4167
4239
|
mode: "workflow",
|
|
4168
4240
|
state: "running",
|
|
4169
4241
|
startedAt,
|
|
4170
4242
|
lastUpdate: startedAt,
|
|
4171
4243
|
...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
|
|
4172
|
-
cwd:
|
|
4244
|
+
cwd: workflowCwd,
|
|
4173
4245
|
pid: process.pid,
|
|
4174
4246
|
steps: [],
|
|
4175
4247
|
workflow: { trace: [], emits: [], console: [] },
|
|
@@ -4182,6 +4254,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4182
4254
|
if (job) {
|
|
4183
4255
|
job.status = status.state;
|
|
4184
4256
|
job.updatedAt = status.lastUpdate;
|
|
4257
|
+
job.activityState = status.activityState;
|
|
4258
|
+
job.lastActivityAt = status.lastActivityAt;
|
|
4259
|
+
job.currentTool = status.currentTool;
|
|
4260
|
+
job.currentToolStartedAt = status.currentToolStartedAt;
|
|
4261
|
+
job.currentPath = status.currentPath;
|
|
4262
|
+
job.turnCount = status.turnCount;
|
|
4263
|
+
job.toolCount = status.toolCount;
|
|
4264
|
+
job.currentStep = status.currentStep;
|
|
4185
4265
|
if (status.steps) {
|
|
4186
4266
|
job.steps = status.steps.map((step, index) => ({ ...step, index }));
|
|
4187
4267
|
job.agents = status.steps.map((step) => step.agent);
|
|
@@ -4192,7 +4272,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4192
4272
|
job.workflow = status.workflow;
|
|
4193
4273
|
}
|
|
4194
4274
|
};
|
|
4195
|
-
const
|
|
4275
|
+
const projectWorkflowActivity = () => {
|
|
4276
|
+
const runningSteps = (status.steps ?? []).filter((step) => step.status === "running");
|
|
4277
|
+
const lastActivityAt = runningSteps.reduce<number | undefined>((latest, step) => step.lastActivityAt === undefined ? latest : Math.max(latest ?? step.lastActivityAt, step.lastActivityAt), undefined);
|
|
4278
|
+
const activeToolStep = runningSteps
|
|
4279
|
+
.filter((step) => step.currentTool)
|
|
4280
|
+
.sort((left, right) => (left.lastActivityAt ?? 0) - (right.lastActivityAt ?? 0))
|
|
4281
|
+
.at(-1);
|
|
4282
|
+
status.activityState = runningSteps.some((step) => step.activityState === "needs_attention")
|
|
4283
|
+
? "needs_attention"
|
|
4284
|
+
: runningSteps.some((step) => step.activityState === "active_long_running") ? "active_long_running" : undefined;
|
|
4285
|
+
status.lastActivityAt = lastActivityAt;
|
|
4286
|
+
status.currentTool = activeToolStep?.currentTool;
|
|
4287
|
+
status.currentToolStartedAt = activeToolStep?.currentToolStartedAt;
|
|
4288
|
+
status.currentPath = activeToolStep?.currentPath;
|
|
4289
|
+
const turnCounts = (status.steps ?? []).flatMap((step) => step.turnCount === undefined ? [] : [step.turnCount]);
|
|
4290
|
+
const toolCounts = (status.steps ?? []).flatMap((step) => step.toolCount === undefined ? [] : [step.toolCount]);
|
|
4291
|
+
status.turnCount = turnCounts.length > 0 ? turnCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4292
|
+
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4293
|
+
status.currentStep = runningSteps.length === 1 ? status.steps?.indexOf(runningSteps[0]!) : undefined;
|
|
4294
|
+
};
|
|
4295
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
4196
4296
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4197
4297
|
deps.state.fleetJobs ??= new Map();
|
|
4198
4298
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4215,9 +4315,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4215
4315
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4216
4316
|
else existing.durationMs = entry.durationMs;
|
|
4217
4317
|
} else {
|
|
4218
|
-
status.steps?.push({ agent: entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped });
|
|
4318
|
+
status.steps?.push({ agent: entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped, startedAt: Date.now() });
|
|
4219
4319
|
}
|
|
4220
4320
|
}
|
|
4321
|
+
projectWorkflowActivity();
|
|
4221
4322
|
persist();
|
|
4222
4323
|
appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
|
|
4223
4324
|
};
|
|
@@ -4238,8 +4339,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4238
4339
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4239
4340
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4240
4341
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4241
|
-
|
|
4242
|
-
const
|
|
4342
|
+
patchMissionObjective(childParams.task);
|
|
4343
|
+
const childRequest = prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions });
|
|
4344
|
+
const result = await execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4345
|
+
const progress = update.details.progress?.[0];
|
|
4346
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4347
|
+
if (!progress || !step) return;
|
|
4348
|
+
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4349
|
+
step.activityState = progress.activityState;
|
|
4350
|
+
step.lastActivityAt = progress.lastActivityAt;
|
|
4351
|
+
step.currentTool = progress.currentTool;
|
|
4352
|
+
step.currentToolArgs = progress.currentToolArgs;
|
|
4353
|
+
step.currentToolStartedAt = progress.currentToolStartedAt;
|
|
4354
|
+
step.currentPath = progress.currentPath;
|
|
4355
|
+
step.recentTools = progress.recentTools.map((tool) => ({ ...tool }));
|
|
4356
|
+
step.recentOutput = [...progress.recentOutput];
|
|
4357
|
+
step.turnCount = progress.turnCount;
|
|
4358
|
+
step.toolCount = progress.toolCount;
|
|
4359
|
+
step.model = progress.model;
|
|
4360
|
+
step.thinking = progress.thinking;
|
|
4361
|
+
step.error = progress.error;
|
|
4362
|
+
projectWorkflowActivity();
|
|
4363
|
+
persist();
|
|
4364
|
+
}, ctx, preserveActiveSession);
|
|
4243
4365
|
workflowResults.push(...result.details.results);
|
|
4244
4366
|
const child = workflowChildResult(key, result);
|
|
4245
4367
|
if (result.details.asyncId) {
|
|
@@ -4255,23 +4377,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4255
4377
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
4256
4378
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4257
4379
|
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4380
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ agent: child.key, ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4258
4381
|
persist();
|
|
4259
4382
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4260
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ agent: child.key, output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: parentCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4261
4383
|
} catch (error) {
|
|
4262
4384
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4263
4385
|
const stopped = controller.signal.aborted;
|
|
4264
4386
|
status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
4387
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ agent: child.key, ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4265
4388
|
persist();
|
|
4266
4389
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4267
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ agent: child.key, output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: parentCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4268
4390
|
} finally {
|
|
4269
4391
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4270
4392
|
}
|
|
4271
4393
|
});
|
|
4272
4394
|
return attachWorkflowMission({
|
|
4273
4395
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4274
|
-
details: { mode: "workflow", runId: workflowRunId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4396
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4275
4397
|
});
|
|
4276
4398
|
}
|
|
4277
4399
|
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
|
|
@@ -4299,7 +4421,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4299
4421
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4300
4422
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4301
4423
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4302
|
-
|
|
4424
|
+
patchMissionObjective(childParams.task);
|
|
4425
|
+
const childRequest = prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card" });
|
|
4303
4426
|
const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
|
|
4304
4427
|
workflowResults.push(...result.details.results);
|
|
4305
4428
|
return workflowChildResult(key, result);
|
|
@@ -4572,13 +4695,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4572
4695
|
}
|
|
4573
4696
|
if (action === "status") {
|
|
4574
4697
|
if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4575
|
-
const withBudget = (result: AgentToolResult<Details>) => withSpawnBudgetStatus(
|
|
4576
|
-
result,
|
|
4577
|
-
deps.state,
|
|
4578
|
-
deps.config,
|
|
4579
|
-
deps.state.currentSessionId,
|
|
4580
|
-
);
|
|
4581
4698
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
4699
|
+
const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
|
|
4700
|
+
const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
4701
|
+
const withBudget = (result: AgentToolResult<Details>) => {
|
|
4702
|
+
const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
|
|
4703
|
+
return {
|
|
4704
|
+
...budgeted,
|
|
4705
|
+
content: budgeted.content.map((item, index) => index === 0 && item.type === "text"
|
|
4706
|
+
? { ...item, text: `${targetLabel}\n${item.text}` }
|
|
4707
|
+
: item),
|
|
4708
|
+
};
|
|
4709
|
+
};
|
|
4582
4710
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
4583
4711
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
4584
4712
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
@@ -4603,7 +4731,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4603
4731
|
const message = error instanceof Error ? error.message : String(error);
|
|
4604
4732
|
return withBudget({ content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } });
|
|
4605
4733
|
}
|
|
4606
|
-
} else {
|
|
4734
|
+
} else if (!hasDirectoryTarget) {
|
|
4607
4735
|
const foreground = getForegroundControl(deps.state, undefined);
|
|
4608
4736
|
if (foreground && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(foreground));
|
|
4609
4737
|
if (foreground && paramsWithResolvedCwd.view === "transcript") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -206,16 +207,18 @@ function isSubagentToolCallBlock(block: unknown): boolean {
|
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
const PORTABLE_TOOL_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
210
|
+
const MAX_PORTABLE_TOOL_ID_LENGTH = 64;
|
|
209
211
|
const COMPOSITE_TOOL_ID_APIS = new Set([
|
|
210
212
|
"azure-openai-responses",
|
|
211
|
-
"openai-codex-responses",
|
|
212
213
|
"openai-completions",
|
|
213
214
|
"openai-responses",
|
|
214
215
|
]);
|
|
215
216
|
|
|
216
217
|
function portableToolId(id: string): string {
|
|
217
|
-
if (PORTABLE_TOOL_ID_PATTERN.test(id)) return id;
|
|
218
|
-
|
|
218
|
+
if (PORTABLE_TOOL_ID_PATTERN.test(id) && id.length <= MAX_PORTABLE_TOOL_ID_LENGTH) return id;
|
|
219
|
+
const encoded = `tool_${Buffer.from(id).toString("base64url") || "empty"}`;
|
|
220
|
+
if (encoded.length <= MAX_PORTABLE_TOOL_ID_LENGTH) return encoded;
|
|
221
|
+
return `tool_${createHash("sha256").update(id).digest("base64url")}`;
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
function sanitizeToolHistoryMessage(message: unknown): unknown {
|
package/src/shared/settings.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
|
+
import * as os from "node:os";
|
|
6
7
|
import * as path from "node:path";
|
|
7
8
|
import type { AgentConfig } from "../agents/agents.ts";
|
|
8
9
|
import { normalizeSkillInput } from "../agents/skills.ts";
|
|
@@ -23,8 +24,10 @@ export interface ResolvedStepBehavior {
|
|
|
23
24
|
model?: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
export type OutputOverrideInput = string | boolean;
|
|
28
|
+
|
|
26
29
|
export interface StepOverrides {
|
|
27
|
-
output?:
|
|
30
|
+
output?: OutputOverrideInput;
|
|
28
31
|
outputMode?: OutputMode;
|
|
29
32
|
reads?: string[] | false;
|
|
30
33
|
progress?: boolean;
|
|
@@ -32,8 +35,10 @@ export interface StepOverrides {
|
|
|
32
35
|
model?: string;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
function normalizeOutputOverride(output:
|
|
36
|
-
|
|
38
|
+
function normalizeOutputOverride(output: unknown): string | false | undefined {
|
|
39
|
+
if (output === false || output === "false") return false;
|
|
40
|
+
if (output === true || output === "true") return undefined;
|
|
41
|
+
return typeof output === "string" && output.length > 0 ? output : undefined;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
// =============================================================================
|
|
@@ -49,7 +54,7 @@ export interface SequentialStep {
|
|
|
49
54
|
as?: string;
|
|
50
55
|
outputSchema?: JsonSchemaObject;
|
|
51
56
|
cwd?: string;
|
|
52
|
-
output?:
|
|
57
|
+
output?: OutputOverrideInput;
|
|
53
58
|
outputMode?: OutputMode;
|
|
54
59
|
reads?: string[] | false;
|
|
55
60
|
progress?: boolean;
|
|
@@ -71,7 +76,7 @@ export interface ParallelTaskItem {
|
|
|
71
76
|
outputSchema?: JsonSchemaObject;
|
|
72
77
|
cwd?: string;
|
|
73
78
|
count?: number;
|
|
74
|
-
output?:
|
|
79
|
+
output?: OutputOverrideInput;
|
|
75
80
|
outputMode?: OutputMode;
|
|
76
81
|
reads?: string[] | false;
|
|
77
82
|
progress?: boolean;
|
|
@@ -123,7 +128,7 @@ export interface CheckpointStep {
|
|
|
123
128
|
agent?: string;
|
|
124
129
|
task?: string;
|
|
125
130
|
as?: string;
|
|
126
|
-
output?:
|
|
131
|
+
output?: OutputOverrideInput;
|
|
127
132
|
outputMode?: OutputMode;
|
|
128
133
|
reads?: string[] | false;
|
|
129
134
|
progress?: boolean;
|
|
@@ -330,10 +335,22 @@ export function suppressProgressForReadOnlyTask(behavior: ResolvedStepBehavior,
|
|
|
330
335
|
// =============================================================================
|
|
331
336
|
|
|
332
337
|
/**
|
|
333
|
-
*
|
|
338
|
+
* Expand a leading `~`/`~/` to the user's home directory. Other forms (relative,
|
|
339
|
+
* absolute, `~user/`) pass through unchanged.
|
|
340
|
+
*/
|
|
341
|
+
export function expandHomePath(filePath: string): string {
|
|
342
|
+
if (filePath === "~") return os.homedir();
|
|
343
|
+
if (filePath.startsWith("~/")) return path.join(os.homedir(), filePath.slice(2));
|
|
344
|
+
return filePath;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Resolve a file path: `~`/`~/` expand to home first, then absolute paths pass
|
|
349
|
+
* through and relative paths get chainDir prepended.
|
|
334
350
|
*/
|
|
335
|
-
function resolveChainPath(filePath: string, chainDir: string): string {
|
|
336
|
-
|
|
351
|
+
export function resolveChainPath(filePath: string, chainDir: string): string {
|
|
352
|
+
const expanded = expandHomePath(filePath);
|
|
353
|
+
return path.isAbsolute(expanded) ? expanded : path.join(chainDir, expanded);
|
|
337
354
|
}
|
|
338
355
|
|
|
339
356
|
/**
|
package/src/shared/types.ts
CHANGED
|
@@ -947,12 +947,46 @@ export interface SpawnBudgetSnapshot {
|
|
|
947
947
|
grantHistory: SpawnBudgetGrant[];
|
|
948
948
|
}
|
|
949
949
|
|
|
950
|
+
/** Slim per-child projection of a terminal result payload, safe to surface in tool_result details. */
|
|
951
|
+
export interface WaitCompletionChild {
|
|
952
|
+
agent?: string;
|
|
953
|
+
/** Child run identity where the producer records one (workflow children); artifact files are keyed by it. */
|
|
954
|
+
runId?: string;
|
|
955
|
+
success?: boolean;
|
|
956
|
+
outputState?: SubagentOutputState;
|
|
957
|
+
error?: string;
|
|
958
|
+
model?: string;
|
|
959
|
+
artifactPaths?: Partial<ArtifactPaths>;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Terminal completion observed for a run a subagent_wait call covered. Carries run
|
|
964
|
+
* identity and the artifact trail; output text stays in the tool result content.
|
|
965
|
+
*/
|
|
966
|
+
export interface WaitCompletion {
|
|
967
|
+
runId: string;
|
|
968
|
+
agent?: string;
|
|
969
|
+
mode?: string;
|
|
970
|
+
state?: string;
|
|
971
|
+
success?: boolean;
|
|
972
|
+
results?: WaitCompletionChild[];
|
|
973
|
+
}
|
|
974
|
+
|
|
950
975
|
export interface Details {
|
|
951
976
|
mode: SubagentResultMode | "management";
|
|
952
977
|
runId?: string;
|
|
978
|
+
/** Host tool-call id retained when it differs from the internal run id. */
|
|
979
|
+
toolCallId?: string;
|
|
953
980
|
/** Run-level context summary. "mixed" when children resolved to different modes. */
|
|
954
981
|
context?: "fresh" | "fork" | "mixed";
|
|
955
982
|
results: SingleResult[];
|
|
983
|
+
/**
|
|
984
|
+
* Terminal completion payloads for runs this subagent_wait call observed
|
|
985
|
+
* finishing. Async completions travel as result files that are consumed and
|
|
986
|
+
* deleted after text delivery, so without this field their run and artifact
|
|
987
|
+
* identity never reaches tool_result details.
|
|
988
|
+
*/
|
|
989
|
+
completions?: WaitCompletion[];
|
|
956
990
|
controlEvents?: ControlEvent[];
|
|
957
991
|
steering?: SteerActionResult;
|
|
958
992
|
asyncId?: string;
|
|
@@ -1246,6 +1280,8 @@ export interface ExternalProcessStatus {
|
|
|
1246
1280
|
export interface AsyncStatus {
|
|
1247
1281
|
lifecycleArtifactVersion?: SubagentLifecycleArtifactVersion;
|
|
1248
1282
|
runId: string;
|
|
1283
|
+
/** Host tool-call id retained when it differs from the internal run id. */
|
|
1284
|
+
toolCallId?: string;
|
|
1249
1285
|
sessionId?: string;
|
|
1250
1286
|
mode: SubagentRunMode;
|
|
1251
1287
|
context?: "fresh" | "fork" | "mixed";
|
|
@@ -1578,6 +1614,8 @@ export interface SubagentState {
|
|
|
1578
1614
|
lastUiContext: ExtensionContext | null;
|
|
1579
1615
|
poller: NodeJS.Timeout | null;
|
|
1580
1616
|
completionSeen: Map<string, number>;
|
|
1617
|
+
/** Terminal result payloads observed by the result watcher, keyed by run id and pruned by the completion TTL. */
|
|
1618
|
+
completedResults?: Map<string, { seenAt: number; completion: WaitCompletion }>;
|
|
1581
1619
|
watcher: FSWatcher | null;
|
|
1582
1620
|
watcherRestartTimer: ReturnType<typeof setTimeout> | null;
|
|
1583
1621
|
resultFileCoalescer: {
|
|
@@ -1736,6 +1774,8 @@ export type InlineToolDisplay = "rich" | "summary";
|
|
|
1736
1774
|
export interface ScheduledRunsConfig {
|
|
1737
1775
|
enabled?: boolean;
|
|
1738
1776
|
maxPending?: number;
|
|
1777
|
+
/** Absolute or `~/` root for per-project durable schedules. */
|
|
1778
|
+
storeRoot?: string;
|
|
1739
1779
|
}
|
|
1740
1780
|
|
|
1741
1781
|
export type FleetViewPlacement = "aboveEditor" | "belowEditor";
|
|
@@ -385,8 +385,12 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
385
385
|
|
|
386
386
|
const respond = (promise: Promise<unknown>) => {
|
|
387
387
|
void promise.then(
|
|
388
|
-
(value) =>
|
|
389
|
-
|
|
388
|
+
(value) => {
|
|
389
|
+
if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: true, value: omitUndefinedWorkflowValues(value) });
|
|
390
|
+
},
|
|
391
|
+
(error: unknown) => {
|
|
392
|
+
if (!settled) worker.postMessage({ type: "response", callId: message.callId, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
393
|
+
},
|
|
390
394
|
);
|
|
391
395
|
};
|
|
392
396
|
|