iosm-cli 0.2.4 → 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 +31 -0
- package/README.md +23 -17
- package/dist/core/agent-profiles.d.ts +1 -0
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +10 -6
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +6 -2
- 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/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/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/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 +11 -3
- 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/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.map +1 -1
- package/dist/core/tools/task.js +464 -73
- package/dist/core/tools/task.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.map +1 -1
- package/dist/modes/interactive/components/footer.js +2 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +13 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +756 -74
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/cli-reference.md +4 -0
- package/docs/interactive-mode.md +2 -0
- package/docs/orchestration-and-subagents.md +5 -0
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ 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";
|
|
@@ -88,8 +88,8 @@ function parseRequestedParallelAgentCount(text) {
|
|
|
88
88
|
const patterns = [
|
|
89
89
|
/(\d+)\s+(?:parallel|concurrent)\s+agents?/i,
|
|
90
90
|
/(\d+)\s+agents?/i,
|
|
91
|
-
/(\d+)\s
|
|
92
|
-
/(\d+)\s
|
|
91
|
+
/(\d+)\s+паралл[\p{L}\p{N}_-]*\s+агент[\p{L}\p{N}_-]*/iu,
|
|
92
|
+
/(\d+)\s+агент[\p{L}\p{N}_-]*/iu,
|
|
93
93
|
];
|
|
94
94
|
for (const pattern of patterns) {
|
|
95
95
|
const match = text.match(pattern);
|
|
@@ -127,6 +127,9 @@ function buildMetaParallelismCorrection(input) {
|
|
|
127
127
|
`Meta runtime correction: this run currently has ${input.launchedTopLevelTasks} top-level task call(s), but it should have at least ${input.requiredTopLevelTasks}.`,
|
|
128
128
|
"Stop manual sequential execution in the main agent and convert the execution graph into parallel task calls now.",
|
|
129
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,
|
|
130
133
|
"Each task tool call MUST include description and prompt.",
|
|
131
134
|
'If you want a custom subagent, pass it via agent="name"; keep profile set to the capability profile, not the custom subagent name.',
|
|
132
135
|
input.rawRootDelegateBlocks && input.rawRootDelegateBlocks > 0
|
|
@@ -151,19 +154,29 @@ async function promptMetaWithParallelismGuard(input) {
|
|
|
151
154
|
let rawRootDelegateBlocks = 0;
|
|
152
155
|
let delegationImpossibleDeclared = false;
|
|
153
156
|
let correctionText;
|
|
154
|
-
|
|
157
|
+
let distinctTaskWorkers = new Set();
|
|
158
|
+
let delegatedChildTasksSeen = 0;
|
|
159
|
+
const maybeScheduleCorrection = (options) => {
|
|
155
160
|
const requiredTopLevelTasks = deriveMetaRequiredTopLevelTaskCalls(input.userInput, taskPlanSnapshot);
|
|
156
|
-
if (!requiredTopLevelTasks ||
|
|
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) {
|
|
157
170
|
return;
|
|
158
171
|
}
|
|
159
172
|
const hasComplexPlan = !!taskPlanSnapshot;
|
|
160
173
|
const hasTaskFailure = !!taskToolError;
|
|
161
174
|
const hasRawRootDelegates = rawRootDelegateBlocks > 0;
|
|
162
|
-
if (!hasTaskFailure && !hasRawRootDelegates
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
175
|
+
if (needsMoreTopLevelTasks && !hasTaskFailure && !hasRawRootDelegates) {
|
|
176
|
+
const minimumNonTaskCalls = hasComplexPlan ? 0 : 2;
|
|
177
|
+
if (!options?.finalize && nonTaskToolCalls < minimumNonTaskCalls) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
167
180
|
}
|
|
168
181
|
correctionText = buildMetaParallelismCorrection({
|
|
169
182
|
requiredTopLevelTasks,
|
|
@@ -171,6 +184,8 @@ async function promptMetaWithParallelismGuard(input) {
|
|
|
171
184
|
taskPlanSnapshot,
|
|
172
185
|
taskToolError,
|
|
173
186
|
rawRootDelegateBlocks,
|
|
187
|
+
workerDiversityMissing,
|
|
188
|
+
distinctWorkers: distinctTaskWorkers.size,
|
|
174
189
|
});
|
|
175
190
|
};
|
|
176
191
|
const unsubscribe = input.session.subscribe((event) => {
|
|
@@ -192,6 +207,17 @@ async function promptMetaWithParallelismGuard(input) {
|
|
|
192
207
|
if (event.type === "tool_execution_start") {
|
|
193
208
|
if (event.toolName === "task") {
|
|
194
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
|
+
}
|
|
195
221
|
}
|
|
196
222
|
else {
|
|
197
223
|
nonTaskToolCalls += 1;
|
|
@@ -202,20 +228,54 @@ async function promptMetaWithParallelismGuard(input) {
|
|
|
202
228
|
if (event.type === "tool_execution_end" && event.toolName === "task" && event.isError) {
|
|
203
229
|
taskToolError = extractTaskToolErrorText(event.result) ?? taskToolError;
|
|
204
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();
|
|
205
240
|
}
|
|
206
241
|
});
|
|
207
242
|
try {
|
|
208
243
|
await input.session.prompt(input.userInput);
|
|
209
|
-
|
|
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;
|
|
210
250
|
if (correctionText &&
|
|
211
251
|
requiredTopLevelTasks &&
|
|
212
|
-
taskToolCalls < requiredTopLevelTasks &&
|
|
252
|
+
(taskToolCalls < requiredTopLevelTasks || workerDiversityMissingAfterRun) &&
|
|
213
253
|
!delegationImpossibleDeclared) {
|
|
214
254
|
await input.session.prompt(correctionText, {
|
|
215
255
|
expandPromptTemplates: false,
|
|
216
256
|
skipOrchestrationDirective: true,
|
|
217
257
|
source: "interactive",
|
|
218
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
|
+
});
|
|
219
279
|
}
|
|
220
280
|
}
|
|
221
281
|
finally {
|
|
@@ -549,6 +609,8 @@ export class InteractiveMode {
|
|
|
549
609
|
this.sessionAllowedToolSignatures = new Set();
|
|
550
610
|
this.singularLastEffectiveContract = {};
|
|
551
611
|
this.swarmActiveRunId = undefined;
|
|
612
|
+
this.swarmStopRequested = false;
|
|
613
|
+
this.swarmAbortController = undefined;
|
|
552
614
|
this.lastSigintTime = 0;
|
|
553
615
|
this.lastEscapeTime = 0;
|
|
554
616
|
this.changelogMarkdown = undefined;
|
|
@@ -559,6 +621,7 @@ export class InteractiveMode {
|
|
|
559
621
|
this.streamingComponent = undefined;
|
|
560
622
|
this.streamingMessage = undefined;
|
|
561
623
|
this.currentTurnSawAssistantMessage = false;
|
|
624
|
+
this.currentTurnSawTaskToolCall = false;
|
|
562
625
|
// Tool execution tracking: toolCallId -> component
|
|
563
626
|
this.pendingTools = new Map();
|
|
564
627
|
// Subagent execution tracking with live progress/metadata for task tool calls.
|
|
@@ -737,6 +800,17 @@ export class InteractiveMode {
|
|
|
737
800
|
}
|
|
738
801
|
}
|
|
739
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
|
+
}
|
|
740
814
|
for (const rule of this.permissionDenyRules) {
|
|
741
815
|
if (this.matchesPermissionRule(rule, request)) {
|
|
742
816
|
this.showWarning(`Denied by rule: ${rule}`);
|
|
@@ -2138,7 +2212,7 @@ export class InteractiveMode {
|
|
|
2138
2212
|
sessionManager: this.sessionManager,
|
|
2139
2213
|
modelRegistry: this.session.modelRegistry,
|
|
2140
2214
|
model: this.session.model,
|
|
2141
|
-
isIdle: () => !this.session.isStreaming,
|
|
2215
|
+
isIdle: () => !this.session.isStreaming && this.swarmActiveRunId === undefined,
|
|
2142
2216
|
abort: () => this.session.abort(),
|
|
2143
2217
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
2144
2218
|
shutdown: () => {
|
|
@@ -2710,6 +2784,7 @@ export class InteractiveMode {
|
|
|
2710
2784
|
this.session.isRetrying ||
|
|
2711
2785
|
this.iosmAutomationRun !== undefined ||
|
|
2712
2786
|
this.iosmVerificationSession !== undefined ||
|
|
2787
|
+
this.swarmActiveRunId !== undefined ||
|
|
2713
2788
|
this.singularAnalysisSession !== undefined ||
|
|
2714
2789
|
queuedMessages.steering.length > 0 ||
|
|
2715
2790
|
queuedMessages.followUp.length > 0 ||
|
|
@@ -3116,6 +3191,173 @@ export class InteractiveMode {
|
|
|
3116
3191
|
durationMs: Date.now() - subagent.startTime,
|
|
3117
3192
|
});
|
|
3118
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
|
+
}
|
|
3119
3361
|
ensureSubagentElapsedTimer() {
|
|
3120
3362
|
if (this.subagentElapsedTimer || this.subagentComponents.size === 0) {
|
|
3121
3363
|
return;
|
|
@@ -3156,6 +3398,7 @@ export class InteractiveMode {
|
|
|
3156
3398
|
switch (event.type) {
|
|
3157
3399
|
case "agent_start":
|
|
3158
3400
|
this.currentTurnSawAssistantMessage = false;
|
|
3401
|
+
this.currentTurnSawTaskToolCall = false;
|
|
3159
3402
|
// Restore main escape handler if retry handler is still active
|
|
3160
3403
|
// (retry success event fires later, but we need main handler now)
|
|
3161
3404
|
if (this.retryEscapeHandler) {
|
|
@@ -3286,6 +3529,9 @@ export class InteractiveMode {
|
|
|
3286
3529
|
this.ui.requestRender();
|
|
3287
3530
|
break;
|
|
3288
3531
|
case "tool_execution_start": {
|
|
3532
|
+
if (event.toolName === "task") {
|
|
3533
|
+
this.currentTurnSawTaskToolCall = true;
|
|
3534
|
+
}
|
|
3289
3535
|
if (event.toolName === "task" && !this.subagentComponents.has(event.toolCallId)) {
|
|
3290
3536
|
const staleTaskComponent = this.pendingTools.get(event.toolCallId);
|
|
3291
3537
|
if (staleTaskComponent) {
|
|
@@ -3922,6 +4168,15 @@ export class InteractiveMode {
|
|
|
3922
4168
|
// =========================================================================
|
|
3923
4169
|
handleCtrlC() {
|
|
3924
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
|
+
}
|
|
3925
4180
|
if (this.iosmAutomationRun) {
|
|
3926
4181
|
if (this.iosmAutomationRun.cancelRequested && now - this.lastSigintTime < 500) {
|
|
3927
4182
|
void this.shutdown();
|
|
@@ -4329,6 +4584,7 @@ export class InteractiveMode {
|
|
|
4329
4584
|
const hasAutomationWork = this.iosmAutomationRun !== undefined;
|
|
4330
4585
|
const verificationSession = this.iosmVerificationSession;
|
|
4331
4586
|
const hasVerificationWork = verificationSession !== undefined;
|
|
4587
|
+
const hasSwarmWork = this.swarmActiveRunId !== undefined;
|
|
4332
4588
|
const singularSession = this.singularAnalysisSession;
|
|
4333
4589
|
const hasSingularWork = singularSession !== undefined;
|
|
4334
4590
|
const hasMainStreaming = this.session.isStreaming;
|
|
@@ -4338,6 +4594,7 @@ export class InteractiveMode {
|
|
|
4338
4594
|
if (!hasPendingQueuedMessages &&
|
|
4339
4595
|
!hasAutomationWork &&
|
|
4340
4596
|
!hasVerificationWork &&
|
|
4597
|
+
!hasSwarmWork &&
|
|
4341
4598
|
!hasSingularWork &&
|
|
4342
4599
|
!hasMainStreaming &&
|
|
4343
4600
|
!hasRetryWork &&
|
|
@@ -4351,6 +4608,10 @@ export class InteractiveMode {
|
|
|
4351
4608
|
if (this.iosmAutomationRun) {
|
|
4352
4609
|
this.iosmAutomationRun.cancelRequested = true;
|
|
4353
4610
|
}
|
|
4611
|
+
if (hasSwarmWork) {
|
|
4612
|
+
this.swarmStopRequested = true;
|
|
4613
|
+
this.swarmAbortController?.abort();
|
|
4614
|
+
}
|
|
4354
4615
|
if (hasPendingQueuedMessages) {
|
|
4355
4616
|
this.restoreQueuedMessagesToEditor();
|
|
4356
4617
|
}
|
|
@@ -4371,9 +4632,11 @@ export class InteractiveMode {
|
|
|
4371
4632
|
? "Stopping IOSM automation..."
|
|
4372
4633
|
: hasVerificationWork
|
|
4373
4634
|
? "Stopping IOSM verification..."
|
|
4374
|
-
:
|
|
4375
|
-
? "Stopping
|
|
4376
|
-
:
|
|
4635
|
+
: hasSwarmWork
|
|
4636
|
+
? "Stopping swarm run..."
|
|
4637
|
+
: hasSingularWork
|
|
4638
|
+
? "Stopping /singular analysis..."
|
|
4639
|
+
: "Stopping current run...");
|
|
4377
4640
|
const abortPromises = [];
|
|
4378
4641
|
if (hasMainStreaming) {
|
|
4379
4642
|
abortPromises.push(this.session.abort());
|
|
@@ -7866,8 +8129,10 @@ export class InteractiveMode {
|
|
|
7866
8129
|
});
|
|
7867
8130
|
options.push("Close without decision");
|
|
7868
8131
|
const selected = await this.showExtensionSelector("/singular: choose next step", options);
|
|
7869
|
-
if (!selected || selected === "Close without decision")
|
|
8132
|
+
if (!selected || selected === "Close without decision") {
|
|
8133
|
+
this.showStatus("Singular: decision closed without execution.");
|
|
7870
8134
|
return;
|
|
8135
|
+
}
|
|
7871
8136
|
const match = selected.match(/^Option\s+(\d+)/);
|
|
7872
8137
|
if (!match)
|
|
7873
8138
|
return;
|
|
@@ -8879,6 +9144,20 @@ export class InteractiveMode {
|
|
|
8879
9144
|
await promptMetaWithParallelismGuard({
|
|
8880
9145
|
session: this.session,
|
|
8881
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
|
+
},
|
|
8882
9161
|
});
|
|
8883
9162
|
return;
|
|
8884
9163
|
}
|
|
@@ -9116,6 +9395,40 @@ export class InteractiveMode {
|
|
|
9116
9395
|
command: commandParts.join(" "),
|
|
9117
9396
|
};
|
|
9118
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
|
+
}
|
|
9119
9432
|
isEffectiveContractReady(contract) {
|
|
9120
9433
|
const hasText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
9121
9434
|
const hasList = (value) => Array.isArray(value) && value.some((item) => item.trim().length > 0);
|
|
@@ -9452,12 +9765,15 @@ export class InteractiveMode {
|
|
|
9452
9765
|
};
|
|
9453
9766
|
}
|
|
9454
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";
|
|
9455
9773
|
if (task.concurrency_class === "analysis")
|
|
9456
9774
|
return "explore";
|
|
9457
9775
|
if (task.concurrency_class === "verification" || task.concurrency_class === "tests")
|
|
9458
9776
|
return "iosm_verifier";
|
|
9459
|
-
if (task.concurrency_class === "docs")
|
|
9460
|
-
return "plan";
|
|
9461
9777
|
return "full";
|
|
9462
9778
|
}
|
|
9463
9779
|
estimateSwarmTaskCostUsd(task) {
|
|
@@ -9473,6 +9789,13 @@ export class InteractiveMode {
|
|
|
9473
9789
|
const hasVeryComplexSignal = /(overhaul|major|system-wide|cross-cutting|multi-service|facet|registry)/i.test(brief) ||
|
|
9474
9790
|
task.touches.length >= 5 ||
|
|
9475
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
|
+
}
|
|
9476
9799
|
if (task.severity === "low" && task.touches.length <= 2 && task.scopes.length <= 2 && !hasComplexKeyword) {
|
|
9477
9800
|
return 1;
|
|
9478
9801
|
}
|
|
@@ -9487,6 +9810,38 @@ export class InteractiveMode {
|
|
|
9487
9810
|
return 5;
|
|
9488
9811
|
return hasComplexKeyword ? 3 : 1;
|
|
9489
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
|
+
}
|
|
9490
9845
|
parseSwarmSpawnCandidates(output, parentTaskId) {
|
|
9491
9846
|
const lines = output.split(/\r?\n/);
|
|
9492
9847
|
const results = [];
|
|
@@ -9530,6 +9885,16 @@ export class InteractiveMode {
|
|
|
9530
9885
|
}
|
|
9531
9886
|
const profile = this.resolveSwarmTaskProfile(input.task);
|
|
9532
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;
|
|
9533
9898
|
const safeDescription = input.task.brief.replace(/\s+/g, " ").trim().slice(0, 120).replace(/"/g, "'");
|
|
9534
9899
|
const prompt = [
|
|
9535
9900
|
`<swarm_task run_id="${input.meta.runId}" task_id="${input.task.id}" profile_hint="${profile}">`,
|
|
@@ -9542,10 +9907,15 @@ export class InteractiveMode {
|
|
|
9542
9907
|
"",
|
|
9543
9908
|
"Execution requirements:",
|
|
9544
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}".`,
|
|
9545
|
-
|
|
9546
|
-
|
|
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.",
|
|
9547
9916
|
"- Keep edits inside declared scopes/touches. If scope expansion is required, explain and stop.",
|
|
9548
9917
|
"- If blocked, respond with line: BLOCKED: <reason>",
|
|
9918
|
+
"- Return concise execution output; avoid long narrative if not needed.",
|
|
9549
9919
|
"- Optional spawn candidates format: '- <description> | <path> | <change_type> | <low|medium|high>'",
|
|
9550
9920
|
"</swarm_task>",
|
|
9551
9921
|
].join("\n");
|
|
@@ -9583,7 +9953,7 @@ export class InteractiveMode {
|
|
|
9583
9953
|
resourceLoader,
|
|
9584
9954
|
model,
|
|
9585
9955
|
thinkingLevel: this.session.thinkingLevel,
|
|
9586
|
-
profile: "
|
|
9956
|
+
profile: "meta",
|
|
9587
9957
|
enableTaskTool: true,
|
|
9588
9958
|
});
|
|
9589
9959
|
swarmSession = created.session;
|
|
@@ -9600,6 +9970,14 @@ export class InteractiveMode {
|
|
|
9600
9970
|
const taskErrors = [];
|
|
9601
9971
|
let delegatedFailed = 0;
|
|
9602
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;
|
|
9603
9981
|
const delegatedFailureCauses = new Map();
|
|
9604
9982
|
const accumulateFailureCauses = (raw) => {
|
|
9605
9983
|
if (!raw || typeof raw !== "object")
|
|
@@ -9613,13 +9991,71 @@ export class InteractiveMode {
|
|
|
9613
9991
|
delegatedFailureCauses.set(cause, (delegatedFailureCauses.get(cause) ?? 0) + numeric);
|
|
9614
9992
|
}
|
|
9615
9993
|
};
|
|
9994
|
+
const emitProgress = (progress) => {
|
|
9995
|
+
input.onProgress?.({
|
|
9996
|
+
activeTool,
|
|
9997
|
+
toolCallsStarted,
|
|
9998
|
+
toolCallsCompleted,
|
|
9999
|
+
assistantMessages,
|
|
10000
|
+
delegatedTasks,
|
|
10001
|
+
delegatedFailed,
|
|
10002
|
+
...progress,
|
|
10003
|
+
});
|
|
10004
|
+
};
|
|
9616
10005
|
const chunks = [];
|
|
9617
10006
|
const unsubscribe = swarmSession.subscribe((event) => {
|
|
9618
|
-
if (event.type === "tool_execution_start"
|
|
9619
|
-
|
|
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
|
+
}
|
|
9620
10052
|
return;
|
|
9621
10053
|
}
|
|
9622
10054
|
if (event.type === "tool_execution_end" && event.toolName === "task") {
|
|
10055
|
+
toolCallsCompleted += 1;
|
|
10056
|
+
if (activeTool === event.toolName) {
|
|
10057
|
+
activeTool = undefined;
|
|
10058
|
+
}
|
|
9623
10059
|
const result = event.result;
|
|
9624
10060
|
const details = result?.details;
|
|
9625
10061
|
if (typeof details?.delegatedFailed === "number" && Number.isFinite(details.delegatedFailed)) {
|
|
@@ -9629,38 +10065,127 @@ export class InteractiveMode {
|
|
|
9629
10065
|
delegatedTasks += Math.max(0, details.delegatedTasks);
|
|
9630
10066
|
}
|
|
9631
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
|
+
});
|
|
9632
10081
|
if (event.isError) {
|
|
9633
10082
|
taskErrors.push(result?.error ?? result?.output ?? "task tool failed");
|
|
9634
10083
|
}
|
|
9635
10084
|
return;
|
|
9636
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
|
+
}
|
|
9637
10097
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
10098
|
+
assistantMessages += 1;
|
|
9638
10099
|
for (const part of event.message.content) {
|
|
9639
10100
|
if (part.type === "text" && part.text.trim()) {
|
|
9640
10101
|
chunks.push(part.text.trim());
|
|
9641
10102
|
}
|
|
9642
10103
|
}
|
|
10104
|
+
emitProgress({
|
|
10105
|
+
phase: "drafting response",
|
|
10106
|
+
phaseState: "responding",
|
|
10107
|
+
});
|
|
9643
10108
|
}
|
|
9644
10109
|
});
|
|
9645
10110
|
try {
|
|
9646
|
-
|
|
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, {
|
|
9647
10125
|
expandPromptTemplates: false,
|
|
9648
10126
|
skipIosmAutopilot: true,
|
|
9649
10127
|
skipOrchestrationDirective: true,
|
|
9650
10128
|
source: "interactive",
|
|
9651
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]);
|
|
9652
10168
|
}
|
|
9653
10169
|
catch (error) {
|
|
9654
10170
|
return {
|
|
9655
10171
|
taskId: input.task.id,
|
|
9656
10172
|
status: "error",
|
|
9657
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,
|
|
9658
10179
|
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
9659
10180
|
};
|
|
9660
10181
|
}
|
|
9661
10182
|
finally {
|
|
10183
|
+
detachStopListener?.();
|
|
10184
|
+
if (timeoutHandle) {
|
|
10185
|
+
clearTimeout(timeoutHandle);
|
|
10186
|
+
}
|
|
9662
10187
|
unsubscribe();
|
|
9663
|
-
if (swarmSession.isStreaming) {
|
|
10188
|
+
if (timedOut || swarmSession.isStreaming) {
|
|
9664
10189
|
await swarmSession.abort().catch(() => {
|
|
9665
10190
|
// best effort
|
|
9666
10191
|
});
|
|
@@ -9682,6 +10207,7 @@ export class InteractiveMode {
|
|
|
9682
10207
|
taskId: input.task.id,
|
|
9683
10208
|
status: "error",
|
|
9684
10209
|
error: "No task tool call executed by assistant.",
|
|
10210
|
+
failureCause: "protocol_violation",
|
|
9685
10211
|
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
9686
10212
|
};
|
|
9687
10213
|
}
|
|
@@ -9723,6 +10249,7 @@ export class InteractiveMode {
|
|
|
9723
10249
|
if (!input.resumeState) {
|
|
9724
10250
|
store.init(input.meta, input.plan, initialState);
|
|
9725
10251
|
}
|
|
10252
|
+
const swarmTaskById = new Map(input.plan.tasks.map((task) => [task.id, task]));
|
|
9726
10253
|
let rollingIndex = input.projectIndex;
|
|
9727
10254
|
let localStopRequested = false;
|
|
9728
10255
|
const refreshIncrementalIndex = () => {
|
|
@@ -9740,52 +10267,159 @@ export class InteractiveMode {
|
|
|
9740
10267
|
rollingIndex = rebuilt;
|
|
9741
10268
|
};
|
|
9742
10269
|
this.swarmActiveRunId = input.runId;
|
|
10270
|
+
this.swarmStopRequested = false;
|
|
10271
|
+
this.swarmAbortController = new AbortController();
|
|
10272
|
+
this.footerDataProvider.setSwarmBusy(true);
|
|
10273
|
+
this.footer.invalidate();
|
|
9743
10274
|
this.showStatus(`Swarm run started: ${input.runId}`);
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
budgetUsd: input.budgetUsd,
|
|
9750
|
-
existingState: initialState,
|
|
9751
|
-
dispatchTask: async ({ task, runtime }) => this.dispatchSwarmTaskWithAgent({
|
|
9752
|
-
meta: input.meta,
|
|
9753
|
-
task,
|
|
9754
|
-
runtime,
|
|
10275
|
+
let schedulerResult;
|
|
10276
|
+
try {
|
|
10277
|
+
schedulerResult = await runSwarmScheduler({
|
|
10278
|
+
runId: input.runId,
|
|
10279
|
+
plan: input.plan,
|
|
9755
10280
|
contract: input.contract,
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
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
|
+
}
|
|
9760
10319
|
return true;
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
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
|
+
}
|
|
9789
10423
|
const taskStates = Object.values(schedulerResult.state.tasks);
|
|
9790
10424
|
const doneCount = taskStates.filter((task) => task.status === "done").length;
|
|
9791
10425
|
const errorCount = taskStates.filter((task) => task.status === "error").length;
|
|
@@ -9841,6 +10475,8 @@ export class InteractiveMode {
|
|
|
9841
10475
|
].join("\n"));
|
|
9842
10476
|
}
|
|
9843
10477
|
async runSwarmFromTask(task, options) {
|
|
10478
|
+
if (!this.ensureSwarmModelReady("plain"))
|
|
10479
|
+
return;
|
|
9844
10480
|
const contract = await this.ensureSwarmEffectiveContract(task);
|
|
9845
10481
|
if (!contract)
|
|
9846
10482
|
return;
|
|
@@ -9855,7 +10491,11 @@ export class InteractiveMode {
|
|
|
9855
10491
|
index: indexInfo.index,
|
|
9856
10492
|
});
|
|
9857
10493
|
const runId = `swarm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9858
|
-
const maxParallel =
|
|
10494
|
+
const maxParallel = this.resolveSwarmMaxParallel({
|
|
10495
|
+
requested: options.maxParallel,
|
|
10496
|
+
plan,
|
|
10497
|
+
source: "plain",
|
|
10498
|
+
});
|
|
9859
10499
|
const meta = this.buildSwarmRunMeta({
|
|
9860
10500
|
runId,
|
|
9861
10501
|
source: "plain",
|
|
@@ -9877,6 +10517,8 @@ export class InteractiveMode {
|
|
|
9877
10517
|
});
|
|
9878
10518
|
}
|
|
9879
10519
|
async runSwarmFromSingular(input) {
|
|
10520
|
+
if (!this.ensureSwarmModelReady("singular"))
|
|
10521
|
+
return;
|
|
9880
10522
|
const analysis = this.loadSingularAnalysisByRunId(input.runId);
|
|
9881
10523
|
if (!analysis) {
|
|
9882
10524
|
this.showWarning(`Singular run not found: ${input.runId}`);
|
|
@@ -9899,7 +10541,11 @@ export class InteractiveMode {
|
|
|
9899
10541
|
index: indexInfo.index,
|
|
9900
10542
|
});
|
|
9901
10543
|
const runId = `swarm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9902
|
-
const maxParallel =
|
|
10544
|
+
const maxParallel = this.resolveSwarmMaxParallel({
|
|
10545
|
+
requested: input.maxParallel,
|
|
10546
|
+
plan,
|
|
10547
|
+
source: "singular",
|
|
10548
|
+
});
|
|
9903
10549
|
const meta = this.buildSwarmRunMeta({
|
|
9904
10550
|
runId,
|
|
9905
10551
|
source: "singular",
|
|
@@ -10087,6 +10733,10 @@ export class InteractiveMode {
|
|
|
10087
10733
|
});
|
|
10088
10734
|
}
|
|
10089
10735
|
async handleSwarmCommand(text) {
|
|
10736
|
+
if (this.swarmActiveRunId) {
|
|
10737
|
+
this.showWarning(`Swarm run already in progress: ${this.swarmActiveRunId}. Use /swarm watch.`);
|
|
10738
|
+
return;
|
|
10739
|
+
}
|
|
10090
10740
|
if (this.session.isStreaming) {
|
|
10091
10741
|
this.showWarning("Cannot run /swarm while the agent is processing another request.");
|
|
10092
10742
|
return;
|
|
@@ -10300,6 +10950,9 @@ export class InteractiveMode {
|
|
|
10300
10950
|
this.showWarning("--max-parallel is only valid with --parallel mode.");
|
|
10301
10951
|
return undefined;
|
|
10302
10952
|
}
|
|
10953
|
+
if (mode === "parallel" && maxParallel === undefined) {
|
|
10954
|
+
maxParallel = Math.max(1, Math.min(MAX_ORCHESTRATION_PARALLEL, agents));
|
|
10955
|
+
}
|
|
10303
10956
|
if (maxParallel !== undefined && maxParallel > agents) {
|
|
10304
10957
|
maxParallel = agents;
|
|
10305
10958
|
}
|
|
@@ -10366,11 +11019,24 @@ export class InteractiveMode {
|
|
|
10366
11019
|
const currentCwd = this.sessionManager.getCwd();
|
|
10367
11020
|
const assignments = [];
|
|
10368
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 = [];
|
|
10369
11025
|
for (let index = 0; index < parsed.agents; index++) {
|
|
10370
|
-
const assignmentProfile = parsed.profiles?.[index] ?? parsed.profile ??
|
|
11026
|
+
const assignmentProfile = parsed.profiles?.[index] ?? parsed.profile ?? defaultAssignmentProfile;
|
|
10371
11027
|
const assignmentCwd = parsed.cwds?.[index] ?? ".";
|
|
10372
11028
|
const assignmentLock = parsed.locks?.[index];
|
|
10373
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);
|
|
10374
11040
|
const resolvedCwd = path.resolve(currentCwd, assignmentCwd);
|
|
10375
11041
|
assignmentRecords.push({
|
|
10376
11042
|
profile: assignmentProfile,
|
|
@@ -10378,7 +11044,8 @@ export class InteractiveMode {
|
|
|
10378
11044
|
lockKey: assignmentLock,
|
|
10379
11045
|
dependsOn,
|
|
10380
11046
|
});
|
|
10381
|
-
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
|
+
}`);
|
|
10382
11049
|
}
|
|
10383
11050
|
const teamRun = createTeamRun({
|
|
10384
11051
|
cwd: currentCwd,
|
|
@@ -10391,8 +11058,16 @@ export class InteractiveMode {
|
|
|
10391
11058
|
const runAssignments = teamRun.tasks.map((task, index) => `${assignments[index]} run_id=${teamRun.runId} task_id=${task.id}`);
|
|
10392
11059
|
const taskCallHints = teamRun.tasks.map((task, index) => {
|
|
10393
11060
|
const assignment = assignmentRecords[index];
|
|
10394
|
-
|
|
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}`;
|
|
10395
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
|
+
}
|
|
10396
11071
|
this.chatContainer.addChild(new Spacer(1));
|
|
10397
11072
|
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "◆ ")) + theme.bold("/orchestrate") + theme.fg("muted", ` ${currentCwd}`), 1, 0));
|
|
10398
11073
|
this.ui.requestRender();
|
|
@@ -10407,6 +11082,9 @@ export class InteractiveMode {
|
|
|
10407
11082
|
"- for parallel mode, emit all independent task calls in one assistant response",
|
|
10408
11083
|
"- in parallel mode, use parallel tool-call style (<use_parallel_tool_calls>)",
|
|
10409
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>",
|
|
10410
11088
|
"- keep required orchestration task calls in foreground; do not set background=true unless user explicitly requested detached async runs",
|
|
10411
11089
|
"- do not poll .iosm/subagents/background via bash/read during orchestration; wait for task results and then synthesize",
|
|
10412
11090
|
"- include run_id and task_id from each assignment in the task tool arguments",
|
|
@@ -12394,6 +13072,10 @@ The agent will automatically receive IOSM context on every turn.`;
|
|
|
12394
13072
|
}
|
|
12395
13073
|
}
|
|
12396
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
|
+
}
|
|
12397
13079
|
const extensionRunner = this.session.extensionRunner;
|
|
12398
13080
|
// Emit user_bash event to let extensions intercept
|
|
12399
13081
|
const eventResult = extensionRunner
|