@wrongstack/core 0.148.0 → 0.236.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/dist/{agent-bridge-r9y6gdn4.d.ts → agent-bridge-Cimv7bK7.d.ts} +1 -1
- package/dist/{agent-subagent-runner-1GeQE_L0.d.ts → agent-subagent-runner-C658wj_c.d.ts} +9 -8
- package/dist/{brain-Cp_3GIS2.d.ts → brain-sCZ3lCjq.d.ts} +28 -2
- package/dist/{compactor-BueGt7LG.d.ts → compactor-BRfg3QPd.d.ts} +1 -1
- package/dist/{config-BaVThgnT.d.ts → config-Koq6f3fs.d.ts} +2 -2
- package/dist/{context-C7G_MtLV.d.ts → context-CLz3z_E8.d.ts} +126 -2
- package/dist/coordination/index.d.ts +70 -13
- package/dist/coordination/index.js +2126 -151
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +27 -27
- package/dist/defaults/index.js +1328 -354
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +45 -16
- package/dist/execution/index.js +367 -59
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +86 -0
- package/dist/execution/prompt-enhancer.js +125 -0
- package/dist/execution/prompt-enhancer.js.map +1 -0
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js +3 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{goal-preamble-CYJLg0wk.d.ts → goal-preamble-CnbzyVvl.d.ts} +19 -10
- package/dist/{index-BZdezm3g.d.ts → index-BlMqh5GO.d.ts} +8 -8
- package/dist/{index-CPweVoFM.d.ts → index-C2eSNPsB.d.ts} +7 -5
- package/dist/index.d.ts +439 -129
- package/dist/index.js +5206 -905
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +7 -7
- package/dist/infrastructure/index.js +72 -15
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +7 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-CP72f1lC.d.ts → llm-selector-D22R4AFz.d.ts} +2 -2
- package/dist/logger-DmmQhf4P.d.ts +65 -0
- package/dist/{mcp-servers-Bl5LTvQg.d.ts → mcp-servers-DFbirBv6.d.ts} +11 -4
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +89 -9
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-D90K9UnM.d.ts → models-registry-CnJRjTXc.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-QWEzJDlm.d.ts → multi-agent-coordinator-60weDZoA.d.ts} +8 -8
- package/dist/{null-fleet-bus-BUyfqh23.d.ts → null-fleet-bus-1068dEnr.d.ts} +7 -7
- package/dist/observability/index.d.ts +2 -2
- package/dist/package-outdated-watcher-pzJ5w7y8.d.ts +560 -0
- package/dist/{parallel-eternal-engine-C75QuhAI.d.ts → parallel-eternal-engine-DtG1fjc9.d.ts} +13 -9
- package/dist/{path-resolver-DRjQBkoO.d.ts → path-resolver-CA1ULU0J.d.ts} +3 -3
- package/dist/{permission-B7nKnEvQ.d.ts → permission-DbWPbuoA.d.ts} +1 -1
- package/dist/{permission-policy-8-6zBmfA.d.ts → permission-policy-AOk0LVsV.d.ts} +2 -2
- package/dist/pipeline-DsmlwTXu.d.ts +493 -0
- package/dist/{plan-templates-CkKNPU3I.d.ts → plan-templates-DPABrDvy.d.ts} +19 -8
- package/dist/{provider-runner-BNpuIyOL.d.ts → provider-runner-D0HgUqwV.d.ts} +3 -3
- package/dist/{retry-policy-rutAfVeR.d.ts → retry-policy-BVnkbMET.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +358 -85
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-DoISxaKO.d.ts → secret-vault-BJDY28ev.d.ts} +7 -1
- package/dist/{secret-vault-BTcC_T5v.d.ts → secret-vault-CeVNiy_f.d.ts} +4 -3
- package/dist/security/index.d.ts +6 -5
- package/dist/security/index.js +214 -35
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-4vDFZKt3.d.ts → selector-Cb4_9-hf.d.ts} +1 -1
- package/dist/{session-event-bridge-DWlvglC2.d.ts → session-event-bridge-BhtkkFFy.d.ts} +4 -2
- package/dist/{session-reader-BAtCxdaw.d.ts → session-reader-CCOssnBS.d.ts} +1 -1
- package/dist/skills/index.js +171 -21
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +151 -13
- package/dist/storage/index.js +1117 -256
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +68 -21
- package/dist/types/index.js +616 -74
- package/dist/types/index.js.map +1 -1
- package/dist/utils/expect-defined.js +3 -1
- package/dist/utils/expect-defined.js.map +1 -1
- package/dist/utils/index.d.ts +80 -4
- package/dist/utils/index.js +100 -15
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-DD50Omgn.d.ts → wstack-paths-CJjEwPXn.d.ts} +14 -1
- package/package.json +7 -3
- package/skills/chimera/SKILL.md +105 -0
- package/skills/research-web/SKILL.md +342 -0
- package/dist/logger-B9J5puGM.d.ts +0 -32
- package/dist/pipeline-BG7UgbDc.d.ts +0 -239
package/dist/sdd/index.js
CHANGED
|
@@ -617,6 +617,81 @@ function topologicalSort(graph) {
|
|
|
617
617
|
return result;
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
+
// src/types/errors.ts
|
|
621
|
+
var ERROR_CODES = {
|
|
622
|
+
// Provider
|
|
623
|
+
PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
|
|
624
|
+
PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
|
|
625
|
+
PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
|
|
626
|
+
PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
|
|
627
|
+
PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
|
|
628
|
+
PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
|
|
629
|
+
// Agent
|
|
630
|
+
AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
|
|
631
|
+
AGENT_ABORTED: "AGENT_ABORTED",
|
|
632
|
+
AGENT_RUN_FAILED: "AGENT_RUN_FAILED",
|
|
633
|
+
// SDD (Spec-Driven Development)
|
|
634
|
+
SDD_VALIDATION_FAILED: "SDD_VALIDATION_FAILED",
|
|
635
|
+
SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
|
|
636
|
+
SDD_INVALID_STATE: "SDD_INVALID_STATE",
|
|
637
|
+
SDD_NOT_READY: "SDD_NOT_READY"};
|
|
638
|
+
var WrongStackError = class extends Error {
|
|
639
|
+
code;
|
|
640
|
+
subsystem;
|
|
641
|
+
severity;
|
|
642
|
+
recoverable;
|
|
643
|
+
context;
|
|
644
|
+
constructor(opts) {
|
|
645
|
+
super(opts.message, { cause: opts.cause });
|
|
646
|
+
this.name = "WrongStackError";
|
|
647
|
+
this.code = opts.code;
|
|
648
|
+
this.subsystem = opts.subsystem;
|
|
649
|
+
this.severity = opts.severity ?? "error";
|
|
650
|
+
this.recoverable = opts.recoverable ?? false;
|
|
651
|
+
this.context = opts.context;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Render a one-line user-facing description.
|
|
655
|
+
* Subclasses should override for domain-specific formatting.
|
|
656
|
+
*/
|
|
657
|
+
describe() {
|
|
658
|
+
const ctx = this.context ? ` ${formatContext(this.context)}` : "";
|
|
659
|
+
return `${this.code}: ${this.message}${ctx}`;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
function formatContext(ctx) {
|
|
663
|
+
const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
|
|
664
|
+
return parts.length > 0 ? `[${parts.join(" ")}]` : "";
|
|
665
|
+
}
|
|
666
|
+
var AgentError = class extends WrongStackError {
|
|
667
|
+
constructor(opts) {
|
|
668
|
+
super({
|
|
669
|
+
message: opts.message,
|
|
670
|
+
code: opts.code,
|
|
671
|
+
subsystem: "agent",
|
|
672
|
+
severity: opts.code === ERROR_CODES.AGENT_ABORTED ? "warning" : "error",
|
|
673
|
+
recoverable: opts.recoverable ?? opts.code === ERROR_CODES.AGENT_ITERATION_LIMIT,
|
|
674
|
+
context: opts.context,
|
|
675
|
+
cause: opts.cause
|
|
676
|
+
});
|
|
677
|
+
this.name = "AgentError";
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
var SddError = class extends WrongStackError {
|
|
681
|
+
constructor(opts) {
|
|
682
|
+
super({
|
|
683
|
+
message: opts.message,
|
|
684
|
+
code: opts.code,
|
|
685
|
+
subsystem: "sdd",
|
|
686
|
+
severity: opts.code === ERROR_CODES.SDD_PARSE_FAILED ? "warning" : "error",
|
|
687
|
+
recoverable: opts.code === ERROR_CODES.SDD_NOT_READY,
|
|
688
|
+
context: opts.context,
|
|
689
|
+
cause: opts.cause
|
|
690
|
+
});
|
|
691
|
+
this.name = "SddError";
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
|
|
620
695
|
// src/sdd/task-tracker.ts
|
|
621
696
|
var TaskTracker = class {
|
|
622
697
|
constructor(opts) {
|
|
@@ -651,7 +726,10 @@ var TaskTracker = class {
|
|
|
651
726
|
return this.graph;
|
|
652
727
|
}
|
|
653
728
|
addNode(node) {
|
|
654
|
-
if (!this.graph) throw new
|
|
729
|
+
if (!this.graph) throw new SddError({
|
|
730
|
+
message: "No graph loaded",
|
|
731
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
732
|
+
});
|
|
655
733
|
const now = Date.now();
|
|
656
734
|
const newNode = {
|
|
657
735
|
...node,
|
|
@@ -669,7 +747,10 @@ var TaskTracker = class {
|
|
|
669
747
|
return newNode;
|
|
670
748
|
}
|
|
671
749
|
addEdge(from, to, type = "depends_on") {
|
|
672
|
-
if (!this.graph) throw new
|
|
750
|
+
if (!this.graph) throw new SddError({
|
|
751
|
+
message: "No graph loaded",
|
|
752
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
753
|
+
});
|
|
673
754
|
this.graph.edges.push({
|
|
674
755
|
id: crypto.randomUUID(),
|
|
675
756
|
from,
|
|
@@ -680,9 +761,16 @@ var TaskTracker = class {
|
|
|
680
761
|
this.persist();
|
|
681
762
|
}
|
|
682
763
|
updateNodeStatus(id, status, reason) {
|
|
683
|
-
if (!this.graph) throw new
|
|
764
|
+
if (!this.graph) throw new SddError({
|
|
765
|
+
message: "No graph loaded",
|
|
766
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
767
|
+
});
|
|
684
768
|
const node = this.graph.nodes.get(id);
|
|
685
|
-
if (!node) throw new
|
|
769
|
+
if (!node) throw new SddError({
|
|
770
|
+
message: `Node ${id} not found`,
|
|
771
|
+
code: ERROR_CODES.SDD_NOT_READY,
|
|
772
|
+
context: { nodeId: id }
|
|
773
|
+
});
|
|
686
774
|
const from = node.status;
|
|
687
775
|
const now = Date.now();
|
|
688
776
|
node.status = status;
|
|
@@ -705,9 +793,16 @@ var TaskTracker = class {
|
|
|
705
793
|
this.persist();
|
|
706
794
|
}
|
|
707
795
|
updateNode(id, patch) {
|
|
708
|
-
if (!this.graph) throw new
|
|
796
|
+
if (!this.graph) throw new SddError({
|
|
797
|
+
message: "No graph loaded",
|
|
798
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
799
|
+
});
|
|
709
800
|
const node = this.graph.nodes.get(id);
|
|
710
|
-
if (!node) throw new
|
|
801
|
+
if (!node) throw new SddError({
|
|
802
|
+
message: `Node ${id} not found`,
|
|
803
|
+
code: ERROR_CODES.SDD_NOT_READY,
|
|
804
|
+
context: { nodeId: id }
|
|
805
|
+
});
|
|
711
806
|
if (patch.title !== void 0) node.title = patch.title;
|
|
712
807
|
if (patch.description !== void 0) node.description = patch.description;
|
|
713
808
|
if (patch.priority !== void 0) node.priority = patch.priority;
|
|
@@ -826,7 +921,12 @@ var TaskTracker = class {
|
|
|
826
921
|
persist() {
|
|
827
922
|
if (!this.graph) return;
|
|
828
923
|
this.opts.store.saveGraph(this.graph).catch((err) => {
|
|
829
|
-
this.opts.onPersistError ? this.opts.onPersistError(err) : console.warn(
|
|
924
|
+
this.opts.onPersistError ? this.opts.onPersistError(err) : console.warn(JSON.stringify({
|
|
925
|
+
level: "warn",
|
|
926
|
+
event: "task_tracker.save_graph_failed",
|
|
927
|
+
message: err instanceof Error ? err.message : String(err),
|
|
928
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
929
|
+
}));
|
|
830
930
|
});
|
|
831
931
|
}
|
|
832
932
|
};
|
|
@@ -879,12 +979,14 @@ var TaskFlow = class {
|
|
|
879
979
|
const analysis = parser.analyze(this.spec);
|
|
880
980
|
this.emit("spec.analyzed", { analysis });
|
|
881
981
|
if (analysis.completeness < 50) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
982
|
+
const err = new SddError({
|
|
983
|
+
message: `Spec completeness too low: ${analysis.completeness}%`,
|
|
984
|
+
code: ERROR_CODES.SDD_VALIDATION_FAILED,
|
|
985
|
+
context: { completeness: analysis.completeness }
|
|
885
986
|
});
|
|
987
|
+
this.emit("error", { phase: "analyzing", error: err });
|
|
886
988
|
this.setPhase("failed");
|
|
887
|
-
throw
|
|
989
|
+
throw err;
|
|
888
990
|
}
|
|
889
991
|
this.setPhase("generating");
|
|
890
992
|
const generator = new TaskGenerator({ taskTracker: this.opts.tracker });
|
|
@@ -892,7 +994,11 @@ var TaskFlow = class {
|
|
|
892
994
|
return this.graph;
|
|
893
995
|
}
|
|
894
996
|
async execute(ctx) {
|
|
895
|
-
if (!this.graph) throw new
|
|
997
|
+
if (!this.graph) throw new SddError({
|
|
998
|
+
message: "No graph loaded. Call fromSpec first.",
|
|
999
|
+
code: ERROR_CODES.SDD_INVALID_STATE,
|
|
1000
|
+
context: { phase: this.phase }
|
|
1001
|
+
});
|
|
896
1002
|
this.setPhase("executing");
|
|
897
1003
|
this.stopped = false;
|
|
898
1004
|
const pendingTasks = this.getExecutableTasks();
|
|
@@ -932,7 +1038,11 @@ var TaskFlow = class {
|
|
|
932
1038
|
}
|
|
933
1039
|
async reviewTask(taskId, approved, comment) {
|
|
934
1040
|
const task = this.opts.tracker.getNode(taskId);
|
|
935
|
-
if (!task) throw new
|
|
1041
|
+
if (!task) throw new SddError({
|
|
1042
|
+
message: `Task ${taskId} not found`,
|
|
1043
|
+
code: ERROR_CODES.SDD_NOT_READY,
|
|
1044
|
+
context: { taskId }
|
|
1045
|
+
});
|
|
936
1046
|
if (approved) {
|
|
937
1047
|
this.opts.tracker.updateNodeStatus(taskId, "completed", comment);
|
|
938
1048
|
this.emit("task.completed", { taskId });
|
|
@@ -1246,7 +1356,9 @@ var TaskGraphStore = class {
|
|
|
1246
1356
|
// src/utils/expect-defined.ts
|
|
1247
1357
|
function expectDefined(value, label) {
|
|
1248
1358
|
if (value === null || value === void 0) {
|
|
1249
|
-
|
|
1359
|
+
const err = new Error("Expected value to be defined");
|
|
1360
|
+
err.name = "ExpectDefinedError";
|
|
1361
|
+
throw err;
|
|
1250
1362
|
}
|
|
1251
1363
|
return value;
|
|
1252
1364
|
}
|
|
@@ -1584,7 +1696,11 @@ var AISpecBuilder = class {
|
|
|
1584
1696
|
switch (this.session.phase) {
|
|
1585
1697
|
case "questioning":
|
|
1586
1698
|
if (!this.session.spec) {
|
|
1587
|
-
throw new
|
|
1699
|
+
throw new SddError({
|
|
1700
|
+
message: "Cannot approve: no spec generated yet.",
|
|
1701
|
+
code: ERROR_CODES.SDD_INVALID_STATE,
|
|
1702
|
+
context: { phase: "questioning", sessionId: this.session.id }
|
|
1703
|
+
});
|
|
1588
1704
|
}
|
|
1589
1705
|
this.session.phase = "spec_review";
|
|
1590
1706
|
break;
|
|
@@ -1642,7 +1758,11 @@ var AISpecBuilder = class {
|
|
|
1642
1758
|
*/
|
|
1643
1759
|
async saveSpec() {
|
|
1644
1760
|
if (!this.session.spec) {
|
|
1645
|
-
throw new
|
|
1761
|
+
throw new SddError({
|
|
1762
|
+
message: "No spec to save.",
|
|
1763
|
+
code: ERROR_CODES.SDD_NOT_READY,
|
|
1764
|
+
context: { sessionId: this.session.id }
|
|
1765
|
+
});
|
|
1646
1766
|
}
|
|
1647
1767
|
await this.store.save(this.session.spec);
|
|
1648
1768
|
return this.session.spec;
|
|
@@ -1657,17 +1777,30 @@ var AISpecBuilder = class {
|
|
|
1657
1777
|
try {
|
|
1658
1778
|
parsed = JSON.parse(jsonStr);
|
|
1659
1779
|
} catch (e) {
|
|
1660
|
-
throw new
|
|
1780
|
+
throw new SddError({
|
|
1781
|
+
message: "Invalid JSON for spec",
|
|
1782
|
+
code: ERROR_CODES.SDD_PARSE_FAILED,
|
|
1783
|
+
cause: e,
|
|
1784
|
+
context: { detail: e instanceof Error ? e.message : "parse error" }
|
|
1785
|
+
});
|
|
1661
1786
|
}
|
|
1662
1787
|
if (!parsed || typeof parsed !== "object") {
|
|
1663
|
-
throw new
|
|
1788
|
+
throw new SddError({
|
|
1789
|
+
message: "Spec JSON must be an object",
|
|
1790
|
+
code: ERROR_CODES.SDD_VALIDATION_FAILED,
|
|
1791
|
+
context: { actualType: typeof parsed }
|
|
1792
|
+
});
|
|
1664
1793
|
}
|
|
1665
1794
|
const raw = parsed;
|
|
1666
1795
|
const now = Date.now();
|
|
1667
1796
|
const title = String(raw.title ?? this.session.title ?? "Untitled");
|
|
1668
1797
|
const overview = String(raw.overview ?? "");
|
|
1669
1798
|
if (!overview || overview === "undefined") {
|
|
1670
|
-
throw new
|
|
1799
|
+
throw new SddError({
|
|
1800
|
+
message: "Spec must have an overview",
|
|
1801
|
+
code: ERROR_CODES.SDD_VALIDATION_FAILED,
|
|
1802
|
+
context: { field: "overview", title }
|
|
1803
|
+
});
|
|
1671
1804
|
}
|
|
1672
1805
|
const rawSections = Array.isArray(raw.sections) ? raw.sections : [];
|
|
1673
1806
|
const sections = rawSections.filter((s) => s && typeof s === "object").map((s) => ({
|
|
@@ -2211,9 +2344,11 @@ function computeParallelGroups(graph, blockedByMap) {
|
|
|
2211
2344
|
|
|
2212
2345
|
// src/utils/assert-never.ts
|
|
2213
2346
|
function assertNever(x, message) {
|
|
2214
|
-
|
|
2347
|
+
const err = new Error(
|
|
2215
2348
|
`Unhandled case: ${JSON.stringify(x)}`
|
|
2216
2349
|
);
|
|
2350
|
+
err.name = "AssertNeverError";
|
|
2351
|
+
throw err;
|
|
2217
2352
|
}
|
|
2218
2353
|
|
|
2219
2354
|
// src/sdd/spec-versioning.ts
|
|
@@ -2987,6 +3122,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2987
3122
|
function makeAgentSubagentRunner(opts) {
|
|
2988
3123
|
const format = opts.formatTaskInput ?? defaultFormatTaskInput;
|
|
2989
3124
|
return async (task, ctx) => {
|
|
3125
|
+
const taskStartedAt = Date.now();
|
|
2990
3126
|
const factoryResult = await opts.factory(ctx.config);
|
|
2991
3127
|
const { agent, events } = factoryResult;
|
|
2992
3128
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
@@ -3083,7 +3219,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3083
3219
|
}),
|
|
3084
3220
|
events.on("provider.text_delta", (e) => {
|
|
3085
3221
|
ctx.budget.markActivity();
|
|
3086
|
-
streamingTextAcc = (streamingTextAcc + e.text).slice(-
|
|
3222
|
+
streamingTextAcc = (streamingTextAcc + e.text).slice(-2e3);
|
|
3087
3223
|
})
|
|
3088
3224
|
);
|
|
3089
3225
|
const onParentAbort = () => aborter.abort();
|
|
@@ -3091,6 +3227,15 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3091
3227
|
let result;
|
|
3092
3228
|
try {
|
|
3093
3229
|
result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
|
|
3230
|
+
events.emit("subagent.task_completed", {
|
|
3231
|
+
subagentId: ctx.subagentId,
|
|
3232
|
+
taskId: task.id,
|
|
3233
|
+
status: result.status === "done" ? "success" : "failed",
|
|
3234
|
+
iterations: result.iterations,
|
|
3235
|
+
toolCalls: ctx.budget.usage().toolCalls,
|
|
3236
|
+
durationMs: Date.now() - taskStartedAt,
|
|
3237
|
+
finalText: result.finalText?.trim() || void 0
|
|
3238
|
+
});
|
|
3094
3239
|
} finally {
|
|
3095
3240
|
detachFleet?.();
|
|
3096
3241
|
ctx.signal.removeEventListener("abort", onParentAbort);
|
|
@@ -3126,21 +3271,40 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3126
3271
|
if (budgetError) throw budgetError;
|
|
3127
3272
|
}
|
|
3128
3273
|
if (result.status === "failed") {
|
|
3129
|
-
throw result.error instanceof
|
|
3274
|
+
throw result.error instanceof AgentError ? result.error : new AgentError({
|
|
3275
|
+
message: result.error instanceof Error ? result.error.message : String(result.error ?? "agent failed"),
|
|
3276
|
+
code: ERROR_CODES.AGENT_RUN_FAILED,
|
|
3277
|
+
cause: result.error
|
|
3278
|
+
});
|
|
3130
3279
|
}
|
|
3131
3280
|
if (result.status === "aborted") {
|
|
3132
|
-
throw new
|
|
3281
|
+
throw new AgentError({
|
|
3282
|
+
message: "agent aborted",
|
|
3283
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
3284
|
+
});
|
|
3133
3285
|
}
|
|
3134
3286
|
if (result.status === "max_iterations") {
|
|
3135
|
-
throw new
|
|
3287
|
+
throw new AgentError({
|
|
3288
|
+
message: "agent exhausted iteration limit",
|
|
3289
|
+
code: ERROR_CODES.AGENT_ITERATION_LIMIT,
|
|
3290
|
+
recoverable: true
|
|
3291
|
+
});
|
|
3136
3292
|
}
|
|
3137
3293
|
const usage = ctx.budget.usage();
|
|
3138
3294
|
const finalText = (result.finalText ?? "").trim();
|
|
3139
3295
|
if (finalText.length === 0 && usage.toolCalls === 0) {
|
|
3140
|
-
throw new
|
|
3296
|
+
throw new AgentError({
|
|
3297
|
+
message: "empty response \u2014 agent produced no text and no tool calls",
|
|
3298
|
+
code: ERROR_CODES.AGENT_RUN_FAILED,
|
|
3299
|
+
context: { iterations: result.iterations }
|
|
3300
|
+
});
|
|
3141
3301
|
}
|
|
3142
3302
|
if (finalText.length === 0 && lastToolFailed !== null) {
|
|
3143
|
-
throw new
|
|
3303
|
+
throw new AgentError({
|
|
3304
|
+
message: `unrecovered tool failure: ${lastToolFailed} \u2014 agent ended turn without acknowledging the error`,
|
|
3305
|
+
code: ERROR_CODES.AGENT_RUN_FAILED,
|
|
3306
|
+
context: { tool: lastToolFailed, iterations: result.iterations }
|
|
3307
|
+
});
|
|
3144
3308
|
}
|
|
3145
3309
|
return {
|
|
3146
3310
|
result: result.finalText,
|
|
@@ -3153,44 +3317,6 @@ function defaultFormatTaskInput(task) {
|
|
|
3153
3317
|
return task.description ?? "";
|
|
3154
3318
|
}
|
|
3155
3319
|
|
|
3156
|
-
// src/types/errors.ts
|
|
3157
|
-
var ERROR_CODES = {
|
|
3158
|
-
// Provider
|
|
3159
|
-
PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
|
|
3160
|
-
PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
|
|
3161
|
-
PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
|
|
3162
|
-
PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
|
|
3163
|
-
PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
|
|
3164
|
-
PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR"};
|
|
3165
|
-
var WrongStackError = class extends Error {
|
|
3166
|
-
code;
|
|
3167
|
-
subsystem;
|
|
3168
|
-
severity;
|
|
3169
|
-
recoverable;
|
|
3170
|
-
context;
|
|
3171
|
-
constructor(opts) {
|
|
3172
|
-
super(opts.message, { cause: opts.cause });
|
|
3173
|
-
this.name = "WrongStackError";
|
|
3174
|
-
this.code = opts.code;
|
|
3175
|
-
this.subsystem = opts.subsystem;
|
|
3176
|
-
this.severity = opts.severity ?? "error";
|
|
3177
|
-
this.recoverable = opts.recoverable ?? false;
|
|
3178
|
-
this.context = opts.context;
|
|
3179
|
-
}
|
|
3180
|
-
/**
|
|
3181
|
-
* Render a one-line user-facing description.
|
|
3182
|
-
* Subclasses should override for domain-specific formatting.
|
|
3183
|
-
*/
|
|
3184
|
-
describe() {
|
|
3185
|
-
const ctx = this.context ? ` ${formatContext(this.context)}` : "";
|
|
3186
|
-
return `${this.code}: ${this.message}${ctx}`;
|
|
3187
|
-
}
|
|
3188
|
-
};
|
|
3189
|
-
function formatContext(ctx) {
|
|
3190
|
-
const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
|
|
3191
|
-
return parts.length > 0 ? `[${parts.join(" ")}]` : "";
|
|
3192
|
-
}
|
|
3193
|
-
|
|
3194
3320
|
// src/types/provider.ts
|
|
3195
3321
|
var ProviderError = class extends WrongStackError {
|
|
3196
3322
|
status;
|
|
@@ -3265,6 +3391,9 @@ function providerStatusToCode(status, type) {
|
|
|
3265
3391
|
|
|
3266
3392
|
// src/coordination/coordinator/error-classifier.ts
|
|
3267
3393
|
function classifySubagentError(err, hints = {}) {
|
|
3394
|
+
if (err instanceof AgentError && err.cause) {
|
|
3395
|
+
return classifySubagentError(err.cause, hints);
|
|
3396
|
+
}
|
|
3268
3397
|
const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
|
|
3269
3398
|
if (err instanceof ProviderError) {
|
|
3270
3399
|
const baseMessage2 = err.describe();
|
|
@@ -3297,7 +3426,7 @@ function classifySubagentError(err, hints = {}) {
|
|
|
3297
3426
|
if (/agent exhausted iteration limit$/i.test(baseMessage)) {
|
|
3298
3427
|
return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
|
|
3299
3428
|
}
|
|
3300
|
-
if (/empty response
|
|
3429
|
+
if (/empty response/i.test(baseMessage)) {
|
|
3301
3430
|
return { kind: "empty_response", message: baseMessage, retryable: false, cause };
|
|
3302
3431
|
}
|
|
3303
3432
|
if (/^tool failed: /i.test(baseMessage)) {
|
|
@@ -3347,11 +3476,11 @@ var HEAVY_BUDGET = {
|
|
|
3347
3476
|
};
|
|
3348
3477
|
var TOOLS = {
|
|
3349
3478
|
/** Pure read/inspect — safe for analysis and review agents. */
|
|
3350
|
-
read: ["read", "grep", "glob", "search", "tree"],
|
|
3479
|
+
read: ["read", "grep", "glob", "search", "tree", "mailbox"],
|
|
3351
3480
|
/** Read + structured inspection (logs, diffs, json, dependency audit). */
|
|
3352
|
-
inspect: ["read", "grep", "glob", "search", "tree", "json", "diff", "logs", "audit"],
|
|
3481
|
+
inspect: ["read", "grep", "glob", "search", "tree", "json", "diff", "logs", "audit", "mailbox"],
|
|
3353
3482
|
/** Read + edit (no shell). For agents that write code/docs but don't run it. */
|
|
3354
|
-
write: ["read", "grep", "glob", "search", "tree", "write", "edit", "replace", "patch"],
|
|
3483
|
+
write: ["read", "grep", "glob", "search", "tree", "write", "edit", "replace", "patch", "mailbox"],
|
|
3355
3484
|
/** Full build loop: edit + run (lint/format/typecheck/test/bash). */
|
|
3356
3485
|
build: [
|
|
3357
3486
|
"read",
|
|
@@ -3368,16 +3497,17 @@ var TOOLS = {
|
|
|
3368
3497
|
"lint",
|
|
3369
3498
|
"format",
|
|
3370
3499
|
"typecheck",
|
|
3371
|
-
"test"
|
|
3500
|
+
"test",
|
|
3501
|
+
"mailbox"
|
|
3372
3502
|
],
|
|
3373
3503
|
/** Version control. */
|
|
3374
3504
|
vcs: ["read", "grep", "glob", "git", "diff"],
|
|
3375
3505
|
/** Dependency management + CVE audit. */
|
|
3376
|
-
deps: ["read", "grep", "glob", "install", "outdated", "audit", "json"],
|
|
3506
|
+
deps: ["read", "grep", "glob", "install", "outdated", "audit", "json", "mailbox"],
|
|
3377
3507
|
/** Documentation authoring. */
|
|
3378
|
-
docs: ["read", "grep", "glob", "search", "tree", "write", "edit", "document"],
|
|
3508
|
+
docs: ["read", "grep", "glob", "search", "tree", "write", "edit", "document", "mailbox"],
|
|
3379
3509
|
/** Web research. */
|
|
3380
|
-
research: ["read", "grep", "glob", "search", "fetch"]
|
|
3510
|
+
research: ["read", "grep", "glob", "search", "fetch", "mailbox"]
|
|
3381
3511
|
};
|
|
3382
3512
|
|
|
3383
3513
|
// src/coordination/agents/phase1-discovery.ts
|
|
@@ -4175,15 +4305,44 @@ Working rules:
|
|
|
4175
4305
|
id: "e2e",
|
|
4176
4306
|
name: "E2E",
|
|
4177
4307
|
role: "e2e",
|
|
4178
|
-
tools: [
|
|
4308
|
+
tools: [
|
|
4309
|
+
...TOOLS.build,
|
|
4310
|
+
"fetch",
|
|
4311
|
+
"playwright_navigate",
|
|
4312
|
+
"playwright_screenshot",
|
|
4313
|
+
"playwright_click",
|
|
4314
|
+
"playwright_type",
|
|
4315
|
+
"playwright_evaluate",
|
|
4316
|
+
"playwright_select_option",
|
|
4317
|
+
"playwright_hover",
|
|
4318
|
+
"playwright_fill_form",
|
|
4319
|
+
"playwright_wait_for",
|
|
4320
|
+
"playwright_press_key",
|
|
4321
|
+
"playwright_drag"
|
|
4322
|
+
],
|
|
4179
4323
|
prompt: `You are the E2E agent. Your job is end-to-end testing: drive the whole
|
|
4180
4324
|
system the way a user would and verify the full flow works across boundaries.
|
|
4181
4325
|
|
|
4182
4326
|
Scope:
|
|
4183
4327
|
- Author end-to-end scenarios that exercise real user journeys
|
|
4184
4328
|
- Drive UI/CLI/API across process and network boundaries
|
|
4329
|
+
- Use Playwright browser tools (navigate, click, type, screenshot, evaluate)
|
|
4330
|
+
to automate web UI flows \u2014 open pages, interact with forms, capture evidence
|
|
4185
4331
|
- Set up and tear down realistic test state
|
|
4186
|
-
- Capture failures with enough detail to reproduce (screenshots, logs)
|
|
4332
|
+
- Capture failures with enough detail to reproduce (screenshots, logs, page HTML)
|
|
4333
|
+
|
|
4334
|
+
Playwright tools available (require the "playwright" MCP server to be enabled):
|
|
4335
|
+
playwright_navigate(url) \u2014 open a page at the given URL
|
|
4336
|
+
playwright_screenshot() \u2014 capture a full-page or viewport screenshot
|
|
4337
|
+
playwright_click(selector) \u2014 click on an element matching a CSS selector
|
|
4338
|
+
playwright_type(selector, text) \u2014 type text into a focused input element
|
|
4339
|
+
playwright_evaluate(script) \u2014 run arbitrary JavaScript in the page context
|
|
4340
|
+
playwright_select_option(selector, value) \u2014 pick a <select> dropdown option
|
|
4341
|
+
playwright_hover(selector) \u2014 hover the mouse over an element
|
|
4342
|
+
playwright_fill_form(fields) \u2014 fill multiple form fields in one call
|
|
4343
|
+
playwright_wait_for(selector) \u2014 block until an element appears on the page
|
|
4344
|
+
playwright_press_key(key) \u2014 press a keyboard key (Enter, Tab, Escape, \u2026)
|
|
4345
|
+
playwright_drag(from, to) \u2014 drag an element from one selector to another
|
|
4187
4346
|
|
|
4188
4347
|
Input format you accept:
|
|
4189
4348
|
{ "task": "scenario | smoke | journey", "flow": "<user journey>", "surface": "ui | cli | api" }
|
|
@@ -4197,8 +4356,10 @@ Output: Markdown e2e report:
|
|
|
4197
4356
|
Working rules:
|
|
4198
4357
|
- Test the real flow end to end; don't stub the thing under test
|
|
4199
4358
|
- Make scenarios deterministic \u2014 control time, randomness, and external state
|
|
4200
|
-
- On failure, capture artifacts (logs
|
|
4201
|
-
- Keep scenarios independent so one failure doesn't cascade
|
|
4359
|
+
- On failure, capture artifacts (screenshots, page HTML, logs) for reproduction
|
|
4360
|
+
- Keep scenarios independent so one failure doesn't cascade
|
|
4361
|
+
- For browser tests: playwright_navigate first, then interact, then playwright_screenshot as evidence
|
|
4362
|
+
- If playwright tools are unavailable, report it and fall back to API/CLI testing`
|
|
4202
4363
|
},
|
|
4203
4364
|
budget: HEAVY_BUDGET,
|
|
4204
4365
|
capability: {
|
|
@@ -4211,10 +4372,106 @@ Working rules:
|
|
|
4211
4372
|
"user journey",
|
|
4212
4373
|
"smoke test",
|
|
4213
4374
|
"playwright",
|
|
4375
|
+
"browser",
|
|
4376
|
+
"screenshot",
|
|
4377
|
+
"web ui",
|
|
4378
|
+
"headless",
|
|
4214
4379
|
"cypress",
|
|
4215
4380
|
"full flow",
|
|
4216
4381
|
"browser test",
|
|
4217
|
-
"acceptance test"
|
|
4382
|
+
"acceptance test",
|
|
4383
|
+
"navigate",
|
|
4384
|
+
"click",
|
|
4385
|
+
"form fill",
|
|
4386
|
+
"dom",
|
|
4387
|
+
"page load"
|
|
4388
|
+
]
|
|
4389
|
+
}
|
|
4390
|
+
},
|
|
4391
|
+
{
|
|
4392
|
+
config: {
|
|
4393
|
+
id: "browser",
|
|
4394
|
+
name: "Browser",
|
|
4395
|
+
role: "browser",
|
|
4396
|
+
tools: [
|
|
4397
|
+
...TOOLS.read,
|
|
4398
|
+
"fetch",
|
|
4399
|
+
"playwright_navigate",
|
|
4400
|
+
"playwright_screenshot",
|
|
4401
|
+
"playwright_click",
|
|
4402
|
+
"playwright_type",
|
|
4403
|
+
"playwright_evaluate",
|
|
4404
|
+
"playwright_select_option",
|
|
4405
|
+
"playwright_hover",
|
|
4406
|
+
"playwright_fill_form",
|
|
4407
|
+
"playwright_wait_for",
|
|
4408
|
+
"playwright_press_key",
|
|
4409
|
+
"playwright_drag"
|
|
4410
|
+
],
|
|
4411
|
+
prompt: `You are the Browser agent. Your job is browser automation: open web pages,
|
|
4412
|
+
interact with them, extract data, capture screenshots, and return structured
|
|
4413
|
+
results. You are a read-focused agent \u2014 you drive the browser, not the filesystem.
|
|
4414
|
+
|
|
4415
|
+
Scope:
|
|
4416
|
+
- Navigate to URLs and wait for pages to load
|
|
4417
|
+
- Take full-page or element screenshots as evidence
|
|
4418
|
+
- Click buttons, fill forms, select options, type text \u2014 full user simulation
|
|
4419
|
+
- Extract page content: text, HTML, element attributes, data tables
|
|
4420
|
+
- Evaluate JavaScript in the page context to extract structured data
|
|
4421
|
+
- Verify visual state (element visibility, text content, attribute values)
|
|
4422
|
+
|
|
4423
|
+
Playwright tools available (require the "playwright" MCP server to be enabled):
|
|
4424
|
+
playwright_navigate(url) \u2014 open a page at the given URL
|
|
4425
|
+
playwright_screenshot() \u2014 capture a full-page or viewport screenshot
|
|
4426
|
+
playwright_click(selector) \u2014 click on an element matching a CSS selector
|
|
4427
|
+
playwright_type(selector, text) \u2014 type text into a focused input element
|
|
4428
|
+
playwright_evaluate(script) \u2014 run arbitrary JavaScript in the page context
|
|
4429
|
+
playwright_select_option(selector, value) \u2014 pick a <select> dropdown option
|
|
4430
|
+
playwright_hover(selector) \u2014 hover the mouse over an element
|
|
4431
|
+
playwright_fill_form(fields) \u2014 fill multiple form fields in one call
|
|
4432
|
+
playwright_wait_for(selector) \u2014 block until an element appears on the page
|
|
4433
|
+
playwright_press_key(key) \u2014 press a keyboard key (Enter, Tab, Escape, \u2026)
|
|
4434
|
+
playwright_drag(from, to) \u2014 drag an element from one selector to another
|
|
4435
|
+
|
|
4436
|
+
Input format you accept:
|
|
4437
|
+
{ "task": "navigate | screenshot | extract | interact | verify", "url": "<url>", "steps": ["step1", "step2"] }
|
|
4438
|
+
|
|
4439
|
+
Output: Structured markdown report:
|
|
4440
|
+
- ## Page (URL, title, load status)
|
|
4441
|
+
- ## Actions Taken (step-by-step with timestamps)
|
|
4442
|
+
- ## Results (extracted data, element states, verification results)
|
|
4443
|
+
- ## Screenshots (list attached screenshot references)
|
|
4444
|
+
- ## Errors (any failures with stack traces)
|
|
4445
|
+
|
|
4446
|
+
Working rules:
|
|
4447
|
+
- Always playwright_navigate first before any interaction
|
|
4448
|
+
- Always playwright_wait_for after navigation to ensure the page is ready
|
|
4449
|
+
- playwright_screenshot is your primary evidence \u2014 use it before and after interactions
|
|
4450
|
+
- Use playwright_evaluate for structured data extraction (JSON, text content)
|
|
4451
|
+
- If a selector fails, try alternative selectors before giving up
|
|
4452
|
+
- Report exact CSS selectors used \u2014 they're part of the evidence
|
|
4453
|
+
- If playwright tools are unavailable, report the error immediately \u2014 do not guess`
|
|
4454
|
+
},
|
|
4455
|
+
budget: MEDIUM_BUDGET,
|
|
4456
|
+
capability: {
|
|
4457
|
+
phase: "verify",
|
|
4458
|
+
summary: "Browser automation: opens pages, clicks, types, screenshots, extracts data via Playwright headless Chromium.",
|
|
4459
|
+
keywords: [
|
|
4460
|
+
"browser",
|
|
4461
|
+
"screenshot",
|
|
4462
|
+
"navigate",
|
|
4463
|
+
"web page",
|
|
4464
|
+
"scrape",
|
|
4465
|
+
"crawl",
|
|
4466
|
+
"headless",
|
|
4467
|
+
"chrome",
|
|
4468
|
+
"open url",
|
|
4469
|
+
"capture",
|
|
4470
|
+
"page title",
|
|
4471
|
+
"extract data",
|
|
4472
|
+
"fill form",
|
|
4473
|
+
"click button",
|
|
4474
|
+
"take screenshot"
|
|
4218
4475
|
]
|
|
4219
4476
|
}
|
|
4220
4477
|
},
|
|
@@ -5663,7 +5920,7 @@ Working rules:
|
|
|
5663
5920
|
id: "tech-stack",
|
|
5664
5921
|
name: "Tech Stack Validator",
|
|
5665
5922
|
role: "tech-stack",
|
|
5666
|
-
tools: ["search", "fetch", "read", "grep", "glob", "outdated", "audit", "json"],
|
|
5923
|
+
tools: ["search", "fetch", "read", "grep", "glob", "outdated", "audit", "json", "mailbox"],
|
|
5667
5924
|
prompt: `You are the Tech Stack Validator \u2014 a single-shot validation agent that fires
|
|
5668
5925
|
before any package, library, or framework choice is committed.
|
|
5669
5926
|
|
|
@@ -5671,6 +5928,16 @@ Your ONLY job: verify that a technology choice is current, real, and not obsolet
|
|
|
5671
5928
|
You are the "this isn't code, this is 10-year-old technology" agent. Intervene
|
|
5672
5929
|
hard when the LLM hallucinates a version number or suggests dead tech.
|
|
5673
5930
|
|
|
5931
|
+
## Before you begin
|
|
5932
|
+
|
|
5933
|
+
Check the inter-agent mailbox for pending tasks. Other agents or the file-watcher
|
|
5934
|
+
may have left assign messages with dependency files to audit:
|
|
5935
|
+
- mailbox action=check
|
|
5936
|
+
|
|
5937
|
+
If you find an assign message, use the specified file path and packages.
|
|
5938
|
+
When done, post results back:
|
|
5939
|
+
- mailbox action=send to=<sender> type=result subject="Tech stack audit results" body="..."
|
|
5940
|
+
|
|
5674
5941
|
## Critical rules
|
|
5675
5942
|
|
|
5676
5943
|
1. **Verify existence.** Search npm registry (fetch https://registry.npmjs.org/<pkg>/latest)
|
|
@@ -5729,11 +5996,11 @@ When APPROVED:
|
|
|
5729
5996
|
**Install**: pnpm add <name>@^<major>.<minor>.0`
|
|
5730
5997
|
},
|
|
5731
5998
|
budget: {
|
|
5732
|
-
timeoutMs:
|
|
5733
|
-
maxIterations:
|
|
5734
|
-
maxToolCalls:
|
|
5735
|
-
maxTokens:
|
|
5736
|
-
maxCostUsd: 0.
|
|
5999
|
+
timeoutMs: 12e4,
|
|
6000
|
+
maxIterations: 10,
|
|
6001
|
+
maxToolCalls: 40,
|
|
6002
|
+
maxTokens: 6e4,
|
|
6003
|
+
maxCostUsd: 0.25
|
|
5737
6004
|
},
|
|
5738
6005
|
capability: {
|
|
5739
6006
|
phase: "meta",
|
|
@@ -6774,7 +7041,10 @@ var SddParallelRun = class {
|
|
|
6774
7041
|
"\u2022 Do not ask before routine in-project tool use; if a permission gate appears, wait for that flow.",
|
|
6775
7042
|
"\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
|
|
6776
7043
|
].join("\n");
|
|
6777
|
-
if (!this.coordinator) throw new
|
|
7044
|
+
if (!this.coordinator) throw new SddError({
|
|
7045
|
+
message: "SDD parallel runner requires a coordinator",
|
|
7046
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
7047
|
+
});
|
|
6778
7048
|
const coordinator = this.coordinator;
|
|
6779
7049
|
const spawns = subagentIds.map(
|
|
6780
7050
|
(subagentId) => coordinator.spawn({
|
|
@@ -6786,7 +7056,10 @@ var SddParallelRun = class {
|
|
|
6786
7056
|
);
|
|
6787
7057
|
const spawnResults = await Promise.all(spawns);
|
|
6788
7058
|
if (!spawnResults.every((r) => Boolean(r.subagentId))) {
|
|
6789
|
-
throw new
|
|
7059
|
+
throw new SddError({
|
|
7060
|
+
message: "One or more subagent spawns failed",
|
|
7061
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
7062
|
+
});
|
|
6790
7063
|
}
|
|
6791
7064
|
const assignPromises = tasks.map((task, i) => {
|
|
6792
7065
|
const spec = {
|