iosm-cli 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +43 -10
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-profiles.d.ts +2 -1
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +18 -5
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts +7 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +64 -15
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-teams.d.ts.map +1 -1
- package/dist/core/agent-teams.js +90 -19
- package/dist/core/agent-teams.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +6 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +9 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/orchestration-limits.d.ts +6 -0
- package/dist/core/orchestration-limits.d.ts.map +1 -0
- package/dist/core/orchestration-limits.js +6 -0
- package/dist/core/orchestration-limits.js.map +1 -0
- package/dist/core/parallel-task-agent.d.ts +23 -1
- package/dist/core/parallel-task-agent.d.ts.map +1 -1
- package/dist/core/parallel-task-agent.js +110 -20
- package/dist/core/parallel-task-agent.js.map +1 -1
- package/dist/core/sdk.d.ts +6 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +34 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/shared-memory.d.ts +2 -2
- package/dist/core/shared-memory.d.ts.map +1 -1
- package/dist/core/shared-memory.js +220 -91
- package/dist/core/shared-memory.js.map +1 -1
- package/dist/core/singular.d.ts.map +1 -1
- package/dist/core/singular.js +3 -1
- package/dist/core/singular.js.map +1 -1
- package/dist/core/subagents.d.ts +1 -1
- package/dist/core/subagents.d.ts.map +1 -1
- package/dist/core/subagents.js +32 -10
- package/dist/core/subagents.js.map +1 -1
- package/dist/core/swarm/planner.d.ts.map +1 -1
- package/dist/core/swarm/planner.js +200 -12
- package/dist/core/swarm/planner.js.map +1 -1
- package/dist/core/swarm/scheduler.d.ts +2 -0
- package/dist/core/swarm/scheduler.d.ts.map +1 -1
- package/dist/core/swarm/scheduler.js +87 -6
- package/dist/core/swarm/scheduler.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -0
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/ast-grep.d.ts.map +1 -1
- package/dist/core/tools/ast-grep.js +2 -0
- package/dist/core/tools/ast-grep.js.map +1 -1
- package/dist/core/tools/edit.d.ts +8 -4
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +18 -3
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -7
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/shared-memory.d.ts.map +1 -1
- package/dist/core/tools/shared-memory.js +34 -6
- package/dist/core/tools/shared-memory.js.map +1 -1
- package/dist/core/tools/task.d.ts +10 -3
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +830 -184
- package/dist/core/tools/task.js.map +1 -1
- package/dist/core/tools/todo.d.ts +10 -10
- package/dist/core/tools/todo.d.ts.map +1 -1
- package/dist/core/tools/todo.js +135 -17
- package/dist/core/tools/todo.js.map +1 -1
- package/dist/core/tools/yq.d.ts.map +1 -1
- package/dist/core/tools/yq.js +2 -0
- package/dist/core/tools/yq.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +10 -9
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +22 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +957 -75
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/cli-reference.md +8 -0
- package/docs/configuration.md +5 -1
- package/docs/interactive-mode.md +7 -1
- package/docs/orchestration-and-subagents.md +5 -0
- package/package.json +1 -1
|
@@ -10,11 +10,12 @@ import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown,
|
|
|
10
10
|
import { spawn, spawnSync } from "child_process";
|
|
11
11
|
import { APP_NAME, CHANGELOG_URL, ENV_SESSION_TRACE, ENV_OFFLINE, ENV_SKIP_VERSION_CHECK, getAgentDir, getAuthPath, getDebugLogPath, getModelsPath, getSessionTracePath, getShareViewerUrl, getUpdateInstruction, isSessionTraceEnabled, PACKAGE_NAME, VERSION, } from "../../config.js";
|
|
12
12
|
import { AuthStorage } from "../../core/auth-storage.js";
|
|
13
|
-
import { getAgentProfile, getMainProfileNames, getProfileNames, isValidProfileName, } from "../../core/agent-profiles.js";
|
|
13
|
+
import { getAgentProfile, getMainProfileNames, getProfileNames, isReadOnlyProfileName, isValidProfileName, } from "../../core/agent-profiles.js";
|
|
14
14
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
15
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
16
16
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
17
17
|
import { createCompactionSummaryMessage, INTERNAL_UI_META_CUSTOM_TYPE, isInternalUiMetaDetails, } from "../../core/messages.js";
|
|
18
|
+
import { MAX_ORCHESTRATION_AGENTS, MAX_ORCHESTRATION_PARALLEL, MAX_SUBAGENT_DELEGATE_PARALLEL, } from "../../core/orchestration-limits.js";
|
|
18
19
|
import { loadModelsDevProviderCatalog, } from "../../core/models-dev-provider-catalog.js";
|
|
19
20
|
import { ModelRegistry } from "../../core/model-registry.js";
|
|
20
21
|
import { MODELS_DEV_PROVIDERS } from "../../core/models-dev-providers.js";
|
|
@@ -75,6 +76,212 @@ import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMar
|
|
|
75
76
|
function isExpandable(obj) {
|
|
76
77
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
77
78
|
}
|
|
79
|
+
export function resolveStreamingSubmissionMode(input) {
|
|
80
|
+
if (input.configuredMode === "meta" &&
|
|
81
|
+
input.activeProfileName === "meta" &&
|
|
82
|
+
(input.activeSubagentCount > 0 || input.activeAssistantOrchestrationContext)) {
|
|
83
|
+
return "followUp";
|
|
84
|
+
}
|
|
85
|
+
return input.configuredMode;
|
|
86
|
+
}
|
|
87
|
+
function parseRequestedParallelAgentCount(text) {
|
|
88
|
+
const patterns = [
|
|
89
|
+
/(\d+)\s+(?:parallel|concurrent)\s+agents?/i,
|
|
90
|
+
/(\d+)\s+agents?/i,
|
|
91
|
+
/(\d+)\s+паралл[\p{L}\p{N}_-]*\s+агент[\p{L}\p{N}_-]*/iu,
|
|
92
|
+
/(\d+)\s+агент[\p{L}\p{N}_-]*/iu,
|
|
93
|
+
];
|
|
94
|
+
for (const pattern of patterns) {
|
|
95
|
+
const match = text.match(pattern);
|
|
96
|
+
const parsed = match?.[1] ? Number.parseInt(match[1], 10) : Number.NaN;
|
|
97
|
+
if (Number.isInteger(parsed) && parsed >= 1) {
|
|
98
|
+
return Math.max(1, Math.min(MAX_ORCHESTRATION_AGENTS, parsed));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
function deriveMetaRequiredTopLevelTaskCalls(userInput, taskPlanSnapshot) {
|
|
104
|
+
const requested = parseRequestedParallelAgentCount(userInput);
|
|
105
|
+
if (requested)
|
|
106
|
+
return requested;
|
|
107
|
+
if (!taskPlanSnapshot)
|
|
108
|
+
return undefined;
|
|
109
|
+
return Math.max(2, Math.min(3, taskPlanSnapshot.totalSteps));
|
|
110
|
+
}
|
|
111
|
+
function extractTaskToolErrorText(result) {
|
|
112
|
+
if (!result || typeof result !== "object")
|
|
113
|
+
return undefined;
|
|
114
|
+
const candidate = result;
|
|
115
|
+
if (typeof candidate.error === "string" && candidate.error.trim())
|
|
116
|
+
return candidate.error.trim();
|
|
117
|
+
if (typeof candidate.output === "string" && candidate.output.trim())
|
|
118
|
+
return candidate.output.trim();
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
function buildMetaParallelismCorrection(input) {
|
|
122
|
+
const validationFailure = input.taskToolError && /Validation failed for tool "task"/i.test(input.taskToolError)
|
|
123
|
+
? input.taskToolError.split("\n").slice(0, 3).join("\n")
|
|
124
|
+
: undefined;
|
|
125
|
+
return [
|
|
126
|
+
"[META_PARALLELISM_CORRECTION]",
|
|
127
|
+
`Meta runtime correction: this run currently has ${input.launchedTopLevelTasks} top-level task call(s), but it should have at least ${input.requiredTopLevelTasks}.`,
|
|
128
|
+
"Stop manual sequential execution in the main agent and convert the execution graph into parallel task calls now.",
|
|
129
|
+
"Emit the missing top-level task calls in the same assistant response when branches are independent.",
|
|
130
|
+
input.workerDiversityMissing
|
|
131
|
+
? `Top-level task fan-out currently targets only ${input.distinctWorkers ?? 1} worker identity. Use at least 2 focused worker identities (profile/agent) or force nested delegation inside each stream.`
|
|
132
|
+
: undefined,
|
|
133
|
+
"Each task tool call MUST include description and prompt.",
|
|
134
|
+
'If you want a custom subagent, pass it via agent="name"; keep profile set to the capability profile, not the custom subagent name.',
|
|
135
|
+
input.rawRootDelegateBlocks && input.rawRootDelegateBlocks > 0
|
|
136
|
+
? `You emitted ${input.rawRootDelegateBlocks} raw root-level <delegate_task> block(s). Those are not executed automatically in the parent session. Convert each one into an actual top-level task tool call now.`
|
|
137
|
+
: undefined,
|
|
138
|
+
"If a child workstream is still broad, require nested <delegate_task> fan-out instead of letting one child do everything alone.",
|
|
139
|
+
input.taskPlanSnapshot
|
|
140
|
+
? `You already produced a complex plan with ${input.taskPlanSnapshot.totalSteps} steps. Turn that plan into parallel execution now.`
|
|
141
|
+
: undefined,
|
|
142
|
+
validationFailure ? `Previous task validation failure:\n${validationFailure}` : undefined,
|
|
143
|
+
"If you truly cannot split safely, output exactly one line: DELEGATION_IMPOSSIBLE: <precise reason>.",
|
|
144
|
+
"[/META_PARALLELISM_CORRECTION]",
|
|
145
|
+
]
|
|
146
|
+
.filter((line) => typeof line === "string" && line.trim().length > 0)
|
|
147
|
+
.join("\n");
|
|
148
|
+
}
|
|
149
|
+
async function promptMetaWithParallelismGuard(input) {
|
|
150
|
+
let taskToolCalls = 0;
|
|
151
|
+
let nonTaskToolCalls = 0;
|
|
152
|
+
let taskPlanSnapshot;
|
|
153
|
+
let taskToolError;
|
|
154
|
+
let rawRootDelegateBlocks = 0;
|
|
155
|
+
let delegationImpossibleDeclared = false;
|
|
156
|
+
let correctionText;
|
|
157
|
+
let distinctTaskWorkers = new Set();
|
|
158
|
+
let delegatedChildTasksSeen = 0;
|
|
159
|
+
const maybeScheduleCorrection = (options) => {
|
|
160
|
+
const requiredTopLevelTasks = deriveMetaRequiredTopLevelTaskCalls(input.userInput, taskPlanSnapshot);
|
|
161
|
+
if (!requiredTopLevelTasks || delegationImpossibleDeclared) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const needsMoreTopLevelTasks = taskToolCalls < requiredTopLevelTasks;
|
|
165
|
+
const workerDiversityMissing = !needsMoreTopLevelTasks &&
|
|
166
|
+
requiredTopLevelTasks >= 2 &&
|
|
167
|
+
distinctTaskWorkers.size === 1 &&
|
|
168
|
+
delegatedChildTasksSeen === 0;
|
|
169
|
+
if (!needsMoreTopLevelTasks && !workerDiversityMissing) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const hasComplexPlan = !!taskPlanSnapshot;
|
|
173
|
+
const hasTaskFailure = !!taskToolError;
|
|
174
|
+
const hasRawRootDelegates = rawRootDelegateBlocks > 0;
|
|
175
|
+
if (needsMoreTopLevelTasks && !hasTaskFailure && !hasRawRootDelegates) {
|
|
176
|
+
const minimumNonTaskCalls = hasComplexPlan ? 0 : 2;
|
|
177
|
+
if (!options?.finalize && nonTaskToolCalls < minimumNonTaskCalls) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
correctionText = buildMetaParallelismCorrection({
|
|
182
|
+
requiredTopLevelTasks,
|
|
183
|
+
launchedTopLevelTasks: taskToolCalls,
|
|
184
|
+
taskPlanSnapshot,
|
|
185
|
+
taskToolError,
|
|
186
|
+
rawRootDelegateBlocks,
|
|
187
|
+
workerDiversityMissing,
|
|
188
|
+
distinctWorkers: distinctTaskWorkers.size,
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
const unsubscribe = input.session.subscribe((event) => {
|
|
192
|
+
if (event.type === "message_end" && event.message.role === "custom") {
|
|
193
|
+
if (event.message.customType === TASK_PLAN_CUSTOM_TYPE && isTaskPlanSnapshot(event.message.details)) {
|
|
194
|
+
taskPlanSnapshot = event.message.details;
|
|
195
|
+
maybeScheduleCorrection();
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
200
|
+
const rawText = extractAssistantText(event.message);
|
|
201
|
+
rawRootDelegateBlocks += (rawText.match(/<delegate_task\b/gi) ?? []).length;
|
|
202
|
+
delegationImpossibleDeclared =
|
|
203
|
+
delegationImpossibleDeclared || /^\s*DELEGATION_IMPOSSIBLE\s*:/im.test(rawText);
|
|
204
|
+
maybeScheduleCorrection();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (event.type === "tool_execution_start") {
|
|
208
|
+
if (event.toolName === "task") {
|
|
209
|
+
taskToolCalls += 1;
|
|
210
|
+
const args = event.args && typeof event.args === "object" ? event.args : undefined;
|
|
211
|
+
const workerIdentityRaw = (typeof args?.agent === "string" && args.agent.trim()) ||
|
|
212
|
+
(typeof args?.profile === "string" && args.profile.trim()) ||
|
|
213
|
+
undefined;
|
|
214
|
+
if (workerIdentityRaw) {
|
|
215
|
+
distinctTaskWorkers.add(workerIdentityRaw.toLowerCase());
|
|
216
|
+
}
|
|
217
|
+
else if (args) {
|
|
218
|
+
// Distinguish "explicit task call with omitted identity" from missing event payload.
|
|
219
|
+
distinctTaskWorkers.add("__default_profile__");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
nonTaskToolCalls += 1;
|
|
224
|
+
}
|
|
225
|
+
maybeScheduleCorrection();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (event.type === "tool_execution_end" && event.toolName === "task" && event.isError) {
|
|
229
|
+
taskToolError = extractTaskToolErrorText(event.result) ?? taskToolError;
|
|
230
|
+
maybeScheduleCorrection();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (event.type === "tool_execution_end" && event.toolName === "task" && !event.isError) {
|
|
234
|
+
const result = event.result;
|
|
235
|
+
const delegatedTasks = result?.details?.delegatedTasks;
|
|
236
|
+
if (typeof delegatedTasks === "number" && Number.isFinite(delegatedTasks) && delegatedTasks > 0) {
|
|
237
|
+
delegatedChildTasksSeen += delegatedTasks;
|
|
238
|
+
}
|
|
239
|
+
maybeScheduleCorrection();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
try {
|
|
243
|
+
await input.session.prompt(input.userInput);
|
|
244
|
+
maybeScheduleCorrection({ finalize: true });
|
|
245
|
+
let requiredTopLevelTasks = deriveMetaRequiredTopLevelTaskCalls(input.userInput, taskPlanSnapshot);
|
|
246
|
+
let workerDiversityMissingAfterRun = !!requiredTopLevelTasks &&
|
|
247
|
+
requiredTopLevelTasks >= 2 &&
|
|
248
|
+
distinctTaskWorkers.size === 1 &&
|
|
249
|
+
delegatedChildTasksSeen === 0;
|
|
250
|
+
if (correctionText &&
|
|
251
|
+
requiredTopLevelTasks &&
|
|
252
|
+
(taskToolCalls < requiredTopLevelTasks || workerDiversityMissingAfterRun) &&
|
|
253
|
+
!delegationImpossibleDeclared) {
|
|
254
|
+
await input.session.prompt(correctionText, {
|
|
255
|
+
expandPromptTemplates: false,
|
|
256
|
+
skipOrchestrationDirective: true,
|
|
257
|
+
source: "interactive",
|
|
258
|
+
});
|
|
259
|
+
maybeScheduleCorrection({ finalize: true });
|
|
260
|
+
requiredTopLevelTasks = deriveMetaRequiredTopLevelTaskCalls(input.userInput, taskPlanSnapshot);
|
|
261
|
+
workerDiversityMissingAfterRun =
|
|
262
|
+
!!requiredTopLevelTasks &&
|
|
263
|
+
requiredTopLevelTasks >= 2 &&
|
|
264
|
+
distinctTaskWorkers.size === 1 &&
|
|
265
|
+
delegatedChildTasksSeen === 0;
|
|
266
|
+
}
|
|
267
|
+
if (requiredTopLevelTasks &&
|
|
268
|
+
(taskToolCalls < requiredTopLevelTasks || workerDiversityMissingAfterRun) &&
|
|
269
|
+
!delegationImpossibleDeclared &&
|
|
270
|
+
input.onPersistentNonCompliance) {
|
|
271
|
+
await input.onPersistentNonCompliance({
|
|
272
|
+
requiredTopLevelTasks,
|
|
273
|
+
launchedTopLevelTasks: taskToolCalls,
|
|
274
|
+
distinctWorkers: distinctTaskWorkers.size,
|
|
275
|
+
workerDiversityMissing: workerDiversityMissingAfterRun,
|
|
276
|
+
taskPlanSnapshot,
|
|
277
|
+
taskToolError,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
unsubscribe();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
78
285
|
const IOSM_PROFILE_ONLY_COMMANDS = new Set(["iosm", "cycle-list", "cycle-plan", "cycle-status", "cycle-report"]);
|
|
79
286
|
const CHECKPOINT_LABEL_PREFIX = "checkpoint:";
|
|
80
287
|
const INTERRUPT_ABORT_TIMEOUT_MS = 8_000;
|
|
@@ -402,6 +609,8 @@ export class InteractiveMode {
|
|
|
402
609
|
this.sessionAllowedToolSignatures = new Set();
|
|
403
610
|
this.singularLastEffectiveContract = {};
|
|
404
611
|
this.swarmActiveRunId = undefined;
|
|
612
|
+
this.swarmStopRequested = false;
|
|
613
|
+
this.swarmAbortController = undefined;
|
|
405
614
|
this.lastSigintTime = 0;
|
|
406
615
|
this.lastEscapeTime = 0;
|
|
407
616
|
this.changelogMarkdown = undefined;
|
|
@@ -411,6 +620,8 @@ export class InteractiveMode {
|
|
|
411
620
|
// Streaming message tracking
|
|
412
621
|
this.streamingComponent = undefined;
|
|
413
622
|
this.streamingMessage = undefined;
|
|
623
|
+
this.currentTurnSawAssistantMessage = false;
|
|
624
|
+
this.currentTurnSawTaskToolCall = false;
|
|
414
625
|
// Tool execution tracking: toolCallId -> component
|
|
415
626
|
this.pendingTools = new Map();
|
|
416
627
|
// Subagent execution tracking with live progress/metadata for task tool calls.
|
|
@@ -589,6 +800,17 @@ export class InteractiveMode {
|
|
|
589
800
|
}
|
|
590
801
|
}
|
|
591
802
|
async requestToolPermission(request) {
|
|
803
|
+
if (this.activeProfileName === "meta" &&
|
|
804
|
+
!this.currentTurnSawTaskToolCall &&
|
|
805
|
+
(request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write")) {
|
|
806
|
+
this.showWarning(`META mode orchestration guard: direct ${request.toolName} is blocked before the first task call in a turn. Launch subagents via task or switch profile to full.`);
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
if (isReadOnlyProfileName(this.activeProfileName) &&
|
|
810
|
+
(request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write")) {
|
|
811
|
+
this.showWarning(`Tool ${request.toolName} is disabled in ${this.activeProfileName} profile. Switch to full/meta/iosm for mutating operations.`);
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
592
814
|
for (const rule of this.permissionDenyRules) {
|
|
593
815
|
if (this.matchesPermissionRule(rule, request)) {
|
|
594
816
|
this.showWarning(`Denied by rule: ${rule}`);
|
|
@@ -1990,7 +2212,7 @@ export class InteractiveMode {
|
|
|
1990
2212
|
sessionManager: this.sessionManager,
|
|
1991
2213
|
modelRegistry: this.session.modelRegistry,
|
|
1992
2214
|
model: this.session.model,
|
|
1993
|
-
isIdle: () => !this.session.isStreaming,
|
|
2215
|
+
isIdle: () => !this.session.isStreaming && this.swarmActiveRunId === undefined,
|
|
1994
2216
|
abort: () => this.session.abort(),
|
|
1995
2217
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
1996
2218
|
shutdown: () => {
|
|
@@ -2562,6 +2784,7 @@ export class InteractiveMode {
|
|
|
2562
2784
|
this.session.isRetrying ||
|
|
2563
2785
|
this.iosmAutomationRun !== undefined ||
|
|
2564
2786
|
this.iosmVerificationSession !== undefined ||
|
|
2787
|
+
this.swarmActiveRunId !== undefined ||
|
|
2565
2788
|
this.singularAnalysisSession !== undefined ||
|
|
2566
2789
|
queuedMessages.steering.length > 0 ||
|
|
2567
2790
|
queuedMessages.followUp.length > 0 ||
|
|
@@ -2917,9 +3140,18 @@ export class InteractiveMode {
|
|
|
2917
3140
|
// If streaming, use configured stream input mode (meta/followUp/steer)
|
|
2918
3141
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
2919
3142
|
if (this.session.isStreaming) {
|
|
3143
|
+
const streamingBehavior = resolveStreamingSubmissionMode({
|
|
3144
|
+
configuredMode: this.session.streamInputMode,
|
|
3145
|
+
activeProfileName: this.activeProfileName,
|
|
3146
|
+
activeSubagentCount: this.subagentComponents.size,
|
|
3147
|
+
activeAssistantOrchestrationContext: this.activeAssistantOrchestrationContext,
|
|
3148
|
+
});
|
|
2920
3149
|
this.editor.addToHistory?.(text);
|
|
2921
3150
|
this.editor.setText("");
|
|
2922
|
-
await this.session.prompt(text, { streamingBehavior
|
|
3151
|
+
await this.session.prompt(text, { streamingBehavior });
|
|
3152
|
+
if (streamingBehavior !== this.session.streamInputMode) {
|
|
3153
|
+
this.showStatus("Queued follow-up until meta orchestration completes");
|
|
3154
|
+
}
|
|
2923
3155
|
this.updatePendingMessagesDisplay();
|
|
2924
3156
|
this.ui.requestRender();
|
|
2925
3157
|
return;
|
|
@@ -2959,6 +3191,173 @@ export class InteractiveMode {
|
|
|
2959
3191
|
durationMs: Date.now() - subagent.startTime,
|
|
2960
3192
|
});
|
|
2961
3193
|
}
|
|
3194
|
+
getSwarmSubagentKey(runId, taskId) {
|
|
3195
|
+
return `swarm:${runId}:${taskId}`;
|
|
3196
|
+
}
|
|
3197
|
+
ensureSwarmSubagentDisplay(input) {
|
|
3198
|
+
const key = this.getSwarmSubagentKey(input.runId, input.taskId);
|
|
3199
|
+
const existing = this.subagentComponents.get(key);
|
|
3200
|
+
if (existing) {
|
|
3201
|
+
return existing;
|
|
3202
|
+
}
|
|
3203
|
+
const profile = (input.profile?.trim() || this.resolveSwarmTaskProfile(input.task)).trim();
|
|
3204
|
+
const info = {
|
|
3205
|
+
description: input.task.brief || input.task.id,
|
|
3206
|
+
profile,
|
|
3207
|
+
status: "running",
|
|
3208
|
+
phase: "starting subagent",
|
|
3209
|
+
phaseState: "starting",
|
|
3210
|
+
cwd: this.sessionManager.getCwd(),
|
|
3211
|
+
toolCallsStarted: 0,
|
|
3212
|
+
toolCallsCompleted: 0,
|
|
3213
|
+
assistantMessages: 0,
|
|
3214
|
+
delegatedTasks: 0,
|
|
3215
|
+
delegatedSucceeded: 0,
|
|
3216
|
+
delegatedFailed: 0,
|
|
3217
|
+
};
|
|
3218
|
+
const component = new SubagentMessageComponent(info);
|
|
3219
|
+
this.chatContainer.addChild(component);
|
|
3220
|
+
const state = {
|
|
3221
|
+
component,
|
|
3222
|
+
startTime: Date.now(),
|
|
3223
|
+
profile: info.profile,
|
|
3224
|
+
description: info.description,
|
|
3225
|
+
cwd: info.cwd,
|
|
3226
|
+
agent: info.agent,
|
|
3227
|
+
lockKey: info.lockKey,
|
|
3228
|
+
isolation: info.isolation,
|
|
3229
|
+
phase: info.phase,
|
|
3230
|
+
phaseState: info.phaseState,
|
|
3231
|
+
activeTool: info.activeTool,
|
|
3232
|
+
toolCallsStarted: info.toolCallsStarted ?? 0,
|
|
3233
|
+
toolCallsCompleted: info.toolCallsCompleted ?? 0,
|
|
3234
|
+
assistantMessages: info.assistantMessages ?? 0,
|
|
3235
|
+
delegatedTasks: info.delegatedTasks,
|
|
3236
|
+
delegatedSucceeded: info.delegatedSucceeded,
|
|
3237
|
+
delegatedFailed: info.delegatedFailed,
|
|
3238
|
+
delegateIndex: info.delegateIndex,
|
|
3239
|
+
delegateTotal: info.delegateTotal,
|
|
3240
|
+
delegateDescription: info.delegateDescription,
|
|
3241
|
+
delegateProfile: info.delegateProfile,
|
|
3242
|
+
delegateItems: info.delegateItems,
|
|
3243
|
+
};
|
|
3244
|
+
this.subagentComponents.set(key, state);
|
|
3245
|
+
this.ensureSubagentElapsedTimer();
|
|
3246
|
+
this.ui.requestRender();
|
|
3247
|
+
return state;
|
|
3248
|
+
}
|
|
3249
|
+
updateSwarmSubagentProgress(input) {
|
|
3250
|
+
const state = this.ensureSwarmSubagentDisplay({
|
|
3251
|
+
runId: input.runId,
|
|
3252
|
+
taskId: input.taskId,
|
|
3253
|
+
task: input.task,
|
|
3254
|
+
profile: input.profile,
|
|
3255
|
+
});
|
|
3256
|
+
const progress = input.progress;
|
|
3257
|
+
if (typeof progress.phase === "string" && progress.phase.trim()) {
|
|
3258
|
+
state.phase = progress.phase.trim();
|
|
3259
|
+
}
|
|
3260
|
+
if (isSubagentPhaseState(progress.phaseState)) {
|
|
3261
|
+
state.phaseState = progress.phaseState;
|
|
3262
|
+
}
|
|
3263
|
+
if (typeof progress.cwd === "string" && progress.cwd.trim()) {
|
|
3264
|
+
state.cwd = progress.cwd.trim();
|
|
3265
|
+
}
|
|
3266
|
+
if ("activeTool" in progress) {
|
|
3267
|
+
state.activeTool =
|
|
3268
|
+
typeof progress.activeTool === "string" && progress.activeTool.trim().length > 0
|
|
3269
|
+
? progress.activeTool.trim()
|
|
3270
|
+
: undefined;
|
|
3271
|
+
}
|
|
3272
|
+
if (typeof progress.toolCallsStarted === "number" && Number.isFinite(progress.toolCallsStarted)) {
|
|
3273
|
+
state.toolCallsStarted = Math.max(0, progress.toolCallsStarted);
|
|
3274
|
+
}
|
|
3275
|
+
if (typeof progress.toolCallsCompleted === "number" && Number.isFinite(progress.toolCallsCompleted)) {
|
|
3276
|
+
state.toolCallsCompleted = Math.max(0, progress.toolCallsCompleted);
|
|
3277
|
+
}
|
|
3278
|
+
if (typeof progress.assistantMessages === "number" && Number.isFinite(progress.assistantMessages)) {
|
|
3279
|
+
state.assistantMessages = Math.max(0, progress.assistantMessages);
|
|
3280
|
+
}
|
|
3281
|
+
if (typeof progress.delegatedTasks === "number" && Number.isFinite(progress.delegatedTasks)) {
|
|
3282
|
+
state.delegatedTasks = Math.max(0, progress.delegatedTasks);
|
|
3283
|
+
}
|
|
3284
|
+
if (typeof progress.delegatedSucceeded === "number" && Number.isFinite(progress.delegatedSucceeded)) {
|
|
3285
|
+
state.delegatedSucceeded = Math.max(0, progress.delegatedSucceeded);
|
|
3286
|
+
}
|
|
3287
|
+
if (typeof progress.delegatedFailed === "number" && Number.isFinite(progress.delegatedFailed)) {
|
|
3288
|
+
state.delegatedFailed = Math.max(0, progress.delegatedFailed);
|
|
3289
|
+
}
|
|
3290
|
+
if ("delegateIndex" in progress) {
|
|
3291
|
+
state.delegateIndex =
|
|
3292
|
+
typeof progress.delegateIndex === "number" && progress.delegateIndex > 0
|
|
3293
|
+
? Math.floor(progress.delegateIndex)
|
|
3294
|
+
: undefined;
|
|
3295
|
+
}
|
|
3296
|
+
if ("delegateTotal" in progress) {
|
|
3297
|
+
state.delegateTotal =
|
|
3298
|
+
typeof progress.delegateTotal === "number" && progress.delegateTotal > 0
|
|
3299
|
+
? Math.floor(progress.delegateTotal)
|
|
3300
|
+
: undefined;
|
|
3301
|
+
}
|
|
3302
|
+
if ("delegateDescription" in progress) {
|
|
3303
|
+
state.delegateDescription =
|
|
3304
|
+
typeof progress.delegateDescription === "string" && progress.delegateDescription.trim().length > 0
|
|
3305
|
+
? progress.delegateDescription.trim()
|
|
3306
|
+
: undefined;
|
|
3307
|
+
}
|
|
3308
|
+
if ("delegateProfile" in progress) {
|
|
3309
|
+
state.delegateProfile =
|
|
3310
|
+
typeof progress.delegateProfile === "string" && progress.delegateProfile.trim().length > 0
|
|
3311
|
+
? progress.delegateProfile.trim()
|
|
3312
|
+
: undefined;
|
|
3313
|
+
}
|
|
3314
|
+
if ("delegateItems" in progress) {
|
|
3315
|
+
state.delegateItems = Array.isArray(progress.delegateItems) ? progress.delegateItems : undefined;
|
|
3316
|
+
}
|
|
3317
|
+
this.updateRunningSubagentDisplay(state);
|
|
3318
|
+
this.ui.requestRender();
|
|
3319
|
+
}
|
|
3320
|
+
finalizeSwarmSubagentDisplay(input) {
|
|
3321
|
+
const key = this.getSwarmSubagentKey(input.runId, input.taskId);
|
|
3322
|
+
const state = this.subagentComponents.get(key);
|
|
3323
|
+
if (!state)
|
|
3324
|
+
return;
|
|
3325
|
+
const durationMs = Date.now() - state.startTime;
|
|
3326
|
+
state.component.update({
|
|
3327
|
+
description: state.description,
|
|
3328
|
+
profile: state.profile,
|
|
3329
|
+
status: input.status,
|
|
3330
|
+
durationMs,
|
|
3331
|
+
phaseState: state.phaseState,
|
|
3332
|
+
cwd: state.cwd,
|
|
3333
|
+
agent: state.agent,
|
|
3334
|
+
lockKey: state.lockKey,
|
|
3335
|
+
isolation: state.isolation,
|
|
3336
|
+
toolCallsStarted: state.toolCallsStarted,
|
|
3337
|
+
toolCallsCompleted: state.toolCallsCompleted,
|
|
3338
|
+
assistantMessages: state.assistantMessages,
|
|
3339
|
+
delegatedTasks: state.delegatedTasks,
|
|
3340
|
+
delegatedSucceeded: state.delegatedSucceeded,
|
|
3341
|
+
delegatedFailed: state.delegatedFailed,
|
|
3342
|
+
errorMessage: input.status === "error" ? input.errorMessage ?? "error" : undefined,
|
|
3343
|
+
});
|
|
3344
|
+
this.subagentComponents.delete(key);
|
|
3345
|
+
this.stopSubagentElapsedTimerIfIdle();
|
|
3346
|
+
this.ui.requestRender();
|
|
3347
|
+
}
|
|
3348
|
+
finalizeSwarmRunSubagentDisplays(runId, errorMessage) {
|
|
3349
|
+
for (const [key] of this.subagentComponents.entries()) {
|
|
3350
|
+
if (!key.startsWith(`swarm:${runId}:`))
|
|
3351
|
+
continue;
|
|
3352
|
+
const taskId = key.slice(`swarm:${runId}:`.length);
|
|
3353
|
+
this.finalizeSwarmSubagentDisplay({
|
|
3354
|
+
runId,
|
|
3355
|
+
taskId,
|
|
3356
|
+
status: "error",
|
|
3357
|
+
errorMessage,
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
2962
3361
|
ensureSubagentElapsedTimer() {
|
|
2963
3362
|
if (this.subagentElapsedTimer || this.subagentComponents.size === 0) {
|
|
2964
3363
|
return;
|
|
@@ -2998,6 +3397,8 @@ export class InteractiveMode {
|
|
|
2998
3397
|
this.footer.invalidate();
|
|
2999
3398
|
switch (event.type) {
|
|
3000
3399
|
case "agent_start":
|
|
3400
|
+
this.currentTurnSawAssistantMessage = false;
|
|
3401
|
+
this.currentTurnSawTaskToolCall = false;
|
|
3001
3402
|
// Restore main escape handler if retry handler is still active
|
|
3002
3403
|
// (retry success event fires later, but we need main handler now)
|
|
3003
3404
|
if (this.retryEscapeHandler) {
|
|
@@ -3034,6 +3435,7 @@ export class InteractiveMode {
|
|
|
3034
3435
|
this.ui.requestRender();
|
|
3035
3436
|
}
|
|
3036
3437
|
else if (event.message.role === "assistant") {
|
|
3438
|
+
this.currentTurnSawAssistantMessage = true;
|
|
3037
3439
|
this.activeAssistantOrchestrationContext = this.pendingAssistantOrchestrationContexts > 0;
|
|
3038
3440
|
if (this.activeAssistantOrchestrationContext) {
|
|
3039
3441
|
this.pendingAssistantOrchestrationContexts -= 1;
|
|
@@ -3084,6 +3486,7 @@ export class InteractiveMode {
|
|
|
3084
3486
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
3085
3487
|
this.streamingMessage = event.message;
|
|
3086
3488
|
let errorMessage;
|
|
3489
|
+
let interruptedStopReason;
|
|
3087
3490
|
if (this.streamingMessage.stopReason === "aborted") {
|
|
3088
3491
|
const retryAttempt = this.session.retryAttempt;
|
|
3089
3492
|
errorMessage =
|
|
@@ -3091,9 +3494,13 @@ export class InteractiveMode {
|
|
|
3091
3494
|
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
3092
3495
|
: "Operation aborted";
|
|
3093
3496
|
this.streamingMessage.errorMessage = errorMessage;
|
|
3497
|
+
interruptedStopReason = "aborted";
|
|
3094
3498
|
}
|
|
3095
3499
|
this.streamingComponent.updateContent(this.sanitizeAssistantDisplayMessage(this.streamingMessage));
|
|
3096
3500
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
3501
|
+
if (this.streamingMessage.stopReason === "error") {
|
|
3502
|
+
interruptedStopReason = "error";
|
|
3503
|
+
}
|
|
3097
3504
|
if (!errorMessage) {
|
|
3098
3505
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
3099
3506
|
}
|
|
@@ -3111,6 +3518,9 @@ export class InteractiveMode {
|
|
|
3111
3518
|
component.setArgsComplete();
|
|
3112
3519
|
}
|
|
3113
3520
|
}
|
|
3521
|
+
if (interruptedStopReason) {
|
|
3522
|
+
this.showMetaModeInterruptionHint(interruptedStopReason);
|
|
3523
|
+
}
|
|
3114
3524
|
this.streamingComponent = undefined;
|
|
3115
3525
|
this.streamingMessage = undefined;
|
|
3116
3526
|
this.activeAssistantOrchestrationContext = false;
|
|
@@ -3119,6 +3529,9 @@ export class InteractiveMode {
|
|
|
3119
3529
|
this.ui.requestRender();
|
|
3120
3530
|
break;
|
|
3121
3531
|
case "tool_execution_start": {
|
|
3532
|
+
if (event.toolName === "task") {
|
|
3533
|
+
this.currentTurnSawTaskToolCall = true;
|
|
3534
|
+
}
|
|
3122
3535
|
if (event.toolName === "task" && !this.subagentComponents.has(event.toolCallId)) {
|
|
3123
3536
|
const staleTaskComponent = this.pendingTools.get(event.toolCallId);
|
|
3124
3537
|
if (staleTaskComponent) {
|
|
@@ -3330,6 +3743,9 @@ export class InteractiveMode {
|
|
|
3330
3743
|
break;
|
|
3331
3744
|
}
|
|
3332
3745
|
case "agent_end":
|
|
3746
|
+
if (this.activeProfileName === "meta" && !this.currentTurnSawAssistantMessage) {
|
|
3747
|
+
this.showMetaModeInterruptionHint("error");
|
|
3748
|
+
}
|
|
3333
3749
|
if (this.loadingAnimation) {
|
|
3334
3750
|
this.loadingAnimation.stop();
|
|
3335
3751
|
this.loadingAnimation = undefined;
|
|
@@ -3343,6 +3759,7 @@ export class InteractiveMode {
|
|
|
3343
3759
|
this.pendingTools.clear();
|
|
3344
3760
|
this.subagentComponents.clear();
|
|
3345
3761
|
this.clearSubagentElapsedTimer();
|
|
3762
|
+
this.currentTurnSawAssistantMessage = false;
|
|
3346
3763
|
await this.checkShutdownRequested();
|
|
3347
3764
|
this.ui.requestRender();
|
|
3348
3765
|
break;
|
|
@@ -3459,7 +3876,12 @@ export class InteractiveMode {
|
|
|
3459
3876
|
return;
|
|
3460
3877
|
if (message.details.kind !== "orchestration_context")
|
|
3461
3878
|
return;
|
|
3462
|
-
|
|
3879
|
+
// Hide assistant prose only for explicit legacy orchestration contracts.
|
|
3880
|
+
// META profile guidance should not suppress normal chat/task responses.
|
|
3881
|
+
const rawPrompt = message.details.rawPrompt ?? "";
|
|
3882
|
+
if (rawPrompt.includes("[ORCHESTRATION_DIRECTIVE]")) {
|
|
3883
|
+
this.pendingAssistantOrchestrationContexts += 1;
|
|
3884
|
+
}
|
|
3463
3885
|
if (message.details.rawPrompt && message.details.displayText) {
|
|
3464
3886
|
this.pendingInternalUserDisplayAliases.push({
|
|
3465
3887
|
rawPrompt: message.details.rawPrompt,
|
|
@@ -3490,6 +3912,21 @@ export class InteractiveMode {
|
|
|
3490
3912
|
this.lastStatusText = text;
|
|
3491
3913
|
this.ui.requestRender();
|
|
3492
3914
|
}
|
|
3915
|
+
showMetaModeInterruptionHint(reason) {
|
|
3916
|
+
if (this.activeProfileName !== "meta")
|
|
3917
|
+
return;
|
|
3918
|
+
const reasonText = reason === "error" ? "response failed unexpectedly" : "response was interrupted";
|
|
3919
|
+
this.showWarning(`META mode ${reasonText}. ` +
|
|
3920
|
+
"Please repeat your request following META profile rules: concrete repository task (goal + scope + constraints + expected output). " +
|
|
3921
|
+
"For conversational chat, switch profile to `full` (Shift+Tab).");
|
|
3922
|
+
}
|
|
3923
|
+
showMetaModeProfileHint() {
|
|
3924
|
+
if (this.activeProfileName !== "meta")
|
|
3925
|
+
return;
|
|
3926
|
+
this.showWarning("META mode is orchestration-first. " +
|
|
3927
|
+
"Send concrete repository tasks (goal + scope + constraints + expected output). " +
|
|
3928
|
+
"For conversational chat, switch profile to `full` (Shift+Tab).");
|
|
3929
|
+
}
|
|
3493
3930
|
showProgressLine(message) {
|
|
3494
3931
|
this.chatContainer.addChild(new Spacer(1));
|
|
3495
3932
|
this.chatContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
|
|
@@ -3731,6 +4168,15 @@ export class InteractiveMode {
|
|
|
3731
4168
|
// =========================================================================
|
|
3732
4169
|
handleCtrlC() {
|
|
3733
4170
|
const now = Date.now();
|
|
4171
|
+
if (this.swarmActiveRunId) {
|
|
4172
|
+
if (this.swarmStopRequested && now - this.lastSigintTime < 500) {
|
|
4173
|
+
void this.shutdown();
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4176
|
+
this.lastSigintTime = now;
|
|
4177
|
+
void this.interruptCurrentWork();
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
3734
4180
|
if (this.iosmAutomationRun) {
|
|
3735
4181
|
if (this.iosmAutomationRun.cancelRequested && now - this.lastSigintTime < 500) {
|
|
3736
4182
|
void this.shutdown();
|
|
@@ -3890,6 +4336,7 @@ export class InteractiveMode {
|
|
|
3890
4336
|
const nextActiveTools = this.getProfileToolNames(profile.name);
|
|
3891
4337
|
this.session.setActiveToolsByName(nextActiveTools);
|
|
3892
4338
|
this.session.setThinkingLevel(profile.thinkingLevel);
|
|
4339
|
+
this.session.setProfileName(profile.name);
|
|
3893
4340
|
this.profilePromptSuffix = profile.systemPromptAppend || undefined;
|
|
3894
4341
|
this.syncRuntimePromptSuffix();
|
|
3895
4342
|
this.session.setIosmAutopilotEnabled(profile.name === "iosm");
|
|
@@ -3901,6 +4348,7 @@ export class InteractiveMode {
|
|
|
3901
4348
|
this.updateEditorBorderColor();
|
|
3902
4349
|
this.refreshBuiltInHeader();
|
|
3903
4350
|
this.showStatus(`Profile: ${profile.name}`);
|
|
4351
|
+
this.showMetaModeProfileHint();
|
|
3904
4352
|
}
|
|
3905
4353
|
cycleProfile(direction) {
|
|
3906
4354
|
if (this.session.isStreaming || this.session.isCompacting || this.iosmAutomationRun || this.iosmVerificationSession) {
|
|
@@ -4136,6 +4584,7 @@ export class InteractiveMode {
|
|
|
4136
4584
|
const hasAutomationWork = this.iosmAutomationRun !== undefined;
|
|
4137
4585
|
const verificationSession = this.iosmVerificationSession;
|
|
4138
4586
|
const hasVerificationWork = verificationSession !== undefined;
|
|
4587
|
+
const hasSwarmWork = this.swarmActiveRunId !== undefined;
|
|
4139
4588
|
const singularSession = this.singularAnalysisSession;
|
|
4140
4589
|
const hasSingularWork = singularSession !== undefined;
|
|
4141
4590
|
const hasMainStreaming = this.session.isStreaming;
|
|
@@ -4145,6 +4594,7 @@ export class InteractiveMode {
|
|
|
4145
4594
|
if (!hasPendingQueuedMessages &&
|
|
4146
4595
|
!hasAutomationWork &&
|
|
4147
4596
|
!hasVerificationWork &&
|
|
4597
|
+
!hasSwarmWork &&
|
|
4148
4598
|
!hasSingularWork &&
|
|
4149
4599
|
!hasMainStreaming &&
|
|
4150
4600
|
!hasRetryWork &&
|
|
@@ -4158,6 +4608,10 @@ export class InteractiveMode {
|
|
|
4158
4608
|
if (this.iosmAutomationRun) {
|
|
4159
4609
|
this.iosmAutomationRun.cancelRequested = true;
|
|
4160
4610
|
}
|
|
4611
|
+
if (hasSwarmWork) {
|
|
4612
|
+
this.swarmStopRequested = true;
|
|
4613
|
+
this.swarmAbortController?.abort();
|
|
4614
|
+
}
|
|
4161
4615
|
if (hasPendingQueuedMessages) {
|
|
4162
4616
|
this.restoreQueuedMessagesToEditor();
|
|
4163
4617
|
}
|
|
@@ -4178,9 +4632,11 @@ export class InteractiveMode {
|
|
|
4178
4632
|
? "Stopping IOSM automation..."
|
|
4179
4633
|
: hasVerificationWork
|
|
4180
4634
|
? "Stopping IOSM verification..."
|
|
4181
|
-
:
|
|
4182
|
-
? "Stopping
|
|
4183
|
-
:
|
|
4635
|
+
: hasSwarmWork
|
|
4636
|
+
? "Stopping swarm run..."
|
|
4637
|
+
: hasSingularWork
|
|
4638
|
+
? "Stopping /singular analysis..."
|
|
4639
|
+
: "Stopping current run...");
|
|
4184
4640
|
const abortPromises = [];
|
|
4185
4641
|
if (hasMainStreaming) {
|
|
4186
4642
|
abortPromises.push(this.session.abort());
|
|
@@ -7673,8 +8129,10 @@ export class InteractiveMode {
|
|
|
7673
8129
|
});
|
|
7674
8130
|
options.push("Close without decision");
|
|
7675
8131
|
const selected = await this.showExtensionSelector("/singular: choose next step", options);
|
|
7676
|
-
if (!selected || selected === "Close without decision")
|
|
8132
|
+
if (!selected || selected === "Close without decision") {
|
|
8133
|
+
this.showStatus("Singular: decision closed without execution.");
|
|
7677
8134
|
return;
|
|
8135
|
+
}
|
|
7678
8136
|
const match = selected.match(/^Option\s+(\d+)/);
|
|
7679
8137
|
if (!match)
|
|
7680
8138
|
return;
|
|
@@ -8655,7 +9113,7 @@ export class InteractiveMode {
|
|
|
8655
9113
|
const mentionTask = cleaned.length > 0 ? cleaned : userInput;
|
|
8656
9114
|
const orchestrationAwareAgent = /orchestrator/i.test(mentionedAgent);
|
|
8657
9115
|
const mentionMode = orchestrationAwareAgent ? "parallel" : "sequential";
|
|
8658
|
-
const mentionMaxParallel = orchestrationAwareAgent ?
|
|
9116
|
+
const mentionMaxParallel = orchestrationAwareAgent ? MAX_ORCHESTRATION_PARALLEL : undefined;
|
|
8659
9117
|
const mentionPrompt = [
|
|
8660
9118
|
`<orchestrate mode="${mentionMode}" agents="1"${mentionMaxParallel ? ` max_parallel="${mentionMaxParallel}"` : ""}>`,
|
|
8661
9119
|
`- agent 1: profile=${this.activeProfileName} cwd=${this.sessionManager.getCwd()} agent=${mentionedAgent}`,
|
|
@@ -8666,8 +9124,8 @@ export class InteractiveMode {
|
|
|
8666
9124
|
...(orchestrationAwareAgent
|
|
8667
9125
|
? [
|
|
8668
9126
|
"- Include delegate_parallel_hint in the task call.",
|
|
8669
|
-
|
|
8670
|
-
|
|
9127
|
+
`- If user explicitly requested an agent count, set delegate_parallel_hint to that count (clamp 1..${MAX_SUBAGENT_DELEGATE_PARALLEL}).`,
|
|
9128
|
+
`- Otherwise set delegate_parallel_hint based on complexity: simple=1, medium=3-6, complex/risky=7-${MAX_SUBAGENT_DELEGATE_PARALLEL}.`,
|
|
8671
9129
|
"- For non-trivial tasks, prefer delegate_parallel_hint >= 2 and split into independent <delegate_task> workstreams.",
|
|
8672
9130
|
'- Prefer existing custom agents for delegated work when suitable (use <delegate_task agent="name" ...>).',
|
|
8673
9131
|
"- If no existing custom agent fits, create focused delegate streams via profile-based <delegate_task> blocks.",
|
|
@@ -8682,6 +9140,27 @@ export class InteractiveMode {
|
|
|
8682
9140
|
});
|
|
8683
9141
|
return;
|
|
8684
9142
|
}
|
|
9143
|
+
if (this.activeProfileName === "meta") {
|
|
9144
|
+
await promptMetaWithParallelismGuard({
|
|
9145
|
+
session: this.session,
|
|
9146
|
+
userInput,
|
|
9147
|
+
onPersistentNonCompliance: async (details) => {
|
|
9148
|
+
if (typeof this.runSwarmFromTask !== "function")
|
|
9149
|
+
return;
|
|
9150
|
+
if (this.session.isStreaming || this.iosmAutomationRun || this.iosmVerificationSession)
|
|
9151
|
+
return;
|
|
9152
|
+
const explicitRequested = parseRequestedParallelAgentCount(userInput);
|
|
9153
|
+
const hasComplexSignal = /\b(audit|security|hardening|refactor|migration|orchestrat|parallel|delegate|multi[-\s]?agent)\b/i.test(userInput);
|
|
9154
|
+
const fallbackParallel = Math.max(1, Math.min(MAX_ORCHESTRATION_PARALLEL, explicitRequested ??
|
|
9155
|
+
(hasComplexSignal
|
|
9156
|
+
? Math.max(details.requiredTopLevelTasks, 6)
|
|
9157
|
+
: Math.max(details.requiredTopLevelTasks, 3))));
|
|
9158
|
+
this.showWarning(`META enforcement fallback: orchestration contract not satisfied (${details.launchedTopLevelTasks}/${details.requiredTopLevelTasks} task calls). Launching /swarm run.`);
|
|
9159
|
+
await this.runSwarmFromTask(userInput, { maxParallel: fallbackParallel });
|
|
9160
|
+
},
|
|
9161
|
+
});
|
|
9162
|
+
return;
|
|
9163
|
+
}
|
|
8685
9164
|
await this.session.prompt(userInput);
|
|
8686
9165
|
}
|
|
8687
9166
|
createIosmVerificationEventBridge(options) {
|
|
@@ -8916,6 +9395,40 @@ export class InteractiveMode {
|
|
|
8916
9395
|
command: commandParts.join(" "),
|
|
8917
9396
|
};
|
|
8918
9397
|
}
|
|
9398
|
+
resolveOrchestrateDefaultAssignmentProfile(parsed) {
|
|
9399
|
+
const active = this.activeProfileName || "full";
|
|
9400
|
+
if (parsed.mode !== "parallel")
|
|
9401
|
+
return active;
|
|
9402
|
+
if (parsed.profile || (parsed.profiles && parsed.profiles.length > 0))
|
|
9403
|
+
return active;
|
|
9404
|
+
if (isReadOnlyProfileName(active))
|
|
9405
|
+
return active;
|
|
9406
|
+
return "meta";
|
|
9407
|
+
}
|
|
9408
|
+
deriveOrchestrateDelegateParallelHint(input) {
|
|
9409
|
+
const normalizedTask = input.task.toLowerCase();
|
|
9410
|
+
const highRisk = /\b(refactor|rewrite|migration|migrate|breaking|rollback|security|auth|authentication|authorization|permission|payment|billing|schema|database|critical)\b/i.test(normalizedTask);
|
|
9411
|
+
const mediumRisk = /\b(cross[-\s]?module|architecture|infra|platform|multi[-\s]?file|integration|audit|hardening)\b/i.test(normalizedTask);
|
|
9412
|
+
let hint = input.mode === "parallel"
|
|
9413
|
+
? Math.max(2, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, input.maxParallel ?? input.agents))
|
|
9414
|
+
: 1;
|
|
9415
|
+
if (highRisk) {
|
|
9416
|
+
hint = Math.max(hint, 7);
|
|
9417
|
+
}
|
|
9418
|
+
else if (mediumRisk) {
|
|
9419
|
+
hint = Math.max(hint, 5);
|
|
9420
|
+
}
|
|
9421
|
+
if (input.mode === "parallel" && input.dependencyEdges >= input.agents) {
|
|
9422
|
+
hint = Math.max(hint, 6);
|
|
9423
|
+
}
|
|
9424
|
+
if (input.hasDependencies) {
|
|
9425
|
+
hint = Math.max(2, Math.min(hint, 6));
|
|
9426
|
+
}
|
|
9427
|
+
if (input.hasLock) {
|
|
9428
|
+
hint = Math.max(1, Math.min(hint, 4));
|
|
9429
|
+
}
|
|
9430
|
+
return Math.max(1, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, hint));
|
|
9431
|
+
}
|
|
8919
9432
|
isEffectiveContractReady(contract) {
|
|
8920
9433
|
const hasText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
8921
9434
|
const hasList = (value) => Array.isArray(value) && value.some((item) => item.trim().length > 0);
|
|
@@ -9137,8 +9650,8 @@ export class InteractiveMode {
|
|
|
9137
9650
|
if (token === "--max-parallel") {
|
|
9138
9651
|
const next = rest[index + 1];
|
|
9139
9652
|
const parsed = next ? Number.parseInt(next, 10) : Number.NaN;
|
|
9140
|
-
if (!Number.isInteger(parsed) || parsed < 1 || parsed >
|
|
9141
|
-
this.showWarning(
|
|
9653
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > MAX_ORCHESTRATION_PARALLEL) {
|
|
9654
|
+
this.showWarning(`Invalid --max-parallel value (expected 1..${MAX_ORCHESTRATION_PARALLEL}).`);
|
|
9142
9655
|
return undefined;
|
|
9143
9656
|
}
|
|
9144
9657
|
maxParallel = parsed;
|
|
@@ -9252,12 +9765,15 @@ export class InteractiveMode {
|
|
|
9252
9765
|
};
|
|
9253
9766
|
}
|
|
9254
9767
|
resolveSwarmTaskProfile(task) {
|
|
9768
|
+
const hint = this.deriveSwarmTaskDelegateParallelHint(task);
|
|
9769
|
+
if (task.concurrency_class === "docs")
|
|
9770
|
+
return "plan";
|
|
9771
|
+
if (hint >= 2)
|
|
9772
|
+
return "meta";
|
|
9255
9773
|
if (task.concurrency_class === "analysis")
|
|
9256
9774
|
return "explore";
|
|
9257
9775
|
if (task.concurrency_class === "verification" || task.concurrency_class === "tests")
|
|
9258
9776
|
return "iosm_verifier";
|
|
9259
|
-
if (task.concurrency_class === "docs")
|
|
9260
|
-
return "plan";
|
|
9261
9777
|
return "full";
|
|
9262
9778
|
}
|
|
9263
9779
|
estimateSwarmTaskCostUsd(task) {
|
|
@@ -9273,6 +9789,13 @@ export class InteractiveMode {
|
|
|
9273
9789
|
const hasVeryComplexSignal = /(overhaul|major|system-wide|cross-cutting|multi-service|facet|registry)/i.test(brief) ||
|
|
9274
9790
|
task.touches.length >= 5 ||
|
|
9275
9791
|
task.scopes.length >= 4;
|
|
9792
|
+
if (task.concurrency_class === "analysis" || task.concurrency_class === "docs") {
|
|
9793
|
+
if (task.severity === "low")
|
|
9794
|
+
return 1;
|
|
9795
|
+
if (task.touches.length >= 6 || task.scopes.length >= 4 || hasComplexKeyword)
|
|
9796
|
+
return 2;
|
|
9797
|
+
return 1;
|
|
9798
|
+
}
|
|
9276
9799
|
if (task.severity === "low" && task.touches.length <= 2 && task.scopes.length <= 2 && !hasComplexKeyword) {
|
|
9277
9800
|
return 1;
|
|
9278
9801
|
}
|
|
@@ -9287,6 +9810,38 @@ export class InteractiveMode {
|
|
|
9287
9810
|
return 5;
|
|
9288
9811
|
return hasComplexKeyword ? 3 : 1;
|
|
9289
9812
|
}
|
|
9813
|
+
ensureSwarmModelReady(source) {
|
|
9814
|
+
if (this.session.model)
|
|
9815
|
+
return true;
|
|
9816
|
+
if (source === "singular") {
|
|
9817
|
+
this.showWarning("Cannot launch Swarm from /singular: no active model. Select one via /model and retry.");
|
|
9818
|
+
}
|
|
9819
|
+
else {
|
|
9820
|
+
this.showWarning("Cannot run /swarm: no active model selected. Configure /model first.");
|
|
9821
|
+
}
|
|
9822
|
+
return false;
|
|
9823
|
+
}
|
|
9824
|
+
resolveSwarmMaxParallel(input) {
|
|
9825
|
+
const requested = input.requested;
|
|
9826
|
+
if (typeof requested === "number" && Number.isFinite(requested)) {
|
|
9827
|
+
return Math.max(1, Math.min(MAX_ORCHESTRATION_PARALLEL, Math.floor(requested)));
|
|
9828
|
+
}
|
|
9829
|
+
const totalTasks = Math.max(1, input.plan.tasks.length);
|
|
9830
|
+
const initialFanout = Math.max(1, input.plan.tasks.filter((task) => task.depends_on.length === 0).length);
|
|
9831
|
+
const parallelizable = input.plan.tasks.filter((task) => task.concurrency_class === "implementation" || task.concurrency_class === "tests")
|
|
9832
|
+
.length;
|
|
9833
|
+
const sourceFloor = input.source === "singular" ? 4 : 3;
|
|
9834
|
+
const autoCap = Math.min(MAX_ORCHESTRATION_PARALLEL, 10);
|
|
9835
|
+
const heuristic = Math.max(sourceFloor, Math.ceil(totalTasks / 2), initialFanout, parallelizable >= 4 ? Math.ceil(parallelizable / 2) : 1);
|
|
9836
|
+
return Math.max(1, Math.min(autoCap, heuristic));
|
|
9837
|
+
}
|
|
9838
|
+
resolveSwarmDispatchTimeoutMs() {
|
|
9839
|
+
const dispatchTimeoutRaw = Number.parseInt(process.env.IOSM_SWARM_DISPATCH_TIMEOUT_MS ?? "", 10);
|
|
9840
|
+
if (Number.isInteger(dispatchTimeoutRaw) && dispatchTimeoutRaw > 0) {
|
|
9841
|
+
return Math.max(1_000, Math.min(1_800_000, dispatchTimeoutRaw));
|
|
9842
|
+
}
|
|
9843
|
+
return 180_000;
|
|
9844
|
+
}
|
|
9290
9845
|
parseSwarmSpawnCandidates(output, parentTaskId) {
|
|
9291
9846
|
const lines = output.split(/\r?\n/);
|
|
9292
9847
|
const results = [];
|
|
@@ -9330,6 +9885,16 @@ export class InteractiveMode {
|
|
|
9330
9885
|
}
|
|
9331
9886
|
const profile = this.resolveSwarmTaskProfile(input.task);
|
|
9332
9887
|
const delegateParallelHint = this.deriveSwarmTaskDelegateParallelHint(input.task);
|
|
9888
|
+
const requiresStrongDelegation = input.task.concurrency_class !== "analysis" &&
|
|
9889
|
+
input.task.concurrency_class !== "docs" &&
|
|
9890
|
+
(input.task.severity === "high" || delegateParallelHint >= 7);
|
|
9891
|
+
const minDelegatesRequired = !requiresStrongDelegation
|
|
9892
|
+
? 0
|
|
9893
|
+
: delegateParallelHint >= 8
|
|
9894
|
+
? 3
|
|
9895
|
+
: delegateParallelHint >= 5
|
|
9896
|
+
? 2
|
|
9897
|
+
: 1;
|
|
9333
9898
|
const safeDescription = input.task.brief.replace(/\s+/g, " ").trim().slice(0, 120).replace(/"/g, "'");
|
|
9334
9899
|
const prompt = [
|
|
9335
9900
|
`<swarm_task run_id="${input.meta.runId}" task_id="${input.task.id}" profile_hint="${profile}">`,
|
|
@@ -9342,10 +9907,15 @@ export class InteractiveMode {
|
|
|
9342
9907
|
"",
|
|
9343
9908
|
"Execution requirements:",
|
|
9344
9909
|
`- Use the task tool exactly once with description="${safeDescription || input.task.id}", profile="${profile}", delegate_parallel_hint=${delegateParallelHint}, run_id="${input.meta.runId}", task_id="${input.task.id}".`,
|
|
9345
|
-
|
|
9346
|
-
|
|
9910
|
+
minDelegatesRequired > 0
|
|
9911
|
+
? `- Inside this single task call, emit at least ${minDelegatesRequired} independent <delegate_task> subtasks (target parallel fan-out up to ${delegateParallelHint}).`
|
|
9912
|
+
: "- Delegation is optional for this task; keep execution focused.",
|
|
9913
|
+
minDelegatesRequired > 0
|
|
9914
|
+
? '- If safe decomposition is impossible, output exactly one line: DELEGATION_IMPOSSIBLE: <reason>.'
|
|
9915
|
+
: "- If decomposition is not beneficial, continue with single-agent execution.",
|
|
9347
9916
|
"- Keep edits inside declared scopes/touches. If scope expansion is required, explain and stop.",
|
|
9348
9917
|
"- If blocked, respond with line: BLOCKED: <reason>",
|
|
9918
|
+
"- Return concise execution output; avoid long narrative if not needed.",
|
|
9349
9919
|
"- Optional spawn candidates format: '- <description> | <path> | <change_type> | <low|medium|high>'",
|
|
9350
9920
|
"</swarm_task>",
|
|
9351
9921
|
].join("\n");
|
|
@@ -9383,7 +9953,7 @@ export class InteractiveMode {
|
|
|
9383
9953
|
resourceLoader,
|
|
9384
9954
|
model,
|
|
9385
9955
|
thinkingLevel: this.session.thinkingLevel,
|
|
9386
|
-
profile: "
|
|
9956
|
+
profile: "meta",
|
|
9387
9957
|
enableTaskTool: true,
|
|
9388
9958
|
});
|
|
9389
9959
|
swarmSession = created.session;
|
|
@@ -9400,6 +9970,14 @@ export class InteractiveMode {
|
|
|
9400
9970
|
const taskErrors = [];
|
|
9401
9971
|
let delegatedFailed = 0;
|
|
9402
9972
|
let delegatedTasks = 0;
|
|
9973
|
+
let toolCallsStarted = 0;
|
|
9974
|
+
let toolCallsCompleted = 0;
|
|
9975
|
+
let assistantMessages = 0;
|
|
9976
|
+
let activeTool;
|
|
9977
|
+
const dispatchTimeoutMs = this.resolveSwarmDispatchTimeoutMs();
|
|
9978
|
+
let timedOut = false;
|
|
9979
|
+
let timeoutHandle;
|
|
9980
|
+
let detachStopListener;
|
|
9403
9981
|
const delegatedFailureCauses = new Map();
|
|
9404
9982
|
const accumulateFailureCauses = (raw) => {
|
|
9405
9983
|
if (!raw || typeof raw !== "object")
|
|
@@ -9413,13 +9991,71 @@ export class InteractiveMode {
|
|
|
9413
9991
|
delegatedFailureCauses.set(cause, (delegatedFailureCauses.get(cause) ?? 0) + numeric);
|
|
9414
9992
|
}
|
|
9415
9993
|
};
|
|
9994
|
+
const emitProgress = (progress) => {
|
|
9995
|
+
input.onProgress?.({
|
|
9996
|
+
activeTool,
|
|
9997
|
+
toolCallsStarted,
|
|
9998
|
+
toolCallsCompleted,
|
|
9999
|
+
assistantMessages,
|
|
10000
|
+
delegatedTasks,
|
|
10001
|
+
delegatedFailed,
|
|
10002
|
+
...progress,
|
|
10003
|
+
});
|
|
10004
|
+
};
|
|
9416
10005
|
const chunks = [];
|
|
9417
10006
|
const unsubscribe = swarmSession.subscribe((event) => {
|
|
9418
|
-
if (event.type === "tool_execution_start"
|
|
9419
|
-
|
|
10007
|
+
if (event.type === "tool_execution_start") {
|
|
10008
|
+
toolCallsStarted += 1;
|
|
10009
|
+
activeTool = event.toolName;
|
|
10010
|
+
if (event.toolName === "task") {
|
|
10011
|
+
taskToolCalls += 1;
|
|
10012
|
+
}
|
|
10013
|
+
emitProgress({
|
|
10014
|
+
phase: event.toolName ? `running ${event.toolName}` : "running",
|
|
10015
|
+
phaseState: "running",
|
|
10016
|
+
});
|
|
10017
|
+
return;
|
|
10018
|
+
}
|
|
10019
|
+
if (event.type === "tool_execution_update" && event.toolName === "task") {
|
|
10020
|
+
const partial = event.partialResult;
|
|
10021
|
+
const progressCandidate = partial?.details?.progress;
|
|
10022
|
+
if (progressCandidate && typeof progressCandidate === "object") {
|
|
10023
|
+
const progress = progressCandidate;
|
|
10024
|
+
const delegateItems = parseSubagentDelegateItems(progress.delegateItems);
|
|
10025
|
+
emitProgress({
|
|
10026
|
+
phase: typeof progress.message === "string" ? progress.message : undefined,
|
|
10027
|
+
phaseState: isSubagentPhaseState(progress.phase) ? progress.phase : undefined,
|
|
10028
|
+
cwd: typeof progress.cwd === "string" ? progress.cwd : undefined,
|
|
10029
|
+
activeTool: typeof progress.activeTool === "string" && progress.activeTool.trim().length > 0
|
|
10030
|
+
? progress.activeTool.trim()
|
|
10031
|
+
: activeTool,
|
|
10032
|
+
toolCallsStarted: typeof progress.toolCallsStarted === "number" && Number.isFinite(progress.toolCallsStarted)
|
|
10033
|
+
? progress.toolCallsStarted
|
|
10034
|
+
: undefined,
|
|
10035
|
+
toolCallsCompleted: typeof progress.toolCallsCompleted === "number" && Number.isFinite(progress.toolCallsCompleted)
|
|
10036
|
+
? progress.toolCallsCompleted
|
|
10037
|
+
: undefined,
|
|
10038
|
+
assistantMessages: typeof progress.assistantMessages === "number" && Number.isFinite(progress.assistantMessages)
|
|
10039
|
+
? progress.assistantMessages
|
|
10040
|
+
: undefined,
|
|
10041
|
+
delegateIndex: typeof progress.delegateIndex === "number" && Number.isFinite(progress.delegateIndex)
|
|
10042
|
+
? progress.delegateIndex
|
|
10043
|
+
: undefined,
|
|
10044
|
+
delegateTotal: typeof progress.delegateTotal === "number" && Number.isFinite(progress.delegateTotal)
|
|
10045
|
+
? progress.delegateTotal
|
|
10046
|
+
: undefined,
|
|
10047
|
+
delegateDescription: typeof progress.delegateDescription === "string" ? progress.delegateDescription : undefined,
|
|
10048
|
+
delegateProfile: typeof progress.delegateProfile === "string" ? progress.delegateProfile : undefined,
|
|
10049
|
+
delegateItems,
|
|
10050
|
+
});
|
|
10051
|
+
}
|
|
9420
10052
|
return;
|
|
9421
10053
|
}
|
|
9422
10054
|
if (event.type === "tool_execution_end" && event.toolName === "task") {
|
|
10055
|
+
toolCallsCompleted += 1;
|
|
10056
|
+
if (activeTool === event.toolName) {
|
|
10057
|
+
activeTool = undefined;
|
|
10058
|
+
}
|
|
9423
10059
|
const result = event.result;
|
|
9424
10060
|
const details = result?.details;
|
|
9425
10061
|
if (typeof details?.delegatedFailed === "number" && Number.isFinite(details.delegatedFailed)) {
|
|
@@ -9429,38 +10065,127 @@ export class InteractiveMode {
|
|
|
9429
10065
|
delegatedTasks += Math.max(0, details.delegatedTasks);
|
|
9430
10066
|
}
|
|
9431
10067
|
accumulateFailureCauses(details?.failureCauses);
|
|
10068
|
+
emitProgress({
|
|
10069
|
+
phase: event.isError ? "task tool failed" : "task tool completed",
|
|
10070
|
+
phaseState: "running",
|
|
10071
|
+
delegatedTasks: typeof details?.delegatedTasks === "number" && Number.isFinite(details.delegatedTasks)
|
|
10072
|
+
? details.delegatedTasks
|
|
10073
|
+
: undefined,
|
|
10074
|
+
delegatedSucceeded: typeof details?.delegatedSucceeded === "number" && Number.isFinite(details.delegatedSucceeded)
|
|
10075
|
+
? details.delegatedSucceeded
|
|
10076
|
+
: undefined,
|
|
10077
|
+
delegatedFailed: typeof details?.delegatedFailed === "number" && Number.isFinite(details.delegatedFailed)
|
|
10078
|
+
? details.delegatedFailed
|
|
10079
|
+
: undefined,
|
|
10080
|
+
});
|
|
9432
10081
|
if (event.isError) {
|
|
9433
10082
|
taskErrors.push(result?.error ?? result?.output ?? "task tool failed");
|
|
9434
10083
|
}
|
|
9435
10084
|
return;
|
|
9436
10085
|
}
|
|
10086
|
+
if (event.type === "tool_execution_end") {
|
|
10087
|
+
toolCallsCompleted += 1;
|
|
10088
|
+
if (activeTool === event.toolName) {
|
|
10089
|
+
activeTool = undefined;
|
|
10090
|
+
}
|
|
10091
|
+
emitProgress({
|
|
10092
|
+
phase: event.toolName ? `completed ${event.toolName}` : "running",
|
|
10093
|
+
phaseState: "running",
|
|
10094
|
+
});
|
|
10095
|
+
return;
|
|
10096
|
+
}
|
|
9437
10097
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
10098
|
+
assistantMessages += 1;
|
|
9438
10099
|
for (const part of event.message.content) {
|
|
9439
10100
|
if (part.type === "text" && part.text.trim()) {
|
|
9440
10101
|
chunks.push(part.text.trim());
|
|
9441
10102
|
}
|
|
9442
10103
|
}
|
|
10104
|
+
emitProgress({
|
|
10105
|
+
phase: "drafting response",
|
|
10106
|
+
phaseState: "responding",
|
|
10107
|
+
});
|
|
9443
10108
|
}
|
|
9444
10109
|
});
|
|
9445
10110
|
try {
|
|
9446
|
-
|
|
10111
|
+
emitProgress({
|
|
10112
|
+
phase: "booting subagent",
|
|
10113
|
+
phaseState: "starting",
|
|
10114
|
+
});
|
|
10115
|
+
if (input.stopSignal?.aborted) {
|
|
10116
|
+
return {
|
|
10117
|
+
taskId: input.task.id,
|
|
10118
|
+
status: "blocked",
|
|
10119
|
+
error: "Swarm run interrupted.",
|
|
10120
|
+
failureCause: "interrupted",
|
|
10121
|
+
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
10122
|
+
};
|
|
10123
|
+
}
|
|
10124
|
+
const promptPromise = swarmSession.prompt(prompt, {
|
|
9447
10125
|
expandPromptTemplates: false,
|
|
9448
10126
|
skipIosmAutopilot: true,
|
|
9449
10127
|
skipOrchestrationDirective: true,
|
|
9450
10128
|
source: "interactive",
|
|
9451
10129
|
});
|
|
10130
|
+
// Guard against provider/model hangs in isolated swarm sessions.
|
|
10131
|
+
void promptPromise.catch(() => {
|
|
10132
|
+
// handled by race below
|
|
10133
|
+
});
|
|
10134
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
10135
|
+
timeoutHandle = setTimeout(() => {
|
|
10136
|
+
timedOut = true;
|
|
10137
|
+
void swarmSession.abort().catch(() => {
|
|
10138
|
+
// best effort
|
|
10139
|
+
});
|
|
10140
|
+
emitProgress({
|
|
10141
|
+
phase: "dispatch timeout",
|
|
10142
|
+
phaseState: "responding",
|
|
10143
|
+
});
|
|
10144
|
+
reject(new Error(`Swarm task dispatch timed out after ${dispatchTimeoutMs}ms.`));
|
|
10145
|
+
}, dispatchTimeoutMs);
|
|
10146
|
+
});
|
|
10147
|
+
const stopPromise = new Promise((_, reject) => {
|
|
10148
|
+
if (!input.stopSignal)
|
|
10149
|
+
return;
|
|
10150
|
+
const onAbort = () => {
|
|
10151
|
+
void swarmSession.abort().catch(() => {
|
|
10152
|
+
// best effort
|
|
10153
|
+
});
|
|
10154
|
+
emitProgress({
|
|
10155
|
+
phase: "dispatch interrupted",
|
|
10156
|
+
phaseState: "responding",
|
|
10157
|
+
});
|
|
10158
|
+
reject(new Error("Swarm task dispatch interrupted."));
|
|
10159
|
+
};
|
|
10160
|
+
if (input.stopSignal.aborted) {
|
|
10161
|
+
onAbort();
|
|
10162
|
+
return;
|
|
10163
|
+
}
|
|
10164
|
+
input.stopSignal.addEventListener("abort", onAbort, { once: true });
|
|
10165
|
+
detachStopListener = () => input.stopSignal?.removeEventListener("abort", onAbort);
|
|
10166
|
+
});
|
|
10167
|
+
await Promise.race([promptPromise, timeoutPromise, stopPromise]);
|
|
9452
10168
|
}
|
|
9453
10169
|
catch (error) {
|
|
9454
10170
|
return {
|
|
9455
10171
|
taskId: input.task.id,
|
|
9456
10172
|
status: "error",
|
|
9457
10173
|
error: error instanceof Error ? error.message : String(error),
|
|
10174
|
+
failureCause: error instanceof Error && /interrupted/i.test(error.message)
|
|
10175
|
+
? "interrupted"
|
|
10176
|
+
: timedOut
|
|
10177
|
+
? "timeout"
|
|
10178
|
+
: undefined,
|
|
9458
10179
|
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
9459
10180
|
};
|
|
9460
10181
|
}
|
|
9461
10182
|
finally {
|
|
10183
|
+
detachStopListener?.();
|
|
10184
|
+
if (timeoutHandle) {
|
|
10185
|
+
clearTimeout(timeoutHandle);
|
|
10186
|
+
}
|
|
9462
10187
|
unsubscribe();
|
|
9463
|
-
if (swarmSession.isStreaming) {
|
|
10188
|
+
if (timedOut || swarmSession.isStreaming) {
|
|
9464
10189
|
await swarmSession.abort().catch(() => {
|
|
9465
10190
|
// best effort
|
|
9466
10191
|
});
|
|
@@ -9482,6 +10207,7 @@ export class InteractiveMode {
|
|
|
9482
10207
|
taskId: input.task.id,
|
|
9483
10208
|
status: "error",
|
|
9484
10209
|
error: "No task tool call executed by assistant.",
|
|
10210
|
+
failureCause: "protocol_violation",
|
|
9485
10211
|
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
9486
10212
|
};
|
|
9487
10213
|
}
|
|
@@ -9523,6 +10249,7 @@ export class InteractiveMode {
|
|
|
9523
10249
|
if (!input.resumeState) {
|
|
9524
10250
|
store.init(input.meta, input.plan, initialState);
|
|
9525
10251
|
}
|
|
10252
|
+
const swarmTaskById = new Map(input.plan.tasks.map((task) => [task.id, task]));
|
|
9526
10253
|
let rollingIndex = input.projectIndex;
|
|
9527
10254
|
let localStopRequested = false;
|
|
9528
10255
|
const refreshIncrementalIndex = () => {
|
|
@@ -9540,52 +10267,159 @@ export class InteractiveMode {
|
|
|
9540
10267
|
rollingIndex = rebuilt;
|
|
9541
10268
|
};
|
|
9542
10269
|
this.swarmActiveRunId = input.runId;
|
|
10270
|
+
this.swarmStopRequested = false;
|
|
10271
|
+
this.swarmAbortController = new AbortController();
|
|
10272
|
+
this.footerDataProvider.setSwarmBusy(true);
|
|
10273
|
+
this.footer.invalidate();
|
|
9543
10274
|
this.showStatus(`Swarm run started: ${input.runId}`);
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
budgetUsd: input.budgetUsd,
|
|
9550
|
-
existingState: initialState,
|
|
9551
|
-
dispatchTask: async ({ task, runtime }) => this.dispatchSwarmTaskWithAgent({
|
|
9552
|
-
meta: input.meta,
|
|
9553
|
-
task,
|
|
9554
|
-
runtime,
|
|
10275
|
+
let schedulerResult;
|
|
10276
|
+
try {
|
|
10277
|
+
schedulerResult = await runSwarmScheduler({
|
|
10278
|
+
runId: input.runId,
|
|
10279
|
+
plan: input.plan,
|
|
9555
10280
|
contract: input.contract,
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
10281
|
+
maxParallel: input.meta.maxParallel,
|
|
10282
|
+
budgetUsd: input.budgetUsd,
|
|
10283
|
+
existingState: initialState,
|
|
10284
|
+
dispatchTask: async ({ task, runtime }) => this.dispatchSwarmTaskWithAgent({
|
|
10285
|
+
meta: input.meta,
|
|
10286
|
+
task,
|
|
10287
|
+
runtime,
|
|
10288
|
+
contract: input.contract,
|
|
10289
|
+
stopSignal: this.swarmAbortController?.signal,
|
|
10290
|
+
onProgress: (progress) => {
|
|
10291
|
+
this.updateSwarmSubagentProgress({
|
|
10292
|
+
runId: input.runId,
|
|
10293
|
+
taskId: task.id,
|
|
10294
|
+
task,
|
|
10295
|
+
profile: this.resolveSwarmTaskProfile(task),
|
|
10296
|
+
progress,
|
|
10297
|
+
});
|
|
10298
|
+
},
|
|
10299
|
+
}),
|
|
10300
|
+
confirmSpawn: async ({ candidate, parentTask }) => {
|
|
10301
|
+
const requiresConfirmation = candidate.severity === "high" || parentTask.spawn_policy === "manual_high_risk";
|
|
10302
|
+
if (!requiresConfirmation)
|
|
10303
|
+
return true;
|
|
10304
|
+
const choice = await this.showExtensionSelector([
|
|
10305
|
+
`/swarm spawn candidate requires confirmation`,
|
|
10306
|
+
`severity=${candidate.severity} task=${parentTask.id}`,
|
|
10307
|
+
`description=${candidate.description}`,
|
|
10308
|
+
`path=${candidate.path}`,
|
|
10309
|
+
].join("\n"), ["Approve spawn", "Reject spawn (Recommended)", "Abort run"]);
|
|
10310
|
+
if (!choice || choice.startsWith("Reject")) {
|
|
10311
|
+
this.showStatus(`Swarm spawn rejected: ${candidate.description}`);
|
|
10312
|
+
return false;
|
|
10313
|
+
}
|
|
10314
|
+
if (choice === "Abort run") {
|
|
10315
|
+
localStopRequested = true;
|
|
10316
|
+
this.showWarning("Swarm run marked to stop after current scheduling step.");
|
|
10317
|
+
return false;
|
|
10318
|
+
}
|
|
9560
10319
|
return true;
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
10320
|
+
},
|
|
10321
|
+
dispatchTimeoutMs: Math.min(1_800_000, this.resolveSwarmDispatchTimeoutMs() + 5_000),
|
|
10322
|
+
onEvent: (event) => {
|
|
10323
|
+
store.appendEvent(event);
|
|
10324
|
+
if (event.type === "task_running" && event.taskId) {
|
|
10325
|
+
const task = swarmTaskById.get(event.taskId);
|
|
10326
|
+
if (task) {
|
|
10327
|
+
this.updateSwarmSubagentProgress({
|
|
10328
|
+
runId: input.runId,
|
|
10329
|
+
taskId: task.id,
|
|
10330
|
+
task,
|
|
10331
|
+
profile: this.resolveSwarmTaskProfile(task),
|
|
10332
|
+
progress: {
|
|
10333
|
+
phase: "starting subagent",
|
|
10334
|
+
phaseState: "starting",
|
|
10335
|
+
},
|
|
10336
|
+
});
|
|
10337
|
+
}
|
|
10338
|
+
this.showStatus(`Swarm ${event.taskId}: running`);
|
|
10339
|
+
}
|
|
10340
|
+
if (event.type === "task_done" && event.taskId) {
|
|
10341
|
+
this.finalizeSwarmSubagentDisplay({
|
|
10342
|
+
runId: input.runId,
|
|
10343
|
+
taskId: event.taskId,
|
|
10344
|
+
status: "done",
|
|
10345
|
+
});
|
|
10346
|
+
this.showStatus(`Swarm ${event.taskId}: done`);
|
|
10347
|
+
}
|
|
10348
|
+
if (event.type === "task_retry" && event.taskId) {
|
|
10349
|
+
const task = swarmTaskById.get(event.taskId);
|
|
10350
|
+
if (task) {
|
|
10351
|
+
this.updateSwarmSubagentProgress({
|
|
10352
|
+
runId: input.runId,
|
|
10353
|
+
taskId: event.taskId,
|
|
10354
|
+
task,
|
|
10355
|
+
profile: this.resolveSwarmTaskProfile(task),
|
|
10356
|
+
progress: {
|
|
10357
|
+
phase: event.message,
|
|
10358
|
+
phaseState: "running",
|
|
10359
|
+
},
|
|
10360
|
+
});
|
|
10361
|
+
}
|
|
10362
|
+
this.showWarning(`Swarm ${event.taskId}: ${event.message}`);
|
|
10363
|
+
}
|
|
10364
|
+
if (event.type === "task_error" && event.taskId) {
|
|
10365
|
+
this.finalizeSwarmSubagentDisplay({
|
|
10366
|
+
runId: input.runId,
|
|
10367
|
+
taskId: event.taskId,
|
|
10368
|
+
status: "error",
|
|
10369
|
+
errorMessage: event.message,
|
|
10370
|
+
});
|
|
10371
|
+
this.showWarning(`Swarm ${event.taskId} failed: ${event.message}`);
|
|
10372
|
+
}
|
|
10373
|
+
if (event.type === "task_blocked" && event.taskId) {
|
|
10374
|
+
this.finalizeSwarmSubagentDisplay({
|
|
10375
|
+
runId: input.runId,
|
|
10376
|
+
taskId: event.taskId,
|
|
10377
|
+
status: "error",
|
|
10378
|
+
errorMessage: event.message,
|
|
10379
|
+
});
|
|
10380
|
+
this.showWarning(`Swarm ${event.taskId} blocked: ${event.message}`);
|
|
10381
|
+
}
|
|
10382
|
+
if ((event.type === "run_blocked" || event.type === "run_failed" || event.type === "run_stopped") && event.message) {
|
|
10383
|
+
this.showWarning(`Swarm ${event.type.replace("run_", "")}: ${event.message}`);
|
|
10384
|
+
}
|
|
10385
|
+
},
|
|
10386
|
+
onStateChanged: (state) => {
|
|
10387
|
+
store.saveState(state);
|
|
10388
|
+
store.saveCheckpoint(state);
|
|
10389
|
+
refreshIncrementalIndex();
|
|
10390
|
+
},
|
|
10391
|
+
shouldStop: () => this.shutdownRequested || localStopRequested || this.swarmStopRequested,
|
|
10392
|
+
});
|
|
10393
|
+
}
|
|
10394
|
+
catch (error) {
|
|
10395
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10396
|
+
const failedState = store.loadState() ?? initialState;
|
|
10397
|
+
failedState.status = "failed";
|
|
10398
|
+
failedState.lastError = message;
|
|
10399
|
+
failedState.updatedAt = new Date().toISOString();
|
|
10400
|
+
store.appendEvent({
|
|
10401
|
+
type: "run_failed",
|
|
10402
|
+
timestamp: new Date().toISOString(),
|
|
10403
|
+
runId: input.runId,
|
|
10404
|
+
tick: failedState.tick,
|
|
10405
|
+
message,
|
|
10406
|
+
});
|
|
10407
|
+
store.saveState(failedState);
|
|
10408
|
+
store.saveCheckpoint(failedState);
|
|
10409
|
+
this.finalizeSwarmRunSubagentDisplays(input.runId, message);
|
|
10410
|
+
this.showWarning(`Swarm run failed unexpectedly: ${message}`);
|
|
10411
|
+
return;
|
|
10412
|
+
}
|
|
10413
|
+
finally {
|
|
10414
|
+
this.swarmActiveRunId = undefined;
|
|
10415
|
+
this.swarmStopRequested = false;
|
|
10416
|
+
this.swarmAbortController = undefined;
|
|
10417
|
+
this.footerDataProvider.setSwarmBusy(false);
|
|
10418
|
+
this.footer.invalidate();
|
|
10419
|
+
}
|
|
10420
|
+
if (schedulerResult.state.status !== "completed") {
|
|
10421
|
+
this.finalizeSwarmRunSubagentDisplays(input.runId, schedulerResult.state.lastError ?? `Swarm run ${schedulerResult.state.status}`);
|
|
10422
|
+
}
|
|
9589
10423
|
const taskStates = Object.values(schedulerResult.state.tasks);
|
|
9590
10424
|
const doneCount = taskStates.filter((task) => task.status === "done").length;
|
|
9591
10425
|
const errorCount = taskStates.filter((task) => task.status === "error").length;
|
|
@@ -9641,6 +10475,8 @@ export class InteractiveMode {
|
|
|
9641
10475
|
].join("\n"));
|
|
9642
10476
|
}
|
|
9643
10477
|
async runSwarmFromTask(task, options) {
|
|
10478
|
+
if (!this.ensureSwarmModelReady("plain"))
|
|
10479
|
+
return;
|
|
9644
10480
|
const contract = await this.ensureSwarmEffectiveContract(task);
|
|
9645
10481
|
if (!contract)
|
|
9646
10482
|
return;
|
|
@@ -9655,7 +10491,11 @@ export class InteractiveMode {
|
|
|
9655
10491
|
index: indexInfo.index,
|
|
9656
10492
|
});
|
|
9657
10493
|
const runId = `swarm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9658
|
-
const maxParallel =
|
|
10494
|
+
const maxParallel = this.resolveSwarmMaxParallel({
|
|
10495
|
+
requested: options.maxParallel,
|
|
10496
|
+
plan,
|
|
10497
|
+
source: "plain",
|
|
10498
|
+
});
|
|
9659
10499
|
const meta = this.buildSwarmRunMeta({
|
|
9660
10500
|
runId,
|
|
9661
10501
|
source: "plain",
|
|
@@ -9677,6 +10517,8 @@ export class InteractiveMode {
|
|
|
9677
10517
|
});
|
|
9678
10518
|
}
|
|
9679
10519
|
async runSwarmFromSingular(input) {
|
|
10520
|
+
if (!this.ensureSwarmModelReady("singular"))
|
|
10521
|
+
return;
|
|
9680
10522
|
const analysis = this.loadSingularAnalysisByRunId(input.runId);
|
|
9681
10523
|
if (!analysis) {
|
|
9682
10524
|
this.showWarning(`Singular run not found: ${input.runId}`);
|
|
@@ -9699,7 +10541,11 @@ export class InteractiveMode {
|
|
|
9699
10541
|
index: indexInfo.index,
|
|
9700
10542
|
});
|
|
9701
10543
|
const runId = `swarm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9702
|
-
const maxParallel =
|
|
10544
|
+
const maxParallel = this.resolveSwarmMaxParallel({
|
|
10545
|
+
requested: input.maxParallel,
|
|
10546
|
+
plan,
|
|
10547
|
+
source: "singular",
|
|
10548
|
+
});
|
|
9703
10549
|
const meta = this.buildSwarmRunMeta({
|
|
9704
10550
|
runId,
|
|
9705
10551
|
source: "singular",
|
|
@@ -9887,6 +10733,10 @@ export class InteractiveMode {
|
|
|
9887
10733
|
});
|
|
9888
10734
|
}
|
|
9889
10735
|
async handleSwarmCommand(text) {
|
|
10736
|
+
if (this.swarmActiveRunId) {
|
|
10737
|
+
this.showWarning(`Swarm run already in progress: ${this.swarmActiveRunId}. Use /swarm watch.`);
|
|
10738
|
+
return;
|
|
10739
|
+
}
|
|
9890
10740
|
if (this.session.isStreaming) {
|
|
9891
10741
|
this.showWarning("Cannot run /swarm while the agent is processing another request.");
|
|
9892
10742
|
return;
|
|
@@ -9969,8 +10819,8 @@ export class InteractiveMode {
|
|
|
9969
10819
|
if (arg === "--agents") {
|
|
9970
10820
|
const value = args[index + 1];
|
|
9971
10821
|
const parsed = value ? Number.parseInt(value, 10) : Number.NaN;
|
|
9972
|
-
if (!Number.isInteger(parsed) || parsed < 1 || parsed >
|
|
9973
|
-
this.showWarning(
|
|
10822
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > MAX_ORCHESTRATION_AGENTS) {
|
|
10823
|
+
this.showWarning(`Invalid --agents value (expected 1..${MAX_ORCHESTRATION_AGENTS}).`);
|
|
9974
10824
|
return undefined;
|
|
9975
10825
|
}
|
|
9976
10826
|
agents = parsed;
|
|
@@ -9980,8 +10830,8 @@ export class InteractiveMode {
|
|
|
9980
10830
|
if (arg === "--max-parallel") {
|
|
9981
10831
|
const value = args[index + 1];
|
|
9982
10832
|
const parsed = value ? Number.parseInt(value, 10) : Number.NaN;
|
|
9983
|
-
if (!Number.isInteger(parsed) || parsed < 1 || parsed >
|
|
9984
|
-
this.showWarning(
|
|
10833
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > MAX_ORCHESTRATION_PARALLEL) {
|
|
10834
|
+
this.showWarning(`Invalid --max-parallel value (expected 1..${MAX_ORCHESTRATION_PARALLEL}).`);
|
|
9985
10835
|
return undefined;
|
|
9986
10836
|
}
|
|
9987
10837
|
maxParallel = parsed;
|
|
@@ -10100,6 +10950,9 @@ export class InteractiveMode {
|
|
|
10100
10950
|
this.showWarning("--max-parallel is only valid with --parallel mode.");
|
|
10101
10951
|
return undefined;
|
|
10102
10952
|
}
|
|
10953
|
+
if (mode === "parallel" && maxParallel === undefined) {
|
|
10954
|
+
maxParallel = Math.max(1, Math.min(MAX_ORCHESTRATION_PARALLEL, agents));
|
|
10955
|
+
}
|
|
10103
10956
|
if (maxParallel !== undefined && maxParallel > agents) {
|
|
10104
10957
|
maxParallel = agents;
|
|
10105
10958
|
}
|
|
@@ -10166,11 +11019,24 @@ export class InteractiveMode {
|
|
|
10166
11019
|
const currentCwd = this.sessionManager.getCwd();
|
|
10167
11020
|
const assignments = [];
|
|
10168
11021
|
const assignmentRecords = [];
|
|
11022
|
+
const dependencyEdges = parsed.dependencies?.reduce((sum, entry) => sum + entry.dependsOn.length, 0) ?? 0;
|
|
11023
|
+
const defaultAssignmentProfile = this.resolveOrchestrateDefaultAssignmentProfile(parsed);
|
|
11024
|
+
const delegateParallelHints = [];
|
|
10169
11025
|
for (let index = 0; index < parsed.agents; index++) {
|
|
10170
|
-
const assignmentProfile = parsed.profiles?.[index] ?? parsed.profile ??
|
|
11026
|
+
const assignmentProfile = parsed.profiles?.[index] ?? parsed.profile ?? defaultAssignmentProfile;
|
|
10171
11027
|
const assignmentCwd = parsed.cwds?.[index] ?? ".";
|
|
10172
11028
|
const assignmentLock = parsed.locks?.[index];
|
|
10173
11029
|
const dependsOn = parsed.dependencies?.find((entry) => entry.agent === index + 1)?.dependsOn ?? [];
|
|
11030
|
+
const delegateParallelHint = this.deriveOrchestrateDelegateParallelHint({
|
|
11031
|
+
task: parsed.task,
|
|
11032
|
+
mode: parsed.mode,
|
|
11033
|
+
agents: parsed.agents,
|
|
11034
|
+
maxParallel: parsed.maxParallel,
|
|
11035
|
+
dependencyEdges,
|
|
11036
|
+
hasLock: !!assignmentLock,
|
|
11037
|
+
hasDependencies: dependsOn.length > 0,
|
|
11038
|
+
});
|
|
11039
|
+
delegateParallelHints.push(delegateParallelHint);
|
|
10174
11040
|
const resolvedCwd = path.resolve(currentCwd, assignmentCwd);
|
|
10175
11041
|
assignmentRecords.push({
|
|
10176
11042
|
profile: assignmentProfile,
|
|
@@ -10178,7 +11044,8 @@ export class InteractiveMode {
|
|
|
10178
11044
|
lockKey: assignmentLock,
|
|
10179
11045
|
dependsOn,
|
|
10180
11046
|
});
|
|
10181
|
-
assignments.push(`- agent ${index + 1}: profile=${assignmentProfile} cwd=${resolvedCwd}${assignmentLock ? ` lock_key=${assignmentLock}` : ""}${parsed.isolation === "worktree" ? " isolation=worktree" : ""}${dependsOn.length > 0 ? ` depends_on=${dependsOn.join("|")}` : ""}
|
|
11047
|
+
assignments.push(`- agent ${index + 1}: profile=${assignmentProfile} cwd=${resolvedCwd}${assignmentLock ? ` lock_key=${assignmentLock}` : ""}${parsed.isolation === "worktree" ? " isolation=worktree" : ""}${dependsOn.length > 0 ? ` depends_on=${dependsOn.join("|")}` : ""} delegate_parallel_hint=${delegateParallelHint}
|
|
11048
|
+
}`);
|
|
10182
11049
|
}
|
|
10183
11050
|
const teamRun = createTeamRun({
|
|
10184
11051
|
cwd: currentCwd,
|
|
@@ -10191,8 +11058,16 @@ export class InteractiveMode {
|
|
|
10191
11058
|
const runAssignments = teamRun.tasks.map((task, index) => `${assignments[index]} run_id=${teamRun.runId} task_id=${task.id}`);
|
|
10192
11059
|
const taskCallHints = teamRun.tasks.map((task, index) => {
|
|
10193
11060
|
const assignment = assignmentRecords[index];
|
|
10194
|
-
|
|
11061
|
+
const hint = delegateParallelHints[index] ?? 1;
|
|
11062
|
+
return `- task_call_${index + 1}: description="agent ${index + 1} execution" profile="${assignment.profile}" cwd="${assignment.cwd}" run_id="${teamRun.runId}" task_id="${task.id}"${assignment.lockKey ? ` lock_key="${assignment.lockKey}"` : ""}${parsed.isolation === "worktree" ? ' isolation="worktree"' : ""} delegate_parallel_hint=${hint}`;
|
|
10195
11063
|
});
|
|
11064
|
+
if (parsed.mode === "parallel" &&
|
|
11065
|
+
!parsed.profile &&
|
|
11066
|
+
!(parsed.profiles && parsed.profiles.length > 0) &&
|
|
11067
|
+
defaultAssignmentProfile === "meta" &&
|
|
11068
|
+
this.activeProfileName !== "meta") {
|
|
11069
|
+
this.showStatus("Orchestrate auto-profile: using `meta` workers for stronger fan-out and nested delegation.");
|
|
11070
|
+
}
|
|
10196
11071
|
this.chatContainer.addChild(new Spacer(1));
|
|
10197
11072
|
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "◆ ")) + theme.bold("/orchestrate") + theme.fg("muted", ` ${currentCwd}`), 1, 0));
|
|
10198
11073
|
this.ui.requestRender();
|
|
@@ -10207,6 +11082,9 @@ export class InteractiveMode {
|
|
|
10207
11082
|
"- for parallel mode, emit all independent task calls in one assistant response",
|
|
10208
11083
|
"- in parallel mode, use parallel tool-call style (<use_parallel_tool_calls>)",
|
|
10209
11084
|
"- when assignment lines include depends_on, still emit one task call per assignment; runtime enforces dependency gating",
|
|
11085
|
+
"- include delegate_parallel_hint from each assignment/task_call hint in every corresponding task tool call",
|
|
11086
|
+
"- for delegate_parallel_hint >= 2, split child work into nested <delegate_task> streams unless impossible",
|
|
11087
|
+
"- if nested split is impossible for a non-trivial stream, emit one line: DELEGATION_IMPOSSIBLE: <reason>",
|
|
10210
11088
|
"- keep required orchestration task calls in foreground; do not set background=true unless user explicitly requested detached async runs",
|
|
10211
11089
|
"- do not poll .iosm/subagents/background via bash/read during orchestration; wait for task results and then synthesize",
|
|
10212
11090
|
"- include run_id and task_id from each assignment in the task tool arguments",
|
|
@@ -10512,7 +11390,7 @@ export class InteractiveMode {
|
|
|
10512
11390
|
"You are generating a custom IOSM subagent specification.",
|
|
10513
11391
|
"Return ONLY a single JSON object and no additional text.",
|
|
10514
11392
|
'Allowed JSON keys: name, description, profile, tools, disallowed_tools, system_prompt, cwd, background, instructions.',
|
|
10515
|
-
'profile must be one of: full, plan, iosm, explore, iosm_analyst, iosm_verifier, cycle_planner.',
|
|
11393
|
+
'profile must be one of: full, plan, iosm, meta, explore, iosm_analyst, iosm_verifier, cycle_planner.',
|
|
10516
11394
|
"name must be short snake/kebab case, suitable for @mention.",
|
|
10517
11395
|
"tools/disallowed_tools must be arrays of tool names when present.",
|
|
10518
11396
|
"instructions must be a concise markdown body for the agent file.",
|
|
@@ -12194,6 +13072,10 @@ The agent will automatically receive IOSM context on every turn.`;
|
|
|
12194
13072
|
}
|
|
12195
13073
|
}
|
|
12196
13074
|
async handleBashCommand(command, excludeFromContext = false) {
|
|
13075
|
+
if (isReadOnlyProfileName(this.activeProfileName)) {
|
|
13076
|
+
this.showWarning(`Bash is disabled in ${this.activeProfileName} profile. Switch to full/meta/iosm (Shift+Tab).`);
|
|
13077
|
+
return;
|
|
13078
|
+
}
|
|
12197
13079
|
const extensionRunner = this.session.extensionRunner;
|
|
12198
13080
|
// Emit user_bash event to let extensions intercept
|
|
12199
13081
|
const eventResult = extensionRunner
|