open-multi-agent-kit 0.95.1 → 0.96.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +16 -1
- package/dist/approvals/approval-receipt-store.d.ts +18 -0
- package/dist/approvals/approval-receipt-store.d.ts.map +1 -0
- package/dist/approvals/approval-receipt-store.js +194 -0
- package/dist/approvals/approval-receipt-store.js.map +1 -0
- package/dist/approvals/approval-receipt.d.ts +43 -0
- package/dist/approvals/approval-receipt.d.ts.map +1 -0
- package/dist/approvals/approval-receipt.js +208 -0
- package/dist/approvals/approval-receipt.js.map +1 -0
- package/dist/approvals/approval-verifier.d.ts +17 -0
- package/dist/approvals/approval-verifier.d.ts.map +1 -0
- package/dist/approvals/approval-verifier.js +23 -0
- package/dist/approvals/approval-verifier.js.map +1 -0
- package/dist/approvals/plannotator-approval-bridge.d.ts +58 -0
- package/dist/approvals/plannotator-approval-bridge.d.ts.map +1 -0
- package/dist/approvals/plannotator-approval-bridge.js +169 -0
- package/dist/approvals/plannotator-approval-bridge.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/package-doctor-cli.d.ts +13 -0
- package/dist/commands/package-doctor-cli.d.ts.map +1 -0
- package/dist/commands/package-doctor-cli.js +80 -0
- package/dist/commands/package-doctor-cli.js.map +1 -0
- package/dist/commands/router-feedback-cli.d.ts +11 -0
- package/dist/commands/router-feedback-cli.d.ts.map +1 -0
- package/dist/commands/router-feedback-cli.js +99 -0
- package/dist/commands/router-feedback-cli.js.map +1 -0
- package/dist/commands/stats-cli.d.ts +20 -0
- package/dist/commands/stats-cli.d.ts.map +1 -0
- package/dist/commands/stats-cli.js +99 -0
- package/dist/commands/stats-cli.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +4 -1
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +99 -31
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +624 -216
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +8 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts +9 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +17 -2
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/context-budget-governor-v2.d.ts +1 -1
- package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
- package/dist/core/context-budget-governor-v2.js +1 -1
- package/dist/core/context-budget-governor-v2.js.map +1 -1
- package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt.js +18 -2
- package/dist/core/context-budget-system-prompt.js.map +1 -1
- package/dist/core/context-budget-v2-cache-disk.d.ts +109 -0
- package/dist/core/context-budget-v2-cache-disk.d.ts.map +1 -0
- package/dist/core/context-budget-v2-cache-disk.js +343 -0
- package/dist/core/context-budget-v2-cache-disk.js.map +1 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts +22 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.js +4 -4
- package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-cache.d.ts +1 -0
- package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache.js +1 -0
- package/dist/core/context-budget-v2-cache.js.map +1 -1
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +17 -14
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +1 -0
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +45 -22
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +4 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/mcp/client.d.ts +105 -0
- package/dist/core/mcp/client.d.ts.map +1 -0
- package/dist/core/mcp/client.js +198 -0
- package/dist/core/mcp/client.js.map +1 -0
- package/dist/core/mcp/config.d.ts +23 -0
- package/dist/core/mcp/config.d.ts.map +1 -0
- package/dist/core/mcp/config.js +89 -0
- package/dist/core/mcp/config.js.map +1 -0
- package/dist/core/mcp/index.d.ts +15 -0
- package/dist/core/mcp/index.d.ts.map +1 -0
- package/dist/core/mcp/index.js +15 -0
- package/dist/core/mcp/index.js.map +1 -0
- package/dist/core/mcp/manager.d.ts +84 -0
- package/dist/core/mcp/manager.d.ts.map +1 -0
- package/dist/core/mcp/manager.js +173 -0
- package/dist/core/mcp/manager.js.map +1 -0
- package/dist/core/mcp/protocol.d.ts +59 -0
- package/dist/core/mcp/protocol.d.ts.map +1 -0
- package/dist/core/mcp/protocol.js +103 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/stdio-transport.d.ts +63 -0
- package/dist/core/mcp/stdio-transport.d.ts.map +1 -0
- package/dist/core/mcp/stdio-transport.js +132 -0
- package/dist/core/mcp/stdio-transport.js.map +1 -0
- package/dist/core/mcp/tools.d.ts +47 -0
- package/dist/core/mcp/tools.d.ts.map +1 -0
- package/dist/core/mcp/tools.js +102 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/package-archive.d.ts +3 -0
- package/dist/core/package-archive.d.ts.map +1 -0
- package/dist/core/package-archive.js +161 -0
- package/dist/core/package-archive.js.map +1 -0
- package/dist/core/package-doctor-source-scan.d.ts +13 -0
- package/dist/core/package-doctor-source-scan.d.ts.map +1 -0
- package/dist/core/package-doctor-source-scan.js +124 -0
- package/dist/core/package-doctor-source-scan.js.map +1 -0
- package/dist/core/package-doctor.d.ts +41 -0
- package/dist/core/package-doctor.d.ts.map +1 -0
- package/dist/core/package-doctor.js +145 -0
- package/dist/core/package-doctor.js.map +1 -0
- package/dist/core/package-manager.d.ts +10 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +175 -42
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manifest.d.ts +26 -0
- package/dist/core/package-manifest.d.ts.map +1 -0
- package/dist/core/package-manifest.js +85 -0
- package/dist/core/package-manifest.js.map +1 -0
- package/dist/core/pi-compat.d.ts +5 -0
- package/dist/core/pi-compat.d.ts.map +1 -0
- package/dist/core/pi-compat.js +18 -0
- package/dist/core/pi-compat.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +7 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +11 -0
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/reasoning-router-bias.d.ts +10 -14
- package/dist/core/reasoning-router-bias.d.ts.map +1 -1
- package/dist/core/reasoning-router-bias.js +22 -15
- package/dist/core/reasoning-router-bias.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts +9 -1
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +20 -5
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/redaction.d.ts +2 -5
- package/dist/core/redaction.d.ts.map +1 -1
- package/dist/core/redaction.js +14 -1
- package/dist/core/redaction.js.map +1 -1
- package/dist/core/repository-learning-scope.d.ts +10 -0
- package/dist/core/repository-learning-scope.d.ts.map +1 -0
- package/dist/core/repository-learning-scope.js +42 -0
- package/dist/core/repository-learning-scope.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +16 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/router-feedback-collector.d.ts +2 -1
- package/dist/core/router-feedback-collector.d.ts.map +1 -1
- package/dist/core/router-feedback-collector.js +2 -2
- package/dist/core/router-feedback-collector.js.map +1 -1
- package/dist/core/runtime-provenance.d.ts +54 -0
- package/dist/core/runtime-provenance.d.ts.map +1 -0
- package/dist/core/runtime-provenance.js +115 -0
- package/dist/core/runtime-provenance.js.map +1 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +39 -21
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/default-policy.d.ts +4 -5
- package/dist/core/sandbox/default-policy.d.ts.map +1 -1
- package/dist/core/sandbox/default-policy.js +9 -11
- package/dist/core/sandbox/default-policy.js.map +1 -1
- package/dist/core/session-bash-runtime.d.ts +4 -4
- package/dist/core/session-bash-runtime.d.ts.map +1 -1
- package/dist/core/session-bash-runtime.js +6 -5
- package/dist/core/session-bash-runtime.js.map +1 -1
- package/dist/core/session-compaction-service.d.ts.map +1 -1
- package/dist/core/session-compaction-service.js +7 -7
- package/dist/core/session-compaction-service.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +47 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/subagent-orchestration.d.ts.map +1 -1
- package/dist/core/subagent-orchestration.js +4 -2
- package/dist/core/subagent-orchestration.js.map +1 -1
- package/dist/core/system-prompt.d.ts +9 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +10 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +2 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/diagnostics.d.ts.map +1 -1
- package/dist/core/tools/diagnostics.js +8 -2
- package/dist/core/tools/diagnostics.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +15 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/turn-metrics.d.ts +135 -0
- package/dist/core/turn-metrics.d.ts.map +1 -0
- package/dist/core/turn-metrics.js +261 -0
- package/dist/core/turn-metrics.js.map +1 -0
- package/dist/core/verified-bash-runtime.d.ts.map +1 -1
- package/dist/core/verified-bash-runtime.js +10 -4
- package/dist/core/verified-bash-runtime.js.map +1 -1
- package/dist/guardrails/evidence-protocol.d.ts +10 -0
- package/dist/guardrails/evidence-protocol.d.ts.map +1 -0
- package/dist/guardrails/evidence-protocol.js +34 -0
- package/dist/guardrails/evidence-protocol.js.map +1 -0
- package/dist/guardrails/evidence-system.d.ts +3 -1
- package/dist/guardrails/evidence-system.d.ts.map +1 -1
- package/dist/guardrails/evidence-system.js +22 -14
- package/dist/guardrails/evidence-system.js.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.d.ts +7 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.js +42 -1
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
- package/dist/guardrails/replay-payload-hash.d.ts +6 -0
- package/dist/guardrails/replay-payload-hash.d.ts.map +1 -0
- package/dist/guardrails/replay-payload-hash.js +24 -0
- package/dist/guardrails/replay-payload-hash.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +18 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.js +2 -2
- package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +3 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +40 -12
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.d.ts +14 -2
- package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.js +106 -19
- package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +10 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +104 -17
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +1 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/types/evidence.d.ts +11 -3
- package/dist/types/evidence.d.ts.map +1 -1
- package/dist/types/evidence.js.map +1 -1
- package/docs/book-to-skill.md +79 -0
- package/docs/compaction.md +35 -3
- package/docs/containerization.md +14 -2
- package/docs/docs.json +16 -0
- package/docs/environment-variables.md +1 -1
- package/docs/extensions.md +75 -16
- package/docs/index.md +3 -0
- package/docs/mcp.md +97 -0
- package/docs/metrics.md +87 -0
- package/docs/models.md +12 -0
- package/docs/packages.md +25 -4
- package/docs/provider-resilience.md +37 -56
- package/docs/providers.md +5 -1
- package/docs/run-protocol.md +72 -0
- package/docs/sdk.md +40 -6
- package/docs/sessions.md +6 -0
- package/docs/settings.md +10 -7
- package/docs/skills.md +9 -1
- package/docs/usage.md +14 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/plannotator-approval-bridge.ts +83 -0
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +21 -11
- package/examples/extensions/subagent/adaptive-agent-runtime.ts +9 -3
- package/examples/extensions/subagent/agents/planner.md +0 -2
- package/examples/extensions/subagent/agents/reviewer.md +0 -2
- package/examples/extensions/subagent/agents/scout.md +0 -2
- package/examples/extensions/subagent/agents/worker.md +0 -1
- package/examples/extensions/subagent/agents.test.ts +9 -0
- package/examples/extensions/subagent/agents.ts +11 -4
- package/examples/extensions/subagent/deadline-budget.test.ts +32 -0
- package/examples/extensions/subagent/deadline-budget.ts +25 -1
- package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +10 -0
- package/examples/extensions/subagent/index.ts +234 -55
- package/examples/extensions/subagent/subagent-execution-policy.test.ts +27 -0
- package/examples/extensions/subagent/subagent-extension-smoke.test.ts +167 -2
- package/examples/extensions/subagent/workflow-graph.test.ts +80 -0
- package/examples/extensions/subagent/workflow-graph.ts +89 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +275 -439
- package/package.json +11 -8
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* Spawns a separate `omk` process for each subagent invocation,
|
|
5
5
|
* giving it an isolated context window.
|
|
6
6
|
*
|
|
7
|
-
* Supports
|
|
7
|
+
* Supports four modes:
|
|
8
8
|
* - Single: { agent: "name", task: "..." }
|
|
9
9
|
* - Parallel: { tasks: [{ agent: "name", task: "..." }, ...] }
|
|
10
10
|
* - Chain: { chain: [{ agent: "name", task: "... {previous} ..." }, ...] }
|
|
11
|
+
* - Graph: { graph: [{ id: "research", agent: "scout", task: "...", dependsOn: [] }, ...] }
|
|
11
12
|
*
|
|
12
13
|
* Uses JSON mode to capture structured output from subagents.
|
|
13
14
|
*/
|
|
@@ -33,13 +34,24 @@ import {
|
|
|
33
34
|
parseEmbeddedCapabilities,
|
|
34
35
|
validateCapabilities,
|
|
35
36
|
} from "./capabilities.ts";
|
|
36
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
createExecutionBudget,
|
|
39
|
+
type ExecutionBudget,
|
|
40
|
+
exceedsParallelTaskLimit,
|
|
41
|
+
remainingExecutionMs,
|
|
42
|
+
resolveSubagentExecutionPolicy,
|
|
43
|
+
} from "./deadline-budget.ts";
|
|
37
44
|
import { DeadlineProfileStore } from "./deadline-profile-store.ts";
|
|
38
45
|
import { runManagedProcess } from "./managed-process.ts";
|
|
39
46
|
import { emptyUsage, type SingleResult, type SubagentAttemptResult } from "./subagent-runtime-types.ts";
|
|
47
|
+
import {
|
|
48
|
+
type GraphTask,
|
|
49
|
+
GraphValidationError,
|
|
50
|
+
planTaskGraph,
|
|
51
|
+
renderDependencyContext,
|
|
52
|
+
type TaskGraphPlan,
|
|
53
|
+
} from "./workflow-graph.ts";
|
|
40
54
|
|
|
41
|
-
const MAX_PARALLEL_TASKS = 8;
|
|
42
|
-
const MAX_CONCURRENCY = 4;
|
|
43
55
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
44
56
|
const PER_TASK_OUTPUT_CAP = 50 * 1024;
|
|
45
57
|
const DEFAULT_EXECUTION_BUDGET_MS = 840_000;
|
|
@@ -173,6 +185,7 @@ function formatToolCall(
|
|
|
173
185
|
}
|
|
174
186
|
|
|
175
187
|
interface ExecutionBudgetDetails {
|
|
188
|
+
unbounded: boolean;
|
|
176
189
|
hardDeadlineMs: number;
|
|
177
190
|
elapsedMs: number;
|
|
178
191
|
remainingMs: number;
|
|
@@ -183,18 +196,24 @@ interface ExecutionBudgetDetails {
|
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
interface SubagentDetails {
|
|
186
|
-
mode: "single" | "parallel" | "chain";
|
|
199
|
+
mode: "single" | "parallel" | "chain" | "graph";
|
|
187
200
|
agentScope: AgentScope;
|
|
188
201
|
projectAgentsDir: string | null;
|
|
189
202
|
results: SingleResult[];
|
|
190
203
|
executionBudget: ExecutionBudgetDetails;
|
|
204
|
+
graph?: { waves: readonly (readonly string[])[]; completedNodeIds: readonly string[] };
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
function summarizeExecutionBudget(
|
|
207
|
+
function summarizeExecutionBudget(
|
|
208
|
+
budget: ExecutionBudget,
|
|
209
|
+
results: readonly SingleResult[],
|
|
210
|
+
unbounded: boolean,
|
|
211
|
+
): ExecutionBudgetDetails {
|
|
194
212
|
return {
|
|
195
|
-
|
|
213
|
+
unbounded,
|
|
214
|
+
hardDeadlineMs: unbounded ? 0 : budget.hardDeadlineMs,
|
|
196
215
|
elapsedMs: Date.now() - budget.startedAtMs,
|
|
197
|
-
remainingMs: remainingExecutionMs(budget),
|
|
216
|
+
remainingMs: unbounded ? 0 : remainingExecutionMs(budget),
|
|
198
217
|
estimatedTokens: results.reduce((sum, result) => sum + (result.deadline?.estimatedTokens ?? 0), 0),
|
|
199
218
|
plannedShards: results.reduce((sum, result) => sum + (result.deadline?.plannedShardIds.length ?? 0), 0),
|
|
200
219
|
completedShards: results.reduce((sum, result) => sum + (result.deadline?.completedShardIds.length ?? 0), 0),
|
|
@@ -263,15 +282,16 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
|
|
|
263
282
|
): Promise<TOut[]> {
|
|
264
283
|
if (items.length === 0) return [];
|
|
265
284
|
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
266
|
-
const results: TOut[] =
|
|
285
|
+
const results: TOut[] = [];
|
|
267
286
|
let nextIndex = 0;
|
|
268
|
-
const
|
|
269
|
-
while (
|
|
287
|
+
const runWorker = async (): Promise<void> => {
|
|
288
|
+
while (nextIndex < items.length) {
|
|
270
289
|
const current = nextIndex++;
|
|
271
|
-
if (current >= items.length) return;
|
|
272
290
|
results[current] = await fn(items[current], current);
|
|
273
291
|
}
|
|
274
|
-
}
|
|
292
|
+
};
|
|
293
|
+
const workers: Promise<void>[] = [];
|
|
294
|
+
for (let worker = 0; worker < limit; worker++) workers.push(runWorker());
|
|
275
295
|
await Promise.all(workers);
|
|
276
296
|
return results;
|
|
277
297
|
}
|
|
@@ -515,6 +535,7 @@ async function runSingleAgent(
|
|
|
515
535
|
signal: AbortSignal | undefined,
|
|
516
536
|
onUpdate: OnUpdateCallback | undefined,
|
|
517
537
|
makeDetails: (results: SingleResult[]) => SubagentDetails,
|
|
538
|
+
unbounded: boolean,
|
|
518
539
|
budget: ExecutionBudget,
|
|
519
540
|
profileStore: DeadlineProfileStore,
|
|
520
541
|
pendingSequentialTasks: number,
|
|
@@ -533,6 +554,18 @@ async function runSingleAgent(
|
|
|
533
554
|
step,
|
|
534
555
|
};
|
|
535
556
|
}
|
|
557
|
+
const reportUpdate: ResultUpdateCallback | undefined = onUpdate
|
|
558
|
+
? (partial) =>
|
|
559
|
+
onUpdate({
|
|
560
|
+
content: [{ type: "text", text: getResultOutput(partial) || "(running...)" }],
|
|
561
|
+
details: makeDetails([partial]),
|
|
562
|
+
})
|
|
563
|
+
: undefined;
|
|
564
|
+
if (unbounded) {
|
|
565
|
+
return (
|
|
566
|
+
await runSingleAgentAttempt(defaultCwd, agents, agentName, task, task, cwd, step, 0, signal, reportUpdate)
|
|
567
|
+
).result;
|
|
568
|
+
}
|
|
536
569
|
return await runAdaptiveAgent({
|
|
537
570
|
agentName,
|
|
538
571
|
agentSource: agent.source,
|
|
@@ -548,13 +581,7 @@ async function runSingleAgent(
|
|
|
548
581
|
minimumAttemptMs: MINIMUM_ATTEMPT_MS,
|
|
549
582
|
maxTokensPerShard: MAX_TOKENS_PER_TASK_SHARD,
|
|
550
583
|
},
|
|
551
|
-
onUpdate:
|
|
552
|
-
? (partial) =>
|
|
553
|
-
onUpdate({
|
|
554
|
-
content: [{ type: "text", text: getResultOutput(partial) || "(running...)" }],
|
|
555
|
-
details: makeDetails([partial]),
|
|
556
|
-
})
|
|
557
|
-
: undefined,
|
|
584
|
+
onUpdate: reportUpdate,
|
|
558
585
|
runAttempt: async (input: RunAttemptInput) =>
|
|
559
586
|
await runSingleAgentAttempt(
|
|
560
587
|
defaultCwd,
|
|
@@ -583,6 +610,14 @@ const ChainItem = Type.Object({
|
|
|
583
610
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
|
|
584
611
|
});
|
|
585
612
|
|
|
613
|
+
const GraphItem = Type.Object({
|
|
614
|
+
id: Type.String({ description: "Unique task node id" }),
|
|
615
|
+
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
616
|
+
task: Type.String({ description: "Task; use {dependencies} to inject declared dependency outputs" }),
|
|
617
|
+
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
|
|
618
|
+
dependsOn: Type.Optional(Type.Array(Type.String(), { description: "Task node ids that must complete first" })),
|
|
619
|
+
});
|
|
620
|
+
|
|
586
621
|
const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
|
|
587
622
|
description: 'Which agent directories to use. Default: "user". Use "both" to include project-local agents.',
|
|
588
623
|
default: "user",
|
|
@@ -593,6 +628,7 @@ const SubagentParams = Type.Object({
|
|
|
593
628
|
task: Type.Optional(Type.String({ description: "Task to delegate (for single mode)" })),
|
|
594
629
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
|
|
595
630
|
chain: Type.Optional(Type.Array(ChainItem, { description: "Array of {agent, task} for sequential execution" })),
|
|
631
|
+
graph: Type.Optional(Type.Array(GraphItem, { description: "Validated dependency DAG executed in parallel waves" })),
|
|
596
632
|
agentScope: Type.Optional(AgentScopeSchema),
|
|
597
633
|
confirmProjectAgents: Type.Optional(
|
|
598
634
|
Type.Boolean({ description: "Prompt before running project-local agents. Default: true.", default: true }),
|
|
@@ -600,7 +636,7 @@ const SubagentParams = Type.Object({
|
|
|
600
636
|
executionBudgetMs: Type.Optional(
|
|
601
637
|
Type.Integer({
|
|
602
638
|
description:
|
|
603
|
-
"Internal wall-clock budget. Defaults to 840000ms
|
|
639
|
+
"Internal wall-clock budget. Defaults to 840000ms outside Ultra; supplying it also bounds Ultra execution.",
|
|
604
640
|
minimum: 60_000,
|
|
605
641
|
maximum: MAX_EXECUTION_BUDGET_MS,
|
|
606
642
|
default: DEFAULT_EXECUTION_BUDGET_MS,
|
|
@@ -623,18 +659,27 @@ export default function (omk: ExtensionAPI) {
|
|
|
623
659
|
label: "Subagent",
|
|
624
660
|
description: [
|
|
625
661
|
"Delegate tasks to specialized subagents with isolated context.",
|
|
626
|
-
"Modes: single
|
|
627
|
-
"Long work is predictively sharded by token and wall-clock demand, checkpointed, and resumed only from unfinished work.",
|
|
662
|
+
"Modes: single, parallel, chain, or graph (validated dependency DAG with parallel waves and {dependencies} handoff).",
|
|
663
|
+
"Long work is predictively sharded by token and wall-clock demand, checkpointed, and resumed only from unfinished work; Ultra removes task-count and concurrency caps, plus the execution deadline unless executionBudgetMs is supplied.",
|
|
628
664
|
'Default agent scope is "user" (from ~/.omk/agent/agents).',
|
|
629
665
|
'To enable project-local agents in .omk/agents, set agentScope: "both" (or "project").',
|
|
630
666
|
].join(" "),
|
|
631
667
|
parameters: SubagentParams,
|
|
668
|
+
resolveTimeoutMs: (ctx) => (ctx.thinkingLevel === "ultra" ? 0 : undefined),
|
|
632
669
|
|
|
633
670
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
634
671
|
const agentScope: AgentScope = params.agentScope ?? "user";
|
|
635
672
|
const discovery = discoverAgents(ctx.cwd, agentScope);
|
|
636
|
-
const
|
|
673
|
+
const selectedModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
674
|
+
const agents = discovery.agents.map((agent) =>
|
|
675
|
+
agent.model || !selectedModel ? agent : { ...agent, model: selectedModel },
|
|
676
|
+
);
|
|
637
677
|
const confirmProjectAgents = params.confirmProjectAgents ?? true;
|
|
678
|
+
const executionPolicy = resolveSubagentExecutionPolicy(
|
|
679
|
+
ctx.thinkingLevel,
|
|
680
|
+
params.tasks?.length ?? params.graph?.length ?? 1,
|
|
681
|
+
params.executionBudgetMs !== undefined,
|
|
682
|
+
);
|
|
638
683
|
const executionBudget = createExecutionBudget({
|
|
639
684
|
hardDeadlineMs: params.executionBudgetMs ?? DEFAULT_EXECUTION_BUDGET_MS,
|
|
640
685
|
cleanupReserveMs: EXECUTION_CLEANUP_RESERVE_MS,
|
|
@@ -645,17 +690,20 @@ export default function (omk: ExtensionAPI) {
|
|
|
645
690
|
|
|
646
691
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
647
692
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
693
|
+
const hasGraph = (params.graph?.length ?? 0) > 0;
|
|
648
694
|
const hasSingle = Boolean(params.agent && params.task);
|
|
649
|
-
const modeCount = Number(hasChain) + Number(hasTasks) + Number(hasSingle);
|
|
695
|
+
const modeCount = Number(hasChain) + Number(hasTasks) + Number(hasGraph) + Number(hasSingle);
|
|
650
696
|
|
|
697
|
+
let graphDetails: SubagentDetails["graph"];
|
|
651
698
|
const makeDetails =
|
|
652
|
-
(mode: "
|
|
699
|
+
(mode: SubagentDetails["mode"]) =>
|
|
653
700
|
(results: SingleResult[]): SubagentDetails => ({
|
|
654
701
|
mode,
|
|
655
702
|
agentScope,
|
|
656
703
|
projectAgentsDir: discovery.projectAgentsDir,
|
|
657
704
|
results,
|
|
658
|
-
executionBudget: summarizeExecutionBudget(executionBudget, results),
|
|
705
|
+
executionBudget: summarizeExecutionBudget(executionBudget, results, executionPolicy.unbounded),
|
|
706
|
+
...(mode === "graph" && graphDetails ? { graph: graphDetails } : {}),
|
|
659
707
|
});
|
|
660
708
|
|
|
661
709
|
if (modeCount !== 1) {
|
|
@@ -675,6 +723,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
675
723
|
const requestedAgentNames = new Set<string>();
|
|
676
724
|
if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
|
|
677
725
|
if (params.tasks) for (const t of params.tasks) requestedAgentNames.add(t.agent);
|
|
726
|
+
if (params.graph) for (const node of params.graph) requestedAgentNames.add(node.agent);
|
|
678
727
|
if (params.agent) requestedAgentNames.add(params.agent);
|
|
679
728
|
|
|
680
729
|
const projectAgentsRequested = Array.from(requestedAgentNames)
|
|
@@ -691,7 +740,9 @@ export default function (omk: ExtensionAPI) {
|
|
|
691
740
|
if (!ok)
|
|
692
741
|
return {
|
|
693
742
|
content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
|
|
694
|
-
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")(
|
|
743
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : hasGraph ? "graph" : "single")(
|
|
744
|
+
[],
|
|
745
|
+
),
|
|
695
746
|
};
|
|
696
747
|
}
|
|
697
748
|
}
|
|
@@ -729,6 +780,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
729
780
|
signal,
|
|
730
781
|
chainUpdate,
|
|
731
782
|
makeDetails("chain"),
|
|
783
|
+
executionPolicy.unbounded,
|
|
732
784
|
executionBudget,
|
|
733
785
|
profileStore,
|
|
734
786
|
params.chain.length - i,
|
|
@@ -752,13 +804,104 @@ export default function (omk: ExtensionAPI) {
|
|
|
752
804
|
};
|
|
753
805
|
}
|
|
754
806
|
|
|
807
|
+
if (params.graph && params.graph.length > 0) {
|
|
808
|
+
let plan: TaskGraphPlan;
|
|
809
|
+
try {
|
|
810
|
+
plan = planTaskGraph(params.graph as GraphTask[]);
|
|
811
|
+
} catch (error) {
|
|
812
|
+
const message = error instanceof GraphValidationError ? error.message : "Invalid task graph";
|
|
813
|
+
return {
|
|
814
|
+
content: [{ type: "text", text: message }],
|
|
815
|
+
details: makeDetails("graph")([]),
|
|
816
|
+
isError: true,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
if (exceedsParallelTaskLimit(executionPolicy, params.graph.length)) {
|
|
820
|
+
return {
|
|
821
|
+
content: [
|
|
822
|
+
{
|
|
823
|
+
type: "text",
|
|
824
|
+
text: `Too many graph tasks (${params.graph.length}). Max is ${executionPolicy.maxParallelTasks}.`,
|
|
825
|
+
},
|
|
826
|
+
],
|
|
827
|
+
details: makeDetails("graph")([]),
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const results: SingleResult[] = [];
|
|
832
|
+
const outputs = new Map<string, string>();
|
|
833
|
+
const completedNodeIds: string[] = [];
|
|
834
|
+
graphDetails = { waves: plan.waves, completedNodeIds };
|
|
835
|
+
for (const [waveIndex, wave] of plan.waves.entries()) {
|
|
836
|
+
const waveResults = await mapWithConcurrencyLimit<string, { id: string; result: SingleResult }>(
|
|
837
|
+
[...wave],
|
|
838
|
+
executionPolicy.concurrency,
|
|
839
|
+
async (id) => {
|
|
840
|
+
const task = plan.tasks.get(id);
|
|
841
|
+
if (!task) throw new GraphValidationError(`node '${id}' disappeared after validation`);
|
|
842
|
+
const result = await runSingleAgent(
|
|
843
|
+
ctx.cwd,
|
|
844
|
+
agents,
|
|
845
|
+
task.agent,
|
|
846
|
+
renderDependencyContext(task, outputs),
|
|
847
|
+
task.cwd,
|
|
848
|
+
undefined,
|
|
849
|
+
signal,
|
|
850
|
+
undefined,
|
|
851
|
+
makeDetails("graph"),
|
|
852
|
+
executionPolicy.unbounded,
|
|
853
|
+
executionBudget,
|
|
854
|
+
profileStore,
|
|
855
|
+
plan.waves.length - waveIndex,
|
|
856
|
+
);
|
|
857
|
+
return { id, result };
|
|
858
|
+
},
|
|
859
|
+
);
|
|
860
|
+
for (const { id, result } of waveResults) {
|
|
861
|
+
results.push(result);
|
|
862
|
+
if (!isFailedResult(result)) {
|
|
863
|
+
outputs.set(id, getResultOutput(result));
|
|
864
|
+
completedNodeIds.push(id);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
graphDetails = { waves: plan.waves, completedNodeIds: [...completedNodeIds] };
|
|
868
|
+
const failed = waveResults.filter(({ result }) => isFailedResult(result));
|
|
869
|
+
if (failed.length > 0) {
|
|
870
|
+
const failedIds = failed.map(({ id }) => id).join(", ");
|
|
871
|
+
return {
|
|
872
|
+
content: [
|
|
873
|
+
{
|
|
874
|
+
type: "text",
|
|
875
|
+
text: `Graph stopped after wave ${waveIndex + 1}; failed nodes: ${failedIds}. Downstream nodes were not started.`,
|
|
876
|
+
},
|
|
877
|
+
],
|
|
878
|
+
details: makeDetails("graph")(results),
|
|
879
|
+
isError: true,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const summaries = results.map((result, index) => {
|
|
884
|
+
const id = completedNodeIds[index] ?? `node-${index + 1}`;
|
|
885
|
+
return `### [${id}] completed\n\n${truncateParallelOutput(getResultOutput(result))}`;
|
|
886
|
+
});
|
|
887
|
+
return {
|
|
888
|
+
content: [
|
|
889
|
+
{
|
|
890
|
+
type: "text",
|
|
891
|
+
text: `Graph: ${results.length}/${params.graph.length} completed in ${plan.waves.length} waves\n\n${summaries.join("\n\n---\n\n")}`,
|
|
892
|
+
},
|
|
893
|
+
],
|
|
894
|
+
details: makeDetails("graph")(results),
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
755
898
|
if (params.tasks && params.tasks.length > 0) {
|
|
756
|
-
if (params.tasks.length
|
|
899
|
+
if (exceedsParallelTaskLimit(executionPolicy, params.tasks.length))
|
|
757
900
|
return {
|
|
758
901
|
content: [
|
|
759
902
|
{
|
|
760
903
|
type: "text",
|
|
761
|
-
text: `Too many parallel tasks (${params.tasks.length}). Max is ${
|
|
904
|
+
text: `Too many parallel tasks (${params.tasks.length}). Max is ${executionPolicy.maxParallelTasks}.`,
|
|
762
905
|
},
|
|
763
906
|
],
|
|
764
907
|
details: makeDetails("parallel")([]),
|
|
@@ -793,31 +936,36 @@ export default function (omk: ExtensionAPI) {
|
|
|
793
936
|
}
|
|
794
937
|
};
|
|
795
938
|
|
|
796
|
-
const results = await mapWithConcurrencyLimit(
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
939
|
+
const results = await mapWithConcurrencyLimit(
|
|
940
|
+
params.tasks,
|
|
941
|
+
executionPolicy.concurrency,
|
|
942
|
+
async (t, index) => {
|
|
943
|
+
const result = await runSingleAgent(
|
|
944
|
+
ctx.cwd,
|
|
945
|
+
agents,
|
|
946
|
+
t.agent,
|
|
947
|
+
t.task,
|
|
948
|
+
t.cwd,
|
|
949
|
+
undefined,
|
|
950
|
+
signal,
|
|
951
|
+
// Per-task update callback
|
|
952
|
+
(partial) => {
|
|
953
|
+
if (partial.details?.results[0]) {
|
|
954
|
+
allResults[index] = partial.details.results[0];
|
|
955
|
+
emitParallelUpdate();
|
|
956
|
+
}
|
|
957
|
+
},
|
|
958
|
+
makeDetails("parallel"),
|
|
959
|
+
executionPolicy.unbounded,
|
|
960
|
+
executionBudget,
|
|
961
|
+
profileStore,
|
|
962
|
+
1,
|
|
963
|
+
);
|
|
964
|
+
allResults[index] = result;
|
|
965
|
+
emitParallelUpdate();
|
|
966
|
+
return result;
|
|
967
|
+
},
|
|
968
|
+
);
|
|
821
969
|
|
|
822
970
|
const successCount = results.filter((r) => !isFailedResult(r)).length;
|
|
823
971
|
const summaries = results.map((r) => {
|
|
@@ -849,6 +997,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
849
997
|
signal,
|
|
850
998
|
onUpdate,
|
|
851
999
|
makeDetails("single"),
|
|
1000
|
+
executionPolicy.unbounded,
|
|
852
1001
|
executionBudget,
|
|
853
1002
|
profileStore,
|
|
854
1003
|
1,
|
|
@@ -897,6 +1046,18 @@ export default function (omk: ExtensionAPI) {
|
|
|
897
1046
|
if (args.chain.length > 3) text += `\n ${theme.fg("muted", `... +${args.chain.length - 3} more`)}`;
|
|
898
1047
|
return new Text(text, 0, 0);
|
|
899
1048
|
}
|
|
1049
|
+
if (args.graph && args.graph.length > 0) {
|
|
1050
|
+
let text =
|
|
1051
|
+
theme.fg("toolTitle", theme.bold("subagent ")) +
|
|
1052
|
+
theme.fg("accent", `graph (${args.graph.length} nodes)`) +
|
|
1053
|
+
theme.fg("muted", ` [${scope}]`);
|
|
1054
|
+
for (const node of args.graph.slice(0, 3)) {
|
|
1055
|
+
const dependencies = node.dependsOn?.length ? ` ← ${node.dependsOn.join(",")}` : "";
|
|
1056
|
+
text += `\n ${theme.fg("accent", node.id)}${theme.fg("dim", ` @${node.agent}${dependencies}`)}`;
|
|
1057
|
+
}
|
|
1058
|
+
if (args.graph.length > 3) text += `\n ${theme.fg("muted", `... +${args.graph.length - 3} more`)}`;
|
|
1059
|
+
return new Text(text, 0, 0);
|
|
1060
|
+
}
|
|
900
1061
|
if (args.tasks && args.tasks.length > 0) {
|
|
901
1062
|
let text =
|
|
902
1063
|
theme.fg("toolTitle", theme.bold("subagent ")) +
|
|
@@ -1015,6 +1176,24 @@ export default function (omk: ExtensionAPI) {
|
|
|
1015
1176
|
return total;
|
|
1016
1177
|
};
|
|
1017
1178
|
|
|
1179
|
+
if (details.mode === "graph") {
|
|
1180
|
+
const completed = details.graph?.completedNodeIds.length ?? 0;
|
|
1181
|
+
const total =
|
|
1182
|
+
details.graph?.waves.reduce((count, wave) => count + wave.length, 0) ?? details.results.length;
|
|
1183
|
+
const waves = details.graph?.waves.length ?? 0;
|
|
1184
|
+
const failed = details.results.some(isFailedResult);
|
|
1185
|
+
const icon = failed ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
1186
|
+
let text = `${icon} ${theme.fg("toolTitle", theme.bold("graph "))}${theme.fg("accent", `${completed}/${total} nodes · ${waves} waves`)}`;
|
|
1187
|
+
for (const [index, result] of details.results.entries()) {
|
|
1188
|
+
const id = details.graph?.completedNodeIds[index] ?? result.agent;
|
|
1189
|
+
const resultIcon = isFailedResult(result) ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
1190
|
+
text += `\n${theme.fg("muted", "─── ")}${theme.fg("accent", id)} ${resultIcon}`;
|
|
1191
|
+
if (expanded) text += `\n${theme.fg("toolOutput", getResultOutput(result))}`;
|
|
1192
|
+
}
|
|
1193
|
+
if (!expanded) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
1194
|
+
return new Text(text, 0, 0);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1018
1197
|
if (details.mode === "chain") {
|
|
1019
1198
|
const successCount = details.results.filter((r) => r.exitCode === 0).length;
|
|
1020
1199
|
const icon = successCount === details.results.length ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { exceedsParallelTaskLimit, resolveSubagentExecutionPolicy } from "./deadline-budget.ts";
|
|
3
|
+
|
|
4
|
+
describe("subagent execution policy", () => {
|
|
5
|
+
it("keeps the bounded defaults outside Ultra", () => {
|
|
6
|
+
const policy = resolveSubagentExecutionPolicy("high", 12);
|
|
7
|
+
|
|
8
|
+
expect(policy).toEqual({ unbounded: false, maxParallelTasks: 8, concurrency: 4 });
|
|
9
|
+
expect(exceedsParallelTaskLimit(policy, 9)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("removes task, concurrency, and deadline limits in Ultra", () => {
|
|
13
|
+
const policy = resolveSubagentExecutionPolicy("ultra", 12);
|
|
14
|
+
|
|
15
|
+
expect(policy).toEqual({ unbounded: true, maxParallelTasks: undefined, concurrency: 12 });
|
|
16
|
+
expect(exceedsParallelTaskLimit(policy, 12)).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("keeps the Ultra deadline bounded when an execution budget is explicit", () => {
|
|
20
|
+
// Given an Ultra subagent call with an explicit wall-clock budget
|
|
21
|
+
// When its execution policy is resolved
|
|
22
|
+
const policy = resolveSubagentExecutionPolicy("ultra", 12, true);
|
|
23
|
+
|
|
24
|
+
// Then only the deadline remains bounded; Ultra still removes task and concurrency limits
|
|
25
|
+
expect(policy).toEqual({ unbounded: false, maxParallelTasks: undefined, concurrency: 12 });
|
|
26
|
+
});
|
|
27
|
+
});
|