pi-subagents 0.17.5 → 0.18.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 +16 -0
- package/README.md +19 -19
- package/agents/oracle-executor.md +1 -1
- package/agents/scout.md +1 -1
- package/async-execution.ts +22 -2
- package/async-job-tracker.ts +70 -7
- package/async-status.ts +37 -15
- package/chain-execution.ts +29 -4
- package/execution.ts +18 -30
- package/index.ts +118 -131
- package/install.mjs +2 -3
- package/intercom-bridge.ts +9 -0
- package/notify.ts +25 -6
- package/package.json +3 -6
- package/pi-args.ts +4 -0
- package/render.ts +15 -22
- package/result-watcher.ts +3 -5
- package/run-status.ts +134 -0
- package/schemas.ts +16 -12
- package/skills/pi-subagents/SKILL.md +21 -21
- package/slash-live-state.ts +0 -4
- package/subagent-control.ts +84 -42
- package/subagent-executor.ts +122 -11
- package/subagent-prompt-runtime.ts +6 -0
- package/subagent-runner.ts +118 -10
- package/subagents-status.ts +5 -1
- package/types.ts +29 -9
package/subagent-executor.ts
CHANGED
|
@@ -23,10 +23,11 @@ import {
|
|
|
23
23
|
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
24
24
|
import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async-execution.ts";
|
|
25
25
|
import { createForkContextResolver } from "./fork-context.ts";
|
|
26
|
-
import { applyIntercomBridgeToAgent, resolveIntercomBridge, resolveIntercomSessionTarget } from "./intercom-bridge.ts";
|
|
27
|
-
import { resolveControlConfig } from "./subagent-control.ts";
|
|
26
|
+
import { applyIntercomBridgeToAgent, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "./intercom-bridge.ts";
|
|
27
|
+
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "./subagent-control.ts";
|
|
28
28
|
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
29
29
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "./utils.ts";
|
|
30
|
+
import { inspectSubagentStatus } from "./run-status.ts";
|
|
30
31
|
import { applyForceTopLevelAsyncOverride } from "./top-level-async.ts";
|
|
31
32
|
import {
|
|
32
33
|
cleanupWorktrees,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
type ArtifactConfig,
|
|
43
44
|
type ArtifactPaths,
|
|
44
45
|
type ControlConfig,
|
|
46
|
+
type ControlEvent,
|
|
45
47
|
type Details,
|
|
46
48
|
type ExtensionConfig,
|
|
47
49
|
type MaxOutputConfig,
|
|
@@ -49,6 +51,8 @@ import {
|
|
|
49
51
|
type SingleResult,
|
|
50
52
|
type SubagentState,
|
|
51
53
|
DEFAULT_ARTIFACT_CONFIG,
|
|
54
|
+
SUBAGENT_CONTROL_EVENT,
|
|
55
|
+
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
52
56
|
checkSubagentDepth,
|
|
53
57
|
resolveTopLevelParallelConcurrency,
|
|
54
58
|
resolveTopLevelParallelMaxTasks,
|
|
@@ -70,7 +74,9 @@ interface TaskParam {
|
|
|
70
74
|
|
|
71
75
|
export interface SubagentParamsLike {
|
|
72
76
|
action?: string;
|
|
77
|
+
id?: string;
|
|
73
78
|
runId?: string;
|
|
79
|
+
dir?: string;
|
|
74
80
|
agent?: string;
|
|
75
81
|
task?: string;
|
|
76
82
|
chain?: ChainStep[];
|
|
@@ -122,6 +128,7 @@ interface ExecutionContextData {
|
|
|
122
128
|
backgroundRequestedWhileClarifying: boolean;
|
|
123
129
|
effectiveAsync: boolean;
|
|
124
130
|
controlConfig: ResolvedControlConfig;
|
|
131
|
+
intercomBridge: IntercomBridgeState;
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -141,6 +148,26 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
|
141
148
|
return newest;
|
|
142
149
|
}
|
|
143
150
|
|
|
151
|
+
function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
|
|
152
|
+
if (control.currentTool && control.currentToolStartedAt) {
|
|
153
|
+
return `tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`;
|
|
154
|
+
}
|
|
155
|
+
if (!control.lastActivityAt) return control.currentActivityState === "needs_attention" ? "needs attention" : undefined;
|
|
156
|
+
const seconds = Math.floor(Math.max(0, Date.now() - control.lastActivityAt) / 1000);
|
|
157
|
+
return control.currentActivityState === "needs_attention" ? `no activity for ${seconds}s` : `active ${seconds}s ago`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
161
|
+
const lines = [
|
|
162
|
+
`Run: ${control.runId}`,
|
|
163
|
+
"State: running",
|
|
164
|
+
`Mode: ${control.mode}`,
|
|
165
|
+
control.currentAgent ? `Current: ${control.currentAgent}${control.currentIndex !== undefined ? ` step ${control.currentIndex + 1}` : ""}` : undefined,
|
|
166
|
+
formatForegroundActivity(control) ? `Activity: ${formatForegroundActivity(control)}` : undefined,
|
|
167
|
+
].filter((line): line is string => Boolean(line));
|
|
168
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
|
|
169
|
+
}
|
|
170
|
+
|
|
144
171
|
function getAsyncInterruptTarget(state: SubagentState, runId: string | undefined): { asyncId: string; asyncDir: string } | undefined {
|
|
145
172
|
if (runId) {
|
|
146
173
|
const direct = state.asyncJobs.get(runId);
|
|
@@ -156,6 +183,34 @@ function getAsyncInterruptTarget(state: SubagentState, runId: string | undefined
|
|
|
156
183
|
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
157
184
|
}
|
|
158
185
|
|
|
186
|
+
function emitControlNotification(input: {
|
|
187
|
+
pi: ExtensionAPI;
|
|
188
|
+
controlConfig: ResolvedControlConfig;
|
|
189
|
+
intercomBridge: IntercomBridgeState;
|
|
190
|
+
event: ControlEvent;
|
|
191
|
+
}): void {
|
|
192
|
+
if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
|
|
193
|
+
const childIntercomTarget = input.intercomBridge.active
|
|
194
|
+
? resolveSubagentIntercomTarget(input.event.runId, input.event.agent, input.event.index)
|
|
195
|
+
: undefined;
|
|
196
|
+
const payload = {
|
|
197
|
+
event: input.event,
|
|
198
|
+
source: "foreground" as const,
|
|
199
|
+
childIntercomTarget,
|
|
200
|
+
noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
|
|
201
|
+
};
|
|
202
|
+
if (input.controlConfig.notifyChannels.includes("event")) {
|
|
203
|
+
input.pi.events.emit(SUBAGENT_CONTROL_EVENT, payload);
|
|
204
|
+
}
|
|
205
|
+
if (input.controlConfig.notifyChannels.includes("intercom") && input.intercomBridge.active && input.intercomBridge.orchestratorTarget) {
|
|
206
|
+
input.pi.events.emit(SUBAGENT_CONTROL_INTERCOM_EVENT, {
|
|
207
|
+
...payload,
|
|
208
|
+
to: input.intercomBridge.orchestratorTarget,
|
|
209
|
+
message: formatControlIntercomMessage(input.event, childIntercomTarget),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
159
214
|
function interruptAsyncRun(state: SubagentState, runId: string | undefined): AgentToolResult<Details> | null {
|
|
160
215
|
const target = getAsyncInterruptTarget(state, runId);
|
|
161
216
|
if (!target) return null;
|
|
@@ -171,7 +226,7 @@ function interruptAsyncRun(state: SubagentState, runId: string | undefined): Age
|
|
|
171
226
|
process.kill(status.pid, ASYNC_INTERRUPT_SIGNAL);
|
|
172
227
|
const tracked = state.asyncJobs.get(target.asyncId);
|
|
173
228
|
if (tracked) {
|
|
174
|
-
tracked.activityState =
|
|
229
|
+
tracked.activityState = undefined;
|
|
175
230
|
tracked.updatedAt = Date.now();
|
|
176
231
|
}
|
|
177
232
|
return {
|
|
@@ -188,6 +243,15 @@ function interruptAsyncRun(state: SubagentState, runId: string | undefined): Age
|
|
|
188
243
|
}
|
|
189
244
|
}
|
|
190
245
|
|
|
246
|
+
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi">): (event: ControlEvent) => void {
|
|
247
|
+
return (event) => emitControlNotification({
|
|
248
|
+
pi: deps.pi,
|
|
249
|
+
controlConfig: data.controlConfig,
|
|
250
|
+
intercomBridge: data.intercomBridge,
|
|
251
|
+
event,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
191
255
|
function validateExecutionInput(
|
|
192
256
|
params: SubagentParamsLike,
|
|
193
257
|
agents: AgentConfig[],
|
|
@@ -415,6 +479,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
415
479
|
artifactsDir,
|
|
416
480
|
effectiveAsync,
|
|
417
481
|
controlConfig,
|
|
482
|
+
intercomBridge,
|
|
418
483
|
} = data;
|
|
419
484
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
420
485
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -464,6 +529,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
464
529
|
}));
|
|
465
530
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
466
531
|
const currentProvider = ctx.model?.provider;
|
|
532
|
+
const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
|
|
533
|
+
const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
|
|
467
534
|
|
|
468
535
|
if (hasTasks && params.tasks) {
|
|
469
536
|
const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
|
|
@@ -499,6 +566,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
499
566
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
500
567
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
501
568
|
controlConfig,
|
|
569
|
+
controlIntercomTarget,
|
|
570
|
+
childIntercomTarget,
|
|
502
571
|
});
|
|
503
572
|
}
|
|
504
573
|
|
|
@@ -523,6 +592,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
523
592
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
524
593
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
525
594
|
controlConfig,
|
|
595
|
+
controlIntercomTarget,
|
|
596
|
+
childIntercomTarget,
|
|
526
597
|
});
|
|
527
598
|
}
|
|
528
599
|
|
|
@@ -561,6 +632,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
561
632
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
562
633
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
563
634
|
controlConfig,
|
|
635
|
+
controlIntercomTarget,
|
|
636
|
+
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
564
637
|
});
|
|
565
638
|
}
|
|
566
639
|
|
|
@@ -584,6 +657,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
584
657
|
sessionRoot,
|
|
585
658
|
controlConfig,
|
|
586
659
|
} = data;
|
|
660
|
+
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
661
|
+
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
587
662
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
588
663
|
const normalized = normalizeSkillInput(params.skill);
|
|
589
664
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
@@ -605,7 +680,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
605
680
|
includeProgress: params.includeProgress,
|
|
606
681
|
clarify: params.clarify,
|
|
607
682
|
onUpdate,
|
|
683
|
+
onControlEvent,
|
|
608
684
|
controlConfig,
|
|
685
|
+
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
609
686
|
foregroundControl,
|
|
610
687
|
chainSkills,
|
|
611
688
|
chainDir: params.chainDir,
|
|
@@ -651,6 +728,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
651
728
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
652
729
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
653
730
|
controlConfig,
|
|
731
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
732
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
654
733
|
});
|
|
655
734
|
}
|
|
656
735
|
|
|
@@ -677,6 +756,8 @@ interface ForegroundParallelRunInput {
|
|
|
677
756
|
skillOverrides: (string[] | false | undefined)[];
|
|
678
757
|
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
679
758
|
controlConfig: ResolvedControlConfig;
|
|
759
|
+
onControlEvent?: (event: ControlEvent) => void;
|
|
760
|
+
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
680
761
|
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
681
762
|
concurrencyLimit: number;
|
|
682
763
|
liveResults: (SingleResult | undefined)[];
|
|
@@ -770,12 +851,12 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
770
851
|
if (input.foregroundControl) {
|
|
771
852
|
input.foregroundControl.currentAgent = task.agent;
|
|
772
853
|
input.foregroundControl.currentIndex = index;
|
|
773
|
-
input.foregroundControl.currentActivityState =
|
|
854
|
+
input.foregroundControl.currentActivityState = undefined;
|
|
774
855
|
input.foregroundControl.updatedAt = Date.now();
|
|
775
856
|
input.foregroundControl.interrupt = () => {
|
|
776
857
|
if (interruptController.signal.aborted) return false;
|
|
777
858
|
interruptController.abort();
|
|
778
|
-
input.foregroundControl!.currentActivityState =
|
|
859
|
+
input.foregroundControl!.currentActivityState = undefined;
|
|
779
860
|
input.foregroundControl!.updatedAt = Date.now();
|
|
780
861
|
return true;
|
|
781
862
|
};
|
|
@@ -794,6 +875,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
794
875
|
maxOutput: input.maxOutput,
|
|
795
876
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
796
877
|
controlConfig: input.controlConfig,
|
|
878
|
+
onControlEvent: input.onControlEvent,
|
|
879
|
+
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
797
880
|
modelOverride: input.modelOverrides[index],
|
|
798
881
|
availableModels: input.availableModels,
|
|
799
882
|
preferredModelProvider: input.ctx.model?.provider,
|
|
@@ -807,6 +890,9 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
807
890
|
input.foregroundControl.currentAgent = task.agent;
|
|
808
891
|
input.foregroundControl.currentIndex = index;
|
|
809
892
|
input.foregroundControl.currentActivityState = current?.activityState;
|
|
893
|
+
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
894
|
+
input.foregroundControl.currentTool = current?.currentTool;
|
|
895
|
+
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
810
896
|
input.foregroundControl.updatedAt = Date.now();
|
|
811
897
|
}
|
|
812
898
|
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
@@ -852,6 +938,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
852
938
|
sessionRoot,
|
|
853
939
|
controlConfig,
|
|
854
940
|
} = data;
|
|
941
|
+
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
942
|
+
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
855
943
|
const allProgress: AgentProgress[] = [];
|
|
856
944
|
const allArtifactPaths: ArtifactPaths[] = [];
|
|
857
945
|
const tasks = params.tasks!;
|
|
@@ -976,6 +1064,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
976
1064
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
977
1065
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
978
1066
|
controlConfig,
|
|
1067
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1068
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
979
1069
|
});
|
|
980
1070
|
}
|
|
981
1071
|
}
|
|
@@ -1020,6 +1110,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1020
1110
|
skillOverrides,
|
|
1021
1111
|
behaviors,
|
|
1022
1112
|
controlConfig,
|
|
1113
|
+
onControlEvent,
|
|
1114
|
+
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
1023
1115
|
foregroundControl,
|
|
1024
1116
|
concurrencyLimit: parallelConcurrency,
|
|
1025
1117
|
maxSubagentDepths,
|
|
@@ -1100,6 +1192,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1100
1192
|
sessionRoot,
|
|
1101
1193
|
controlConfig,
|
|
1102
1194
|
} = data;
|
|
1195
|
+
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1196
|
+
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget(runId, params.agent!, undefined) : undefined;
|
|
1103
1197
|
const allProgress: AgentProgress[] = [];
|
|
1104
1198
|
const allArtifactPaths: ArtifactPaths[] = [];
|
|
1105
1199
|
const agentConfig = agents.find((a) => a.name === params.agent);
|
|
@@ -1196,6 +1290,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1196
1290
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1197
1291
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1198
1292
|
controlConfig,
|
|
1293
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1294
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1199
1295
|
});
|
|
1200
1296
|
}
|
|
1201
1297
|
}
|
|
@@ -1218,12 +1314,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1218
1314
|
if (foregroundControl) {
|
|
1219
1315
|
foregroundControl.currentAgent = params.agent;
|
|
1220
1316
|
foregroundControl.currentIndex = 0;
|
|
1221
|
-
foregroundControl.currentActivityState =
|
|
1317
|
+
foregroundControl.currentActivityState = undefined;
|
|
1222
1318
|
foregroundControl.updatedAt = Date.now();
|
|
1223
1319
|
foregroundControl.interrupt = () => {
|
|
1224
1320
|
if (interruptController.signal.aborted) return false;
|
|
1225
1321
|
interruptController.abort();
|
|
1226
|
-
foregroundControl.currentActivityState =
|
|
1322
|
+
foregroundControl.currentActivityState = undefined;
|
|
1227
1323
|
foregroundControl.updatedAt = Date.now();
|
|
1228
1324
|
return true;
|
|
1229
1325
|
};
|
|
@@ -1236,6 +1332,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1236
1332
|
foregroundControl.currentAgent = params.agent;
|
|
1237
1333
|
foregroundControl.currentIndex = firstProgress?.index ?? 0;
|
|
1238
1334
|
foregroundControl.currentActivityState = firstProgress?.activityState;
|
|
1335
|
+
foregroundControl.lastActivityAt = firstProgress?.lastActivityAt;
|
|
1336
|
+
foregroundControl.currentTool = firstProgress?.currentTool;
|
|
1337
|
+
foregroundControl.currentToolStartedAt = firstProgress?.currentToolStartedAt;
|
|
1239
1338
|
foregroundControl.updatedAt = Date.now();
|
|
1240
1339
|
}
|
|
1241
1340
|
onUpdate(update);
|
|
@@ -1259,6 +1358,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1259
1358
|
maxSubagentDepth,
|
|
1260
1359
|
onUpdate: forwardSingleUpdate,
|
|
1261
1360
|
controlConfig,
|
|
1361
|
+
onControlEvent,
|
|
1362
|
+
intercomSessionName: childIntercomTarget,
|
|
1262
1363
|
modelOverride,
|
|
1263
1364
|
availableModels,
|
|
1264
1365
|
preferredModelProvider: currentProvider,
|
|
@@ -1267,6 +1368,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1267
1368
|
if (foregroundControl?.currentIndex === 0) {
|
|
1268
1369
|
foregroundControl.interrupt = undefined;
|
|
1269
1370
|
foregroundControl.currentActivityState = r.progress?.activityState;
|
|
1371
|
+
foregroundControl.lastActivityAt = r.progress?.lastActivityAt;
|
|
1372
|
+
foregroundControl.currentTool = r.progress?.currentTool;
|
|
1373
|
+
foregroundControl.currentToolStartedAt = r.progress?.currentToolStartedAt;
|
|
1270
1374
|
foregroundControl.updatedAt = Date.now();
|
|
1271
1375
|
}
|
|
1272
1376
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
@@ -1356,13 +1460,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1356
1460
|
const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
|
|
1357
1461
|
const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
|
|
1358
1462
|
if (params.action) {
|
|
1463
|
+
if (params.action === "status") {
|
|
1464
|
+
const foreground = getForegroundControl(deps.state, paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId);
|
|
1465
|
+
if (foreground) return foregroundStatusResult(foreground);
|
|
1466
|
+
return inspectSubagentStatus(paramsWithResolvedCwd);
|
|
1467
|
+
}
|
|
1359
1468
|
if (params.action === "interrupt") {
|
|
1360
|
-
const
|
|
1469
|
+
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
1470
|
+
const foreground = getForegroundControl(deps.state, targetRunId);
|
|
1361
1471
|
if (foreground?.interrupt) {
|
|
1362
1472
|
const interrupted = foreground.interrupt();
|
|
1363
1473
|
if (interrupted) {
|
|
1364
1474
|
foreground.updatedAt = Date.now();
|
|
1365
|
-
foreground.currentActivityState =
|
|
1475
|
+
foreground.currentActivityState = undefined;
|
|
1366
1476
|
return {
|
|
1367
1477
|
content: [{ type: "text", text: `Interrupt requested for foreground run ${foreground.runId}.` }],
|
|
1368
1478
|
details: { mode: "management", results: [] },
|
|
@@ -1374,7 +1484,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1374
1484
|
details: { mode: "management", results: [] },
|
|
1375
1485
|
};
|
|
1376
1486
|
}
|
|
1377
|
-
const asyncInterruptResult = interruptAsyncRun(deps.state,
|
|
1487
|
+
const asyncInterruptResult = interruptAsyncRun(deps.state, targetRunId);
|
|
1378
1488
|
if (asyncInterruptResult) return asyncInterruptResult;
|
|
1379
1489
|
return {
|
|
1380
1490
|
content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
|
|
@@ -1382,7 +1492,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1382
1492
|
details: { mode: "management", results: [] },
|
|
1383
1493
|
};
|
|
1384
1494
|
}
|
|
1385
|
-
const validActions = ["list", "get", "create", "update", "delete", "interrupt"];
|
|
1495
|
+
const validActions = ["list", "get", "create", "update", "delete", "status", "interrupt"];
|
|
1386
1496
|
if (!validActions.includes(params.action)) {
|
|
1387
1497
|
return {
|
|
1388
1498
|
content: [{ type: "text", text: `Unknown action: ${params.action}. Valid: ${validActions.join(", ")}` }],
|
|
@@ -1514,6 +1624,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1514
1624
|
backgroundRequestedWhileClarifying,
|
|
1515
1625
|
effectiveAsync,
|
|
1516
1626
|
controlConfig,
|
|
1627
|
+
intercomBridge,
|
|
1517
1628
|
};
|
|
1518
1629
|
|
|
1519
1630
|
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
|
|
3
3
|
export const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = "PI_SUBAGENT_INHERIT_PROJECT_CONTEXT";
|
|
4
4
|
export const SUBAGENT_INHERIT_SKILLS_ENV = "PI_SUBAGENT_INHERIT_SKILLS";
|
|
5
|
+
export const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "PI_SUBAGENT_INTERCOM_SESSION_NAME";
|
|
5
6
|
|
|
6
7
|
const PROJECT_CONTEXT_HEADER = "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n";
|
|
7
8
|
const SKILLS_HEADER = "\n\nThe following skills provide specialized instructions for specific tasks.";
|
|
@@ -54,6 +55,11 @@ export function rewriteSubagentPrompt(
|
|
|
54
55
|
|
|
55
56
|
export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
56
57
|
pi.on("before_agent_start", async (event) => {
|
|
58
|
+
const intercomSessionName = process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV]?.trim();
|
|
59
|
+
if (intercomSessionName && typeof pi.setSessionName === "function") {
|
|
60
|
+
pi.setSessionName(intercomSessionName);
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
const inheritProjectContext = readBooleanEnv(SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV);
|
|
58
64
|
const inheritSkills = readBooleanEnv(SUBAGENT_INHERIT_SKILLS_ENV);
|
|
59
65
|
if (inheritProjectContext === undefined && inheritSkills === undefined) return;
|
package/subagent-runner.ts
CHANGED
|
@@ -19,7 +19,15 @@ import {
|
|
|
19
19
|
truncateOutput,
|
|
20
20
|
getSubagentDepthEnv,
|
|
21
21
|
} from "./types.ts";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_CONTROL_CONFIG,
|
|
24
|
+
buildControlEvent,
|
|
25
|
+
deriveActivityState,
|
|
26
|
+
claimControlNotification,
|
|
27
|
+
formatControlIntercomMessage,
|
|
28
|
+
formatControlNoticeMessage,
|
|
29
|
+
shouldEmitControlEvent,
|
|
30
|
+
} from "./subagent-control.ts";
|
|
23
31
|
import {
|
|
24
32
|
type RunnerSubagentStep as SubagentStep,
|
|
25
33
|
type RunnerStep,
|
|
@@ -64,6 +72,8 @@ interface SubagentRunConfig {
|
|
|
64
72
|
worktreeSetupHook?: string;
|
|
65
73
|
worktreeSetupHookTimeoutMs?: number;
|
|
66
74
|
controlConfig?: ResolvedControlConfig;
|
|
75
|
+
controlIntercomTarget?: string;
|
|
76
|
+
childIntercomTargets?: Array<string | undefined>;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
interface StepResult {
|
|
@@ -514,6 +524,7 @@ interface SingleStepContext {
|
|
|
514
524
|
piPackageRoot?: string;
|
|
515
525
|
piArgv1?: string;
|
|
516
526
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
527
|
+
childIntercomTarget?: string;
|
|
517
528
|
}
|
|
518
529
|
|
|
519
530
|
/** Run a single pi agent step, returning output and metadata */
|
|
@@ -575,6 +586,7 @@ async function runSingleStep(
|
|
|
575
586
|
systemPromptMode: step.systemPromptMode,
|
|
576
587
|
mcpDirectTools: step.mcpDirectTools,
|
|
577
588
|
promptFileStem: step.agent,
|
|
589
|
+
intercomSessionName: ctx.childIntercomTarget,
|
|
578
590
|
});
|
|
579
591
|
const run = await runPiStreaming(
|
|
580
592
|
args,
|
|
@@ -671,6 +683,9 @@ type RunnerStatusPayload = {
|
|
|
671
683
|
mode: "single" | "chain";
|
|
672
684
|
state: "queued" | "running" | "complete" | "failed" | "paused";
|
|
673
685
|
activityState?: ActivityState;
|
|
686
|
+
lastActivityAt?: number;
|
|
687
|
+
currentTool?: string;
|
|
688
|
+
currentToolStartedAt?: number;
|
|
674
689
|
startedAt: number;
|
|
675
690
|
endedAt?: number;
|
|
676
691
|
lastUpdate: number;
|
|
@@ -681,6 +696,9 @@ type RunnerStatusPayload = {
|
|
|
681
696
|
agent: string;
|
|
682
697
|
status: "pending" | "running" | "complete" | "failed";
|
|
683
698
|
activityState?: ActivityState;
|
|
699
|
+
lastActivityAt?: number;
|
|
700
|
+
currentTool?: string;
|
|
701
|
+
currentToolStartedAt?: number;
|
|
684
702
|
startedAt?: number;
|
|
685
703
|
endedAt?: number;
|
|
686
704
|
durationMs?: number;
|
|
@@ -752,11 +770,12 @@ function markParallelGroupRunning(input: {
|
|
|
752
770
|
for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
|
|
753
771
|
const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
|
|
754
772
|
input.statusPayload.steps[flatTaskIndex].status = "running";
|
|
755
|
-
input.statusPayload.steps[flatTaskIndex].activityState = "active";
|
|
756
773
|
input.statusPayload.steps[flatTaskIndex].startedAt = input.groupStartTime;
|
|
774
|
+
input.statusPayload.steps[flatTaskIndex].lastActivityAt = input.groupStartTime;
|
|
757
775
|
}
|
|
758
776
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
759
|
-
input.statusPayload.activityState =
|
|
777
|
+
input.statusPayload.activityState = undefined;
|
|
778
|
+
input.statusPayload.lastActivityAt = input.groupStartTime;
|
|
760
779
|
input.statusPayload.lastUpdate = input.groupStartTime;
|
|
761
780
|
input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
|
|
762
781
|
writeJson(input.statusPath, input.statusPayload);
|
|
@@ -812,6 +831,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
812
831
|
const controlConfig = config.controlConfig ?? DEFAULT_CONTROL_CONFIG;
|
|
813
832
|
let activeChildInterrupt: (() => void) | undefined;
|
|
814
833
|
let interrupted = false;
|
|
834
|
+
let currentActivityState: ActivityState | undefined;
|
|
835
|
+
let activityTimer: NodeJS.Timeout | undefined;
|
|
815
836
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
816
837
|
let latestSessionFile: string | undefined;
|
|
817
838
|
|
|
@@ -823,7 +844,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
823
844
|
runId: id,
|
|
824
845
|
mode: flatSteps.length > 1 ? "chain" : "single",
|
|
825
846
|
state: "running",
|
|
826
|
-
|
|
847
|
+
lastActivityAt: overallStartTime,
|
|
827
848
|
startedAt: overallStartTime,
|
|
828
849
|
lastUpdate: overallStartTime,
|
|
829
850
|
pid: process.pid,
|
|
@@ -832,7 +853,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
832
853
|
steps: flatSteps.map((step) => ({
|
|
833
854
|
agent: step.agent,
|
|
834
855
|
status: "pending",
|
|
835
|
-
activityState: controlConfig.enabled ? "starting" : undefined,
|
|
836
856
|
skills: step.skills,
|
|
837
857
|
model: step.model,
|
|
838
858
|
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
@@ -844,16 +864,95 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
844
864
|
|
|
845
865
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
846
866
|
writeJson(statusPath, statusPayload);
|
|
867
|
+
|
|
868
|
+
const currentStepAgent = () => statusPayload.steps[statusPayload.currentStep]?.agent ?? flatSteps[statusPayload.currentStep]?.agent ?? "subagent";
|
|
869
|
+
const currentOutputActivityAt = (): number => {
|
|
870
|
+
const runningIndexes = statusPayload.steps
|
|
871
|
+
.map((step, index) => step.status === "running" ? index : -1)
|
|
872
|
+
.filter((index) => index >= 0);
|
|
873
|
+
let lastActivityAt = statusPayload.steps[statusPayload.currentStep]?.startedAt ?? overallStartTime;
|
|
874
|
+
for (const index of runningIndexes.length > 0 ? runningIndexes : [statusPayload.currentStep]) {
|
|
875
|
+
try {
|
|
876
|
+
lastActivityAt = Math.max(lastActivityAt, fs.statSync(path.join(asyncDir, `output-${index}.log`)).mtimeMs);
|
|
877
|
+
} catch {
|
|
878
|
+
// Missing output files are normal before a child writes its first line.
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return lastActivityAt;
|
|
882
|
+
};
|
|
883
|
+
const emittedControlEventKeys = new Set<string>();
|
|
884
|
+
const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
|
|
885
|
+
const childIntercomTarget = config.childIntercomTargets?.[statusPayload.currentStep];
|
|
886
|
+
if (controlConfig.notifyChannels.length === 0 || !claimControlNotification(controlConfig, event, emittedControlEventKeys, childIntercomTarget)) return;
|
|
887
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
888
|
+
type: "subagent.control",
|
|
889
|
+
event,
|
|
890
|
+
channels: controlConfig.notifyChannels,
|
|
891
|
+
childIntercomTarget,
|
|
892
|
+
noticeText: formatControlNoticeMessage(event, childIntercomTarget),
|
|
893
|
+
...(config.controlIntercomTarget && controlConfig.notifyChannels.includes("intercom") ? {
|
|
894
|
+
intercom: {
|
|
895
|
+
to: config.controlIntercomTarget,
|
|
896
|
+
message: formatControlIntercomMessage(event, childIntercomTarget),
|
|
897
|
+
},
|
|
898
|
+
} : {}),
|
|
899
|
+
}));
|
|
900
|
+
};
|
|
901
|
+
const updateRunnerActivityState = (now: number): boolean => {
|
|
902
|
+
const lastActivityAt = currentOutputActivityAt();
|
|
903
|
+
const next = deriveActivityState({
|
|
904
|
+
config: controlConfig,
|
|
905
|
+
startedAt: overallStartTime,
|
|
906
|
+
lastActivityAt,
|
|
907
|
+
now,
|
|
908
|
+
});
|
|
909
|
+
if (next === currentActivityState && statusPayload.lastActivityAt === lastActivityAt) return false;
|
|
910
|
+
const previous = currentActivityState;
|
|
911
|
+
currentActivityState = next;
|
|
912
|
+
statusPayload.activityState = next;
|
|
913
|
+
statusPayload.lastActivityAt = lastActivityAt;
|
|
914
|
+
for (const step of statusPayload.steps) {
|
|
915
|
+
if (step.status === "running") {
|
|
916
|
+
step.activityState = next;
|
|
917
|
+
step.lastActivityAt = lastActivityAt;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
statusPayload.lastUpdate = now;
|
|
921
|
+
if (shouldEmitControlEvent(controlConfig, previous, next)) {
|
|
922
|
+
const event = buildControlEvent({
|
|
923
|
+
from: previous,
|
|
924
|
+
to: next,
|
|
925
|
+
runId: id,
|
|
926
|
+
agent: currentStepAgent(),
|
|
927
|
+
index: statusPayload.currentStep,
|
|
928
|
+
ts: now,
|
|
929
|
+
lastActivityAt,
|
|
930
|
+
});
|
|
931
|
+
appendControlEvent(event);
|
|
932
|
+
}
|
|
933
|
+
writeJson(statusPath, statusPayload);
|
|
934
|
+
return true;
|
|
935
|
+
};
|
|
936
|
+
if (controlConfig.enabled) {
|
|
937
|
+
activityTimer = setInterval(() => {
|
|
938
|
+
if (statusPayload.state !== "running") return;
|
|
939
|
+
const now = Date.now();
|
|
940
|
+
updateRunnerActivityState(now);
|
|
941
|
+
}, 1000);
|
|
942
|
+
activityTimer.unref?.();
|
|
943
|
+
}
|
|
944
|
+
|
|
847
945
|
const interruptRunner = () => {
|
|
848
946
|
if (interrupted || statusPayload.state !== "running") return;
|
|
849
947
|
interrupted = true;
|
|
850
948
|
const now = Date.now();
|
|
851
949
|
statusPayload.state = "paused";
|
|
852
|
-
|
|
950
|
+
currentActivityState = undefined;
|
|
951
|
+
statusPayload.activityState = undefined;
|
|
853
952
|
statusPayload.lastUpdate = now;
|
|
854
953
|
const current = statusPayload.steps[statusPayload.currentStep];
|
|
855
954
|
if (current?.status === "running") {
|
|
856
|
-
current.activityState =
|
|
955
|
+
current.activityState = undefined;
|
|
857
956
|
current.endedAt = now;
|
|
858
957
|
current.durationMs = current.startedAt ? now - current.startedAt : undefined;
|
|
859
958
|
}
|
|
@@ -980,6 +1079,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
980
1079
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
981
1080
|
piPackageRoot: config.piPackageRoot,
|
|
982
1081
|
piArgv1: config.piArgv1,
|
|
1082
|
+
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
983
1083
|
registerInterrupt: (interrupt) => {
|
|
984
1084
|
activeChildInterrupt = interrupt;
|
|
985
1085
|
},
|
|
@@ -1077,10 +1177,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1077
1177
|
const stepStartTime = Date.now();
|
|
1078
1178
|
statusPayload.currentStep = flatIndex;
|
|
1079
1179
|
statusPayload.steps[flatIndex].status = "running";
|
|
1080
|
-
statusPayload.steps[flatIndex].activityState =
|
|
1081
|
-
statusPayload.activityState =
|
|
1180
|
+
statusPayload.steps[flatIndex].activityState = undefined;
|
|
1181
|
+
statusPayload.activityState = undefined;
|
|
1082
1182
|
statusPayload.steps[flatIndex].skills = seqStep.skills;
|
|
1083
1183
|
statusPayload.steps[flatIndex].startedAt = stepStartTime;
|
|
1184
|
+
statusPayload.steps[flatIndex].lastActivityAt = stepStartTime;
|
|
1185
|
+
statusPayload.lastActivityAt = stepStartTime;
|
|
1084
1186
|
statusPayload.lastUpdate = stepStartTime;
|
|
1085
1187
|
statusPayload.outputFile = path.join(asyncDir, `output-${flatIndex}.log`);
|
|
1086
1188
|
writeJson(statusPath, statusPayload);
|
|
@@ -1101,6 +1203,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1101
1203
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
1102
1204
|
piPackageRoot: config.piPackageRoot,
|
|
1103
1205
|
piArgv1: config.piArgv1,
|
|
1206
|
+
childIntercomTarget: config.childIntercomTargets?.[flatIndex],
|
|
1104
1207
|
registerInterrupt: (interrupt) => {
|
|
1105
1208
|
activeChildInterrupt = interrupt;
|
|
1106
1209
|
},
|
|
@@ -1221,10 +1324,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1221
1324
|
}
|
|
1222
1325
|
}
|
|
1223
1326
|
|
|
1327
|
+
if (activityTimer) {
|
|
1328
|
+
clearInterval(activityTimer);
|
|
1329
|
+
activityTimer = undefined;
|
|
1330
|
+
}
|
|
1224
1331
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
1225
1332
|
const runEndedAt = Date.now();
|
|
1226
1333
|
statusPayload.state = interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
1227
|
-
statusPayload.activityState =
|
|
1334
|
+
statusPayload.activityState = undefined;
|
|
1228
1335
|
statusPayload.endedAt = runEndedAt;
|
|
1229
1336
|
statusPayload.lastUpdate = runEndedAt;
|
|
1230
1337
|
statusPayload.sessionFile = effectiveSessionFile;
|
|
@@ -1272,6 +1379,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1272
1379
|
id,
|
|
1273
1380
|
agent: agentName,
|
|
1274
1381
|
success: !interrupted && results.every((r) => r.success),
|
|
1382
|
+
state: interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
1275
1383
|
summary: interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
1276
1384
|
results: results.map((r) => ({
|
|
1277
1385
|
agent: r.agent,
|
package/subagents-status.ts
CHANGED
|
@@ -172,7 +172,11 @@ export class SubagentsStatusComponent implements Component {
|
|
|
172
172
|
: "";
|
|
173
173
|
const duration = step.durationMs !== undefined ? ` | ${formatDuration(step.durationMs)}` : "";
|
|
174
174
|
const tokens = step.tokens ? ` | ${formatTokens(step.tokens.total)} tok` : "";
|
|
175
|
-
const activity = step.
|
|
175
|
+
const activity = step.lastActivityAt
|
|
176
|
+
? step.activityState === "needs_attention"
|
|
177
|
+
? ` | no activity for ${formatDuration(Math.max(0, Date.now() - step.lastActivityAt))}`
|
|
178
|
+
: ` | active ${formatDuration(Math.max(0, Date.now() - step.lastActivityAt))} ago`
|
|
179
|
+
: "";
|
|
176
180
|
const line = ` ${step.index + 1}. ${step.agent} | ${stepStatusColor(this.theme, step.status)}${activity}${model}${attempts}${duration}${tokens}`;
|
|
177
181
|
lines.push(row(truncateToWidth(line, innerW), width, this.theme));
|
|
178
182
|
if (step.error) {
|