iosm-cli 0.2.4 → 0.2.6
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 +60 -0
- package/README.md +447 -285
- 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 +16 -2
- package/dist/core/shared-memory.d.ts.map +1 -1
- package/dist/core/shared-memory.js +283 -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 +79 -11
- package/dist/core/tools/shared-memory.js.map +1 -1
- package/dist/core/tools/task.d.ts +12 -1
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +1023 -76
- 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 +881 -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";
|
|
@@ -23,6 +23,7 @@ import { resolveModelScope } from "../../core/model-resolver.js";
|
|
|
23
23
|
import { getMcpCommandHelp, parseMcpAddCommand, parseMcpTargetCommand, } from "../../core/mcp/index.js";
|
|
24
24
|
import { addMemoryEntry, getMemoryFilePath, readMemoryEntries, removeMemoryEntry, updateMemoryEntry, } from "../../core/memory.js";
|
|
25
25
|
import { ContractService, normalizeEngineeringContract, } from "../../core/contract.js";
|
|
26
|
+
import { readSharedMemory } from "../../core/shared-memory.js";
|
|
26
27
|
import { buildProjectIndex, collectChangedFilesSince, ensureProjectIndex, loadProjectIndex, queryProjectIndex, saveProjectIndex, } from "../../core/project-index/index.js";
|
|
27
28
|
import { SingularService, } from "../../core/singular.js";
|
|
28
29
|
import { getDefaultSemanticSearchConfig, getSemanticConfigPath, getSemanticIndexDir, isLikelyEmbeddingModelId, listOllamaLocalModels, listOpenRouterEmbeddingModels, loadMergedSemanticConfig, readScopedSemanticConfig, SemanticConfigMissingError, SemanticIndexRequiredError, SemanticRebuildRequiredError, SemanticSearchRuntime, upsertScopedSemanticSearchConfig, } from "../../core/semantic/index.js";
|
|
@@ -88,8 +89,8 @@ function parseRequestedParallelAgentCount(text) {
|
|
|
88
89
|
const patterns = [
|
|
89
90
|
/(\d+)\s+(?:parallel|concurrent)\s+agents?/i,
|
|
90
91
|
/(\d+)\s+agents?/i,
|
|
91
|
-
/(\d+)\s
|
|
92
|
-
/(\d+)\s
|
|
92
|
+
/(\d+)\s+паралл[\p{L}\p{N}_-]*\s+агент[\p{L}\p{N}_-]*/iu,
|
|
93
|
+
/(\d+)\s+агент[\p{L}\p{N}_-]*/iu,
|
|
93
94
|
];
|
|
94
95
|
for (const pattern of patterns) {
|
|
95
96
|
const match = text.match(pattern);
|
|
@@ -127,6 +128,12 @@ function buildMetaParallelismCorrection(input) {
|
|
|
127
128
|
`Meta runtime correction: this run currently has ${input.launchedTopLevelTasks} top-level task call(s), but it should have at least ${input.requiredTopLevelTasks}.`,
|
|
128
129
|
"Stop manual sequential execution in the main agent and convert the execution graph into parallel task calls now.",
|
|
129
130
|
"Emit the missing top-level task calls in the same assistant response when branches are independent.",
|
|
131
|
+
input.workerDiversityMissing
|
|
132
|
+
? `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.`
|
|
133
|
+
: undefined,
|
|
134
|
+
input.nestedDelegationMissing
|
|
135
|
+
? "Top-level fan-out exists, but no nested delegates were observed. For each broad top-level stream, emit nested <delegate_task> fan-out or one explicit line: DELEGATION_IMPOSSIBLE: <precise reason>."
|
|
136
|
+
: undefined,
|
|
130
137
|
"Each task tool call MUST include description and prompt.",
|
|
131
138
|
'If you want a custom subagent, pass it via agent="name"; keep profile set to the capability profile, not the custom subagent name.',
|
|
132
139
|
input.rawRootDelegateBlocks && input.rawRootDelegateBlocks > 0
|
|
@@ -145,25 +152,44 @@ function buildMetaParallelismCorrection(input) {
|
|
|
145
152
|
}
|
|
146
153
|
async function promptMetaWithParallelismGuard(input) {
|
|
147
154
|
let taskToolCalls = 0;
|
|
155
|
+
let completedTaskToolCalls = 0;
|
|
148
156
|
let nonTaskToolCalls = 0;
|
|
149
157
|
let taskPlanSnapshot;
|
|
150
158
|
let taskToolError;
|
|
151
159
|
let rawRootDelegateBlocks = 0;
|
|
152
160
|
let delegationImpossibleDeclared = false;
|
|
153
161
|
let correctionText;
|
|
154
|
-
|
|
162
|
+
let distinctTaskWorkers = new Set();
|
|
163
|
+
let delegatedChildTasksSeen = 0;
|
|
164
|
+
const maybeScheduleCorrection = (options) => {
|
|
155
165
|
const requiredTopLevelTasks = deriveMetaRequiredTopLevelTaskCalls(input.userInput, taskPlanSnapshot);
|
|
156
|
-
if (!requiredTopLevelTasks ||
|
|
166
|
+
if (!requiredTopLevelTasks || delegationImpossibleDeclared) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const canAssessNestedDelegation = options?.finalize || completedTaskToolCalls >= taskToolCalls;
|
|
170
|
+
const needsMoreTopLevelTasks = taskToolCalls < requiredTopLevelTasks;
|
|
171
|
+
const workerDiversityMissing = !needsMoreTopLevelTasks &&
|
|
172
|
+
requiredTopLevelTasks >= 2 &&
|
|
173
|
+
distinctTaskWorkers.size === 1 &&
|
|
174
|
+
delegatedChildTasksSeen === 0;
|
|
175
|
+
const nestedDelegationMissing = !needsMoreTopLevelTasks &&
|
|
176
|
+
!workerDiversityMissing &&
|
|
177
|
+
requiredTopLevelTasks >= 2 &&
|
|
178
|
+
taskToolCalls > 0 &&
|
|
179
|
+
delegatedChildTasksSeen === 0 &&
|
|
180
|
+
canAssessNestedDelegation &&
|
|
181
|
+
!taskToolError;
|
|
182
|
+
if (!needsMoreTopLevelTasks && !workerDiversityMissing && !nestedDelegationMissing) {
|
|
157
183
|
return;
|
|
158
184
|
}
|
|
159
185
|
const hasComplexPlan = !!taskPlanSnapshot;
|
|
160
186
|
const hasTaskFailure = !!taskToolError;
|
|
161
187
|
const hasRawRootDelegates = rawRootDelegateBlocks > 0;
|
|
162
|
-
if (!hasTaskFailure && !hasRawRootDelegates &&
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
188
|
+
if (needsMoreTopLevelTasks && !hasTaskFailure && !hasRawRootDelegates && !nestedDelegationMissing) {
|
|
189
|
+
const minimumNonTaskCalls = hasComplexPlan ? 0 : 2;
|
|
190
|
+
if (!options?.finalize && nonTaskToolCalls < minimumNonTaskCalls) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
167
193
|
}
|
|
168
194
|
correctionText = buildMetaParallelismCorrection({
|
|
169
195
|
requiredTopLevelTasks,
|
|
@@ -171,6 +197,9 @@ async function promptMetaWithParallelismGuard(input) {
|
|
|
171
197
|
taskPlanSnapshot,
|
|
172
198
|
taskToolError,
|
|
173
199
|
rawRootDelegateBlocks,
|
|
200
|
+
workerDiversityMissing,
|
|
201
|
+
distinctWorkers: distinctTaskWorkers.size,
|
|
202
|
+
nestedDelegationMissing,
|
|
174
203
|
});
|
|
175
204
|
};
|
|
176
205
|
const unsubscribe = input.session.subscribe((event) => {
|
|
@@ -192,6 +221,17 @@ async function promptMetaWithParallelismGuard(input) {
|
|
|
192
221
|
if (event.type === "tool_execution_start") {
|
|
193
222
|
if (event.toolName === "task") {
|
|
194
223
|
taskToolCalls += 1;
|
|
224
|
+
const args = event.args && typeof event.args === "object" ? event.args : undefined;
|
|
225
|
+
const workerIdentityRaw = (typeof args?.agent === "string" && args.agent.trim()) ||
|
|
226
|
+
(typeof args?.profile === "string" && args.profile.trim()) ||
|
|
227
|
+
undefined;
|
|
228
|
+
if (workerIdentityRaw) {
|
|
229
|
+
distinctTaskWorkers.add(workerIdentityRaw.toLowerCase());
|
|
230
|
+
}
|
|
231
|
+
else if (args) {
|
|
232
|
+
// Distinguish "explicit task call with omitted identity" from missing event payload.
|
|
233
|
+
distinctTaskWorkers.add("__default_profile__");
|
|
234
|
+
}
|
|
195
235
|
}
|
|
196
236
|
else {
|
|
197
237
|
nonTaskToolCalls += 1;
|
|
@@ -200,22 +240,76 @@ async function promptMetaWithParallelismGuard(input) {
|
|
|
200
240
|
return;
|
|
201
241
|
}
|
|
202
242
|
if (event.type === "tool_execution_end" && event.toolName === "task" && event.isError) {
|
|
243
|
+
completedTaskToolCalls += 1;
|
|
203
244
|
taskToolError = extractTaskToolErrorText(event.result) ?? taskToolError;
|
|
204
245
|
maybeScheduleCorrection();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (event.type === "tool_execution_end" && event.toolName === "task" && !event.isError) {
|
|
249
|
+
completedTaskToolCalls += 1;
|
|
250
|
+
const result = event.result;
|
|
251
|
+
const delegatedTasks = result?.details?.delegatedTasks;
|
|
252
|
+
if (typeof delegatedTasks === "number" && Number.isFinite(delegatedTasks) && delegatedTasks > 0) {
|
|
253
|
+
delegatedChildTasksSeen += delegatedTasks;
|
|
254
|
+
}
|
|
255
|
+
maybeScheduleCorrection();
|
|
205
256
|
}
|
|
206
257
|
});
|
|
207
258
|
try {
|
|
208
259
|
await input.session.prompt(input.userInput);
|
|
209
|
-
|
|
260
|
+
maybeScheduleCorrection({ finalize: true });
|
|
261
|
+
let requiredTopLevelTasks = deriveMetaRequiredTopLevelTaskCalls(input.userInput, taskPlanSnapshot);
|
|
262
|
+
let workerDiversityMissingAfterRun = !!requiredTopLevelTasks &&
|
|
263
|
+
requiredTopLevelTasks >= 2 &&
|
|
264
|
+
distinctTaskWorkers.size === 1 &&
|
|
265
|
+
delegatedChildTasksSeen === 0;
|
|
266
|
+
let nestedDelegationMissingAfterRun = !!requiredTopLevelTasks &&
|
|
267
|
+
requiredTopLevelTasks >= 2 &&
|
|
268
|
+
taskToolCalls >= requiredTopLevelTasks &&
|
|
269
|
+
taskToolCalls > 0 &&
|
|
270
|
+
delegatedChildTasksSeen === 0 &&
|
|
271
|
+
!workerDiversityMissingAfterRun &&
|
|
272
|
+
!taskToolError;
|
|
210
273
|
if (correctionText &&
|
|
211
274
|
requiredTopLevelTasks &&
|
|
212
|
-
taskToolCalls < requiredTopLevelTasks &&
|
|
275
|
+
(taskToolCalls < requiredTopLevelTasks || workerDiversityMissingAfterRun || nestedDelegationMissingAfterRun) &&
|
|
213
276
|
!delegationImpossibleDeclared) {
|
|
214
277
|
await input.session.prompt(correctionText, {
|
|
215
278
|
expandPromptTemplates: false,
|
|
216
279
|
skipOrchestrationDirective: true,
|
|
217
280
|
source: "interactive",
|
|
218
281
|
});
|
|
282
|
+
maybeScheduleCorrection({ finalize: true });
|
|
283
|
+
requiredTopLevelTasks = deriveMetaRequiredTopLevelTaskCalls(input.userInput, taskPlanSnapshot);
|
|
284
|
+
workerDiversityMissingAfterRun =
|
|
285
|
+
!!requiredTopLevelTasks &&
|
|
286
|
+
requiredTopLevelTasks >= 2 &&
|
|
287
|
+
distinctTaskWorkers.size === 1 &&
|
|
288
|
+
delegatedChildTasksSeen === 0;
|
|
289
|
+
nestedDelegationMissingAfterRun =
|
|
290
|
+
!!requiredTopLevelTasks &&
|
|
291
|
+
requiredTopLevelTasks >= 2 &&
|
|
292
|
+
taskToolCalls >= requiredTopLevelTasks &&
|
|
293
|
+
taskToolCalls > 0 &&
|
|
294
|
+
delegatedChildTasksSeen === 0 &&
|
|
295
|
+
!workerDiversityMissingAfterRun &&
|
|
296
|
+
!taskToolError;
|
|
297
|
+
}
|
|
298
|
+
if (requiredTopLevelTasks &&
|
|
299
|
+
(taskToolCalls < requiredTopLevelTasks ||
|
|
300
|
+
workerDiversityMissingAfterRun ||
|
|
301
|
+
nestedDelegationMissingAfterRun) &&
|
|
302
|
+
!delegationImpossibleDeclared &&
|
|
303
|
+
input.onPersistentNonCompliance) {
|
|
304
|
+
await input.onPersistentNonCompliance({
|
|
305
|
+
requiredTopLevelTasks,
|
|
306
|
+
launchedTopLevelTasks: taskToolCalls,
|
|
307
|
+
distinctWorkers: distinctTaskWorkers.size,
|
|
308
|
+
workerDiversityMissing: workerDiversityMissingAfterRun,
|
|
309
|
+
nestedDelegationMissing: nestedDelegationMissingAfterRun,
|
|
310
|
+
taskPlanSnapshot,
|
|
311
|
+
taskToolError,
|
|
312
|
+
});
|
|
219
313
|
}
|
|
220
314
|
}
|
|
221
315
|
finally {
|
|
@@ -549,6 +643,8 @@ export class InteractiveMode {
|
|
|
549
643
|
this.sessionAllowedToolSignatures = new Set();
|
|
550
644
|
this.singularLastEffectiveContract = {};
|
|
551
645
|
this.swarmActiveRunId = undefined;
|
|
646
|
+
this.swarmStopRequested = false;
|
|
647
|
+
this.swarmAbortController = undefined;
|
|
552
648
|
this.lastSigintTime = 0;
|
|
553
649
|
this.lastEscapeTime = 0;
|
|
554
650
|
this.changelogMarkdown = undefined;
|
|
@@ -559,6 +655,7 @@ export class InteractiveMode {
|
|
|
559
655
|
this.streamingComponent = undefined;
|
|
560
656
|
this.streamingMessage = undefined;
|
|
561
657
|
this.currentTurnSawAssistantMessage = false;
|
|
658
|
+
this.currentTurnSawTaskToolCall = false;
|
|
562
659
|
// Tool execution tracking: toolCallId -> component
|
|
563
660
|
this.pendingTools = new Map();
|
|
564
661
|
// Subagent execution tracking with live progress/metadata for task tool calls.
|
|
@@ -737,6 +834,17 @@ export class InteractiveMode {
|
|
|
737
834
|
}
|
|
738
835
|
}
|
|
739
836
|
async requestToolPermission(request) {
|
|
837
|
+
if (this.activeProfileName === "meta" &&
|
|
838
|
+
!this.currentTurnSawTaskToolCall &&
|
|
839
|
+
(request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write")) {
|
|
840
|
+
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.`);
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
if (isReadOnlyProfileName(this.activeProfileName) &&
|
|
844
|
+
(request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write")) {
|
|
845
|
+
this.showWarning(`Tool ${request.toolName} is disabled in ${this.activeProfileName} profile. Switch to full/meta/iosm for mutating operations.`);
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
740
848
|
for (const rule of this.permissionDenyRules) {
|
|
741
849
|
if (this.matchesPermissionRule(rule, request)) {
|
|
742
850
|
this.showWarning(`Denied by rule: ${rule}`);
|
|
@@ -2138,7 +2246,7 @@ export class InteractiveMode {
|
|
|
2138
2246
|
sessionManager: this.sessionManager,
|
|
2139
2247
|
modelRegistry: this.session.modelRegistry,
|
|
2140
2248
|
model: this.session.model,
|
|
2141
|
-
isIdle: () => !this.session.isStreaming,
|
|
2249
|
+
isIdle: () => !this.session.isStreaming && this.swarmActiveRunId === undefined,
|
|
2142
2250
|
abort: () => this.session.abort(),
|
|
2143
2251
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
2144
2252
|
shutdown: () => {
|
|
@@ -2710,6 +2818,7 @@ export class InteractiveMode {
|
|
|
2710
2818
|
this.session.isRetrying ||
|
|
2711
2819
|
this.iosmAutomationRun !== undefined ||
|
|
2712
2820
|
this.iosmVerificationSession !== undefined ||
|
|
2821
|
+
this.swarmActiveRunId !== undefined ||
|
|
2713
2822
|
this.singularAnalysisSession !== undefined ||
|
|
2714
2823
|
queuedMessages.steering.length > 0 ||
|
|
2715
2824
|
queuedMessages.followUp.length > 0 ||
|
|
@@ -3116,6 +3225,173 @@ export class InteractiveMode {
|
|
|
3116
3225
|
durationMs: Date.now() - subagent.startTime,
|
|
3117
3226
|
});
|
|
3118
3227
|
}
|
|
3228
|
+
getSwarmSubagentKey(runId, taskId) {
|
|
3229
|
+
return `swarm:${runId}:${taskId}`;
|
|
3230
|
+
}
|
|
3231
|
+
ensureSwarmSubagentDisplay(input) {
|
|
3232
|
+
const key = this.getSwarmSubagentKey(input.runId, input.taskId);
|
|
3233
|
+
const existing = this.subagentComponents.get(key);
|
|
3234
|
+
if (existing) {
|
|
3235
|
+
return existing;
|
|
3236
|
+
}
|
|
3237
|
+
const profile = (input.profile?.trim() || this.resolveSwarmTaskProfile(input.task)).trim();
|
|
3238
|
+
const info = {
|
|
3239
|
+
description: input.task.brief || input.task.id,
|
|
3240
|
+
profile,
|
|
3241
|
+
status: "running",
|
|
3242
|
+
phase: "starting subagent",
|
|
3243
|
+
phaseState: "starting",
|
|
3244
|
+
cwd: this.sessionManager.getCwd(),
|
|
3245
|
+
toolCallsStarted: 0,
|
|
3246
|
+
toolCallsCompleted: 0,
|
|
3247
|
+
assistantMessages: 0,
|
|
3248
|
+
delegatedTasks: 0,
|
|
3249
|
+
delegatedSucceeded: 0,
|
|
3250
|
+
delegatedFailed: 0,
|
|
3251
|
+
};
|
|
3252
|
+
const component = new SubagentMessageComponent(info);
|
|
3253
|
+
this.chatContainer.addChild(component);
|
|
3254
|
+
const state = {
|
|
3255
|
+
component,
|
|
3256
|
+
startTime: Date.now(),
|
|
3257
|
+
profile: info.profile,
|
|
3258
|
+
description: info.description,
|
|
3259
|
+
cwd: info.cwd,
|
|
3260
|
+
agent: info.agent,
|
|
3261
|
+
lockKey: info.lockKey,
|
|
3262
|
+
isolation: info.isolation,
|
|
3263
|
+
phase: info.phase,
|
|
3264
|
+
phaseState: info.phaseState,
|
|
3265
|
+
activeTool: info.activeTool,
|
|
3266
|
+
toolCallsStarted: info.toolCallsStarted ?? 0,
|
|
3267
|
+
toolCallsCompleted: info.toolCallsCompleted ?? 0,
|
|
3268
|
+
assistantMessages: info.assistantMessages ?? 0,
|
|
3269
|
+
delegatedTasks: info.delegatedTasks,
|
|
3270
|
+
delegatedSucceeded: info.delegatedSucceeded,
|
|
3271
|
+
delegatedFailed: info.delegatedFailed,
|
|
3272
|
+
delegateIndex: info.delegateIndex,
|
|
3273
|
+
delegateTotal: info.delegateTotal,
|
|
3274
|
+
delegateDescription: info.delegateDescription,
|
|
3275
|
+
delegateProfile: info.delegateProfile,
|
|
3276
|
+
delegateItems: info.delegateItems,
|
|
3277
|
+
};
|
|
3278
|
+
this.subagentComponents.set(key, state);
|
|
3279
|
+
this.ensureSubagentElapsedTimer();
|
|
3280
|
+
this.ui.requestRender();
|
|
3281
|
+
return state;
|
|
3282
|
+
}
|
|
3283
|
+
updateSwarmSubagentProgress(input) {
|
|
3284
|
+
const state = this.ensureSwarmSubagentDisplay({
|
|
3285
|
+
runId: input.runId,
|
|
3286
|
+
taskId: input.taskId,
|
|
3287
|
+
task: input.task,
|
|
3288
|
+
profile: input.profile,
|
|
3289
|
+
});
|
|
3290
|
+
const progress = input.progress;
|
|
3291
|
+
if (typeof progress.phase === "string" && progress.phase.trim()) {
|
|
3292
|
+
state.phase = progress.phase.trim();
|
|
3293
|
+
}
|
|
3294
|
+
if (isSubagentPhaseState(progress.phaseState)) {
|
|
3295
|
+
state.phaseState = progress.phaseState;
|
|
3296
|
+
}
|
|
3297
|
+
if (typeof progress.cwd === "string" && progress.cwd.trim()) {
|
|
3298
|
+
state.cwd = progress.cwd.trim();
|
|
3299
|
+
}
|
|
3300
|
+
if ("activeTool" in progress) {
|
|
3301
|
+
state.activeTool =
|
|
3302
|
+
typeof progress.activeTool === "string" && progress.activeTool.trim().length > 0
|
|
3303
|
+
? progress.activeTool.trim()
|
|
3304
|
+
: undefined;
|
|
3305
|
+
}
|
|
3306
|
+
if (typeof progress.toolCallsStarted === "number" && Number.isFinite(progress.toolCallsStarted)) {
|
|
3307
|
+
state.toolCallsStarted = Math.max(0, progress.toolCallsStarted);
|
|
3308
|
+
}
|
|
3309
|
+
if (typeof progress.toolCallsCompleted === "number" && Number.isFinite(progress.toolCallsCompleted)) {
|
|
3310
|
+
state.toolCallsCompleted = Math.max(0, progress.toolCallsCompleted);
|
|
3311
|
+
}
|
|
3312
|
+
if (typeof progress.assistantMessages === "number" && Number.isFinite(progress.assistantMessages)) {
|
|
3313
|
+
state.assistantMessages = Math.max(0, progress.assistantMessages);
|
|
3314
|
+
}
|
|
3315
|
+
if (typeof progress.delegatedTasks === "number" && Number.isFinite(progress.delegatedTasks)) {
|
|
3316
|
+
state.delegatedTasks = Math.max(0, progress.delegatedTasks);
|
|
3317
|
+
}
|
|
3318
|
+
if (typeof progress.delegatedSucceeded === "number" && Number.isFinite(progress.delegatedSucceeded)) {
|
|
3319
|
+
state.delegatedSucceeded = Math.max(0, progress.delegatedSucceeded);
|
|
3320
|
+
}
|
|
3321
|
+
if (typeof progress.delegatedFailed === "number" && Number.isFinite(progress.delegatedFailed)) {
|
|
3322
|
+
state.delegatedFailed = Math.max(0, progress.delegatedFailed);
|
|
3323
|
+
}
|
|
3324
|
+
if ("delegateIndex" in progress) {
|
|
3325
|
+
state.delegateIndex =
|
|
3326
|
+
typeof progress.delegateIndex === "number" && progress.delegateIndex > 0
|
|
3327
|
+
? Math.floor(progress.delegateIndex)
|
|
3328
|
+
: undefined;
|
|
3329
|
+
}
|
|
3330
|
+
if ("delegateTotal" in progress) {
|
|
3331
|
+
state.delegateTotal =
|
|
3332
|
+
typeof progress.delegateTotal === "number" && progress.delegateTotal > 0
|
|
3333
|
+
? Math.floor(progress.delegateTotal)
|
|
3334
|
+
: undefined;
|
|
3335
|
+
}
|
|
3336
|
+
if ("delegateDescription" in progress) {
|
|
3337
|
+
state.delegateDescription =
|
|
3338
|
+
typeof progress.delegateDescription === "string" && progress.delegateDescription.trim().length > 0
|
|
3339
|
+
? progress.delegateDescription.trim()
|
|
3340
|
+
: undefined;
|
|
3341
|
+
}
|
|
3342
|
+
if ("delegateProfile" in progress) {
|
|
3343
|
+
state.delegateProfile =
|
|
3344
|
+
typeof progress.delegateProfile === "string" && progress.delegateProfile.trim().length > 0
|
|
3345
|
+
? progress.delegateProfile.trim()
|
|
3346
|
+
: undefined;
|
|
3347
|
+
}
|
|
3348
|
+
if ("delegateItems" in progress) {
|
|
3349
|
+
state.delegateItems = Array.isArray(progress.delegateItems) ? progress.delegateItems : undefined;
|
|
3350
|
+
}
|
|
3351
|
+
this.updateRunningSubagentDisplay(state);
|
|
3352
|
+
this.ui.requestRender();
|
|
3353
|
+
}
|
|
3354
|
+
finalizeSwarmSubagentDisplay(input) {
|
|
3355
|
+
const key = this.getSwarmSubagentKey(input.runId, input.taskId);
|
|
3356
|
+
const state = this.subagentComponents.get(key);
|
|
3357
|
+
if (!state)
|
|
3358
|
+
return;
|
|
3359
|
+
const durationMs = Date.now() - state.startTime;
|
|
3360
|
+
state.component.update({
|
|
3361
|
+
description: state.description,
|
|
3362
|
+
profile: state.profile,
|
|
3363
|
+
status: input.status,
|
|
3364
|
+
durationMs,
|
|
3365
|
+
phaseState: state.phaseState,
|
|
3366
|
+
cwd: state.cwd,
|
|
3367
|
+
agent: state.agent,
|
|
3368
|
+
lockKey: state.lockKey,
|
|
3369
|
+
isolation: state.isolation,
|
|
3370
|
+
toolCallsStarted: state.toolCallsStarted,
|
|
3371
|
+
toolCallsCompleted: state.toolCallsCompleted,
|
|
3372
|
+
assistantMessages: state.assistantMessages,
|
|
3373
|
+
delegatedTasks: state.delegatedTasks,
|
|
3374
|
+
delegatedSucceeded: state.delegatedSucceeded,
|
|
3375
|
+
delegatedFailed: state.delegatedFailed,
|
|
3376
|
+
errorMessage: input.status === "error" ? input.errorMessage ?? "error" : undefined,
|
|
3377
|
+
});
|
|
3378
|
+
this.subagentComponents.delete(key);
|
|
3379
|
+
this.stopSubagentElapsedTimerIfIdle();
|
|
3380
|
+
this.ui.requestRender();
|
|
3381
|
+
}
|
|
3382
|
+
finalizeSwarmRunSubagentDisplays(runId, errorMessage) {
|
|
3383
|
+
for (const [key] of this.subagentComponents.entries()) {
|
|
3384
|
+
if (!key.startsWith(`swarm:${runId}:`))
|
|
3385
|
+
continue;
|
|
3386
|
+
const taskId = key.slice(`swarm:${runId}:`.length);
|
|
3387
|
+
this.finalizeSwarmSubagentDisplay({
|
|
3388
|
+
runId,
|
|
3389
|
+
taskId,
|
|
3390
|
+
status: "error",
|
|
3391
|
+
errorMessage,
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3119
3395
|
ensureSubagentElapsedTimer() {
|
|
3120
3396
|
if (this.subagentElapsedTimer || this.subagentComponents.size === 0) {
|
|
3121
3397
|
return;
|
|
@@ -3156,6 +3432,7 @@ export class InteractiveMode {
|
|
|
3156
3432
|
switch (event.type) {
|
|
3157
3433
|
case "agent_start":
|
|
3158
3434
|
this.currentTurnSawAssistantMessage = false;
|
|
3435
|
+
this.currentTurnSawTaskToolCall = false;
|
|
3159
3436
|
// Restore main escape handler if retry handler is still active
|
|
3160
3437
|
// (retry success event fires later, but we need main handler now)
|
|
3161
3438
|
if (this.retryEscapeHandler) {
|
|
@@ -3286,6 +3563,9 @@ export class InteractiveMode {
|
|
|
3286
3563
|
this.ui.requestRender();
|
|
3287
3564
|
break;
|
|
3288
3565
|
case "tool_execution_start": {
|
|
3566
|
+
if (event.toolName === "task") {
|
|
3567
|
+
this.currentTurnSawTaskToolCall = true;
|
|
3568
|
+
}
|
|
3289
3569
|
if (event.toolName === "task" && !this.subagentComponents.has(event.toolCallId)) {
|
|
3290
3570
|
const staleTaskComponent = this.pendingTools.get(event.toolCallId);
|
|
3291
3571
|
if (staleTaskComponent) {
|
|
@@ -3922,6 +4202,15 @@ export class InteractiveMode {
|
|
|
3922
4202
|
// =========================================================================
|
|
3923
4203
|
handleCtrlC() {
|
|
3924
4204
|
const now = Date.now();
|
|
4205
|
+
if (this.swarmActiveRunId) {
|
|
4206
|
+
if (this.swarmStopRequested && now - this.lastSigintTime < 500) {
|
|
4207
|
+
void this.shutdown();
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
4210
|
+
this.lastSigintTime = now;
|
|
4211
|
+
void this.interruptCurrentWork();
|
|
4212
|
+
return;
|
|
4213
|
+
}
|
|
3925
4214
|
if (this.iosmAutomationRun) {
|
|
3926
4215
|
if (this.iosmAutomationRun.cancelRequested && now - this.lastSigintTime < 500) {
|
|
3927
4216
|
void this.shutdown();
|
|
@@ -4329,6 +4618,7 @@ export class InteractiveMode {
|
|
|
4329
4618
|
const hasAutomationWork = this.iosmAutomationRun !== undefined;
|
|
4330
4619
|
const verificationSession = this.iosmVerificationSession;
|
|
4331
4620
|
const hasVerificationWork = verificationSession !== undefined;
|
|
4621
|
+
const hasSwarmWork = this.swarmActiveRunId !== undefined;
|
|
4332
4622
|
const singularSession = this.singularAnalysisSession;
|
|
4333
4623
|
const hasSingularWork = singularSession !== undefined;
|
|
4334
4624
|
const hasMainStreaming = this.session.isStreaming;
|
|
@@ -4338,6 +4628,7 @@ export class InteractiveMode {
|
|
|
4338
4628
|
if (!hasPendingQueuedMessages &&
|
|
4339
4629
|
!hasAutomationWork &&
|
|
4340
4630
|
!hasVerificationWork &&
|
|
4631
|
+
!hasSwarmWork &&
|
|
4341
4632
|
!hasSingularWork &&
|
|
4342
4633
|
!hasMainStreaming &&
|
|
4343
4634
|
!hasRetryWork &&
|
|
@@ -4351,6 +4642,10 @@ export class InteractiveMode {
|
|
|
4351
4642
|
if (this.iosmAutomationRun) {
|
|
4352
4643
|
this.iosmAutomationRun.cancelRequested = true;
|
|
4353
4644
|
}
|
|
4645
|
+
if (hasSwarmWork) {
|
|
4646
|
+
this.swarmStopRequested = true;
|
|
4647
|
+
this.swarmAbortController?.abort();
|
|
4648
|
+
}
|
|
4354
4649
|
if (hasPendingQueuedMessages) {
|
|
4355
4650
|
this.restoreQueuedMessagesToEditor();
|
|
4356
4651
|
}
|
|
@@ -4371,9 +4666,11 @@ export class InteractiveMode {
|
|
|
4371
4666
|
? "Stopping IOSM automation..."
|
|
4372
4667
|
: hasVerificationWork
|
|
4373
4668
|
? "Stopping IOSM verification..."
|
|
4374
|
-
:
|
|
4375
|
-
? "Stopping
|
|
4376
|
-
:
|
|
4669
|
+
: hasSwarmWork
|
|
4670
|
+
? "Stopping swarm run..."
|
|
4671
|
+
: hasSingularWork
|
|
4672
|
+
? "Stopping /singular analysis..."
|
|
4673
|
+
: "Stopping current run...");
|
|
4377
4674
|
const abortPromises = [];
|
|
4378
4675
|
if (hasMainStreaming) {
|
|
4379
4676
|
abortPromises.push(this.session.abort());
|
|
@@ -7866,8 +8163,10 @@ export class InteractiveMode {
|
|
|
7866
8163
|
});
|
|
7867
8164
|
options.push("Close without decision");
|
|
7868
8165
|
const selected = await this.showExtensionSelector("/singular: choose next step", options);
|
|
7869
|
-
if (!selected || selected === "Close without decision")
|
|
8166
|
+
if (!selected || selected === "Close without decision") {
|
|
8167
|
+
this.showStatus("Singular: decision closed without execution.");
|
|
7870
8168
|
return;
|
|
8169
|
+
}
|
|
7871
8170
|
const match = selected.match(/^Option\s+(\d+)/);
|
|
7872
8171
|
if (!match)
|
|
7873
8172
|
return;
|
|
@@ -8879,6 +9178,26 @@ export class InteractiveMode {
|
|
|
8879
9178
|
await promptMetaWithParallelismGuard({
|
|
8880
9179
|
session: this.session,
|
|
8881
9180
|
userInput,
|
|
9181
|
+
onPersistentNonCompliance: async (details) => {
|
|
9182
|
+
if (typeof this.runSwarmFromTask !== "function")
|
|
9183
|
+
return;
|
|
9184
|
+
if (this.session.isStreaming || this.iosmAutomationRun || this.iosmVerificationSession)
|
|
9185
|
+
return;
|
|
9186
|
+
const topLevelSatisfied = details.launchedTopLevelTasks >= details.requiredTopLevelTasks;
|
|
9187
|
+
if (details.nestedDelegationMissing && topLevelSatisfied && !details.workerDiversityMissing) {
|
|
9188
|
+
this.showWarning("META quality warning: top-level fan-out completed, but nested delegate fan-out was not observed. " +
|
|
9189
|
+
"Repeat with explicit nested delegation requirements or include DELEGATION_IMPOSSIBLE for narrow streams.");
|
|
9190
|
+
return;
|
|
9191
|
+
}
|
|
9192
|
+
const explicitRequested = parseRequestedParallelAgentCount(userInput);
|
|
9193
|
+
const hasComplexSignal = /\b(audit|security|hardening|refactor|migration|orchestrat|parallel|delegate|multi[-\s]?agent)\b/i.test(userInput);
|
|
9194
|
+
const fallbackParallel = Math.max(1, Math.min(MAX_ORCHESTRATION_PARALLEL, explicitRequested ??
|
|
9195
|
+
(hasComplexSignal
|
|
9196
|
+
? Math.max(details.requiredTopLevelTasks, 6)
|
|
9197
|
+
: Math.max(details.requiredTopLevelTasks, 3))));
|
|
9198
|
+
this.showWarning(`META enforcement fallback: orchestration contract not satisfied (${details.launchedTopLevelTasks}/${details.requiredTopLevelTasks} task calls). Launching /swarm run.`);
|
|
9199
|
+
await this.runSwarmFromTask(userInput, { maxParallel: fallbackParallel });
|
|
9200
|
+
},
|
|
8882
9201
|
});
|
|
8883
9202
|
return;
|
|
8884
9203
|
}
|
|
@@ -9116,6 +9435,40 @@ export class InteractiveMode {
|
|
|
9116
9435
|
command: commandParts.join(" "),
|
|
9117
9436
|
};
|
|
9118
9437
|
}
|
|
9438
|
+
resolveOrchestrateDefaultAssignmentProfile(parsed) {
|
|
9439
|
+
const active = this.activeProfileName || "full";
|
|
9440
|
+
if (parsed.mode !== "parallel")
|
|
9441
|
+
return active;
|
|
9442
|
+
if (parsed.profile || (parsed.profiles && parsed.profiles.length > 0))
|
|
9443
|
+
return active;
|
|
9444
|
+
if (isReadOnlyProfileName(active))
|
|
9445
|
+
return active;
|
|
9446
|
+
return "meta";
|
|
9447
|
+
}
|
|
9448
|
+
deriveOrchestrateDelegateParallelHint(input) {
|
|
9449
|
+
const normalizedTask = input.task.toLowerCase();
|
|
9450
|
+
const highRisk = /\b(refactor|rewrite|migration|migrate|breaking|rollback|security|auth|authentication|authorization|permission|payment|billing|schema|database|critical)\b/i.test(normalizedTask);
|
|
9451
|
+
const mediumRisk = /\b(cross[-\s]?module|architecture|infra|platform|multi[-\s]?file|integration|audit|hardening)\b/i.test(normalizedTask);
|
|
9452
|
+
let hint = input.mode === "parallel"
|
|
9453
|
+
? Math.max(2, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, input.maxParallel ?? input.agents))
|
|
9454
|
+
: 1;
|
|
9455
|
+
if (highRisk) {
|
|
9456
|
+
hint = Math.max(hint, 7);
|
|
9457
|
+
}
|
|
9458
|
+
else if (mediumRisk) {
|
|
9459
|
+
hint = Math.max(hint, 5);
|
|
9460
|
+
}
|
|
9461
|
+
if (input.mode === "parallel" && input.dependencyEdges >= input.agents) {
|
|
9462
|
+
hint = Math.max(hint, 6);
|
|
9463
|
+
}
|
|
9464
|
+
if (input.hasDependencies) {
|
|
9465
|
+
hint = Math.max(2, Math.min(hint, 6));
|
|
9466
|
+
}
|
|
9467
|
+
if (input.hasLock) {
|
|
9468
|
+
hint = Math.max(1, Math.min(hint, 4));
|
|
9469
|
+
}
|
|
9470
|
+
return Math.max(1, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, hint));
|
|
9471
|
+
}
|
|
9119
9472
|
isEffectiveContractReady(contract) {
|
|
9120
9473
|
const hasText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
9121
9474
|
const hasList = (value) => Array.isArray(value) && value.some((item) => item.trim().length > 0);
|
|
@@ -9452,12 +9805,15 @@ export class InteractiveMode {
|
|
|
9452
9805
|
};
|
|
9453
9806
|
}
|
|
9454
9807
|
resolveSwarmTaskProfile(task) {
|
|
9808
|
+
const hint = this.deriveSwarmTaskDelegateParallelHint(task);
|
|
9809
|
+
if (task.concurrency_class === "docs")
|
|
9810
|
+
return "plan";
|
|
9811
|
+
if (hint >= 2)
|
|
9812
|
+
return "meta";
|
|
9455
9813
|
if (task.concurrency_class === "analysis")
|
|
9456
9814
|
return "explore";
|
|
9457
9815
|
if (task.concurrency_class === "verification" || task.concurrency_class === "tests")
|
|
9458
9816
|
return "iosm_verifier";
|
|
9459
|
-
if (task.concurrency_class === "docs")
|
|
9460
|
-
return "plan";
|
|
9461
9817
|
return "full";
|
|
9462
9818
|
}
|
|
9463
9819
|
estimateSwarmTaskCostUsd(task) {
|
|
@@ -9473,6 +9829,13 @@ export class InteractiveMode {
|
|
|
9473
9829
|
const hasVeryComplexSignal = /(overhaul|major|system-wide|cross-cutting|multi-service|facet|registry)/i.test(brief) ||
|
|
9474
9830
|
task.touches.length >= 5 ||
|
|
9475
9831
|
task.scopes.length >= 4;
|
|
9832
|
+
if (task.concurrency_class === "analysis" || task.concurrency_class === "docs") {
|
|
9833
|
+
if (task.severity === "low")
|
|
9834
|
+
return 1;
|
|
9835
|
+
if (task.touches.length >= 6 || task.scopes.length >= 4 || hasComplexKeyword)
|
|
9836
|
+
return 2;
|
|
9837
|
+
return 1;
|
|
9838
|
+
}
|
|
9476
9839
|
if (task.severity === "low" && task.touches.length <= 2 && task.scopes.length <= 2 && !hasComplexKeyword) {
|
|
9477
9840
|
return 1;
|
|
9478
9841
|
}
|
|
@@ -9487,6 +9850,38 @@ export class InteractiveMode {
|
|
|
9487
9850
|
return 5;
|
|
9488
9851
|
return hasComplexKeyword ? 3 : 1;
|
|
9489
9852
|
}
|
|
9853
|
+
ensureSwarmModelReady(source) {
|
|
9854
|
+
if (this.session.model)
|
|
9855
|
+
return true;
|
|
9856
|
+
if (source === "singular") {
|
|
9857
|
+
this.showWarning("Cannot launch Swarm from /singular: no active model. Select one via /model and retry.");
|
|
9858
|
+
}
|
|
9859
|
+
else {
|
|
9860
|
+
this.showWarning("Cannot run /swarm: no active model selected. Configure /model first.");
|
|
9861
|
+
}
|
|
9862
|
+
return false;
|
|
9863
|
+
}
|
|
9864
|
+
resolveSwarmMaxParallel(input) {
|
|
9865
|
+
const requested = input.requested;
|
|
9866
|
+
if (typeof requested === "number" && Number.isFinite(requested)) {
|
|
9867
|
+
return Math.max(1, Math.min(MAX_ORCHESTRATION_PARALLEL, Math.floor(requested)));
|
|
9868
|
+
}
|
|
9869
|
+
const totalTasks = Math.max(1, input.plan.tasks.length);
|
|
9870
|
+
const initialFanout = Math.max(1, input.plan.tasks.filter((task) => task.depends_on.length === 0).length);
|
|
9871
|
+
const parallelizable = input.plan.tasks.filter((task) => task.concurrency_class === "implementation" || task.concurrency_class === "tests")
|
|
9872
|
+
.length;
|
|
9873
|
+
const sourceFloor = input.source === "singular" ? 4 : 3;
|
|
9874
|
+
const autoCap = Math.min(MAX_ORCHESTRATION_PARALLEL, 10);
|
|
9875
|
+
const heuristic = Math.max(sourceFloor, Math.ceil(totalTasks / 2), initialFanout, parallelizable >= 4 ? Math.ceil(parallelizable / 2) : 1);
|
|
9876
|
+
return Math.max(1, Math.min(autoCap, heuristic));
|
|
9877
|
+
}
|
|
9878
|
+
resolveSwarmDispatchTimeoutMs() {
|
|
9879
|
+
const dispatchTimeoutRaw = Number.parseInt(process.env.IOSM_SWARM_DISPATCH_TIMEOUT_MS ?? "", 10);
|
|
9880
|
+
if (Number.isInteger(dispatchTimeoutRaw) && dispatchTimeoutRaw > 0) {
|
|
9881
|
+
return Math.max(1_000, Math.min(1_800_000, dispatchTimeoutRaw));
|
|
9882
|
+
}
|
|
9883
|
+
return 180_000;
|
|
9884
|
+
}
|
|
9490
9885
|
parseSwarmSpawnCandidates(output, parentTaskId) {
|
|
9491
9886
|
const lines = output.split(/\r?\n/);
|
|
9492
9887
|
const results = [];
|
|
@@ -9530,6 +9925,16 @@ export class InteractiveMode {
|
|
|
9530
9925
|
}
|
|
9531
9926
|
const profile = this.resolveSwarmTaskProfile(input.task);
|
|
9532
9927
|
const delegateParallelHint = this.deriveSwarmTaskDelegateParallelHint(input.task);
|
|
9928
|
+
const requiresStrongDelegation = input.task.concurrency_class !== "analysis" &&
|
|
9929
|
+
input.task.concurrency_class !== "docs" &&
|
|
9930
|
+
(input.task.severity === "high" || delegateParallelHint >= 7);
|
|
9931
|
+
const minDelegatesRequired = !requiresStrongDelegation
|
|
9932
|
+
? 0
|
|
9933
|
+
: delegateParallelHint >= 8
|
|
9934
|
+
? 3
|
|
9935
|
+
: delegateParallelHint >= 5
|
|
9936
|
+
? 2
|
|
9937
|
+
: 1;
|
|
9533
9938
|
const safeDescription = input.task.brief.replace(/\s+/g, " ").trim().slice(0, 120).replace(/"/g, "'");
|
|
9534
9939
|
const prompt = [
|
|
9535
9940
|
`<swarm_task run_id="${input.meta.runId}" task_id="${input.task.id}" profile_hint="${profile}">`,
|
|
@@ -9542,10 +9947,15 @@ export class InteractiveMode {
|
|
|
9542
9947
|
"",
|
|
9543
9948
|
"Execution requirements:",
|
|
9544
9949
|
`- 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
|
-
|
|
9950
|
+
minDelegatesRequired > 0
|
|
9951
|
+
? `- Inside this single task call, emit at least ${minDelegatesRequired} independent <delegate_task> subtasks (target parallel fan-out up to ${delegateParallelHint}).`
|
|
9952
|
+
: "- Delegation is optional for this task; keep execution focused.",
|
|
9953
|
+
minDelegatesRequired > 0
|
|
9954
|
+
? '- If safe decomposition is impossible, output exactly one line: DELEGATION_IMPOSSIBLE: <reason>.'
|
|
9955
|
+
: "- If decomposition is not beneficial, continue with single-agent execution.",
|
|
9547
9956
|
"- Keep edits inside declared scopes/touches. If scope expansion is required, explain and stop.",
|
|
9548
9957
|
"- If blocked, respond with line: BLOCKED: <reason>",
|
|
9958
|
+
"- Return concise execution output; avoid long narrative if not needed.",
|
|
9549
9959
|
"- Optional spawn candidates format: '- <description> | <path> | <change_type> | <low|medium|high>'",
|
|
9550
9960
|
"</swarm_task>",
|
|
9551
9961
|
].join("\n");
|
|
@@ -9583,7 +9993,7 @@ export class InteractiveMode {
|
|
|
9583
9993
|
resourceLoader,
|
|
9584
9994
|
model,
|
|
9585
9995
|
thinkingLevel: this.session.thinkingLevel,
|
|
9586
|
-
profile: "
|
|
9996
|
+
profile: "meta",
|
|
9587
9997
|
enableTaskTool: true,
|
|
9588
9998
|
});
|
|
9589
9999
|
swarmSession = created.session;
|
|
@@ -9600,6 +10010,14 @@ export class InteractiveMode {
|
|
|
9600
10010
|
const taskErrors = [];
|
|
9601
10011
|
let delegatedFailed = 0;
|
|
9602
10012
|
let delegatedTasks = 0;
|
|
10013
|
+
let toolCallsStarted = 0;
|
|
10014
|
+
let toolCallsCompleted = 0;
|
|
10015
|
+
let assistantMessages = 0;
|
|
10016
|
+
let activeTool;
|
|
10017
|
+
const dispatchTimeoutMs = this.resolveSwarmDispatchTimeoutMs();
|
|
10018
|
+
let timedOut = false;
|
|
10019
|
+
let timeoutHandle;
|
|
10020
|
+
let detachStopListener;
|
|
9603
10021
|
const delegatedFailureCauses = new Map();
|
|
9604
10022
|
const accumulateFailureCauses = (raw) => {
|
|
9605
10023
|
if (!raw || typeof raw !== "object")
|
|
@@ -9613,13 +10031,71 @@ export class InteractiveMode {
|
|
|
9613
10031
|
delegatedFailureCauses.set(cause, (delegatedFailureCauses.get(cause) ?? 0) + numeric);
|
|
9614
10032
|
}
|
|
9615
10033
|
};
|
|
10034
|
+
const emitProgress = (progress) => {
|
|
10035
|
+
input.onProgress?.({
|
|
10036
|
+
activeTool,
|
|
10037
|
+
toolCallsStarted,
|
|
10038
|
+
toolCallsCompleted,
|
|
10039
|
+
assistantMessages,
|
|
10040
|
+
delegatedTasks,
|
|
10041
|
+
delegatedFailed,
|
|
10042
|
+
...progress,
|
|
10043
|
+
});
|
|
10044
|
+
};
|
|
9616
10045
|
const chunks = [];
|
|
9617
10046
|
const unsubscribe = swarmSession.subscribe((event) => {
|
|
9618
|
-
if (event.type === "tool_execution_start"
|
|
9619
|
-
|
|
10047
|
+
if (event.type === "tool_execution_start") {
|
|
10048
|
+
toolCallsStarted += 1;
|
|
10049
|
+
activeTool = event.toolName;
|
|
10050
|
+
if (event.toolName === "task") {
|
|
10051
|
+
taskToolCalls += 1;
|
|
10052
|
+
}
|
|
10053
|
+
emitProgress({
|
|
10054
|
+
phase: event.toolName ? `running ${event.toolName}` : "running",
|
|
10055
|
+
phaseState: "running",
|
|
10056
|
+
});
|
|
10057
|
+
return;
|
|
10058
|
+
}
|
|
10059
|
+
if (event.type === "tool_execution_update" && event.toolName === "task") {
|
|
10060
|
+
const partial = event.partialResult;
|
|
10061
|
+
const progressCandidate = partial?.details?.progress;
|
|
10062
|
+
if (progressCandidate && typeof progressCandidate === "object") {
|
|
10063
|
+
const progress = progressCandidate;
|
|
10064
|
+
const delegateItems = parseSubagentDelegateItems(progress.delegateItems);
|
|
10065
|
+
emitProgress({
|
|
10066
|
+
phase: typeof progress.message === "string" ? progress.message : undefined,
|
|
10067
|
+
phaseState: isSubagentPhaseState(progress.phase) ? progress.phase : undefined,
|
|
10068
|
+
cwd: typeof progress.cwd === "string" ? progress.cwd : undefined,
|
|
10069
|
+
activeTool: typeof progress.activeTool === "string" && progress.activeTool.trim().length > 0
|
|
10070
|
+
? progress.activeTool.trim()
|
|
10071
|
+
: activeTool,
|
|
10072
|
+
toolCallsStarted: typeof progress.toolCallsStarted === "number" && Number.isFinite(progress.toolCallsStarted)
|
|
10073
|
+
? progress.toolCallsStarted
|
|
10074
|
+
: undefined,
|
|
10075
|
+
toolCallsCompleted: typeof progress.toolCallsCompleted === "number" && Number.isFinite(progress.toolCallsCompleted)
|
|
10076
|
+
? progress.toolCallsCompleted
|
|
10077
|
+
: undefined,
|
|
10078
|
+
assistantMessages: typeof progress.assistantMessages === "number" && Number.isFinite(progress.assistantMessages)
|
|
10079
|
+
? progress.assistantMessages
|
|
10080
|
+
: undefined,
|
|
10081
|
+
delegateIndex: typeof progress.delegateIndex === "number" && Number.isFinite(progress.delegateIndex)
|
|
10082
|
+
? progress.delegateIndex
|
|
10083
|
+
: undefined,
|
|
10084
|
+
delegateTotal: typeof progress.delegateTotal === "number" && Number.isFinite(progress.delegateTotal)
|
|
10085
|
+
? progress.delegateTotal
|
|
10086
|
+
: undefined,
|
|
10087
|
+
delegateDescription: typeof progress.delegateDescription === "string" ? progress.delegateDescription : undefined,
|
|
10088
|
+
delegateProfile: typeof progress.delegateProfile === "string" ? progress.delegateProfile : undefined,
|
|
10089
|
+
delegateItems,
|
|
10090
|
+
});
|
|
10091
|
+
}
|
|
9620
10092
|
return;
|
|
9621
10093
|
}
|
|
9622
10094
|
if (event.type === "tool_execution_end" && event.toolName === "task") {
|
|
10095
|
+
toolCallsCompleted += 1;
|
|
10096
|
+
if (activeTool === event.toolName) {
|
|
10097
|
+
activeTool = undefined;
|
|
10098
|
+
}
|
|
9623
10099
|
const result = event.result;
|
|
9624
10100
|
const details = result?.details;
|
|
9625
10101
|
if (typeof details?.delegatedFailed === "number" && Number.isFinite(details.delegatedFailed)) {
|
|
@@ -9629,38 +10105,127 @@ export class InteractiveMode {
|
|
|
9629
10105
|
delegatedTasks += Math.max(0, details.delegatedTasks);
|
|
9630
10106
|
}
|
|
9631
10107
|
accumulateFailureCauses(details?.failureCauses);
|
|
10108
|
+
emitProgress({
|
|
10109
|
+
phase: event.isError ? "task tool failed" : "task tool completed",
|
|
10110
|
+
phaseState: "running",
|
|
10111
|
+
delegatedTasks: typeof details?.delegatedTasks === "number" && Number.isFinite(details.delegatedTasks)
|
|
10112
|
+
? details.delegatedTasks
|
|
10113
|
+
: undefined,
|
|
10114
|
+
delegatedSucceeded: typeof details?.delegatedSucceeded === "number" && Number.isFinite(details.delegatedSucceeded)
|
|
10115
|
+
? details.delegatedSucceeded
|
|
10116
|
+
: undefined,
|
|
10117
|
+
delegatedFailed: typeof details?.delegatedFailed === "number" && Number.isFinite(details.delegatedFailed)
|
|
10118
|
+
? details.delegatedFailed
|
|
10119
|
+
: undefined,
|
|
10120
|
+
});
|
|
9632
10121
|
if (event.isError) {
|
|
9633
10122
|
taskErrors.push(result?.error ?? result?.output ?? "task tool failed");
|
|
9634
10123
|
}
|
|
9635
10124
|
return;
|
|
9636
10125
|
}
|
|
10126
|
+
if (event.type === "tool_execution_end") {
|
|
10127
|
+
toolCallsCompleted += 1;
|
|
10128
|
+
if (activeTool === event.toolName) {
|
|
10129
|
+
activeTool = undefined;
|
|
10130
|
+
}
|
|
10131
|
+
emitProgress({
|
|
10132
|
+
phase: event.toolName ? `completed ${event.toolName}` : "running",
|
|
10133
|
+
phaseState: "running",
|
|
10134
|
+
});
|
|
10135
|
+
return;
|
|
10136
|
+
}
|
|
9637
10137
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
10138
|
+
assistantMessages += 1;
|
|
9638
10139
|
for (const part of event.message.content) {
|
|
9639
10140
|
if (part.type === "text" && part.text.trim()) {
|
|
9640
10141
|
chunks.push(part.text.trim());
|
|
9641
10142
|
}
|
|
9642
10143
|
}
|
|
10144
|
+
emitProgress({
|
|
10145
|
+
phase: "drafting response",
|
|
10146
|
+
phaseState: "responding",
|
|
10147
|
+
});
|
|
9643
10148
|
}
|
|
9644
10149
|
});
|
|
9645
10150
|
try {
|
|
9646
|
-
|
|
10151
|
+
emitProgress({
|
|
10152
|
+
phase: "booting subagent",
|
|
10153
|
+
phaseState: "starting",
|
|
10154
|
+
});
|
|
10155
|
+
if (input.stopSignal?.aborted) {
|
|
10156
|
+
return {
|
|
10157
|
+
taskId: input.task.id,
|
|
10158
|
+
status: "blocked",
|
|
10159
|
+
error: "Swarm run interrupted.",
|
|
10160
|
+
failureCause: "interrupted",
|
|
10161
|
+
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
10162
|
+
};
|
|
10163
|
+
}
|
|
10164
|
+
const promptPromise = swarmSession.prompt(prompt, {
|
|
9647
10165
|
expandPromptTemplates: false,
|
|
9648
10166
|
skipIosmAutopilot: true,
|
|
9649
10167
|
skipOrchestrationDirective: true,
|
|
9650
10168
|
source: "interactive",
|
|
9651
10169
|
});
|
|
10170
|
+
// Guard against provider/model hangs in isolated swarm sessions.
|
|
10171
|
+
void promptPromise.catch(() => {
|
|
10172
|
+
// handled by race below
|
|
10173
|
+
});
|
|
10174
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
10175
|
+
timeoutHandle = setTimeout(() => {
|
|
10176
|
+
timedOut = true;
|
|
10177
|
+
void swarmSession.abort().catch(() => {
|
|
10178
|
+
// best effort
|
|
10179
|
+
});
|
|
10180
|
+
emitProgress({
|
|
10181
|
+
phase: "dispatch timeout",
|
|
10182
|
+
phaseState: "responding",
|
|
10183
|
+
});
|
|
10184
|
+
reject(new Error(`Swarm task dispatch timed out after ${dispatchTimeoutMs}ms.`));
|
|
10185
|
+
}, dispatchTimeoutMs);
|
|
10186
|
+
});
|
|
10187
|
+
const stopPromise = new Promise((_, reject) => {
|
|
10188
|
+
if (!input.stopSignal)
|
|
10189
|
+
return;
|
|
10190
|
+
const onAbort = () => {
|
|
10191
|
+
void swarmSession.abort().catch(() => {
|
|
10192
|
+
// best effort
|
|
10193
|
+
});
|
|
10194
|
+
emitProgress({
|
|
10195
|
+
phase: "dispatch interrupted",
|
|
10196
|
+
phaseState: "responding",
|
|
10197
|
+
});
|
|
10198
|
+
reject(new Error("Swarm task dispatch interrupted."));
|
|
10199
|
+
};
|
|
10200
|
+
if (input.stopSignal.aborted) {
|
|
10201
|
+
onAbort();
|
|
10202
|
+
return;
|
|
10203
|
+
}
|
|
10204
|
+
input.stopSignal.addEventListener("abort", onAbort, { once: true });
|
|
10205
|
+
detachStopListener = () => input.stopSignal?.removeEventListener("abort", onAbort);
|
|
10206
|
+
});
|
|
10207
|
+
await Promise.race([promptPromise, timeoutPromise, stopPromise]);
|
|
9652
10208
|
}
|
|
9653
10209
|
catch (error) {
|
|
9654
10210
|
return {
|
|
9655
10211
|
taskId: input.task.id,
|
|
9656
10212
|
status: "error",
|
|
9657
10213
|
error: error instanceof Error ? error.message : String(error),
|
|
10214
|
+
failureCause: error instanceof Error && /interrupted/i.test(error.message)
|
|
10215
|
+
? "interrupted"
|
|
10216
|
+
: timedOut
|
|
10217
|
+
? "timeout"
|
|
10218
|
+
: undefined,
|
|
9658
10219
|
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
9659
10220
|
};
|
|
9660
10221
|
}
|
|
9661
10222
|
finally {
|
|
10223
|
+
detachStopListener?.();
|
|
10224
|
+
if (timeoutHandle) {
|
|
10225
|
+
clearTimeout(timeoutHandle);
|
|
10226
|
+
}
|
|
9662
10227
|
unsubscribe();
|
|
9663
|
-
if (swarmSession.isStreaming) {
|
|
10228
|
+
if (timedOut || swarmSession.isStreaming) {
|
|
9664
10229
|
await swarmSession.abort().catch(() => {
|
|
9665
10230
|
// best effort
|
|
9666
10231
|
});
|
|
@@ -9682,6 +10247,7 @@ export class InteractiveMode {
|
|
|
9682
10247
|
taskId: input.task.id,
|
|
9683
10248
|
status: "error",
|
|
9684
10249
|
error: "No task tool call executed by assistant.",
|
|
10250
|
+
failureCause: "protocol_violation",
|
|
9685
10251
|
costUsd: this.estimateSwarmTaskCostUsd(input.task),
|
|
9686
10252
|
};
|
|
9687
10253
|
}
|
|
@@ -9723,6 +10289,7 @@ export class InteractiveMode {
|
|
|
9723
10289
|
if (!input.resumeState) {
|
|
9724
10290
|
store.init(input.meta, input.plan, initialState);
|
|
9725
10291
|
}
|
|
10292
|
+
const swarmTaskById = new Map(input.plan.tasks.map((task) => [task.id, task]));
|
|
9726
10293
|
let rollingIndex = input.projectIndex;
|
|
9727
10294
|
let localStopRequested = false;
|
|
9728
10295
|
const refreshIncrementalIndex = () => {
|
|
@@ -9740,57 +10307,243 @@ export class InteractiveMode {
|
|
|
9740
10307
|
rollingIndex = rebuilt;
|
|
9741
10308
|
};
|
|
9742
10309
|
this.swarmActiveRunId = input.runId;
|
|
10310
|
+
this.swarmStopRequested = false;
|
|
10311
|
+
this.swarmAbortController = new AbortController();
|
|
10312
|
+
this.footerDataProvider.setSwarmBusy(true);
|
|
10313
|
+
this.footer.invalidate();
|
|
9743
10314
|
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,
|
|
10315
|
+
let schedulerResult;
|
|
10316
|
+
try {
|
|
10317
|
+
schedulerResult = await runSwarmScheduler({
|
|
10318
|
+
runId: input.runId,
|
|
10319
|
+
plan: input.plan,
|
|
9755
10320
|
contract: input.contract,
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
10321
|
+
maxParallel: input.meta.maxParallel,
|
|
10322
|
+
budgetUsd: input.budgetUsd,
|
|
10323
|
+
existingState: initialState,
|
|
10324
|
+
dispatchTask: async ({ task, runtime }) => this.dispatchSwarmTaskWithAgent({
|
|
10325
|
+
meta: input.meta,
|
|
10326
|
+
task,
|
|
10327
|
+
runtime,
|
|
10328
|
+
contract: input.contract,
|
|
10329
|
+
stopSignal: this.swarmAbortController?.signal,
|
|
10330
|
+
onProgress: (progress) => {
|
|
10331
|
+
this.updateSwarmSubagentProgress({
|
|
10332
|
+
runId: input.runId,
|
|
10333
|
+
taskId: task.id,
|
|
10334
|
+
task,
|
|
10335
|
+
profile: this.resolveSwarmTaskProfile(task),
|
|
10336
|
+
progress,
|
|
10337
|
+
});
|
|
10338
|
+
},
|
|
10339
|
+
}),
|
|
10340
|
+
confirmSpawn: async ({ candidate, parentTask }) => {
|
|
10341
|
+
const requiresConfirmation = candidate.severity === "high" || parentTask.spawn_policy === "manual_high_risk";
|
|
10342
|
+
if (!requiresConfirmation)
|
|
10343
|
+
return true;
|
|
10344
|
+
const choice = await this.showExtensionSelector([
|
|
10345
|
+
`/swarm spawn candidate requires confirmation`,
|
|
10346
|
+
`severity=${candidate.severity} task=${parentTask.id}`,
|
|
10347
|
+
`description=${candidate.description}`,
|
|
10348
|
+
`path=${candidate.path}`,
|
|
10349
|
+
].join("\n"), ["Approve spawn", "Reject spawn (Recommended)", "Abort run"]);
|
|
10350
|
+
if (!choice || choice.startsWith("Reject")) {
|
|
10351
|
+
this.showStatus(`Swarm spawn rejected: ${candidate.description}`);
|
|
10352
|
+
return false;
|
|
10353
|
+
}
|
|
10354
|
+
if (choice === "Abort run") {
|
|
10355
|
+
localStopRequested = true;
|
|
10356
|
+
this.showWarning("Swarm run marked to stop after current scheduling step.");
|
|
10357
|
+
return false;
|
|
10358
|
+
}
|
|
9760
10359
|
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
|
-
|
|
10360
|
+
},
|
|
10361
|
+
dispatchTimeoutMs: Math.min(1_800_000, this.resolveSwarmDispatchTimeoutMs() + 5_000),
|
|
10362
|
+
onEvent: (event) => {
|
|
10363
|
+
store.appendEvent(event);
|
|
10364
|
+
if (event.type === "task_running" && event.taskId) {
|
|
10365
|
+
const task = swarmTaskById.get(event.taskId);
|
|
10366
|
+
if (task) {
|
|
10367
|
+
this.updateSwarmSubagentProgress({
|
|
10368
|
+
runId: input.runId,
|
|
10369
|
+
taskId: task.id,
|
|
10370
|
+
task,
|
|
10371
|
+
profile: this.resolveSwarmTaskProfile(task),
|
|
10372
|
+
progress: {
|
|
10373
|
+
phase: "starting subagent",
|
|
10374
|
+
phaseState: "starting",
|
|
10375
|
+
},
|
|
10376
|
+
});
|
|
10377
|
+
}
|
|
10378
|
+
this.showStatus(`Swarm ${event.taskId}: running`);
|
|
10379
|
+
}
|
|
10380
|
+
if (event.type === "task_done" && event.taskId) {
|
|
10381
|
+
this.finalizeSwarmSubagentDisplay({
|
|
10382
|
+
runId: input.runId,
|
|
10383
|
+
taskId: event.taskId,
|
|
10384
|
+
status: "done",
|
|
10385
|
+
});
|
|
10386
|
+
this.showStatus(`Swarm ${event.taskId}: done`);
|
|
10387
|
+
}
|
|
10388
|
+
if (event.type === "task_retry" && event.taskId) {
|
|
10389
|
+
const task = swarmTaskById.get(event.taskId);
|
|
10390
|
+
if (task) {
|
|
10391
|
+
this.updateSwarmSubagentProgress({
|
|
10392
|
+
runId: input.runId,
|
|
10393
|
+
taskId: event.taskId,
|
|
10394
|
+
task,
|
|
10395
|
+
profile: this.resolveSwarmTaskProfile(task),
|
|
10396
|
+
progress: {
|
|
10397
|
+
phase: event.message,
|
|
10398
|
+
phaseState: "running",
|
|
10399
|
+
},
|
|
10400
|
+
});
|
|
10401
|
+
}
|
|
10402
|
+
this.showWarning(`Swarm ${event.taskId}: ${event.message}`);
|
|
10403
|
+
}
|
|
10404
|
+
if (event.type === "task_error" && event.taskId) {
|
|
10405
|
+
this.finalizeSwarmSubagentDisplay({
|
|
10406
|
+
runId: input.runId,
|
|
10407
|
+
taskId: event.taskId,
|
|
10408
|
+
status: "error",
|
|
10409
|
+
errorMessage: event.message,
|
|
10410
|
+
});
|
|
10411
|
+
this.showWarning(`Swarm ${event.taskId} failed: ${event.message}`);
|
|
10412
|
+
}
|
|
10413
|
+
if (event.type === "task_blocked" && event.taskId) {
|
|
10414
|
+
this.finalizeSwarmSubagentDisplay({
|
|
10415
|
+
runId: input.runId,
|
|
10416
|
+
taskId: event.taskId,
|
|
10417
|
+
status: "error",
|
|
10418
|
+
errorMessage: event.message,
|
|
10419
|
+
});
|
|
10420
|
+
this.showWarning(`Swarm ${event.taskId} blocked: ${event.message}`);
|
|
10421
|
+
}
|
|
10422
|
+
if ((event.type === "run_blocked" || event.type === "run_failed" || event.type === "run_stopped") && event.message) {
|
|
10423
|
+
this.showWarning(`Swarm ${event.type.replace("run_", "")}: ${event.message}`);
|
|
10424
|
+
}
|
|
10425
|
+
},
|
|
10426
|
+
onStateChanged: (state) => {
|
|
10427
|
+
store.saveState(state);
|
|
10428
|
+
store.saveCheckpoint(state);
|
|
10429
|
+
refreshIncrementalIndex();
|
|
10430
|
+
},
|
|
10431
|
+
shouldStop: () => this.shutdownRequested || localStopRequested || this.swarmStopRequested,
|
|
10432
|
+
});
|
|
10433
|
+
}
|
|
10434
|
+
catch (error) {
|
|
10435
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10436
|
+
const failedState = store.loadState() ?? initialState;
|
|
10437
|
+
failedState.status = "failed";
|
|
10438
|
+
failedState.lastError = message;
|
|
10439
|
+
failedState.updatedAt = new Date().toISOString();
|
|
10440
|
+
store.appendEvent({
|
|
10441
|
+
type: "run_failed",
|
|
10442
|
+
timestamp: new Date().toISOString(),
|
|
10443
|
+
runId: input.runId,
|
|
10444
|
+
tick: failedState.tick,
|
|
10445
|
+
message,
|
|
10446
|
+
});
|
|
10447
|
+
store.saveState(failedState);
|
|
10448
|
+
store.saveCheckpoint(failedState);
|
|
10449
|
+
this.finalizeSwarmRunSubagentDisplays(input.runId, message);
|
|
10450
|
+
this.showWarning(`Swarm run failed unexpectedly: ${message}`);
|
|
10451
|
+
return;
|
|
10452
|
+
}
|
|
10453
|
+
finally {
|
|
10454
|
+
this.swarmActiveRunId = undefined;
|
|
10455
|
+
this.swarmStopRequested = false;
|
|
10456
|
+
this.swarmAbortController = undefined;
|
|
10457
|
+
this.footerDataProvider.setSwarmBusy(false);
|
|
10458
|
+
this.footer.invalidate();
|
|
10459
|
+
}
|
|
10460
|
+
if (schedulerResult.state.status !== "completed") {
|
|
10461
|
+
this.finalizeSwarmRunSubagentDisplays(input.runId, schedulerResult.state.lastError ?? `Swarm run ${schedulerResult.state.status}`);
|
|
10462
|
+
}
|
|
9789
10463
|
const taskStates = Object.values(schedulerResult.state.tasks);
|
|
9790
10464
|
const doneCount = taskStates.filter((task) => task.status === "done").length;
|
|
9791
10465
|
const errorCount = taskStates.filter((task) => task.status === "error").length;
|
|
9792
10466
|
const blockedCount = taskStates.filter((task) => task.status === "blocked").length;
|
|
9793
10467
|
const total = taskStates.length;
|
|
10468
|
+
const summaryByTask = new Map();
|
|
10469
|
+
let summaryKeysMatched = 0;
|
|
10470
|
+
let findingKeysMatched = 0;
|
|
10471
|
+
try {
|
|
10472
|
+
const summaryRead = await readSharedMemory({
|
|
10473
|
+
rootCwd: cwd,
|
|
10474
|
+
runId: input.runId,
|
|
10475
|
+
}, {
|
|
10476
|
+
scope: "run",
|
|
10477
|
+
prefix: "results/",
|
|
10478
|
+
includeValues: true,
|
|
10479
|
+
limit: Math.max(50, total * 6),
|
|
10480
|
+
});
|
|
10481
|
+
summaryKeysMatched = summaryRead.totalMatched;
|
|
10482
|
+
for (const item of summaryRead.items) {
|
|
10483
|
+
const key = item.key.trim();
|
|
10484
|
+
if (!key.startsWith("results/"))
|
|
10485
|
+
continue;
|
|
10486
|
+
const taskId = key.slice("results/".length).trim();
|
|
10487
|
+
if (!taskId)
|
|
10488
|
+
continue;
|
|
10489
|
+
let parsedSummary;
|
|
10490
|
+
if (item.value) {
|
|
10491
|
+
try {
|
|
10492
|
+
const parsed = JSON.parse(item.value);
|
|
10493
|
+
parsedSummary = {
|
|
10494
|
+
delegatedTotal: parsed.delegated?.total,
|
|
10495
|
+
delegatedSucceeded: parsed.delegated?.succeeded,
|
|
10496
|
+
delegatedFailed: parsed.delegated?.failed,
|
|
10497
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : undefined,
|
|
10498
|
+
};
|
|
10499
|
+
}
|
|
10500
|
+
catch {
|
|
10501
|
+
// keep best-effort behavior for malformed summary payloads
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
summaryByTask.set(taskId, parsedSummary ?? {});
|
|
10505
|
+
}
|
|
10506
|
+
const findingsRead = await readSharedMemory({
|
|
10507
|
+
rootCwd: cwd,
|
|
10508
|
+
runId: input.runId,
|
|
10509
|
+
}, {
|
|
10510
|
+
scope: "run",
|
|
10511
|
+
prefix: "findings/",
|
|
10512
|
+
includeValues: false,
|
|
10513
|
+
limit: Math.max(100, total * 12),
|
|
10514
|
+
});
|
|
10515
|
+
findingKeysMatched = findingsRead.totalMatched;
|
|
10516
|
+
}
|
|
10517
|
+
catch {
|
|
10518
|
+
// shared-memory aggregation is advisory, never fail swarm completion on it
|
|
10519
|
+
}
|
|
10520
|
+
const missingSummaryTasks = input.plan.tasks
|
|
10521
|
+
.map((task) => task.id)
|
|
10522
|
+
.filter((taskId) => !summaryByTask.has(taskId));
|
|
10523
|
+
const sharedMemoryLines = [
|
|
10524
|
+
"## Shared Memory Coordination",
|
|
10525
|
+
`- result_keys: ${summaryKeysMatched}`,
|
|
10526
|
+
`- finding_keys: ${findingKeysMatched}`,
|
|
10527
|
+
`- task_summaries_found: ${summaryByTask.size}/${total}`,
|
|
10528
|
+
missingSummaryTasks.length > 0
|
|
10529
|
+
? `- missing_task_summaries: ${missingSummaryTasks.slice(0, 12).join(", ")}`
|
|
10530
|
+
: "- missing_task_summaries: none",
|
|
10531
|
+
...input.plan.tasks.slice(0, 12).map((task) => {
|
|
10532
|
+
const summary = summaryByTask.get(task.id);
|
|
10533
|
+
if (!summary)
|
|
10534
|
+
return `- ${task.id}: no summary key`;
|
|
10535
|
+
const delegatedPart = typeof summary.delegatedTotal === "number" &&
|
|
10536
|
+
typeof summary.delegatedSucceeded === "number" &&
|
|
10537
|
+
typeof summary.delegatedFailed === "number"
|
|
10538
|
+
? `delegated=${summary.delegatedSucceeded}/${summary.delegatedTotal} (failed=${summary.delegatedFailed})`
|
|
10539
|
+
: "delegated=n/a";
|
|
10540
|
+
const summaryExcerpt = typeof summary.summary === "string" && summary.summary.trim().length > 0
|
|
10541
|
+
? summary.summary.trim().replace(/\s+/g, " ").slice(0, 120)
|
|
10542
|
+
: "no summary excerpt";
|
|
10543
|
+
return `- ${task.id}: ${delegatedPart}; ${summaryExcerpt}`;
|
|
10544
|
+
}),
|
|
10545
|
+
"",
|
|
10546
|
+
];
|
|
9794
10547
|
const reportLines = [
|
|
9795
10548
|
"# Swarm Integration Report",
|
|
9796
10549
|
"",
|
|
@@ -9812,6 +10565,7 @@ export class InteractiveMode {
|
|
|
9812
10565
|
...schedulerResult.runGate.failures.map((item) => `- fail: ${item}`),
|
|
9813
10566
|
...schedulerResult.runGate.warnings.map((item) => `- warn: ${item}`),
|
|
9814
10567
|
"",
|
|
10568
|
+
...sharedMemoryLines,
|
|
9815
10569
|
"## Spawn Backlog",
|
|
9816
10570
|
...(schedulerResult.spawnBacklog.length > 0
|
|
9817
10571
|
? schedulerResult.spawnBacklog.map((item) => `- ${item.description} | ${item.path} | ${item.changeType} | fp=${item.fingerprint}`)
|
|
@@ -9828,6 +10582,7 @@ export class InteractiveMode {
|
|
|
9828
10582
|
"# Shared Context",
|
|
9829
10583
|
"",
|
|
9830
10584
|
`Run ${input.runId} finished with status: ${schedulerResult.state.status}.`,
|
|
10585
|
+
`Shared memory summaries: ${summaryByTask.size}/${total} task result key(s), findings=${findingKeysMatched}.`,
|
|
9831
10586
|
`Recommendation: ${schedulerResult.runGate.pass ? "proceed to /iosm for measurable optimization" : "resolve failed gates before /iosm"}.`,
|
|
9832
10587
|
].join("\n"),
|
|
9833
10588
|
});
|
|
@@ -9836,11 +10591,14 @@ export class InteractiveMode {
|
|
|
9836
10591
|
`status: ${schedulerResult.state.status}`,
|
|
9837
10592
|
`tasks: ${doneCount}/${total} done · ${errorCount} error · ${blockedCount} blocked`,
|
|
9838
10593
|
`budget_usd: ${schedulerResult.state.budget.spentUsd.toFixed(2)}${input.meta.budgetUsd ? `/${input.meta.budgetUsd.toFixed(2)}` : ""}`,
|
|
10594
|
+
`shared_memory: summaries ${summaryByTask.size}/${total} · findings ${findingKeysMatched}`,
|
|
9839
10595
|
`watch: /swarm watch ${input.runId}`,
|
|
9840
10596
|
`resume: /swarm resume ${input.runId}`,
|
|
9841
10597
|
].join("\n"));
|
|
9842
10598
|
}
|
|
9843
10599
|
async runSwarmFromTask(task, options) {
|
|
10600
|
+
if (!this.ensureSwarmModelReady("plain"))
|
|
10601
|
+
return;
|
|
9844
10602
|
const contract = await this.ensureSwarmEffectiveContract(task);
|
|
9845
10603
|
if (!contract)
|
|
9846
10604
|
return;
|
|
@@ -9855,7 +10613,11 @@ export class InteractiveMode {
|
|
|
9855
10613
|
index: indexInfo.index,
|
|
9856
10614
|
});
|
|
9857
10615
|
const runId = `swarm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9858
|
-
const maxParallel =
|
|
10616
|
+
const maxParallel = this.resolveSwarmMaxParallel({
|
|
10617
|
+
requested: options.maxParallel,
|
|
10618
|
+
plan,
|
|
10619
|
+
source: "plain",
|
|
10620
|
+
});
|
|
9859
10621
|
const meta = this.buildSwarmRunMeta({
|
|
9860
10622
|
runId,
|
|
9861
10623
|
source: "plain",
|
|
@@ -9877,6 +10639,8 @@ export class InteractiveMode {
|
|
|
9877
10639
|
});
|
|
9878
10640
|
}
|
|
9879
10641
|
async runSwarmFromSingular(input) {
|
|
10642
|
+
if (!this.ensureSwarmModelReady("singular"))
|
|
10643
|
+
return;
|
|
9880
10644
|
const analysis = this.loadSingularAnalysisByRunId(input.runId);
|
|
9881
10645
|
if (!analysis) {
|
|
9882
10646
|
this.showWarning(`Singular run not found: ${input.runId}`);
|
|
@@ -9899,7 +10663,11 @@ export class InteractiveMode {
|
|
|
9899
10663
|
index: indexInfo.index,
|
|
9900
10664
|
});
|
|
9901
10665
|
const runId = `swarm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9902
|
-
const maxParallel =
|
|
10666
|
+
const maxParallel = this.resolveSwarmMaxParallel({
|
|
10667
|
+
requested: input.maxParallel,
|
|
10668
|
+
plan,
|
|
10669
|
+
source: "singular",
|
|
10670
|
+
});
|
|
9903
10671
|
const meta = this.buildSwarmRunMeta({
|
|
9904
10672
|
runId,
|
|
9905
10673
|
source: "singular",
|
|
@@ -10087,6 +10855,10 @@ export class InteractiveMode {
|
|
|
10087
10855
|
});
|
|
10088
10856
|
}
|
|
10089
10857
|
async handleSwarmCommand(text) {
|
|
10858
|
+
if (this.swarmActiveRunId) {
|
|
10859
|
+
this.showWarning(`Swarm run already in progress: ${this.swarmActiveRunId}. Use /swarm watch.`);
|
|
10860
|
+
return;
|
|
10861
|
+
}
|
|
10090
10862
|
if (this.session.isStreaming) {
|
|
10091
10863
|
this.showWarning("Cannot run /swarm while the agent is processing another request.");
|
|
10092
10864
|
return;
|
|
@@ -10300,6 +11072,9 @@ export class InteractiveMode {
|
|
|
10300
11072
|
this.showWarning("--max-parallel is only valid with --parallel mode.");
|
|
10301
11073
|
return undefined;
|
|
10302
11074
|
}
|
|
11075
|
+
if (mode === "parallel" && maxParallel === undefined) {
|
|
11076
|
+
maxParallel = Math.max(1, Math.min(MAX_ORCHESTRATION_PARALLEL, agents));
|
|
11077
|
+
}
|
|
10303
11078
|
if (maxParallel !== undefined && maxParallel > agents) {
|
|
10304
11079
|
maxParallel = agents;
|
|
10305
11080
|
}
|
|
@@ -10366,11 +11141,24 @@ export class InteractiveMode {
|
|
|
10366
11141
|
const currentCwd = this.sessionManager.getCwd();
|
|
10367
11142
|
const assignments = [];
|
|
10368
11143
|
const assignmentRecords = [];
|
|
11144
|
+
const dependencyEdges = parsed.dependencies?.reduce((sum, entry) => sum + entry.dependsOn.length, 0) ?? 0;
|
|
11145
|
+
const defaultAssignmentProfile = this.resolveOrchestrateDefaultAssignmentProfile(parsed);
|
|
11146
|
+
const delegateParallelHints = [];
|
|
10369
11147
|
for (let index = 0; index < parsed.agents; index++) {
|
|
10370
|
-
const assignmentProfile = parsed.profiles?.[index] ?? parsed.profile ??
|
|
11148
|
+
const assignmentProfile = parsed.profiles?.[index] ?? parsed.profile ?? defaultAssignmentProfile;
|
|
10371
11149
|
const assignmentCwd = parsed.cwds?.[index] ?? ".";
|
|
10372
11150
|
const assignmentLock = parsed.locks?.[index];
|
|
10373
11151
|
const dependsOn = parsed.dependencies?.find((entry) => entry.agent === index + 1)?.dependsOn ?? [];
|
|
11152
|
+
const delegateParallelHint = this.deriveOrchestrateDelegateParallelHint({
|
|
11153
|
+
task: parsed.task,
|
|
11154
|
+
mode: parsed.mode,
|
|
11155
|
+
agents: parsed.agents,
|
|
11156
|
+
maxParallel: parsed.maxParallel,
|
|
11157
|
+
dependencyEdges,
|
|
11158
|
+
hasLock: !!assignmentLock,
|
|
11159
|
+
hasDependencies: dependsOn.length > 0,
|
|
11160
|
+
});
|
|
11161
|
+
delegateParallelHints.push(delegateParallelHint);
|
|
10374
11162
|
const resolvedCwd = path.resolve(currentCwd, assignmentCwd);
|
|
10375
11163
|
assignmentRecords.push({
|
|
10376
11164
|
profile: assignmentProfile,
|
|
@@ -10378,7 +11166,8 @@ export class InteractiveMode {
|
|
|
10378
11166
|
lockKey: assignmentLock,
|
|
10379
11167
|
dependsOn,
|
|
10380
11168
|
});
|
|
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("|")}` : ""}
|
|
11169
|
+
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}
|
|
11170
|
+
}`);
|
|
10382
11171
|
}
|
|
10383
11172
|
const teamRun = createTeamRun({
|
|
10384
11173
|
cwd: currentCwd,
|
|
@@ -10391,8 +11180,16 @@ export class InteractiveMode {
|
|
|
10391
11180
|
const runAssignments = teamRun.tasks.map((task, index) => `${assignments[index]} run_id=${teamRun.runId} task_id=${task.id}`);
|
|
10392
11181
|
const taskCallHints = teamRun.tasks.map((task, index) => {
|
|
10393
11182
|
const assignment = assignmentRecords[index];
|
|
10394
|
-
|
|
11183
|
+
const hint = delegateParallelHints[index] ?? 1;
|
|
11184
|
+
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
11185
|
});
|
|
11186
|
+
if (parsed.mode === "parallel" &&
|
|
11187
|
+
!parsed.profile &&
|
|
11188
|
+
!(parsed.profiles && parsed.profiles.length > 0) &&
|
|
11189
|
+
defaultAssignmentProfile === "meta" &&
|
|
11190
|
+
this.activeProfileName !== "meta") {
|
|
11191
|
+
this.showStatus("Orchestrate auto-profile: using `meta` workers for stronger fan-out and nested delegation.");
|
|
11192
|
+
}
|
|
10396
11193
|
this.chatContainer.addChild(new Spacer(1));
|
|
10397
11194
|
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "◆ ")) + theme.bold("/orchestrate") + theme.fg("muted", ` ${currentCwd}`), 1, 0));
|
|
10398
11195
|
this.ui.requestRender();
|
|
@@ -10407,9 +11204,15 @@ export class InteractiveMode {
|
|
|
10407
11204
|
"- for parallel mode, emit all independent task calls in one assistant response",
|
|
10408
11205
|
"- in parallel mode, use parallel tool-call style (<use_parallel_tool_calls>)",
|
|
10409
11206
|
"- when assignment lines include depends_on, still emit one task call per assignment; runtime enforces dependency gating",
|
|
11207
|
+
"- include delegate_parallel_hint from each assignment/task_call hint in every corresponding task tool call",
|
|
11208
|
+
"- for delegate_parallel_hint >= 2, split child work into nested <delegate_task> streams unless impossible",
|
|
11209
|
+
"- if nested split is impossible for a non-trivial stream, emit one line: DELEGATION_IMPOSSIBLE: <reason>",
|
|
10410
11210
|
"- keep required orchestration task calls in foreground; do not set background=true unless user explicitly requested detached async runs",
|
|
10411
11211
|
"- do not poll .iosm/subagents/background via bash/read during orchestration; wait for task results and then synthesize",
|
|
10412
11212
|
"- include run_id and task_id from each assignment in the task tool arguments",
|
|
11213
|
+
"- publish one run-scoped shared-memory summary key per assignment (results/<task_id>) before final synthesis",
|
|
11214
|
+
"- in final synthesis, only report metrics backed by observed run evidence (task details, shared-memory keys, test output, or verified files); otherwise mark them unknown",
|
|
11215
|
+
"- never claim report/artifact files exist unless created in this run or verified on disk",
|
|
10413
11216
|
"- keep each agent in its assigned cwd",
|
|
10414
11217
|
"- avoid edit collisions; if two write-capable agents target same area, serialize those writes",
|
|
10415
11218
|
"- aggregate outputs into one concise final synthesis",
|
|
@@ -12394,6 +13197,10 @@ The agent will automatically receive IOSM context on every turn.`;
|
|
|
12394
13197
|
}
|
|
12395
13198
|
}
|
|
12396
13199
|
async handleBashCommand(command, excludeFromContext = false) {
|
|
13200
|
+
if (isReadOnlyProfileName(this.activeProfileName)) {
|
|
13201
|
+
this.showWarning(`Bash is disabled in ${this.activeProfileName} profile. Switch to full/meta/iosm (Shift+Tab).`);
|
|
13202
|
+
return;
|
|
13203
|
+
}
|
|
12397
13204
|
const extensionRunner = this.session.extensionRunner;
|
|
12398
13205
|
// Emit user_bash event to let extensions intercept
|
|
12399
13206
|
const eventResult = extensionRunner
|