@multiclawcli/core 0.4.0 → 0.5.1
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/dist/agents/arch-design-reviewer.d.ts +4 -0
- package/dist/agents/architect.d.ts +4 -0
- package/dist/agents/backend-developer.d.ts +4 -0
- package/dist/agents/code-reviewer.d.ts +4 -0
- package/dist/agents/devops.d.ts +4 -0
- package/dist/agents/frontend-developer.d.ts +4 -0
- package/dist/agents/index.d.ts +26 -0
- package/dist/agents/leader.d.ts +4 -0
- package/dist/agents/product-manager.d.ts +4 -0
- package/dist/agents/reviewer.d.ts +4 -0
- package/dist/agents/tester.d.ts +4 -0
- package/dist/agents/ui-design-reviewer.d.ts +4 -0
- package/dist/agents/ui-designer.d.ts +4 -0
- package/dist/config/defineConfig.d.ts +2 -0
- package/dist/event/EventBus.d.ts +7 -0
- package/dist/index.d.ts +11 -720
- package/dist/index.js +463 -165
- package/dist/orchestrator/AgentRunner.d.ts +19 -0
- package/dist/orchestrator/ContextManager.d.ts +7 -0
- package/dist/orchestrator/Orchestrator.d.ts +109 -0
- package/dist/orchestrator/StageBuilder.d.ts +2 -0
- package/dist/providers/bridge.d.ts +29 -0
- package/dist/providers/catalog.d.ts +73 -0
- package/dist/providers/detect.d.ts +34 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/resolve.d.ts +26 -0
- package/dist/runtime/AcpRuntime.d.ts +17 -0
- package/dist/runtime/RuntimeBase.d.ts +6 -0
- package/dist/runtime/index.d.ts +12 -0
- package/dist/types/agent.d.ts +96 -0
- package/dist/types/config.d.ts +126 -0
- package/dist/types/event.d.ts +115 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/leader.d.ts +36 -0
- package/dist/types/runtime.d.ts +146 -0
- package/dist/utils/abort.d.ts +4 -0
- package/dist/utils/conversation.d.ts +29 -0
- package/dist/utils/errors.d.ts +16 -0
- package/dist/utils/json.d.ts +10 -0
- package/dist/utils/leaderDecision.d.ts +17 -0
- package/dist/utils/prompt.d.ts +1 -0
- package/dist/utils/resumePlan.d.ts +35 -0
- package/dist/utils/retry.d.ts +36 -0
- package/dist/utils/timeout.d.ts +1 -0
- package/dist/utils/toolSummary.d.ts +13 -0
- package/dist/utils/upstream.d.ts +69 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ var TASK = `## Architecture Design
|
|
|
16
16
|
|
|
17
17
|
## API Specification
|
|
18
18
|
{{file:api-spec.md}}
|
|
19
|
+
{{upstreamOutputs}}
|
|
19
20
|
|
|
20
21
|
## Task
|
|
21
22
|
Review the architecture design and API specification above. Output arch-review.md:
|
|
@@ -93,6 +94,7 @@ var TASK2 = `## Project
|
|
|
93
94
|
## Pipeline Agents
|
|
94
95
|
The following agents will run in this pipeline (id: name):
|
|
95
96
|
{{pipelineAgents}}
|
|
97
|
+
{{upstreamOutputs}}
|
|
96
98
|
|
|
97
99
|
## Task
|
|
98
100
|
Analyze the requirement and create three files in the current directory:
|
|
@@ -112,6 +114,7 @@ function architect(overrides) {
|
|
|
112
114
|
name: "Architect",
|
|
113
115
|
icon: "\u25C6",
|
|
114
116
|
description: "designs the architecture",
|
|
117
|
+
role: "planner",
|
|
115
118
|
runtime: "acp",
|
|
116
119
|
models: { acp: "opus" },
|
|
117
120
|
modelTier: "strong",
|
|
@@ -141,6 +144,7 @@ var TASK3 = `## Task Scope
|
|
|
141
144
|
|
|
142
145
|
## Plan
|
|
143
146
|
{{agentPlan}}
|
|
147
|
+
{{upstreamOutputs}}
|
|
144
148
|
|
|
145
149
|
---
|
|
146
150
|
|
|
@@ -200,6 +204,7 @@ var TASK4 = `## Project
|
|
|
200
204
|
|
|
201
205
|
## API Specification
|
|
202
206
|
{{file:api-spec.md}}
|
|
207
|
+
{{upstreamOutputs}}
|
|
203
208
|
|
|
204
209
|
## Task
|
|
205
210
|
Perform a thorough code review of all implementation in the current directory and output code-review.md:
|
|
@@ -252,6 +257,7 @@ var TASK5 = `## Task Scope
|
|
|
252
257
|
|
|
253
258
|
## Plan
|
|
254
259
|
{{agentPlan}}
|
|
260
|
+
{{upstreamOutputs}}
|
|
255
261
|
|
|
256
262
|
---
|
|
257
263
|
|
|
@@ -302,6 +308,7 @@ var TASK6 = `## Task Scope
|
|
|
302
308
|
|
|
303
309
|
## Plan
|
|
304
310
|
{{agentPlan}}
|
|
311
|
+
{{upstreamOutputs}}
|
|
305
312
|
|
|
306
313
|
---
|
|
307
314
|
|
|
@@ -356,7 +363,7 @@ The console hands you the turns that came before this one. A requirement is ofte
|
|
|
356
363
|
Output ONLY a single JSON object on its own line \u2014 no prose, no markdown fences:
|
|
357
364
|
{"mode": "reply", "message": "your answer"}
|
|
358
365
|
{"mode": "ask", "message": "your question"}
|
|
359
|
-
{"mode": "run", "run": ["agent-id-1", "agent-id-2"]}`;
|
|
366
|
+
{"mode": "run", "reason": "one sentence on why this set", "run": ["agent-id-1", "agent-id-2"]}`;
|
|
360
367
|
var TASK7 = `## Project
|
|
361
368
|
{{projectName}}
|
|
362
369
|
|
|
@@ -371,7 +378,7 @@ Read the codebase to understand the current state, then decide how to handle the
|
|
|
371
378
|
Output a single JSON object \u2014 exactly one of:
|
|
372
379
|
{"mode": "reply", "message": "your answer"}
|
|
373
380
|
{"mode": "ask", "message": "your question"}
|
|
374
|
-
{"mode": "run", "run": ["agent-id", ...]}
|
|
381
|
+
{"mode": "run", "reason": "one sentence on why this set", "run": ["agent-id", ...]}
|
|
375
382
|
Agent IDs must come from the Available Agents list above.`;
|
|
376
383
|
function leader(overrides) {
|
|
377
384
|
return {
|
|
@@ -403,6 +410,7 @@ Deliverables (save to the working directory):
|
|
|
403
410
|
Principles: Output the document directly without preamble. Mark uncertain items as OP- (open questions) rather than guessing. Do not do technical architecture design. Do not write feature code.`;
|
|
404
411
|
var TASK8 = `## Business Context
|
|
405
412
|
{{requirement}}
|
|
413
|
+
{{upstreamOutputs}}
|
|
406
414
|
|
|
407
415
|
## Task
|
|
408
416
|
Based on the context above, create requirements.md in the current directory containing:
|
|
@@ -448,6 +456,7 @@ var TASK9 = `## Project
|
|
|
448
456
|
|
|
449
457
|
## Requirement
|
|
450
458
|
{{requirement}}
|
|
459
|
+
{{upstreamOutputs}}
|
|
451
460
|
|
|
452
461
|
## Task
|
|
453
462
|
Perform an independent code review of all implementation in the current directory and output review.md:
|
|
@@ -494,6 +503,7 @@ var TASK10 = `## Task Scope
|
|
|
494
503
|
|
|
495
504
|
## Plan
|
|
496
505
|
{{agentPlan}}
|
|
506
|
+
{{upstreamOutputs}}
|
|
497
507
|
|
|
498
508
|
---
|
|
499
509
|
|
|
@@ -548,6 +558,7 @@ var TASK11 = `## Requirement
|
|
|
548
558
|
|
|
549
559
|
## UI Design Specification
|
|
550
560
|
{{file:ui-design.md}}
|
|
561
|
+
{{upstreamOutputs}}
|
|
551
562
|
|
|
552
563
|
## Task
|
|
553
564
|
Review the UI design specification above and output ui-review.md:
|
|
@@ -596,6 +607,7 @@ var TASK12 = `## Task Scope
|
|
|
596
607
|
|
|
597
608
|
## Plan
|
|
598
609
|
{{agentPlan}}
|
|
610
|
+
{{upstreamOutputs}}
|
|
599
611
|
|
|
600
612
|
---
|
|
601
613
|
|
|
@@ -684,112 +696,6 @@ var EventBus = class {
|
|
|
684
696
|
import * as fs4 from "fs";
|
|
685
697
|
import * as path5 from "path";
|
|
686
698
|
|
|
687
|
-
// src/utils/conversation.ts
|
|
688
|
-
var MAX_CONVERSATION_TURNS = 6;
|
|
689
|
-
var MAX_CONVERSATION_CHARS = 4e3;
|
|
690
|
-
var MAX_ENTRY_CHARS = 600;
|
|
691
|
-
var ELLIPSIS = "\u2026";
|
|
692
|
-
function clip(text, max) {
|
|
693
|
-
const trimmed = text.trim();
|
|
694
|
-
if (trimmed.length <= max) return trimmed;
|
|
695
|
-
return `${trimmed.slice(0, Math.max(0, max - 1)).trimEnd()}${ELLIPSIS}`;
|
|
696
|
-
}
|
|
697
|
-
function entry(turn, index) {
|
|
698
|
-
const lines = [`${index}. user: ${clip(turn.requirement, MAX_ENTRY_CHARS)}`];
|
|
699
|
-
if (turn.reply) {
|
|
700
|
-
const verb = turn.reply.mode === "ask" ? "asked" : "answered";
|
|
701
|
-
lines.push(` you ${verb}: ${clip(turn.reply.message, MAX_ENTRY_CHARS)}`);
|
|
702
|
-
}
|
|
703
|
-
return lines.join("\n");
|
|
704
|
-
}
|
|
705
|
-
function renderConversation(history) {
|
|
706
|
-
if (!history || history.length === 0) return "";
|
|
707
|
-
const recent = history.slice(-MAX_CONVERSATION_TURNS);
|
|
708
|
-
const first = history.length - recent.length;
|
|
709
|
-
const entries = recent.map((turn, i) => entry(turn, first + i + 1));
|
|
710
|
-
while (entries.length > 1 && entries.join("\n").length > MAX_CONVERSATION_CHARS) {
|
|
711
|
-
entries.shift();
|
|
712
|
-
}
|
|
713
|
-
const body = clip(entries.join("\n"), MAX_CONVERSATION_CHARS);
|
|
714
|
-
return `
|
|
715
|
-
## Conversation so far
|
|
716
|
-
|
|
717
|
-
Earlier turns of this session, oldest first:
|
|
718
|
-
|
|
719
|
-
${body}
|
|
720
|
-
`;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// src/utils/json.ts
|
|
724
|
-
function extractJsonObjects(text) {
|
|
725
|
-
const objects = [];
|
|
726
|
-
let searchFrom = 0;
|
|
727
|
-
while (searchFrom < text.length) {
|
|
728
|
-
const start = text.indexOf("{", searchFrom);
|
|
729
|
-
if (start === -1) break;
|
|
730
|
-
const end = findMatchingBrace(text, start);
|
|
731
|
-
if (end !== -1) {
|
|
732
|
-
try {
|
|
733
|
-
objects.push(JSON.parse(text.slice(start, end + 1)));
|
|
734
|
-
searchFrom = end + 1;
|
|
735
|
-
continue;
|
|
736
|
-
} catch {
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
searchFrom = start + 1;
|
|
740
|
-
}
|
|
741
|
-
return objects;
|
|
742
|
-
}
|
|
743
|
-
function findMatchingBrace(text, start) {
|
|
744
|
-
let depth = 0;
|
|
745
|
-
let inString = false;
|
|
746
|
-
let escaped = false;
|
|
747
|
-
for (let i = start; i < text.length; i++) {
|
|
748
|
-
const ch = text[i];
|
|
749
|
-
if (inString) {
|
|
750
|
-
if (escaped) escaped = false;
|
|
751
|
-
else if (ch === "\\") escaped = true;
|
|
752
|
-
else if (ch === '"') inString = false;
|
|
753
|
-
continue;
|
|
754
|
-
}
|
|
755
|
-
if (ch === '"') inString = true;
|
|
756
|
-
else if (ch === "{") depth++;
|
|
757
|
-
else if (ch === "}") {
|
|
758
|
-
depth--;
|
|
759
|
-
if (depth === 0) return i;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
return -1;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// src/utils/leaderDecision.ts
|
|
766
|
-
function parseLeaderDecision(text) {
|
|
767
|
-
for (const value of extractJsonObjects(text)) {
|
|
768
|
-
const decision = asDecision(value);
|
|
769
|
-
if (decision) return decision;
|
|
770
|
-
}
|
|
771
|
-
return void 0;
|
|
772
|
-
}
|
|
773
|
-
function asDecision(value) {
|
|
774
|
-
if (typeof value !== "object" || value === null) return void 0;
|
|
775
|
-
const record = value;
|
|
776
|
-
const mode = record.mode;
|
|
777
|
-
if (mode === "reply" || mode === "ask") {
|
|
778
|
-
const message = typeof record.message === "string" ? record.message.trim() : "";
|
|
779
|
-
return message ? { mode, message } : void 0;
|
|
780
|
-
}
|
|
781
|
-
if (mode === "run" || mode === void 0) {
|
|
782
|
-
const run = record.run;
|
|
783
|
-
if (!Array.isArray(run) || !run.every((id) => typeof id === "string")) return void 0;
|
|
784
|
-
return { mode: "run", run };
|
|
785
|
-
}
|
|
786
|
-
return void 0;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// src/orchestrator/AgentRunner.ts
|
|
790
|
-
import * as fs3 from "fs";
|
|
791
|
-
import * as path4 from "path";
|
|
792
|
-
|
|
793
699
|
// src/providers/catalog.ts
|
|
794
700
|
import * as path from "path";
|
|
795
701
|
function normalizeCommandKey(command) {
|
|
@@ -933,7 +839,10 @@ function resolveCommand(agent, config) {
|
|
|
933
839
|
return agent.command ?? config.defaults?.command ?? specForProvider(resolveProvider(agent, config))?.command ?? DEFAULT_COMMAND;
|
|
934
840
|
}
|
|
935
841
|
function resolveArgs(agent, config) {
|
|
936
|
-
|
|
842
|
+
if (agent.args) return agent.args;
|
|
843
|
+
const command = agent.command ?? config.defaults?.command;
|
|
844
|
+
if (command) return specForCommand(command)?.args;
|
|
845
|
+
return specForProvider(resolveProvider(agent, config))?.args;
|
|
937
846
|
}
|
|
938
847
|
function resolveModel(agent, config) {
|
|
939
848
|
if (agent.model) return agent.model;
|
|
@@ -960,6 +869,153 @@ function resolveAgentRuntime(agent, config) {
|
|
|
960
869
|
};
|
|
961
870
|
}
|
|
962
871
|
|
|
872
|
+
// src/utils/conversation.ts
|
|
873
|
+
var MAX_CONVERSATION_TURNS = 6;
|
|
874
|
+
var MAX_CONVERSATION_CHARS = 4e3;
|
|
875
|
+
var MAX_ENTRY_CHARS = 600;
|
|
876
|
+
var ELLIPSIS = "\u2026";
|
|
877
|
+
function clip(text, max) {
|
|
878
|
+
const trimmed = text.trim();
|
|
879
|
+
if (trimmed.length <= max) return trimmed;
|
|
880
|
+
return `${trimmed.slice(0, Math.max(0, max - 1)).trimEnd()}${ELLIPSIS}`;
|
|
881
|
+
}
|
|
882
|
+
function entry(turn, index) {
|
|
883
|
+
const lines = [`${index}. user: ${clip(turn.requirement, MAX_ENTRY_CHARS)}`];
|
|
884
|
+
if (turn.reply) {
|
|
885
|
+
const verb = turn.reply.mode === "ask" ? "asked" : "answered";
|
|
886
|
+
lines.push(` you ${verb}: ${clip(turn.reply.message, MAX_ENTRY_CHARS)}`);
|
|
887
|
+
}
|
|
888
|
+
return lines.join("\n");
|
|
889
|
+
}
|
|
890
|
+
function renderConversation(history) {
|
|
891
|
+
if (!history || history.length === 0) return "";
|
|
892
|
+
const recent = history.slice(-MAX_CONVERSATION_TURNS);
|
|
893
|
+
const first = history.length - recent.length;
|
|
894
|
+
const entries = recent.map((turn, i) => entry(turn, first + i + 1));
|
|
895
|
+
while (entries.length > 1 && entries.join("\n").length > MAX_CONVERSATION_CHARS) {
|
|
896
|
+
entries.shift();
|
|
897
|
+
}
|
|
898
|
+
const body = clip(entries.join("\n"), MAX_CONVERSATION_CHARS);
|
|
899
|
+
return `
|
|
900
|
+
## Conversation so far
|
|
901
|
+
|
|
902
|
+
Earlier turns of this session, oldest first:
|
|
903
|
+
|
|
904
|
+
${body}
|
|
905
|
+
`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/utils/json.ts
|
|
909
|
+
function extractJsonObjects(text) {
|
|
910
|
+
const objects = [];
|
|
911
|
+
let searchFrom = 0;
|
|
912
|
+
while (searchFrom < text.length) {
|
|
913
|
+
const start = text.indexOf("{", searchFrom);
|
|
914
|
+
if (start === -1) break;
|
|
915
|
+
const end = findMatchingBrace(text, start);
|
|
916
|
+
if (end !== -1) {
|
|
917
|
+
try {
|
|
918
|
+
objects.push(JSON.parse(text.slice(start, end + 1)));
|
|
919
|
+
searchFrom = end + 1;
|
|
920
|
+
continue;
|
|
921
|
+
} catch {
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
searchFrom = start + 1;
|
|
925
|
+
}
|
|
926
|
+
return objects;
|
|
927
|
+
}
|
|
928
|
+
function findMatchingBrace(text, start) {
|
|
929
|
+
let depth = 0;
|
|
930
|
+
let inString = false;
|
|
931
|
+
let escaped = false;
|
|
932
|
+
for (let i = start; i < text.length; i++) {
|
|
933
|
+
const ch = text[i];
|
|
934
|
+
if (inString) {
|
|
935
|
+
if (escaped) escaped = false;
|
|
936
|
+
else if (ch === "\\") escaped = true;
|
|
937
|
+
else if (ch === '"') inString = false;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (ch === '"') inString = true;
|
|
941
|
+
else if (ch === "{") depth++;
|
|
942
|
+
else if (ch === "}") {
|
|
943
|
+
depth--;
|
|
944
|
+
if (depth === 0) return i;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return -1;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/utils/leaderDecision.ts
|
|
951
|
+
function parseLeaderDecision(text) {
|
|
952
|
+
for (const value of extractJsonObjects(text)) {
|
|
953
|
+
const decision = asDecision(value);
|
|
954
|
+
if (decision) return decision;
|
|
955
|
+
}
|
|
956
|
+
return void 0;
|
|
957
|
+
}
|
|
958
|
+
function asDecision(value) {
|
|
959
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
960
|
+
const record = value;
|
|
961
|
+
const mode = record.mode;
|
|
962
|
+
if (mode === "reply" || mode === "ask") {
|
|
963
|
+
const message = typeof record.message === "string" ? record.message.trim() : "";
|
|
964
|
+
return message ? { mode, message } : void 0;
|
|
965
|
+
}
|
|
966
|
+
if (mode === "run" || mode === void 0) {
|
|
967
|
+
const run = record.run;
|
|
968
|
+
if (!Array.isArray(run) || !run.every((id) => typeof id === "string")) return void 0;
|
|
969
|
+
const reason = typeof record.reason === "string" ? record.reason.trim() : "";
|
|
970
|
+
return reason ? { mode: "run", run, reason } : { mode: "run", run };
|
|
971
|
+
}
|
|
972
|
+
return void 0;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/utils/upstream.ts
|
|
976
|
+
var MAX_UPSTREAM_ENTRY_CHARS = 600;
|
|
977
|
+
var MAX_UPSTREAM_CHARS = 4e3;
|
|
978
|
+
var ELLIPSIS2 = "\u2026";
|
|
979
|
+
var HEADER = "\n## What upstream produced\n\n";
|
|
980
|
+
var TRAILER = "\n";
|
|
981
|
+
function block(entries) {
|
|
982
|
+
return `${HEADER}${entries.join("\n")}${TRAILER}`;
|
|
983
|
+
}
|
|
984
|
+
function compact(text, max) {
|
|
985
|
+
const trimmed = text.trim();
|
|
986
|
+
if (trimmed.length <= max) return trimmed;
|
|
987
|
+
const marker = (count) => `${ELLIPSIS2} (${count} characters elided) ${ELLIPSIS2}`;
|
|
988
|
+
const reserved = marker(trimmed.length).length;
|
|
989
|
+
const keep = Math.max(0, max - reserved);
|
|
990
|
+
const head = Math.ceil(keep / 2);
|
|
991
|
+
const tail = keep - head;
|
|
992
|
+
const elided = trimmed.length - keep;
|
|
993
|
+
return `${trimmed.slice(0, head)}${marker(elided)}${trimmed.slice(trimmed.length - tail)}`;
|
|
994
|
+
}
|
|
995
|
+
function entry2(output) {
|
|
996
|
+
const label = `- ${output.name} (${output.agentId}):
|
|
997
|
+
`;
|
|
998
|
+
const budget = MAX_UPSTREAM_ENTRY_CHARS - label.length;
|
|
999
|
+
let body = compact(output.output, budget);
|
|
1000
|
+
const newlines = body.length - body.replace(/\n/g, "").length;
|
|
1001
|
+
if (label.length + body.length + 2 * newlines > MAX_UPSTREAM_ENTRY_CHARS) {
|
|
1002
|
+
body = compact(output.output, budget - 2 * newlines);
|
|
1003
|
+
}
|
|
1004
|
+
return `${label}${body.replace(/\n/g, "\n ")}`;
|
|
1005
|
+
}
|
|
1006
|
+
function renderUpstreamOutputs(outputs) {
|
|
1007
|
+
if (outputs.length === 0) return "";
|
|
1008
|
+
const entries = outputs.map(entry2);
|
|
1009
|
+
while (entries.length > 1 && block(entries).length > MAX_UPSTREAM_CHARS) {
|
|
1010
|
+
entries.shift();
|
|
1011
|
+
}
|
|
1012
|
+
return block(entries);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/orchestrator/AgentRunner.ts
|
|
1016
|
+
import * as fs3 from "fs";
|
|
1017
|
+
import * as path4 from "path";
|
|
1018
|
+
|
|
963
1019
|
// src/runtime/AcpRuntime.ts
|
|
964
1020
|
import { spawn } from "child_process";
|
|
965
1021
|
import { randomUUID } from "crypto";
|
|
@@ -1019,6 +1075,17 @@ function isAbortError(err) {
|
|
|
1019
1075
|
return err instanceof Error && err.name === ABORT_ERROR_NAME;
|
|
1020
1076
|
}
|
|
1021
1077
|
|
|
1078
|
+
// src/utils/errors.ts
|
|
1079
|
+
var PermanentError = class extends Error {
|
|
1080
|
+
constructor(message) {
|
|
1081
|
+
super(message);
|
|
1082
|
+
this.name = "PermanentError";
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
function isPermanent(err) {
|
|
1086
|
+
return err instanceof PermanentError;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1022
1089
|
// src/runtime/RuntimeBase.ts
|
|
1023
1090
|
var RuntimeBase = class {
|
|
1024
1091
|
};
|
|
@@ -1103,6 +1170,9 @@ function withAuthHint(acp, err) {
|
|
|
1103
1170
|
}
|
|
1104
1171
|
return error;
|
|
1105
1172
|
}
|
|
1173
|
+
function errorMessage(err) {
|
|
1174
|
+
return err instanceof Error ? err.message : String(err);
|
|
1175
|
+
}
|
|
1106
1176
|
var AcpRuntime = class extends RuntimeBase {
|
|
1107
1177
|
constructor() {
|
|
1108
1178
|
super(...arguments);
|
|
@@ -1120,7 +1190,7 @@ var AcpRuntime = class extends RuntimeBase {
|
|
|
1120
1190
|
capabilities: {
|
|
1121
1191
|
...BASE_CAPABILITIES,
|
|
1122
1192
|
tools: specForCommand(command)?.tools ?? false,
|
|
1123
|
-
models:
|
|
1193
|
+
models: specForCommand(command)?.modelSelection === "config-option"
|
|
1124
1194
|
}
|
|
1125
1195
|
};
|
|
1126
1196
|
}
|
|
@@ -1185,6 +1255,14 @@ ${task.prompt}` : task.prompt;
|
|
|
1185
1255
|
const emit = (event) => {
|
|
1186
1256
|
task.onEvent?.({ ...event, runId, agentId: task.agentId, seq: seq++ });
|
|
1187
1257
|
};
|
|
1258
|
+
const warn = (message) => {
|
|
1259
|
+
if (task.onWarning) {
|
|
1260
|
+
task.onWarning(message);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
process.stderr.write(`[multiclaw] Warning: ${message}
|
|
1264
|
+
`);
|
|
1265
|
+
};
|
|
1188
1266
|
let sessionId;
|
|
1189
1267
|
let agentText = "";
|
|
1190
1268
|
let errorOutput = "";
|
|
@@ -1255,7 +1333,7 @@ ${task.prompt}` : task.prompt;
|
|
|
1255
1333
|
break;
|
|
1256
1334
|
}
|
|
1257
1335
|
case "plan": {
|
|
1258
|
-
emit({ type: "plan", entries: update.entries.map((
|
|
1336
|
+
emit({ type: "plan", entries: update.entries.map((entry3) => entry3.content) });
|
|
1259
1337
|
break;
|
|
1260
1338
|
}
|
|
1261
1339
|
case "usage_update": {
|
|
@@ -1346,7 +1424,7 @@ ${task.prompt}` : task.prompt;
|
|
|
1346
1424
|
settleReject(new Error(`${command} exited with code ${code}: ${errorOutput}`));
|
|
1347
1425
|
});
|
|
1348
1426
|
child.on("error", (err) => {
|
|
1349
|
-
settleReject(new
|
|
1427
|
+
settleReject(new PermanentError(`Failed to start ${command}: ${err.message}`));
|
|
1350
1428
|
});
|
|
1351
1429
|
const run = async () => {
|
|
1352
1430
|
const init = await ctx.request(acp.methods.agent.initialize, {
|
|
@@ -1355,7 +1433,7 @@ ${task.prompt}` : task.prompt;
|
|
|
1355
1433
|
emit({ type: "run_started", model: task.model });
|
|
1356
1434
|
if (task.sessionId) {
|
|
1357
1435
|
if (init.agentCapabilities?.loadSession !== true) {
|
|
1358
|
-
throw new
|
|
1436
|
+
throw new PermanentError(
|
|
1359
1437
|
`Agent does not support session/load (loadSession=false); cannot resume session ${task.sessionId}`
|
|
1360
1438
|
);
|
|
1361
1439
|
}
|
|
@@ -1389,9 +1467,8 @@ ${task.prompt}` : task.prompt;
|
|
|
1389
1467
|
if (task.model && spec?.modelSelection === "config-option" && sessionId) {
|
|
1390
1468
|
const known = discoveredModels.get(command);
|
|
1391
1469
|
if (known && !known.includes(task.model)) {
|
|
1392
|
-
|
|
1393
|
-
`
|
|
1394
|
-
`
|
|
1470
|
+
warn(
|
|
1471
|
+
`model "${task.model}" is not offered by ${command}; running with the provider's default.`
|
|
1395
1472
|
);
|
|
1396
1473
|
} else {
|
|
1397
1474
|
try {
|
|
@@ -1400,7 +1477,10 @@ ${task.prompt}` : task.prompt;
|
|
|
1400
1477
|
configId: "model",
|
|
1401
1478
|
value: task.model
|
|
1402
1479
|
});
|
|
1403
|
-
} catch {
|
|
1480
|
+
} catch (err) {
|
|
1481
|
+
warn(
|
|
1482
|
+
`Could not set model "${task.model}" on ${command}; running with the provider's default (${errorMessage(err)}).`
|
|
1483
|
+
);
|
|
1404
1484
|
}
|
|
1405
1485
|
}
|
|
1406
1486
|
}
|
|
@@ -1492,6 +1572,7 @@ async function renderPrompt(template, context, workDir) {
|
|
|
1492
1572
|
}
|
|
1493
1573
|
|
|
1494
1574
|
// src/utils/retry.ts
|
|
1575
|
+
var DEFAULT_RETRY_DELAY_MS = 2e3;
|
|
1495
1576
|
function sleep(ms, signal) {
|
|
1496
1577
|
if (signal?.aborted) return Promise.reject(abortError());
|
|
1497
1578
|
return new Promise((resolve4, reject) => {
|
|
@@ -1506,7 +1587,7 @@ function sleep(ms, signal) {
|
|
|
1506
1587
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1507
1588
|
});
|
|
1508
1589
|
}
|
|
1509
|
-
async function withRetry(fn, retries, onRetry, delayMs =
|
|
1590
|
+
async function withRetry(fn, retries, onRetry, delayMs = DEFAULT_RETRY_DELAY_MS, signal, shouldRetry) {
|
|
1510
1591
|
let lastError = new Error("Unknown error");
|
|
1511
1592
|
for (let attempt = 1; attempt <= retries + 1; attempt++) {
|
|
1512
1593
|
if (signal?.aborted) throw abortError();
|
|
@@ -1515,6 +1596,7 @@ async function withRetry(fn, retries, onRetry, delayMs = 2e3, signal) {
|
|
|
1515
1596
|
} catch (err) {
|
|
1516
1597
|
lastError = err;
|
|
1517
1598
|
if (signal?.aborted) throw lastError;
|
|
1599
|
+
if (shouldRetry && !shouldRetry(err, attempt)) throw lastError;
|
|
1518
1600
|
if (attempt <= retries) {
|
|
1519
1601
|
onRetry?.(attempt, err);
|
|
1520
1602
|
await sleep(delayMs * attempt, signal);
|
|
@@ -1523,6 +1605,9 @@ async function withRetry(fn, retries, onRetry, delayMs = 2e3, signal) {
|
|
|
1523
1605
|
}
|
|
1524
1606
|
throw lastError;
|
|
1525
1607
|
}
|
|
1608
|
+
function retryBudgetMs(perAttemptMs, retries, delayMs = DEFAULT_RETRY_DELAY_MS) {
|
|
1609
|
+
return perAttemptMs * (retries + 1) + delayMs * retries * (retries + 1) / 2;
|
|
1610
|
+
}
|
|
1526
1611
|
|
|
1527
1612
|
// src/utils/timeout.ts
|
|
1528
1613
|
function withTimeout(factory, ms, label, signal) {
|
|
@@ -1550,6 +1635,31 @@ function withTimeout(factory, ms, label, signal) {
|
|
|
1550
1635
|
});
|
|
1551
1636
|
}
|
|
1552
1637
|
|
|
1638
|
+
// src/utils/toolSummary.ts
|
|
1639
|
+
var MAX_SUMMARY_COLUMNS = 120;
|
|
1640
|
+
function stringifySafe(value) {
|
|
1641
|
+
if (typeof value === "string") return value;
|
|
1642
|
+
if (value === void 0 || value === null) return "";
|
|
1643
|
+
try {
|
|
1644
|
+
return JSON.stringify(value) ?? "";
|
|
1645
|
+
} catch {
|
|
1646
|
+
return String(value);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
function firstLineTruncated(value) {
|
|
1650
|
+
const text = stringifySafe(value);
|
|
1651
|
+
const newline = text.indexOf("\n");
|
|
1652
|
+
const firstLine = newline === -1 ? text : text.slice(0, newline);
|
|
1653
|
+
if (firstLine.length <= MAX_SUMMARY_COLUMNS) return firstLine;
|
|
1654
|
+
return `${firstLine.slice(0, MAX_SUMMARY_COLUMNS - 1)}\u2026`;
|
|
1655
|
+
}
|
|
1656
|
+
function summarizeToolInput(input) {
|
|
1657
|
+
return firstLineTruncated(input);
|
|
1658
|
+
}
|
|
1659
|
+
function summarizeToolOutput(output) {
|
|
1660
|
+
return firstLineTruncated(output);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1553
1663
|
// src/orchestrator/AgentRunner.ts
|
|
1554
1664
|
var AgentRunner = class {
|
|
1555
1665
|
constructor(eventBus, contextManager, globalWorkDir, config) {
|
|
@@ -1574,6 +1684,8 @@ var AgentRunner = class {
|
|
|
1574
1684
|
});
|
|
1575
1685
|
let attempts = 0;
|
|
1576
1686
|
let output = "";
|
|
1687
|
+
let usage;
|
|
1688
|
+
let costUsd;
|
|
1577
1689
|
try {
|
|
1578
1690
|
const workDir = agent.workDir ? path4.resolve(this.globalWorkDir, agent.workDir) : this.globalWorkDir;
|
|
1579
1691
|
fs3.mkdirSync(workDir, { recursive: true });
|
|
@@ -1581,7 +1693,7 @@ var AgentRunner = class {
|
|
|
1581
1693
|
const retries = agent.retries ?? 0;
|
|
1582
1694
|
const timeout = agent.timeout ?? 6e5;
|
|
1583
1695
|
const tools = agent.tools ?? ["Read", "Write", "Bash"];
|
|
1584
|
-
|
|
1696
|
+
const completed = await withTimeout(
|
|
1585
1697
|
(innerSignal) => withRetry(
|
|
1586
1698
|
async (attempt) => {
|
|
1587
1699
|
attempts = attempt;
|
|
@@ -1596,7 +1708,7 @@ var AgentRunner = class {
|
|
|
1596
1708
|
agentPlan: agent.agentPlan ?? "No step-by-step plan was provided \u2014 use your judgement."
|
|
1597
1709
|
};
|
|
1598
1710
|
const prompt = await renderPrompt(agent.taskPrompt, agentContext, this.globalWorkDir);
|
|
1599
|
-
return
|
|
1711
|
+
return await runtime.execute({
|
|
1600
1712
|
agentId: agent.id,
|
|
1601
1713
|
systemPrompt: agent.systemPrompt,
|
|
1602
1714
|
prompt,
|
|
@@ -1605,6 +1717,11 @@ var AgentRunner = class {
|
|
|
1605
1717
|
timeout,
|
|
1606
1718
|
signal: innerSignal,
|
|
1607
1719
|
model,
|
|
1720
|
+
// Only the caller knows which session to continue; absent means a
|
|
1721
|
+
// fresh one. It must stay `undefined` rather than `""` — the ACP
|
|
1722
|
+
// runtime tests `if (task.sessionId)`, so an empty string would
|
|
1723
|
+
// read as "load this session" for a session that does not exist.
|
|
1724
|
+
sessionId: this.config.resume?.sessions?.[agent.id],
|
|
1608
1725
|
// Resolve through provider/defaults exactly as the CLI's
|
|
1609
1726
|
// availability gate does — an agent that pins `provider` (or
|
|
1610
1727
|
// leans on `defaults`) must launch that provider's bridge, not
|
|
@@ -1612,16 +1729,44 @@ var AgentRunner = class {
|
|
|
1612
1729
|
// here sent a `provider: "claude"` config straight to `opencode`.
|
|
1613
1730
|
command: resolveCommand(agent, this.config),
|
|
1614
1731
|
args: resolveArgs(agent, this.config),
|
|
1732
|
+
// A runtime's diagnostics travel on the event bus rather than
|
|
1733
|
+
// stderr: under the TUI's alternate screen a stderr write is
|
|
1734
|
+
// never seen. The agent name prefixes the line so it is
|
|
1735
|
+
// locatable in the one-row status area.
|
|
1736
|
+
onWarning: (message) => {
|
|
1737
|
+
this.eventBus.emit({
|
|
1738
|
+
type: "orchestration:warning",
|
|
1739
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1740
|
+
payload: { message: `[${agent.name}] ${message}` }
|
|
1741
|
+
});
|
|
1742
|
+
},
|
|
1615
1743
|
onEvent: (e) => {
|
|
1616
1744
|
if ((e.type === "message_delta" || e.type === "message") && e.role === "agent" && e.text) {
|
|
1617
|
-
this.
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1745
|
+
this.emitOutput(agent.id, e.text);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
if (e.type === "tool_call") {
|
|
1749
|
+
const summary = summarizeToolInput(e.input);
|
|
1750
|
+
this.emitOutput(
|
|
1751
|
+
agent.id,
|
|
1752
|
+
`
|
|
1753
|
+
\u203A ${e.name}${summary ? ` ${summary}` : ""}
|
|
1754
|
+
`,
|
|
1755
|
+
"tool"
|
|
1756
|
+
);
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
if (e.type === "tool_result") {
|
|
1760
|
+
const summary = summarizeToolOutput(e.output);
|
|
1761
|
+
this.emitOutput(
|
|
1762
|
+
agent.id,
|
|
1763
|
+
` ${e.isError ? "\u2717" : "\u2713"} ${summary}
|
|
1764
|
+
`,
|
|
1765
|
+
e.isError ? "tool" : "tool-detail"
|
|
1766
|
+
);
|
|
1622
1767
|
}
|
|
1623
1768
|
}
|
|
1624
|
-
})
|
|
1769
|
+
});
|
|
1625
1770
|
},
|
|
1626
1771
|
retries,
|
|
1627
1772
|
(attempt, err) => {
|
|
@@ -1637,13 +1782,21 @@ var AgentRunner = class {
|
|
|
1637
1782
|
}
|
|
1638
1783
|
});
|
|
1639
1784
|
},
|
|
1640
|
-
|
|
1641
|
-
innerSignal
|
|
1785
|
+
DEFAULT_RETRY_DELAY_MS,
|
|
1786
|
+
innerSignal,
|
|
1787
|
+
// A permanent failure (a missing binary, an unsupported capability)
|
|
1788
|
+
// will fail the same way on every attempt, so let the loop fail fast
|
|
1789
|
+
// instead of burning the whole backoff budget on a doomed run.
|
|
1790
|
+
(err) => !isPermanent(err)
|
|
1642
1791
|
),
|
|
1643
|
-
timeout,
|
|
1792
|
+
retryBudgetMs(timeout + KILL_ESCALATION_MS, retries),
|
|
1644
1793
|
agent.name,
|
|
1645
1794
|
signal
|
|
1646
1795
|
);
|
|
1796
|
+
output = completed.output;
|
|
1797
|
+
usage = completed.usage;
|
|
1798
|
+
costUsd = completed.costUsd;
|
|
1799
|
+
const sessionId = completed.sessionId;
|
|
1647
1800
|
const endTime = /* @__PURE__ */ new Date();
|
|
1648
1801
|
const result = {
|
|
1649
1802
|
agentId: agent.id,
|
|
@@ -1653,7 +1806,11 @@ var AgentRunner = class {
|
|
|
1653
1806
|
duration: endTime.getTime() - startTime.getTime(),
|
|
1654
1807
|
attempts,
|
|
1655
1808
|
startTime: startTime.toISOString(),
|
|
1656
|
-
endTime: endTime.toISOString()
|
|
1809
|
+
endTime: endTime.toISOString(),
|
|
1810
|
+
model,
|
|
1811
|
+
...usage ? { usage } : {},
|
|
1812
|
+
...costUsd !== void 0 ? { costUsd } : {},
|
|
1813
|
+
...sessionId ? { sessionId } : {}
|
|
1657
1814
|
};
|
|
1658
1815
|
this.eventBus.emit({
|
|
1659
1816
|
type: "agent:complete",
|
|
@@ -1666,13 +1823,16 @@ var AgentRunner = class {
|
|
|
1666
1823
|
const result = {
|
|
1667
1824
|
agentId: agent.id,
|
|
1668
1825
|
agentName: agent.name,
|
|
1669
|
-
|
|
1826
|
+
// An abort is a cancellation, not a failure: the team stopped because it
|
|
1827
|
+
// was told to, and the roster must not paint every agent as broken.
|
|
1828
|
+
status: isAbortError(err) ? "cancelled" : "failed",
|
|
1670
1829
|
output,
|
|
1671
1830
|
duration: endTime.getTime() - startTime.getTime(),
|
|
1672
1831
|
attempts,
|
|
1673
1832
|
error: err.message,
|
|
1674
1833
|
startTime: startTime.toISOString(),
|
|
1675
|
-
endTime: endTime.toISOString()
|
|
1834
|
+
endTime: endTime.toISOString(),
|
|
1835
|
+
model
|
|
1676
1836
|
};
|
|
1677
1837
|
this.eventBus.emit({
|
|
1678
1838
|
type: "agent:failed",
|
|
@@ -1682,6 +1842,19 @@ var AgentRunner = class {
|
|
|
1682
1842
|
return result;
|
|
1683
1843
|
}
|
|
1684
1844
|
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Publish one chunk of agent output.
|
|
1847
|
+
*
|
|
1848
|
+
* `kind` is omitted for text so the payload shape is unchanged for every
|
|
1849
|
+
* consumer written before tool activity was surfaced.
|
|
1850
|
+
*/
|
|
1851
|
+
emitOutput(agentId, chunk, kind) {
|
|
1852
|
+
this.eventBus.emit({
|
|
1853
|
+
type: "agent:output",
|
|
1854
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1855
|
+
payload: { agentId, chunk, ...kind ? { kind } : {} }
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1685
1858
|
};
|
|
1686
1859
|
|
|
1687
1860
|
// src/orchestrator/ContextManager.ts
|
|
@@ -1736,8 +1909,33 @@ var Orchestrator = class {
|
|
|
1736
1909
|
fs4.mkdirSync(this.workDir, { recursive: true });
|
|
1737
1910
|
this.contextManager = new ContextManager(config.context);
|
|
1738
1911
|
this.contextManager.set("conversation", renderConversation(config.history));
|
|
1912
|
+
this.contextManager.set("upstreamOutputs", "");
|
|
1739
1913
|
this.runner = new AgentRunner(this.eventBus, this.contextManager, this.workDir, config);
|
|
1740
1914
|
}
|
|
1915
|
+
async preview(options = {}) {
|
|
1916
|
+
this.clearStaleTaskPlan();
|
|
1917
|
+
const resolution = await this.resolveTurn(options.signal);
|
|
1918
|
+
if (resolution.kind === "message") {
|
|
1919
|
+
return {
|
|
1920
|
+
name: this.config.name,
|
|
1921
|
+
totalAgents: 0,
|
|
1922
|
+
stages: [],
|
|
1923
|
+
reply: { mode: resolution.mode, message: resolution.message },
|
|
1924
|
+
...this.requirementField()
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
const agents2 = resolution.agents;
|
|
1928
|
+
this.contextManager.set("pipelineAgents", agents2.map((a) => `${a.id}: ${a.name}`).join("\n"));
|
|
1929
|
+
const stages = this.buildStagesOrFallback(agents2, "Could not order the configured agents");
|
|
1930
|
+
const stageInfos = this.stageInfos(stages);
|
|
1931
|
+
return {
|
|
1932
|
+
name: this.config.name,
|
|
1933
|
+
totalAgents: agents2.length,
|
|
1934
|
+
stages: stageInfos,
|
|
1935
|
+
...this.reasonField(resolution.reason),
|
|
1936
|
+
...this.requirementField()
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1741
1939
|
async run(options = {}) {
|
|
1742
1940
|
const startTime = /* @__PURE__ */ new Date();
|
|
1743
1941
|
this.clearStaleTaskPlan();
|
|
@@ -1749,20 +1947,7 @@ var Orchestrator = class {
|
|
|
1749
1947
|
if (agents2.length === 0) return this.finishEmpty(startTime);
|
|
1750
1948
|
this.contextManager.set("pipelineAgents", agents2.map((a) => `${a.id}: ${a.name}`).join("\n"));
|
|
1751
1949
|
let stages = this.buildStagesOrFallback(agents2, "Could not order the configured agents");
|
|
1752
|
-
const
|
|
1753
|
-
return agent.model ?? agent.models?.[agent.runtime ?? "acp"];
|
|
1754
|
-
};
|
|
1755
|
-
const toStageInfos = (list) => list.map((stage, i) => ({
|
|
1756
|
-
stageIndex: i,
|
|
1757
|
-
agents: stage.map((a) => ({
|
|
1758
|
-
id: a.id,
|
|
1759
|
-
name: a.name,
|
|
1760
|
-
icon: a.icon,
|
|
1761
|
-
model: getModel(a),
|
|
1762
|
-
description: a.description,
|
|
1763
|
-
taskTitle: a.taskTitle
|
|
1764
|
-
}))
|
|
1765
|
-
}));
|
|
1950
|
+
const toStageInfos = (list) => this.stageInfos(list);
|
|
1766
1951
|
this.eventBus.emit({
|
|
1767
1952
|
type: "orchestration:start",
|
|
1768
1953
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1770,12 +1955,14 @@ var Orchestrator = class {
|
|
|
1770
1955
|
name: this.config.name,
|
|
1771
1956
|
requirement: this.config.context?.requirement,
|
|
1772
1957
|
totalAgents: agents2.length,
|
|
1773
|
-
stages: toStageInfos(stages)
|
|
1958
|
+
stages: toStageInfos(stages),
|
|
1959
|
+
...this.reasonField(resolution.reason)
|
|
1774
1960
|
}
|
|
1775
1961
|
});
|
|
1776
1962
|
const results = [];
|
|
1777
1963
|
let allSuccess = true;
|
|
1778
1964
|
let cancelled = false;
|
|
1965
|
+
let planApplied = false;
|
|
1779
1966
|
for (let i = 0; i < stages.length; i++) {
|
|
1780
1967
|
const stage = stages[i];
|
|
1781
1968
|
this.eventBus.emit({
|
|
@@ -1785,12 +1972,19 @@ var Orchestrator = class {
|
|
|
1785
1972
|
});
|
|
1786
1973
|
const stageResults = await this.runStage(stage, options.signal);
|
|
1787
1974
|
results.push(...stageResults);
|
|
1975
|
+
this.contextManager.set(
|
|
1976
|
+
"upstreamOutputs",
|
|
1977
|
+
renderUpstreamOutputs(
|
|
1978
|
+
results.filter((r) => r.status === "success").map((r) => ({ agentId: r.agentId, name: r.agentName, output: r.output }))
|
|
1979
|
+
)
|
|
1980
|
+
);
|
|
1788
1981
|
if (options.signal?.aborted) {
|
|
1789
1982
|
cancelled = true;
|
|
1790
1983
|
allSuccess = false;
|
|
1791
1984
|
break;
|
|
1792
1985
|
}
|
|
1793
|
-
if (
|
|
1986
|
+
if (!planApplied && stage.some((a) => this.isPlanner(a)) && stageResults.some((r) => r.status === "success")) {
|
|
1987
|
+
planApplied = true;
|
|
1794
1988
|
await this.applyTaskPlan(agents2);
|
|
1795
1989
|
stages = this.buildStagesOrFallback(
|
|
1796
1990
|
agents2,
|
|
@@ -1827,6 +2021,7 @@ var Orchestrator = class {
|
|
|
1827
2021
|
}
|
|
1828
2022
|
}
|
|
1829
2023
|
const endTime = /* @__PURE__ */ new Date();
|
|
2024
|
+
const totalCostUsd = sumCosts(results);
|
|
1830
2025
|
const result = {
|
|
1831
2026
|
name: this.config.name,
|
|
1832
2027
|
success: allSuccess,
|
|
@@ -1834,7 +2029,10 @@ var Orchestrator = class {
|
|
|
1834
2029
|
agentResults: results,
|
|
1835
2030
|
startTime: startTime.toISOString(),
|
|
1836
2031
|
endTime: endTime.toISOString(),
|
|
1837
|
-
...cancelled ? { cancelled: true } : {}
|
|
2032
|
+
...cancelled ? { cancelled: true } : {},
|
|
2033
|
+
...totalCostUsd !== void 0 ? { totalCostUsd } : {},
|
|
2034
|
+
...this.reasonField(resolution.reason),
|
|
2035
|
+
...this.requirementField()
|
|
1838
2036
|
};
|
|
1839
2037
|
this.eventBus.emit({
|
|
1840
2038
|
type: "orchestration:complete",
|
|
@@ -1843,6 +2041,20 @@ var Orchestrator = class {
|
|
|
1843
2041
|
});
|
|
1844
2042
|
return result;
|
|
1845
2043
|
}
|
|
2044
|
+
stageInfos(stages) {
|
|
2045
|
+
const getModel = (agent) => resolveModel(agent, this.config);
|
|
2046
|
+
return stages.map((stage, i) => ({
|
|
2047
|
+
stageIndex: i,
|
|
2048
|
+
agents: stage.map((a) => ({
|
|
2049
|
+
id: a.id,
|
|
2050
|
+
name: a.name,
|
|
2051
|
+
icon: a.icon,
|
|
2052
|
+
model: getModel(a),
|
|
2053
|
+
description: a.description,
|
|
2054
|
+
taskTitle: a.taskTitle
|
|
2055
|
+
}))
|
|
2056
|
+
}));
|
|
2057
|
+
}
|
|
1846
2058
|
/**
|
|
1847
2059
|
* Order the agents into stages, degrading to declaration order if the graph is
|
|
1848
2060
|
* broken.
|
|
@@ -1856,7 +2068,7 @@ var Orchestrator = class {
|
|
|
1856
2068
|
try {
|
|
1857
2069
|
return buildStages(agents2);
|
|
1858
2070
|
} catch (err) {
|
|
1859
|
-
this.warn(`${context} (${
|
|
2071
|
+
this.warn(`${context} (${errorMessage2(err)}) \u2014 running the agents in declaration order.`);
|
|
1860
2072
|
return [agents2];
|
|
1861
2073
|
}
|
|
1862
2074
|
}
|
|
@@ -1880,6 +2092,12 @@ var Orchestrator = class {
|
|
|
1880
2092
|
* job, and that is what `message` records.
|
|
1881
2093
|
*/
|
|
1882
2094
|
async resolveTurn(signal) {
|
|
2095
|
+
if (this.config.resume) {
|
|
2096
|
+
return {
|
|
2097
|
+
kind: "run",
|
|
2098
|
+
agents: this.selectAgents(this.config.resume.agents, "The resume list")
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
1883
2101
|
if (!this.config.leader || this.config.useLeader === false) {
|
|
1884
2102
|
return { kind: "run", agents: this.configuredAgents() };
|
|
1885
2103
|
}
|
|
@@ -1897,7 +2115,7 @@ var Orchestrator = class {
|
|
|
1897
2115
|
if (decision.mode !== "run") {
|
|
1898
2116
|
return { kind: "message", mode: decision.mode, message: decision.message };
|
|
1899
2117
|
}
|
|
1900
|
-
return { kind: "run", agents: this.selectAgents(decision.run) };
|
|
2118
|
+
return { kind: "run", agents: this.selectAgents(decision.run), reason: decision.reason };
|
|
1901
2119
|
}
|
|
1902
2120
|
const message = result.output.trim();
|
|
1903
2121
|
if (!message) {
|
|
@@ -1916,21 +2134,27 @@ var Orchestrator = class {
|
|
|
1916
2134
|
* prevent, but for the in-memory fields rather than the file.
|
|
1917
2135
|
*/
|
|
1918
2136
|
configuredAgents() {
|
|
1919
|
-
return this.config.agents.map(
|
|
2137
|
+
return this.config.agents.map(
|
|
2138
|
+
(a) => a.dependsOn ? { ...a, dependsOn: [...a.dependsOn] } : { ...a }
|
|
2139
|
+
);
|
|
1920
2140
|
}
|
|
1921
2141
|
/**
|
|
1922
|
-
* Narrow the configured team to the ids
|
|
2142
|
+
* Narrow the configured team to the ids `namedBy` asked for.
|
|
1923
2143
|
*
|
|
1924
2144
|
* Always a subset: an id that is not in the config is reported and dropped
|
|
1925
2145
|
* rather than added, so a typo in a prompt cannot conjure an agent that has no
|
|
1926
2146
|
* definition behind it.
|
|
2147
|
+
*
|
|
2148
|
+
* `namedBy` labels the warning. A resume names agents too — with no leader in
|
|
2149
|
+
* the run at all — so blaming "the leader" for a bad resumed id would send the
|
|
2150
|
+
* reader looking in the wrong place.
|
|
1927
2151
|
*/
|
|
1928
|
-
selectAgents(run) {
|
|
2152
|
+
selectAgents(run, namedBy = "The leader") {
|
|
1929
2153
|
const runSet = new Set(run);
|
|
1930
2154
|
const known = new Set(this.config.agents.map((a) => a.id));
|
|
1931
2155
|
for (const id of run) {
|
|
1932
2156
|
if (!known.has(id)) {
|
|
1933
|
-
this.warn(
|
|
2157
|
+
this.warn(`${namedBy} named "${id}", which is not in the config \u2014 ignoring it.`);
|
|
1934
2158
|
}
|
|
1935
2159
|
}
|
|
1936
2160
|
const skippedIds = new Set(this.config.agents.filter((a) => !runSet.has(a.id)).map((a) => a.id));
|
|
@@ -1951,13 +2175,28 @@ var Orchestrator = class {
|
|
|
1951
2175
|
if (!maxConcurrency || agents2.length <= maxConcurrency) {
|
|
1952
2176
|
return Promise.all(agents2.map((a) => this.runner.run(a, signal)));
|
|
1953
2177
|
}
|
|
1954
|
-
const results =
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2178
|
+
const results = new Array(agents2.length);
|
|
2179
|
+
let next = 0;
|
|
2180
|
+
const workers = Array.from({ length: Math.min(maxConcurrency, agents2.length) }, async () => {
|
|
2181
|
+
while (next < agents2.length) {
|
|
2182
|
+
const index = next;
|
|
2183
|
+
next += 1;
|
|
2184
|
+
results[index] = await this.runner.run(agents2[index], signal);
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
await Promise.all(workers);
|
|
1959
2188
|
return results;
|
|
1960
2189
|
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Whether `agent` owns task decomposition.
|
|
2192
|
+
*
|
|
2193
|
+
* The explicit `role` is the source of truth, so a config can name its planner
|
|
2194
|
+
* anything. The id `"architect"` is kept as a fallback so every config written
|
|
2195
|
+
* before `role` existed keeps working unchanged.
|
|
2196
|
+
*/
|
|
2197
|
+
isPlanner(agent) {
|
|
2198
|
+
return agent.role === "planner" || agent.id === "architect";
|
|
2199
|
+
}
|
|
1961
2200
|
async applyTaskPlan(agents2) {
|
|
1962
2201
|
const taskPlanPath = path5.join(this.workDir, "task-plan.json");
|
|
1963
2202
|
if (!fs4.existsSync(taskPlanPath)) return;
|
|
@@ -1966,7 +2205,7 @@ var Orchestrator = class {
|
|
|
1966
2205
|
taskPlan = JSON.parse(fs4.readFileSync(taskPlanPath, "utf-8"));
|
|
1967
2206
|
} catch (err) {
|
|
1968
2207
|
this.warn(
|
|
1969
|
-
`Could not parse task-plan.json \u2014 continuing with the original agent set: ${
|
|
2208
|
+
`Could not parse task-plan.json \u2014 continuing with the original agent set: ${errorMessage2(err)}`
|
|
1970
2209
|
);
|
|
1971
2210
|
return;
|
|
1972
2211
|
}
|
|
@@ -1996,6 +2235,26 @@ var Orchestrator = class {
|
|
|
1996
2235
|
}
|
|
1997
2236
|
}
|
|
1998
2237
|
}
|
|
2238
|
+
/**
|
|
2239
|
+
* The `requirement` field for a result, present only when the caller set one.
|
|
2240
|
+
*
|
|
2241
|
+
* Spread into every result builder so all three outcomes carry it the same
|
|
2242
|
+
* way: a completion event doubles as the report a resume driver reads back,
|
|
2243
|
+
* and a requirement that never reaches the report cannot be resumed.
|
|
2244
|
+
*/
|
|
2245
|
+
requirementField() {
|
|
2246
|
+
const requirement = this.config.context?.requirement;
|
|
2247
|
+
return requirement !== void 0 ? { requirement } : {};
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* The `selectionReason` field, present only when the leader explained itself.
|
|
2251
|
+
*
|
|
2252
|
+
* Omitted rather than sent as `undefined`/`""` so a run without an
|
|
2253
|
+
* explanation stays byte-identical to one from before the field existed.
|
|
2254
|
+
*/
|
|
2255
|
+
reasonField(reason) {
|
|
2256
|
+
return reason ? { selectionReason: reason } : {};
|
|
2257
|
+
}
|
|
1999
2258
|
/**
|
|
2000
2259
|
* Close out a turn the leader answered rather than planned.
|
|
2001
2260
|
*
|
|
@@ -2012,7 +2271,8 @@ var Orchestrator = class {
|
|
|
2012
2271
|
agentResults: [],
|
|
2013
2272
|
startTime: startTime.toISOString(),
|
|
2014
2273
|
endTime: endTime.toISOString(),
|
|
2015
|
-
reply: { mode, message }
|
|
2274
|
+
reply: { mode, message },
|
|
2275
|
+
...this.requirementField()
|
|
2016
2276
|
};
|
|
2017
2277
|
this.eventBus.emit({
|
|
2018
2278
|
type: "orchestration:complete",
|
|
@@ -2047,7 +2307,8 @@ var Orchestrator = class {
|
|
|
2047
2307
|
totalDuration: endTime.getTime() - startTime.getTime(),
|
|
2048
2308
|
agentResults: [],
|
|
2049
2309
|
startTime: startTime.toISOString(),
|
|
2050
|
-
endTime: endTime.toISOString()
|
|
2310
|
+
endTime: endTime.toISOString(),
|
|
2311
|
+
...this.requirementField()
|
|
2051
2312
|
};
|
|
2052
2313
|
this.eventBus.emit({
|
|
2053
2314
|
type: "orchestration:complete",
|
|
@@ -2057,9 +2318,20 @@ var Orchestrator = class {
|
|
|
2057
2318
|
return result;
|
|
2058
2319
|
}
|
|
2059
2320
|
};
|
|
2060
|
-
function
|
|
2321
|
+
function errorMessage2(err) {
|
|
2061
2322
|
return err instanceof Error ? err.message : String(err);
|
|
2062
2323
|
}
|
|
2324
|
+
function sumCosts(results) {
|
|
2325
|
+
let total = 0;
|
|
2326
|
+
let reported = false;
|
|
2327
|
+
for (const result of results) {
|
|
2328
|
+
if (typeof result.costUsd === "number") {
|
|
2329
|
+
total += result.costUsd;
|
|
2330
|
+
reported = true;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return reported ? total : void 0;
|
|
2334
|
+
}
|
|
2063
2335
|
|
|
2064
2336
|
// src/providers/detect.ts
|
|
2065
2337
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -2091,21 +2363,28 @@ async function detectProviders(opts) {
|
|
|
2091
2363
|
const candidates = [spec.command, ...spec.aliases ?? []];
|
|
2092
2364
|
let matched;
|
|
2093
2365
|
let version;
|
|
2366
|
+
let source = "path";
|
|
2367
|
+
let resolvedPath;
|
|
2094
2368
|
for (const cmd of candidates) {
|
|
2095
2369
|
const exe = resolveExecutable(cmd);
|
|
2096
2370
|
if (!exe) continue;
|
|
2371
|
+
const bundled = exe !== cmd;
|
|
2097
2372
|
if (spec.confirm) {
|
|
2098
2373
|
if (opts?.withVersion) {
|
|
2099
2374
|
const v = await versionFor(exe);
|
|
2100
2375
|
if (v && spec.confirm(v)) {
|
|
2101
2376
|
matched = cmd;
|
|
2102
2377
|
version = v;
|
|
2378
|
+
source = bundled ? "bundled" : "path";
|
|
2379
|
+
resolvedPath = bundled ? exe : void 0;
|
|
2103
2380
|
break;
|
|
2104
2381
|
}
|
|
2105
2382
|
}
|
|
2106
2383
|
continue;
|
|
2107
2384
|
}
|
|
2108
2385
|
matched = cmd;
|
|
2386
|
+
source = bundled ? "bundled" : "path";
|
|
2387
|
+
resolvedPath = bundled ? exe : void 0;
|
|
2109
2388
|
if (opts?.withVersion) {
|
|
2110
2389
|
version = await versionFor(exe);
|
|
2111
2390
|
}
|
|
@@ -2118,7 +2397,9 @@ async function detectProviders(opts) {
|
|
|
2118
2397
|
command: matched,
|
|
2119
2398
|
status: "present",
|
|
2120
2399
|
version,
|
|
2121
|
-
note: spec.auth.hint || void 0
|
|
2400
|
+
note: spec.auth.hint || void 0,
|
|
2401
|
+
source,
|
|
2402
|
+
...resolvedPath !== void 0 ? { resolvedPath } : {}
|
|
2122
2403
|
});
|
|
2123
2404
|
} else {
|
|
2124
2405
|
results.push({
|
|
@@ -2135,6 +2416,22 @@ async function detectProviders(opts) {
|
|
|
2135
2416
|
function presentProviders(detected) {
|
|
2136
2417
|
return detected.filter((d) => d.status === "present").map((d) => d.id);
|
|
2137
2418
|
}
|
|
2419
|
+
|
|
2420
|
+
// src/utils/resumePlan.ts
|
|
2421
|
+
function describeResumePlan({ agents: agents2, sessions = {}, team }) {
|
|
2422
|
+
const teamById = new Map(team.map((member) => [member.id, member]));
|
|
2423
|
+
const running = new Set(agents2);
|
|
2424
|
+
const clauses = agents2.map(
|
|
2425
|
+
(id) => sessions[id] ? `${id} continues its session` : `${id} starts fresh`
|
|
2426
|
+
);
|
|
2427
|
+
for (const id of agents2) {
|
|
2428
|
+
const dropped = (teamById.get(id)?.dependsOn ?? []).filter(
|
|
2429
|
+
(dep) => teamById.has(dep) && !running.has(dep)
|
|
2430
|
+
);
|
|
2431
|
+
if (dropped.length > 0) clauses.push(`${id} runs without ${dropped.join(", ")}`);
|
|
2432
|
+
}
|
|
2433
|
+
return clauses.join("; ");
|
|
2434
|
+
}
|
|
2138
2435
|
export {
|
|
2139
2436
|
ABORT_ERROR_NAME,
|
|
2140
2437
|
AcpRuntime,
|
|
@@ -2147,6 +2444,7 @@ export {
|
|
|
2147
2444
|
agents,
|
|
2148
2445
|
buildStages,
|
|
2149
2446
|
defineConfig,
|
|
2447
|
+
describeResumePlan,
|
|
2150
2448
|
detectProviders,
|
|
2151
2449
|
isAbortError,
|
|
2152
2450
|
presentProviders,
|