open-multi-agent-kit 0.90.9 → 0.92.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 +39 -4
- package/README.md +14 -1
- package/dist/core/agent-session.d.ts +27 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +196 -32
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/command-safety.d.ts.map +1 -1
- package/dist/core/command-safety.js +0 -58
- package/dist/core/command-safety.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +7 -3
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/context-budget-headroom-candidates.d.ts.map +1 -1
- package/dist/core/context-budget-headroom-candidates.js +21 -11
- package/dist/core/context-budget-headroom-candidates.js.map +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +7 -8
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +7 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +12 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/loadouts.d.ts.map +1 -1
- package/dist/core/loadouts.js +27 -48
- package/dist/core/loadouts.js.map +1 -1
- package/dist/core/metrics-sampler.d.ts +20 -0
- package/dist/core/metrics-sampler.d.ts.map +1 -1
- package/dist/core/metrics-sampler.js +52 -0
- package/dist/core/metrics-sampler.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +15 -6
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-resilience.d.ts +51 -0
- package/dist/core/provider-resilience.d.ts.map +1 -0
- package/dist/core/provider-resilience.js +86 -0
- package/dist/core/provider-resilience.js.map +1 -0
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +14 -22
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/session-termination.d.ts +8 -2
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +45 -3
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +30 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +17 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +35 -30
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +47 -0
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/omp-pure-seams.d.ts +41 -0
- package/dist/core/tools/omp-pure-seams.d.ts.map +1 -0
- package/dist/core/tools/omp-pure-seams.js +142 -0
- package/dist/core/tools/omp-pure-seams.js.map +1 -0
- package/dist/core/tools/omp-seam-runtime.d.ts +100 -0
- package/dist/core/tools/omp-seam-runtime.d.ts.map +1 -0
- package/dist/core/tools/omp-seam-runtime.js +40 -0
- package/dist/core/tools/omp-seam-runtime.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +34 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +112 -11
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +11 -8
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/theme/omk-aurora-dark.json +80 -0
- package/dist/modes/interactive/theme/omk-aurora-light.json +80 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +5 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +9 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +174 -0
- package/docs/adaptorch-preview.md +2 -2
- package/docs/compaction.md +13 -13
- package/docs/correctness-wall.md +13 -5
- package/docs/custom-provider.md +8 -8
- package/docs/development.md +2 -2
- package/docs/extensions.md +8 -8
- package/docs/json.md +4 -4
- package/docs/provider-resilience.md +87 -0
- package/docs/providers.md +1 -1
- package/docs/session-format.md +5 -5
- package/docs/settings.md +30 -0
- package/docs/skills.md +2 -0
- package/docs/themes.md +4 -2
- package/docs/tui.md +4 -4
- package/docs/usage.md +4 -0
- 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/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +35 -10
- package/examples/extensions/subagent/adaptive-agent-runtime.test.ts +234 -0
- package/examples/extensions/subagent/adaptive-agent-runtime.ts +240 -0
- package/examples/extensions/subagent/adaptive-result.ts +112 -0
- package/examples/extensions/subagent/checkpoint-runtime.test.ts +125 -0
- package/examples/extensions/subagent/checkpoint-runtime.ts +198 -0
- package/examples/extensions/subagent/deadline-budget.test.ts +135 -0
- package/examples/extensions/subagent/deadline-budget.ts +242 -0
- package/examples/extensions/subagent/deadline-profile-store.test.ts +72 -0
- package/examples/extensions/subagent/deadline-profile-store.ts +172 -0
- package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +13 -0
- package/examples/extensions/subagent/fixtures/hanging-process-tree.mjs +40 -0
- package/examples/extensions/subagent/index.ts +253 -137
- package/examples/extensions/subagent/managed-process.test.ts +178 -0
- package/examples/extensions/subagent/managed-process.ts +236 -0
- package/examples/extensions/subagent/subagent-extension-smoke.test.ts +112 -0
- package/examples/extensions/subagent/subagent-runtime-types.ts +76 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +28 -19
- package/package.json +6 -5
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* Uses JSON mode to capture structured output from subagents.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { spawn } from "node:child_process";
|
|
16
15
|
import * as fs from "node:fs";
|
|
17
16
|
import * as os from "node:os";
|
|
18
17
|
import * as path from "node:path";
|
|
@@ -22,6 +21,7 @@ import { StringEnum } from "omk-ai";
|
|
|
22
21
|
import { Container, Markdown, Spacer, Text } from "omk-tui";
|
|
23
22
|
import { type ExtensionAPI, getAgentDir, getMarkdownTheme, withFileMutationQueue } from "open-multi-agent-kit";
|
|
24
23
|
import { Type } from "typebox";
|
|
24
|
+
import { type RunAttemptInput, runAdaptiveAgent } from "./adaptive-agent-runtime.ts";
|
|
25
25
|
import { deriveCapabilities } from "./agent-capability-router.ts";
|
|
26
26
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
|
|
27
27
|
import {
|
|
@@ -33,15 +33,27 @@ import {
|
|
|
33
33
|
parseEmbeddedCapabilities,
|
|
34
34
|
validateCapabilities,
|
|
35
35
|
} from "./capabilities.ts";
|
|
36
|
+
import { createExecutionBudget, type ExecutionBudget, remainingExecutionMs } from "./deadline-budget.ts";
|
|
37
|
+
import { DeadlineProfileStore } from "./deadline-profile-store.ts";
|
|
38
|
+
import { runManagedProcess } from "./managed-process.ts";
|
|
39
|
+
import { emptyUsage, type SingleResult, type SubagentAttemptResult } from "./subagent-runtime-types.ts";
|
|
36
40
|
|
|
37
41
|
const MAX_PARALLEL_TASKS = 8;
|
|
38
42
|
const MAX_CONCURRENCY = 4;
|
|
39
43
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
40
44
|
const PER_TASK_OUTPUT_CAP = 50 * 1024;
|
|
45
|
+
const DEFAULT_EXECUTION_BUDGET_MS = 840_000;
|
|
46
|
+
const MAX_EXECUTION_BUDGET_MS = 840_000;
|
|
47
|
+
const EXECUTION_CLEANUP_RESERVE_MS = 15_000;
|
|
48
|
+
const DEFAULT_ATTEMPT_CUTOFF_MS = 600_000;
|
|
49
|
+
const MINIMUM_ATTEMPT_MS = 30_000;
|
|
50
|
+
const MAX_TASK_SHARDS = 3;
|
|
51
|
+
const MAX_TOKENS_PER_TASK_SHARD = 6_000;
|
|
41
52
|
|
|
42
53
|
// Capability catalog is expensive to build (scans every SKILL.md on disk); memoize
|
|
43
54
|
// once per dispatcher process.
|
|
44
55
|
let cachedCapabilityCatalog: CapabilityCatalog | null = null;
|
|
56
|
+
let cachedDeadlineProfileStore: DeadlineProfileStore | null = null;
|
|
45
57
|
|
|
46
58
|
function getCapabilityCatalog(): CapabilityCatalog {
|
|
47
59
|
if (!cachedCapabilityCatalog) {
|
|
@@ -50,6 +62,15 @@ function getCapabilityCatalog(): CapabilityCatalog {
|
|
|
50
62
|
return cachedCapabilityCatalog;
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
function getDeadlineProfileStore(): DeadlineProfileStore {
|
|
66
|
+
if (!cachedDeadlineProfileStore) {
|
|
67
|
+
cachedDeadlineProfileStore = new DeadlineProfileStore(
|
|
68
|
+
path.join(getAgentDir(), "state", "subagent-deadline-profiles.json"),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return cachedDeadlineProfileStore;
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
function formatTokens(count: number): string {
|
|
54
75
|
if (count < 1000) return count.toString();
|
|
55
76
|
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
@@ -151,28 +172,14 @@ function formatToolCall(
|
|
|
151
172
|
}
|
|
152
173
|
}
|
|
153
174
|
|
|
154
|
-
interface
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
interface SingleResult {
|
|
165
|
-
agent: string;
|
|
166
|
-
agentSource: "user" | "project" | "unknown";
|
|
167
|
-
task: string;
|
|
168
|
-
exitCode: number;
|
|
169
|
-
messages: Message[];
|
|
170
|
-
stderr: string;
|
|
171
|
-
usage: UsageStats;
|
|
172
|
-
model?: string;
|
|
173
|
-
stopReason?: string;
|
|
174
|
-
errorMessage?: string;
|
|
175
|
-
step?: number;
|
|
175
|
+
interface ExecutionBudgetDetails {
|
|
176
|
+
hardDeadlineMs: number;
|
|
177
|
+
elapsedMs: number;
|
|
178
|
+
remainingMs: number;
|
|
179
|
+
estimatedTokens: number;
|
|
180
|
+
plannedShards: number;
|
|
181
|
+
completedShards: number;
|
|
182
|
+
resumeCount: number;
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
interface SubagentDetails {
|
|
@@ -180,6 +187,19 @@ interface SubagentDetails {
|
|
|
180
187
|
agentScope: AgentScope;
|
|
181
188
|
projectAgentsDir: string | null;
|
|
182
189
|
results: SingleResult[];
|
|
190
|
+
executionBudget: ExecutionBudgetDetails;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function summarizeExecutionBudget(budget: ExecutionBudget, results: readonly SingleResult[]): ExecutionBudgetDetails {
|
|
194
|
+
return {
|
|
195
|
+
hardDeadlineMs: budget.hardDeadlineMs,
|
|
196
|
+
elapsedMs: Date.now() - budget.startedAtMs,
|
|
197
|
+
remainingMs: remainingExecutionMs(budget),
|
|
198
|
+
estimatedTokens: results.reduce((sum, result) => sum + (result.deadline?.estimatedTokens ?? 0), 0),
|
|
199
|
+
plannedShards: results.reduce((sum, result) => sum + (result.deadline?.plannedShardIds.length ?? 0), 0),
|
|
200
|
+
completedShards: results.reduce((sum, result) => sum + (result.deadline?.completedShardIds.length ?? 0), 0),
|
|
201
|
+
resumeCount: results.reduce((sum, result) => sum + (result.deadline?.resumeCount ?? 0), 0),
|
|
202
|
+
};
|
|
183
203
|
}
|
|
184
204
|
|
|
185
205
|
function getFinalOutput(messages: Message[]): string {
|
|
@@ -199,10 +219,15 @@ function isFailedResult(result: SingleResult): boolean {
|
|
|
199
219
|
}
|
|
200
220
|
|
|
201
221
|
function getResultOutput(result: SingleResult): string {
|
|
222
|
+
const partialOutput = result.output || getFinalOutput(result.messages);
|
|
202
223
|
if (isFailedResult(result)) {
|
|
203
|
-
|
|
224
|
+
const diagnostic = result.errorMessage || result.stderr;
|
|
225
|
+
if (diagnostic && partialOutput && !diagnostic.includes(partialOutput)) {
|
|
226
|
+
return `${diagnostic}\n\nCheckpointed partial output:\n${partialOutput}`;
|
|
227
|
+
}
|
|
228
|
+
return diagnostic || partialOutput || "(no output)";
|
|
204
229
|
}
|
|
205
|
-
return
|
|
230
|
+
return partialOutput || "(no output)";
|
|
206
231
|
}
|
|
207
232
|
|
|
208
233
|
function truncateParallelOutput(output: string): string {
|
|
@@ -278,33 +303,22 @@ function getOmkInvocation(args: string[]): { command: string; args: string[] } {
|
|
|
278
303
|
}
|
|
279
304
|
|
|
280
305
|
type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
|
|
306
|
+
type ResultUpdateCallback = (partial: SingleResult) => void;
|
|
281
307
|
|
|
282
|
-
async function
|
|
308
|
+
async function runSingleAgentAttempt(
|
|
283
309
|
defaultCwd: string,
|
|
284
310
|
agents: AgentConfig[],
|
|
285
311
|
agentName: string,
|
|
312
|
+
logicalTask: string,
|
|
286
313
|
task: string,
|
|
287
314
|
cwd: string | undefined,
|
|
288
315
|
step: number | undefined,
|
|
316
|
+
cutoffMs: number,
|
|
289
317
|
signal: AbortSignal | undefined,
|
|
290
|
-
onUpdate:
|
|
291
|
-
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (!agent) {
|
|
296
|
-
const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
|
|
297
|
-
return {
|
|
298
|
-
agent: agentName,
|
|
299
|
-
agentSource: "unknown",
|
|
300
|
-
task,
|
|
301
|
-
exitCode: 1,
|
|
302
|
-
messages: [],
|
|
303
|
-
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
|
|
304
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
305
|
-
step,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
318
|
+
onUpdate: ResultUpdateCallback | undefined,
|
|
319
|
+
): Promise<SubagentAttemptResult> {
|
|
320
|
+
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
321
|
+
if (!agent) throw new Error(`Unknown agent: ${agentName}`);
|
|
308
322
|
|
|
309
323
|
const args: string[] = ["--mode", "json", "-p", "--no-session"];
|
|
310
324
|
if (agent.model) args.push("--model", agent.model);
|
|
@@ -353,22 +367,17 @@ async function runSingleAgent(
|
|
|
353
367
|
const currentResult: SingleResult = {
|
|
354
368
|
agent: agentName,
|
|
355
369
|
agentSource: agent.source,
|
|
356
|
-
task,
|
|
370
|
+
task: logicalTask,
|
|
357
371
|
exitCode: 0,
|
|
358
372
|
messages: [],
|
|
359
373
|
stderr: "",
|
|
360
|
-
usage:
|
|
374
|
+
usage: emptyUsage(),
|
|
361
375
|
model: agent.model,
|
|
362
376
|
step,
|
|
363
377
|
};
|
|
364
378
|
|
|
365
379
|
const emitUpdate = () => {
|
|
366
|
-
|
|
367
|
-
onUpdate({
|
|
368
|
-
content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
|
|
369
|
-
details: makeDetails([currentResult]),
|
|
370
|
-
});
|
|
371
|
-
}
|
|
380
|
+
onUpdate?.({ ...currentResult, messages: [...currentResult.messages], usage: { ...currentResult.usage } });
|
|
372
381
|
};
|
|
373
382
|
|
|
374
383
|
try {
|
|
@@ -390,90 +399,72 @@ async function runSingleAgent(
|
|
|
390
399
|
}
|
|
391
400
|
|
|
392
401
|
args.push(`Task: ${task}`);
|
|
393
|
-
let
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (event.type === "message_end" && event.message) {
|
|
414
|
-
const msg = event.message as Message;
|
|
415
|
-
currentResult.messages.push(msg);
|
|
416
|
-
|
|
417
|
-
if (msg.role === "assistant") {
|
|
418
|
-
currentResult.usage.turns++;
|
|
419
|
-
const usage = msg.usage;
|
|
420
|
-
if (usage) {
|
|
421
|
-
currentResult.usage.input += usage.input || 0;
|
|
422
|
-
currentResult.usage.output += usage.output || 0;
|
|
423
|
-
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
424
|
-
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
425
|
-
currentResult.usage.cost += usage.cost?.total || 0;
|
|
426
|
-
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
427
|
-
}
|
|
428
|
-
if (!currentResult.model && msg.model) currentResult.model = msg.model;
|
|
429
|
-
if (msg.stopReason) currentResult.stopReason = msg.stopReason;
|
|
430
|
-
if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
|
|
402
|
+
let buffer = "";
|
|
403
|
+
const processLine = (line: string) => {
|
|
404
|
+
const event = parseSubagentEvent(line);
|
|
405
|
+
if (event === undefined) return;
|
|
406
|
+
if (event.type === "message_end" && event.message !== undefined) {
|
|
407
|
+
const msg = event.message;
|
|
408
|
+
currentResult.messages.push(msg);
|
|
409
|
+
if (msg.role === "assistant") {
|
|
410
|
+
currentResult.usage.turns += 1;
|
|
411
|
+
const usage = msg.usage;
|
|
412
|
+
if (usage) {
|
|
413
|
+
currentResult.usage.input += usage.input || 0;
|
|
414
|
+
currentResult.usage.output += usage.output || 0;
|
|
415
|
+
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
416
|
+
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
417
|
+
currentResult.usage.cost += usage.cost?.total || 0;
|
|
418
|
+
currentResult.usage.contextTokens = Math.max(
|
|
419
|
+
currentResult.usage.contextTokens,
|
|
420
|
+
usage.totalTokens || 0,
|
|
421
|
+
);
|
|
431
422
|
}
|
|
432
|
-
|
|
423
|
+
if (!currentResult.model && msg.model) currentResult.model = msg.model;
|
|
424
|
+
if (msg.stopReason) currentResult.stopReason = msg.stopReason;
|
|
425
|
+
if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
|
|
433
426
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
427
|
+
emitUpdate();
|
|
428
|
+
}
|
|
429
|
+
if (event.type === "tool_result_end" && event.message !== undefined) {
|
|
430
|
+
currentResult.messages.push(event.message);
|
|
431
|
+
emitUpdate();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
const invocation = getOmkInvocation(args);
|
|
435
|
+
const processResult = await runManagedProcess({
|
|
436
|
+
command: invocation.command,
|
|
437
|
+
args: invocation.args,
|
|
438
|
+
cwd: cwd ?? defaultCwd,
|
|
439
|
+
cutoffMs,
|
|
440
|
+
signal,
|
|
441
|
+
onStdout: (chunk) => {
|
|
442
|
+
buffer += chunk;
|
|
443
443
|
const lines = buffer.split("\n");
|
|
444
444
|
buffer = lines.pop() || "";
|
|
445
445
|
for (const line of lines) processLine(line);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
proc.on("close", (code) => {
|
|
453
|
-
if (buffer.trim()) processLine(buffer);
|
|
454
|
-
resolve(code ?? 0);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
proc.on("error", () => {
|
|
458
|
-
resolve(1);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
if (signal) {
|
|
462
|
-
const killProc = () => {
|
|
463
|
-
wasAborted = true;
|
|
464
|
-
proc.kill("SIGTERM");
|
|
465
|
-
setTimeout(() => {
|
|
466
|
-
if (!proc.killed) proc.kill("SIGKILL");
|
|
467
|
-
}, 5000);
|
|
468
|
-
};
|
|
469
|
-
if (signal.aborted) killProc();
|
|
470
|
-
else signal.addEventListener("abort", killProc, { once: true });
|
|
471
|
-
}
|
|
446
|
+
},
|
|
447
|
+
onStderr: (chunk) => {
|
|
448
|
+
currentResult.stderr += chunk;
|
|
449
|
+
},
|
|
472
450
|
});
|
|
473
|
-
|
|
474
|
-
currentResult.exitCode = exitCode;
|
|
475
|
-
|
|
476
|
-
|
|
451
|
+
if (buffer.trim()) processLine(buffer);
|
|
452
|
+
currentResult.exitCode = processResult.exitCode;
|
|
453
|
+
currentResult.output = getFinalOutput(currentResult.messages);
|
|
454
|
+
if (processResult.reason === "cutoff") {
|
|
455
|
+
currentResult.exitCode = 124;
|
|
456
|
+
currentResult.stopReason = "deadline";
|
|
457
|
+
currentResult.errorMessage = `Subagent attempt exceeded ${cutoffMs}ms cutoff`;
|
|
458
|
+
} else if (processResult.reason === "aborted") {
|
|
459
|
+
currentResult.exitCode = 130;
|
|
460
|
+
currentResult.stopReason = "aborted";
|
|
461
|
+
currentResult.errorMessage = "Subagent was aborted";
|
|
462
|
+
} else if (processResult.reason === "spawn-error") {
|
|
463
|
+
currentResult.exitCode = 1;
|
|
464
|
+
currentResult.stopReason = "error";
|
|
465
|
+
currentResult.errorMessage = processResult.errorMessage ?? "Unable to spawn subagent";
|
|
466
|
+
}
|
|
467
|
+
return { result: currentResult, process: processResult };
|
|
477
468
|
} finally {
|
|
478
469
|
if (tmpPromptPath)
|
|
479
470
|
try {
|
|
@@ -490,6 +481,96 @@ async function runSingleAgent(
|
|
|
490
481
|
}
|
|
491
482
|
}
|
|
492
483
|
|
|
484
|
+
interface SubagentJsonEvent {
|
|
485
|
+
readonly type: string;
|
|
486
|
+
readonly message?: Message;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function parseSubagentEvent(line: string): SubagentJsonEvent | undefined {
|
|
490
|
+
if (line.trim() === "") return undefined;
|
|
491
|
+
let value: unknown;
|
|
492
|
+
try {
|
|
493
|
+
value = JSON.parse(line);
|
|
494
|
+
} catch {
|
|
495
|
+
return undefined;
|
|
496
|
+
}
|
|
497
|
+
if (!isRecord(value) || typeof value.type !== "string") return undefined;
|
|
498
|
+
const message = value.message;
|
|
499
|
+
if (message === undefined) return { type: value.type };
|
|
500
|
+
if (!isRecord(message) || typeof message.role !== "string" || !Array.isArray(message.content)) return undefined;
|
|
501
|
+
return { type: value.type, message: message as unknown as Message };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
505
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function runSingleAgent(
|
|
509
|
+
defaultCwd: string,
|
|
510
|
+
agents: AgentConfig[],
|
|
511
|
+
agentName: string,
|
|
512
|
+
task: string,
|
|
513
|
+
cwd: string | undefined,
|
|
514
|
+
step: number | undefined,
|
|
515
|
+
signal: AbortSignal | undefined,
|
|
516
|
+
onUpdate: OnUpdateCallback | undefined,
|
|
517
|
+
makeDetails: (results: SingleResult[]) => SubagentDetails,
|
|
518
|
+
budget: ExecutionBudget,
|
|
519
|
+
profileStore: DeadlineProfileStore,
|
|
520
|
+
pendingSequentialTasks: number,
|
|
521
|
+
): Promise<SingleResult> {
|
|
522
|
+
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
523
|
+
if (!agent) {
|
|
524
|
+
const available = agents.map((candidate) => `"${candidate.name}"`).join(", ") || "none";
|
|
525
|
+
return {
|
|
526
|
+
agent: agentName,
|
|
527
|
+
agentSource: "unknown",
|
|
528
|
+
task,
|
|
529
|
+
exitCode: 1,
|
|
530
|
+
messages: [],
|
|
531
|
+
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
|
|
532
|
+
usage: emptyUsage(),
|
|
533
|
+
step,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
return await runAdaptiveAgent({
|
|
537
|
+
agentName,
|
|
538
|
+
agentSource: agent.source,
|
|
539
|
+
logicalTask: task,
|
|
540
|
+
model: agent.model,
|
|
541
|
+
step,
|
|
542
|
+
signal,
|
|
543
|
+
budget,
|
|
544
|
+
pendingSequentialTasks,
|
|
545
|
+
profileStore,
|
|
546
|
+
policy: {
|
|
547
|
+
fallbackAttemptMs: DEFAULT_ATTEMPT_CUTOFF_MS,
|
|
548
|
+
minimumAttemptMs: MINIMUM_ATTEMPT_MS,
|
|
549
|
+
maxTokensPerShard: MAX_TOKENS_PER_TASK_SHARD,
|
|
550
|
+
},
|
|
551
|
+
onUpdate: onUpdate
|
|
552
|
+
? (partial) =>
|
|
553
|
+
onUpdate({
|
|
554
|
+
content: [{ type: "text", text: getResultOutput(partial) || "(running...)" }],
|
|
555
|
+
details: makeDetails([partial]),
|
|
556
|
+
})
|
|
557
|
+
: undefined,
|
|
558
|
+
runAttempt: async (input: RunAttemptInput) =>
|
|
559
|
+
await runSingleAgentAttempt(
|
|
560
|
+
defaultCwd,
|
|
561
|
+
agents,
|
|
562
|
+
agentName,
|
|
563
|
+
input.logicalTask,
|
|
564
|
+
input.task,
|
|
565
|
+
cwd,
|
|
566
|
+
step,
|
|
567
|
+
input.cutoffMs,
|
|
568
|
+
signal,
|
|
569
|
+
input.onUpdate,
|
|
570
|
+
),
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
493
574
|
const TaskItem = Type.Object({
|
|
494
575
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
495
576
|
task: Type.String({ description: "Task to delegate to the agent" }),
|
|
@@ -516,6 +597,23 @@ const SubagentParams = Type.Object({
|
|
|
516
597
|
confirmProjectAgents: Type.Optional(
|
|
517
598
|
Type.Boolean({ description: "Prompt before running project-local agents. Default: true.", default: true }),
|
|
518
599
|
),
|
|
600
|
+
executionBudgetMs: Type.Optional(
|
|
601
|
+
Type.Integer({
|
|
602
|
+
description:
|
|
603
|
+
"Internal wall-clock budget. Defaults to 840000ms, leaving cleanup time before the 900000ms tool timeout.",
|
|
604
|
+
minimum: 60_000,
|
|
605
|
+
maximum: MAX_EXECUTION_BUDGET_MS,
|
|
606
|
+
default: DEFAULT_EXECUTION_BUDGET_MS,
|
|
607
|
+
}),
|
|
608
|
+
),
|
|
609
|
+
maxResumeAttempts: Type.Optional(
|
|
610
|
+
Type.Integer({
|
|
611
|
+
description: "Bounded retries for only the unfinished shard. Default: 1.",
|
|
612
|
+
minimum: 0,
|
|
613
|
+
maximum: 2,
|
|
614
|
+
default: 1,
|
|
615
|
+
}),
|
|
616
|
+
),
|
|
519
617
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })),
|
|
520
618
|
});
|
|
521
619
|
|
|
@@ -526,6 +624,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
526
624
|
description: [
|
|
527
625
|
"Delegate tasks to specialized subagents with isolated context.",
|
|
528
626
|
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
|
|
627
|
+
"Long work is predictively sharded by token and wall-clock demand, checkpointed, and resumed only from unfinished work.",
|
|
529
628
|
'Default agent scope is "user" (from ~/.omk/agent/agents).',
|
|
530
629
|
'To enable project-local agents in .omk/agents, set agentScope: "both" (or "project").',
|
|
531
630
|
].join(" "),
|
|
@@ -536,6 +635,13 @@ export default function (omk: ExtensionAPI) {
|
|
|
536
635
|
const discovery = discoverAgents(ctx.cwd, agentScope);
|
|
537
636
|
const agents = discovery.agents;
|
|
538
637
|
const confirmProjectAgents = params.confirmProjectAgents ?? true;
|
|
638
|
+
const executionBudget = createExecutionBudget({
|
|
639
|
+
hardDeadlineMs: params.executionBudgetMs ?? DEFAULT_EXECUTION_BUDGET_MS,
|
|
640
|
+
cleanupReserveMs: EXECUTION_CLEANUP_RESERVE_MS,
|
|
641
|
+
maxResumeAttempts: params.maxResumeAttempts ?? 1,
|
|
642
|
+
maxTaskShards: MAX_TASK_SHARDS,
|
|
643
|
+
});
|
|
644
|
+
const profileStore = getDeadlineProfileStore();
|
|
539
645
|
|
|
540
646
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
541
647
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -549,6 +655,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
549
655
|
agentScope,
|
|
550
656
|
projectAgentsDir: discovery.projectAgentsDir,
|
|
551
657
|
results,
|
|
658
|
+
executionBudget: summarizeExecutionBudget(executionBudget, results),
|
|
552
659
|
});
|
|
553
660
|
|
|
554
661
|
if (modeCount !== 1) {
|
|
@@ -622,6 +729,9 @@ export default function (omk: ExtensionAPI) {
|
|
|
622
729
|
signal,
|
|
623
730
|
chainUpdate,
|
|
624
731
|
makeDetails("chain"),
|
|
732
|
+
executionBudget,
|
|
733
|
+
profileStore,
|
|
734
|
+
params.chain.length - i,
|
|
625
735
|
);
|
|
626
736
|
results.push(result);
|
|
627
737
|
|
|
@@ -634,10 +744,10 @@ export default function (omk: ExtensionAPI) {
|
|
|
634
744
|
isError: true,
|
|
635
745
|
};
|
|
636
746
|
}
|
|
637
|
-
previousOutput =
|
|
747
|
+
previousOutput = getResultOutput(result);
|
|
638
748
|
}
|
|
639
749
|
return {
|
|
640
|
-
content: [{ type: "text", text:
|
|
750
|
+
content: [{ type: "text", text: getResultOutput(results[results.length - 1]) }],
|
|
641
751
|
details: makeDetails("chain")(results),
|
|
642
752
|
};
|
|
643
753
|
}
|
|
@@ -666,7 +776,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
666
776
|
exitCode: -1, // -1 = still running
|
|
667
777
|
messages: [],
|
|
668
778
|
stderr: "",
|
|
669
|
-
usage:
|
|
779
|
+
usage: emptyUsage(),
|
|
670
780
|
};
|
|
671
781
|
}
|
|
672
782
|
|
|
@@ -700,6 +810,9 @@ export default function (omk: ExtensionAPI) {
|
|
|
700
810
|
}
|
|
701
811
|
},
|
|
702
812
|
makeDetails("parallel"),
|
|
813
|
+
executionBudget,
|
|
814
|
+
profileStore,
|
|
815
|
+
1,
|
|
703
816
|
);
|
|
704
817
|
allResults[index] = result;
|
|
705
818
|
emitParallelUpdate();
|
|
@@ -736,6 +849,9 @@ export default function (omk: ExtensionAPI) {
|
|
|
736
849
|
signal,
|
|
737
850
|
onUpdate,
|
|
738
851
|
makeDetails("single"),
|
|
852
|
+
executionBudget,
|
|
853
|
+
profileStore,
|
|
854
|
+
1,
|
|
739
855
|
);
|
|
740
856
|
const isError = isFailedResult(result);
|
|
741
857
|
if (isError) {
|
|
@@ -747,7 +863,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
747
863
|
};
|
|
748
864
|
}
|
|
749
865
|
return {
|
|
750
|
-
content: [{ type: "text", text:
|
|
866
|
+
content: [{ type: "text", text: getResultOutput(result) }],
|
|
751
867
|
details: makeDetails("single")([result]),
|
|
752
868
|
};
|
|
753
869
|
}
|
|
@@ -833,7 +949,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
833
949
|
const isError = isFailedResult(r);
|
|
834
950
|
const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
835
951
|
const displayItems = getDisplayItems(r.messages);
|
|
836
|
-
const finalOutput = getFinalOutput(r.messages);
|
|
952
|
+
const finalOutput = r.output || getFinalOutput(r.messages);
|
|
837
953
|
|
|
838
954
|
if (expanded) {
|
|
839
955
|
const container = new Container();
|
|
@@ -919,7 +1035,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
919
1035
|
for (const r of details.results) {
|
|
920
1036
|
const rIcon = r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
921
1037
|
const displayItems = getDisplayItems(r.messages);
|
|
922
|
-
const finalOutput = getFinalOutput(r.messages);
|
|
1038
|
+
const finalOutput = r.output || getFinalOutput(r.messages);
|
|
923
1039
|
|
|
924
1040
|
container.addChild(new Spacer(1));
|
|
925
1041
|
container.addChild(
|
|
@@ -1008,7 +1124,7 @@ export default function (omk: ExtensionAPI) {
|
|
|
1008
1124
|
for (const r of details.results) {
|
|
1009
1125
|
const rIcon = isFailedResult(r) ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
1010
1126
|
const displayItems = getDisplayItems(r.messages);
|
|
1011
|
-
const finalOutput = getFinalOutput(r.messages);
|
|
1127
|
+
const finalOutput = r.output || getFinalOutput(r.messages);
|
|
1012
1128
|
|
|
1013
1129
|
container.addChild(new Spacer(1));
|
|
1014
1130
|
container.addChild(
|