@wrongstack/core 0.1.7 → 0.1.8
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/defaults/index.d.ts +492 -3
- package/dist/defaults/index.js +862 -5
- package/dist/defaults/index.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +869 -12
- package/dist/index.js.map +1 -1
- package/dist/{session-reader-7AutWHut.d.ts → session-reader-9sOTgmeC.d.ts} +31 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1383,11 +1383,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
1383
1383
|
walk(value, schema, "", errors);
|
|
1384
1384
|
return { ok: errors.length === 0, errors };
|
|
1385
1385
|
}
|
|
1386
|
-
function walk(value, schema,
|
|
1386
|
+
function walk(value, schema, path17, errors) {
|
|
1387
1387
|
if (schema.enum !== void 0) {
|
|
1388
1388
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1389
1389
|
errors.push({
|
|
1390
|
-
path:
|
|
1390
|
+
path: path17 || "<root>",
|
|
1391
1391
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1392
1392
|
});
|
|
1393
1393
|
return;
|
|
@@ -1396,7 +1396,7 @@ function walk(value, schema, path15, errors) {
|
|
|
1396
1396
|
if (typeof schema.type === "string") {
|
|
1397
1397
|
if (!checkType(value, schema.type)) {
|
|
1398
1398
|
errors.push({
|
|
1399
|
-
path:
|
|
1399
|
+
path: path17 || "<root>",
|
|
1400
1400
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1401
1401
|
});
|
|
1402
1402
|
return;
|
|
@@ -1406,19 +1406,19 @@ function walk(value, schema, path15, errors) {
|
|
|
1406
1406
|
const obj = value;
|
|
1407
1407
|
for (const req of schema.required ?? []) {
|
|
1408
1408
|
if (!(req in obj)) {
|
|
1409
|
-
errors.push({ path: joinPath(
|
|
1409
|
+
errors.push({ path: joinPath(path17, req), message: "required property missing" });
|
|
1410
1410
|
}
|
|
1411
1411
|
}
|
|
1412
1412
|
if (schema.properties) {
|
|
1413
1413
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1414
1414
|
if (key in obj) {
|
|
1415
|
-
walk(obj[key], subSchema, joinPath(
|
|
1415
|
+
walk(obj[key], subSchema, joinPath(path17, key), errors);
|
|
1416
1416
|
}
|
|
1417
1417
|
}
|
|
1418
1418
|
}
|
|
1419
1419
|
}
|
|
1420
1420
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1421
|
-
value.forEach((item, i) => walk(item, schema.items, `${
|
|
1421
|
+
value.forEach((item, i) => walk(item, schema.items, `${path17}[${i}]`, errors));
|
|
1422
1422
|
}
|
|
1423
1423
|
}
|
|
1424
1424
|
function checkType(value, type) {
|
|
@@ -4710,6 +4710,182 @@ function defaultFormatTaskInput(task) {
|
|
|
4710
4710
|
return task.description ?? "";
|
|
4711
4711
|
}
|
|
4712
4712
|
|
|
4713
|
+
// src/defaults/fleet-bus.ts
|
|
4714
|
+
var FleetBus = class {
|
|
4715
|
+
byId = /* @__PURE__ */ new Map();
|
|
4716
|
+
byType = /* @__PURE__ */ new Map();
|
|
4717
|
+
any = /* @__PURE__ */ new Set();
|
|
4718
|
+
/**
|
|
4719
|
+
* Hook a subagent's EventBus into the fleet. EventBus is strongly
|
|
4720
|
+
* typed and doesn't expose an `onAny` hook, so we subscribe to the
|
|
4721
|
+
* canonical set of event types a subagent emits during a run. New
|
|
4722
|
+
* event types added to the kernel must be added here too — but the
|
|
4723
|
+
* cost is a tiny single line per type, and the explicit list keeps
|
|
4724
|
+
* the wire format clear.
|
|
4725
|
+
*
|
|
4726
|
+
* Returns a disposer that detaches every subscription; call on
|
|
4727
|
+
* subagent teardown so the listeners don't outlive the run.
|
|
4728
|
+
*/
|
|
4729
|
+
attach(subagentId, bus, taskId) {
|
|
4730
|
+
const FORWARDED_TYPES = [
|
|
4731
|
+
"tool.started",
|
|
4732
|
+
"tool.executed",
|
|
4733
|
+
"tool.progress",
|
|
4734
|
+
"tool.confirm_needed",
|
|
4735
|
+
"iteration.started",
|
|
4736
|
+
"iteration.completed",
|
|
4737
|
+
"provider.text_delta",
|
|
4738
|
+
"provider.response",
|
|
4739
|
+
"provider.retry",
|
|
4740
|
+
"provider.error",
|
|
4741
|
+
"session.started",
|
|
4742
|
+
"session.ended",
|
|
4743
|
+
"token.threshold"
|
|
4744
|
+
];
|
|
4745
|
+
const offs = [];
|
|
4746
|
+
for (const t2 of FORWARDED_TYPES) {
|
|
4747
|
+
offs.push(
|
|
4748
|
+
bus.on(t2, (payload) => {
|
|
4749
|
+
this.emit({ subagentId, taskId, ts: Date.now(), type: t2, payload });
|
|
4750
|
+
})
|
|
4751
|
+
);
|
|
4752
|
+
}
|
|
4753
|
+
return () => {
|
|
4754
|
+
for (const off of offs) off();
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
/** Subscribe to every event from one subagent. */
|
|
4758
|
+
subscribe(subagentId, handler) {
|
|
4759
|
+
let set = this.byId.get(subagentId);
|
|
4760
|
+
if (!set) {
|
|
4761
|
+
set = /* @__PURE__ */ new Set();
|
|
4762
|
+
this.byId.set(subagentId, set);
|
|
4763
|
+
}
|
|
4764
|
+
set.add(handler);
|
|
4765
|
+
return () => {
|
|
4766
|
+
set.delete(handler);
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
/** Subscribe to one event type across all subagents. */
|
|
4770
|
+
filter(type, handler) {
|
|
4771
|
+
let set = this.byType.get(type);
|
|
4772
|
+
if (!set) {
|
|
4773
|
+
set = /* @__PURE__ */ new Set();
|
|
4774
|
+
this.byType.set(type, set);
|
|
4775
|
+
}
|
|
4776
|
+
set.add(handler);
|
|
4777
|
+
return () => {
|
|
4778
|
+
set.delete(handler);
|
|
4779
|
+
};
|
|
4780
|
+
}
|
|
4781
|
+
/** Subscribe to literally everything. The fleet roll-up uses this. */
|
|
4782
|
+
onAny(handler) {
|
|
4783
|
+
this.any.add(handler);
|
|
4784
|
+
return () => {
|
|
4785
|
+
this.any.delete(handler);
|
|
4786
|
+
};
|
|
4787
|
+
}
|
|
4788
|
+
emit(event) {
|
|
4789
|
+
const byId = this.byId.get(event.subagentId);
|
|
4790
|
+
if (byId) for (const h of byId) {
|
|
4791
|
+
try {
|
|
4792
|
+
h(event);
|
|
4793
|
+
} catch {
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
const byType = this.byType.get(event.type);
|
|
4797
|
+
if (byType) for (const h of byType) {
|
|
4798
|
+
try {
|
|
4799
|
+
h(event);
|
|
4800
|
+
} catch {
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
for (const h of this.any) {
|
|
4804
|
+
try {
|
|
4805
|
+
h(event);
|
|
4806
|
+
} catch {
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
4810
|
+
};
|
|
4811
|
+
var FleetUsageAggregator = class {
|
|
4812
|
+
constructor(bus, priceLookup, metaLookup) {
|
|
4813
|
+
this.bus = bus;
|
|
4814
|
+
this.priceLookup = priceLookup;
|
|
4815
|
+
this.metaLookup = metaLookup;
|
|
4816
|
+
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
4817
|
+
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
4818
|
+
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
4819
|
+
}
|
|
4820
|
+
bus;
|
|
4821
|
+
priceLookup;
|
|
4822
|
+
metaLookup;
|
|
4823
|
+
perSubagent = /* @__PURE__ */ new Map();
|
|
4824
|
+
total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
4825
|
+
/** Live snapshot — safe to call from a tool's execute() body. */
|
|
4826
|
+
snapshot() {
|
|
4827
|
+
return {
|
|
4828
|
+
total: { ...this.total },
|
|
4829
|
+
perSubagent: Object.fromEntries(
|
|
4830
|
+
Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
|
|
4831
|
+
)
|
|
4832
|
+
};
|
|
4833
|
+
}
|
|
4834
|
+
ensure(subagentId) {
|
|
4835
|
+
let snap = this.perSubagent.get(subagentId);
|
|
4836
|
+
if (!snap) {
|
|
4837
|
+
const meta = this.metaLookup?.(subagentId);
|
|
4838
|
+
snap = {
|
|
4839
|
+
subagentId,
|
|
4840
|
+
provider: meta?.provider,
|
|
4841
|
+
model: meta?.model,
|
|
4842
|
+
input: 0,
|
|
4843
|
+
output: 0,
|
|
4844
|
+
cacheRead: 0,
|
|
4845
|
+
cacheWrite: 0,
|
|
4846
|
+
cost: 0,
|
|
4847
|
+
toolCalls: 0,
|
|
4848
|
+
iterations: 0,
|
|
4849
|
+
startedAt: Date.now(),
|
|
4850
|
+
lastEventAt: Date.now()
|
|
4851
|
+
};
|
|
4852
|
+
this.perSubagent.set(subagentId, snap);
|
|
4853
|
+
}
|
|
4854
|
+
return snap;
|
|
4855
|
+
}
|
|
4856
|
+
onProviderResponse(e) {
|
|
4857
|
+
const snap = this.ensure(e.subagentId);
|
|
4858
|
+
const p = e.payload;
|
|
4859
|
+
const usage = p?.usage;
|
|
4860
|
+
if (!usage) return;
|
|
4861
|
+
snap.input += usage.input ?? 0;
|
|
4862
|
+
snap.output += usage.output ?? 0;
|
|
4863
|
+
snap.cacheRead += usage.cacheRead ?? 0;
|
|
4864
|
+
snap.cacheWrite += usage.cacheWrite ?? 0;
|
|
4865
|
+
this.total.input += usage.input ?? 0;
|
|
4866
|
+
this.total.output += usage.output ?? 0;
|
|
4867
|
+
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
4868
|
+
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
4869
|
+
const price = this.priceLookup?.(e.subagentId);
|
|
4870
|
+
if (price) {
|
|
4871
|
+
const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
|
|
4872
|
+
snap.cost += delta;
|
|
4873
|
+
this.total.cost += delta;
|
|
4874
|
+
}
|
|
4875
|
+
snap.lastEventAt = e.ts;
|
|
4876
|
+
}
|
|
4877
|
+
onToolExecuted(e) {
|
|
4878
|
+
const snap = this.ensure(e.subagentId);
|
|
4879
|
+
snap.toolCalls += 1;
|
|
4880
|
+
snap.lastEventAt = e.ts;
|
|
4881
|
+
}
|
|
4882
|
+
onIterationStarted(e) {
|
|
4883
|
+
const snap = this.ensure(e.subagentId);
|
|
4884
|
+
snap.iterations += 1;
|
|
4885
|
+
snap.lastEventAt = e.ts;
|
|
4886
|
+
}
|
|
4887
|
+
};
|
|
4888
|
+
|
|
4713
4889
|
// src/defaults/transport/in-memory-transport.ts
|
|
4714
4890
|
var InMemoryBridgeTransport = class {
|
|
4715
4891
|
subs = /* @__PURE__ */ new Map();
|
|
@@ -4837,6 +5013,678 @@ function createMessage(type, from, payload, to) {
|
|
|
4837
5013
|
};
|
|
4838
5014
|
}
|
|
4839
5015
|
|
|
5016
|
+
// src/defaults/director-prompts.ts
|
|
5017
|
+
var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
|
|
5018
|
+
subagents by spawning them, assigning tasks, awaiting completions, and
|
|
5019
|
+
rolling up their outputs into your next decision.
|
|
5020
|
+
|
|
5021
|
+
Core fleet tools available to you:
|
|
5022
|
+
- spawn_subagent \u2014 create a worker with a chosen provider / model / role
|
|
5023
|
+
- assign_task \u2014 hand a piece of work to a specific subagent
|
|
5024
|
+
- await_tasks \u2014 block until named task ids complete (parallel-safe)
|
|
5025
|
+
- ask_subagent \u2014 synchronously query a running subagent via the bridge
|
|
5026
|
+
- roll_up \u2014 aggregate finished tasks into a markdown/json summary
|
|
5027
|
+
- terminate_subagent \u2014 abort a stuck worker (use sparingly)
|
|
5028
|
+
- fleet_status \u2014 snapshot of all subagents and pending tasks
|
|
5029
|
+
- fleet_usage \u2014 token + cost breakdown per subagent and total
|
|
5030
|
+
|
|
5031
|
+
Working rules:
|
|
5032
|
+
1. Decompose first. Before spawning, decide which sub-tasks are
|
|
5033
|
+
independent and can run in parallel. Sequential work doesn't need a
|
|
5034
|
+
subagent \u2014 do it yourself.
|
|
5035
|
+
2. Match worker to job. Cheap/fast model for triage, capable model for
|
|
5036
|
+
synthesis. Different providers per sibling is allowed and encouraged.
|
|
5037
|
+
3. Always pair an assign with an await. Don't fire-and-forget; you owe
|
|
5038
|
+
the user a single coherent answer at the end.
|
|
5039
|
+
4. Roll up before deciding. After await_tasks resolves, call roll_up so
|
|
5040
|
+
the results are folded back into your context in a compact form.
|
|
5041
|
+
5. Budget is real. Check fleet_usage periodically. If a subagent is
|
|
5042
|
+
thrashing, terminate it rather than letting cost climb silently.
|
|
5043
|
+
6. Never claim a subagent's work as your own without verifying it. If a
|
|
5044
|
+
result looks wrong, ask_subagent for clarification before passing it
|
|
5045
|
+
to the user.`;
|
|
5046
|
+
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
5047
|
+
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
5048
|
+
|
|
5049
|
+
Bridge contract:
|
|
5050
|
+
- You have a parent (the Director). You may call \`request\` on the
|
|
5051
|
+
parent bridge to ask a clarifying question. Use this sparingly; the
|
|
5052
|
+
parent is also working.
|
|
5053
|
+
- You MAY NOT request the parent's system prompt, tool list, or other
|
|
5054
|
+
subagents' context. Those are not yours to read.
|
|
5055
|
+
- Your final task output is what the Director sees. Be concise,
|
|
5056
|
+
structured, and self-contained \u2014 assume the Director will paste your
|
|
5057
|
+
output into its own context.`;
|
|
5058
|
+
function composeDirectorPrompt(parts = {}) {
|
|
5059
|
+
const sections = [];
|
|
5060
|
+
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
5061
|
+
if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
|
|
5062
|
+
if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
|
|
5063
|
+
sections.push(`Available roles you can spawn:
|
|
5064
|
+
${parts.rosterSummary.trim()}`);
|
|
5065
|
+
}
|
|
5066
|
+
if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
|
|
5067
|
+
sections.push(parts.basePrompt.trim());
|
|
5068
|
+
}
|
|
5069
|
+
return sections.join("\n\n");
|
|
5070
|
+
}
|
|
5071
|
+
function composeSubagentPrompt(parts = {}) {
|
|
5072
|
+
const sections = [];
|
|
5073
|
+
const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
5074
|
+
if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
|
|
5075
|
+
if (parts.role && parts.role.trim().length > 0) {
|
|
5076
|
+
sections.push(`Role:
|
|
5077
|
+
${parts.role.trim()}`);
|
|
5078
|
+
}
|
|
5079
|
+
if (parts.task && parts.task.trim().length > 0) {
|
|
5080
|
+
sections.push(`Task:
|
|
5081
|
+
${parts.task.trim()}`);
|
|
5082
|
+
}
|
|
5083
|
+
if (parts.override && parts.override.trim().length > 0) {
|
|
5084
|
+
sections.push(parts.override.trim());
|
|
5085
|
+
}
|
|
5086
|
+
return sections.join("\n\n");
|
|
5087
|
+
}
|
|
5088
|
+
function rosterSummaryFromConfigs(roster) {
|
|
5089
|
+
const lines = [];
|
|
5090
|
+
for (const [roleId, cfg] of Object.entries(roster)) {
|
|
5091
|
+
const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
|
|
5092
|
+
const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
|
|
5093
|
+
const tail = headline ? ` \u2014 ${headline}` : "";
|
|
5094
|
+
lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
|
|
5095
|
+
}
|
|
5096
|
+
return lines.join("\n");
|
|
5097
|
+
}
|
|
5098
|
+
|
|
5099
|
+
// src/defaults/director.ts
|
|
5100
|
+
var Director = class {
|
|
5101
|
+
id;
|
|
5102
|
+
fleet;
|
|
5103
|
+
usage;
|
|
5104
|
+
/**
|
|
5105
|
+
* Director-side bridge endpoint. Subagents are wired to the same
|
|
5106
|
+
* in-memory transport so the director can `ask()` them synchronously
|
|
5107
|
+
* and they can `send()` progress back. Exposed so external code (e.g.
|
|
5108
|
+
* the TUI) can subscribe to inbound messages.
|
|
5109
|
+
*/
|
|
5110
|
+
bridge;
|
|
5111
|
+
transport;
|
|
5112
|
+
coordinator;
|
|
5113
|
+
/** Resolves with the matching `TaskResult` the first time the
|
|
5114
|
+
* coordinator emits `task.completed` for a given task id. Each entry
|
|
5115
|
+
* is created lazily on first poll/await and cleared once consumed. */
|
|
5116
|
+
taskWaiters = /* @__PURE__ */ new Map();
|
|
5117
|
+
/** Cache of completed results in case the consumer asks AFTER the
|
|
5118
|
+
* coordinator already fired the event — `awaitTasks(['t-1'])` after
|
|
5119
|
+
* t-1 finished should resolve immediately, not hang. */
|
|
5120
|
+
completed = /* @__PURE__ */ new Map();
|
|
5121
|
+
/** Per-subagent provider/model metadata, captured at spawn time so the
|
|
5122
|
+
* FleetUsageAggregator's metaLookup can surface readable rows. */
|
|
5123
|
+
subagentMeta = /* @__PURE__ */ new Map();
|
|
5124
|
+
priceLookups = /* @__PURE__ */ new Map();
|
|
5125
|
+
/** Bridge endpoints we created per subagent (so we can `stop()` them
|
|
5126
|
+
* on shutdown and free transport subscriptions). */
|
|
5127
|
+
subagentBridges = /* @__PURE__ */ new Map();
|
|
5128
|
+
/** Tracks per-spawn config + assigned task ids for manifest writing. */
|
|
5129
|
+
manifestEntries = /* @__PURE__ */ new Map();
|
|
5130
|
+
manifestPath;
|
|
5131
|
+
roster;
|
|
5132
|
+
directorPreamble;
|
|
5133
|
+
subagentBaseline;
|
|
5134
|
+
constructor(opts) {
|
|
5135
|
+
this.id = opts.config.coordinatorId || randomUUID();
|
|
5136
|
+
this.manifestPath = opts.manifestPath;
|
|
5137
|
+
this.roster = opts.roster;
|
|
5138
|
+
this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
5139
|
+
this.subagentBaseline = opts.subagentBaseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
5140
|
+
this.transport = new InMemoryBridgeTransport();
|
|
5141
|
+
this.bridge = new InMemoryAgentBridge(
|
|
5142
|
+
{ agentId: this.id, coordinatorId: this.id },
|
|
5143
|
+
this.transport
|
|
5144
|
+
);
|
|
5145
|
+
this.fleet = new FleetBus();
|
|
5146
|
+
this.usage = new FleetUsageAggregator(
|
|
5147
|
+
this.fleet,
|
|
5148
|
+
(id) => this.priceLookups.get(id),
|
|
5149
|
+
(id) => this.subagentMeta.get(id)
|
|
5150
|
+
);
|
|
5151
|
+
this.coordinator = new DefaultMultiAgentCoordinator(
|
|
5152
|
+
{ ...opts.config, coordinatorId: this.id },
|
|
5153
|
+
{ runner: opts.runner }
|
|
5154
|
+
);
|
|
5155
|
+
this.coordinator.on("task.completed", (payload) => {
|
|
5156
|
+
const r = payload.result;
|
|
5157
|
+
this.completed.set(r.taskId, r);
|
|
5158
|
+
const waiter = this.taskWaiters.get(r.taskId);
|
|
5159
|
+
if (waiter) {
|
|
5160
|
+
waiter.resolve(r);
|
|
5161
|
+
this.taskWaiters.delete(r.taskId);
|
|
5162
|
+
}
|
|
5163
|
+
});
|
|
5164
|
+
}
|
|
5165
|
+
/**
|
|
5166
|
+
* Spawn a subagent. Identical to the coordinator's `spawn()` but
|
|
5167
|
+
* captures provider/model metadata for the usage aggregator and
|
|
5168
|
+
* lets the FleetBus attach to the runner's EventBus when the task
|
|
5169
|
+
* actually runs (see `attachSubagentBus`).
|
|
5170
|
+
*
|
|
5171
|
+
* Caller-supplied `priceLookup` is optional but recommended — without
|
|
5172
|
+
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
5173
|
+
*/
|
|
5174
|
+
async spawn(config, priceLookup) {
|
|
5175
|
+
const result = await this.coordinator.spawn(config);
|
|
5176
|
+
this.subagentMeta.set(result.subagentId, {
|
|
5177
|
+
provider: config.provider,
|
|
5178
|
+
model: config.model
|
|
5179
|
+
});
|
|
5180
|
+
if (priceLookup) this.priceLookups.set(result.subagentId, priceLookup);
|
|
5181
|
+
const subagentBridge = new InMemoryAgentBridge(
|
|
5182
|
+
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
5183
|
+
this.transport
|
|
5184
|
+
);
|
|
5185
|
+
this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
|
|
5186
|
+
this.subagentBridges.set(result.subagentId, subagentBridge);
|
|
5187
|
+
this.manifestEntries.set(result.subagentId, {
|
|
5188
|
+
subagentId: result.subagentId,
|
|
5189
|
+
name: config.name,
|
|
5190
|
+
role: config.role,
|
|
5191
|
+
provider: config.provider,
|
|
5192
|
+
model: config.model,
|
|
5193
|
+
taskIds: []
|
|
5194
|
+
});
|
|
5195
|
+
return result.subagentId;
|
|
5196
|
+
}
|
|
5197
|
+
/**
|
|
5198
|
+
* Synchronously ask a subagent something via the bridge. Sends a
|
|
5199
|
+
* `task` message addressed to the subagent and awaits a matching
|
|
5200
|
+
* reply (matched by message id). Subagent runners that handle these
|
|
5201
|
+
* requests subscribe to `ctx.bridge` and reply with a message whose
|
|
5202
|
+
* `id` equals the incoming request's id (see `InMemoryAgentBridge`'s
|
|
5203
|
+
* `request<T>` implementation).
|
|
5204
|
+
*
|
|
5205
|
+
* Returns the response payload directly (the bridge wrapper is
|
|
5206
|
+
* unwrapped for ergonomics). Times out after `timeoutMs` (default
|
|
5207
|
+
* matches the bridge's own default of 30s) — surface those rejections
|
|
5208
|
+
* to the caller as actionable errors instead of letting tools hang.
|
|
5209
|
+
*/
|
|
5210
|
+
async ask(subagentId, payload, timeoutMs) {
|
|
5211
|
+
if (!this.subagentBridges.has(subagentId)) {
|
|
5212
|
+
throw new Error(
|
|
5213
|
+
`ask: unknown subagent "${subagentId}" (spawn() it first; current fleet: ${Array.from(this.subagentBridges.keys()).join(", ") || "(empty)"})`
|
|
5214
|
+
);
|
|
5215
|
+
}
|
|
5216
|
+
const msg = {
|
|
5217
|
+
id: randomUUID(),
|
|
5218
|
+
type: "task",
|
|
5219
|
+
from: this.id,
|
|
5220
|
+
to: subagentId,
|
|
5221
|
+
payload,
|
|
5222
|
+
timestamp: Date.now(),
|
|
5223
|
+
priority: "normal"
|
|
5224
|
+
};
|
|
5225
|
+
const reply = await this.bridge.request(msg, timeoutMs);
|
|
5226
|
+
return reply.payload;
|
|
5227
|
+
}
|
|
5228
|
+
/**
|
|
5229
|
+
* Read completed task results and format them as a structured text
|
|
5230
|
+
* block the director's LLM can paste into its own context. The
|
|
5231
|
+
* Director keeps every completed `TaskResult` in `completed` so this
|
|
5232
|
+
* is a pure read — no bridge round-trip, cheap to call.
|
|
5233
|
+
*
|
|
5234
|
+
* The returned string is intentionally markdown-flavored: headers per
|
|
5235
|
+
* subagent, a one-line meta row (iter / tools / ms), and the task's
|
|
5236
|
+
* result text. Pass `style: 'json'` for a programmatic shape instead
|
|
5237
|
+
* (useful when the director model is doing structured-output work).
|
|
5238
|
+
*/
|
|
5239
|
+
rollUp(taskIds, style = "markdown") {
|
|
5240
|
+
const rows = taskIds.map((id) => this.completed.get(id)).filter(
|
|
5241
|
+
(r) => !!r
|
|
5242
|
+
);
|
|
5243
|
+
if (style === "json") {
|
|
5244
|
+
return JSON.stringify(
|
|
5245
|
+
rows.map((r) => ({
|
|
5246
|
+
taskId: r.taskId,
|
|
5247
|
+
subagentId: r.subagentId,
|
|
5248
|
+
status: r.status,
|
|
5249
|
+
iterations: r.iterations,
|
|
5250
|
+
toolCalls: r.toolCalls,
|
|
5251
|
+
durationMs: r.durationMs,
|
|
5252
|
+
result: r.result,
|
|
5253
|
+
error: r.error
|
|
5254
|
+
})),
|
|
5255
|
+
null,
|
|
5256
|
+
2
|
|
5257
|
+
);
|
|
5258
|
+
}
|
|
5259
|
+
if (rows.length === 0) {
|
|
5260
|
+
return "_No completed tasks for the requested ids \u2014 try waiting first._";
|
|
5261
|
+
}
|
|
5262
|
+
const lines = [];
|
|
5263
|
+
for (const r of rows) {
|
|
5264
|
+
const meta = this.subagentMeta.get(r.subagentId);
|
|
5265
|
+
const tag = meta?.provider && meta?.model ? ` \xB7 ${meta.provider}/${meta.model}` : "";
|
|
5266
|
+
lines.push(`### ${r.subagentId}${tag}`);
|
|
5267
|
+
lines.push(
|
|
5268
|
+
`_${r.status} \u2014 ${r.iterations} iter \xB7 ${r.toolCalls} tools \xB7 ${r.durationMs}ms_`
|
|
5269
|
+
);
|
|
5270
|
+
lines.push("");
|
|
5271
|
+
if (r.error) lines.push(`**Error:** ${r.error}`);
|
|
5272
|
+
else if (typeof r.result === "string") lines.push(r.result);
|
|
5273
|
+
else if (r.result !== void 0) lines.push("```json\n" + JSON.stringify(r.result, null, 2) + "\n```");
|
|
5274
|
+
else lines.push("_(no output)_");
|
|
5275
|
+
lines.push("");
|
|
5276
|
+
}
|
|
5277
|
+
return lines.join("\n").trimEnd();
|
|
5278
|
+
}
|
|
5279
|
+
/**
|
|
5280
|
+
* Write the fleet manifest to `manifestPath`. Returns the path written
|
|
5281
|
+
* or null when no path was configured. Captures every spawn + its
|
|
5282
|
+
* assigned tasks — paired with per-subagent JSONLs, this is enough to
|
|
5283
|
+
* replay an entire director run.
|
|
5284
|
+
*/
|
|
5285
|
+
async writeManifest() {
|
|
5286
|
+
if (!this.manifestPath) return null;
|
|
5287
|
+
const manifest = {
|
|
5288
|
+
directorRunId: this.id,
|
|
5289
|
+
writtenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5290
|
+
children: Array.from(this.manifestEntries.values()).map((e) => ({
|
|
5291
|
+
...e,
|
|
5292
|
+
// Surface final status from `completed` when available — manifest
|
|
5293
|
+
// becomes much more useful for replay when it carries the
|
|
5294
|
+
// success/failure state.
|
|
5295
|
+
results: e.taskIds.map((tid) => {
|
|
5296
|
+
const r = this.completed.get(tid);
|
|
5297
|
+
return r ? {
|
|
5298
|
+
taskId: tid,
|
|
5299
|
+
status: r.status,
|
|
5300
|
+
iterations: r.iterations,
|
|
5301
|
+
toolCalls: r.toolCalls,
|
|
5302
|
+
durationMs: r.durationMs
|
|
5303
|
+
} : { taskId: tid, status: "pending" };
|
|
5304
|
+
})
|
|
5305
|
+
})),
|
|
5306
|
+
usage: this.usage.snapshot()
|
|
5307
|
+
};
|
|
5308
|
+
await fsp.mkdir(path2.dirname(this.manifestPath), { recursive: true });
|
|
5309
|
+
await fsp.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
5310
|
+
return this.manifestPath;
|
|
5311
|
+
}
|
|
5312
|
+
/**
|
|
5313
|
+
* Tear down the director: stop every subagent, close every bridge
|
|
5314
|
+
* endpoint, and (when configured) write the final manifest. Idempotent
|
|
5315
|
+
* — calling shutdown twice is a no-op on the second invocation.
|
|
5316
|
+
*/
|
|
5317
|
+
async shutdown() {
|
|
5318
|
+
await this.coordinator.stopAll();
|
|
5319
|
+
for (const b of this.subagentBridges.values()) {
|
|
5320
|
+
await b.stop().catch(() => void 0);
|
|
5321
|
+
}
|
|
5322
|
+
this.subagentBridges.clear();
|
|
5323
|
+
await this.bridge.stop().catch(() => void 0);
|
|
5324
|
+
if (this.manifestPath) await this.writeManifest().catch(() => void 0);
|
|
5325
|
+
}
|
|
5326
|
+
/**
|
|
5327
|
+
* Hand a task to the coordinator. Returns the assigned task id so
|
|
5328
|
+
* callers can wait on it via `awaitTasks([id])`. The coordinator's
|
|
5329
|
+
* concurrency limit applies — the task may queue before running.
|
|
5330
|
+
*/
|
|
5331
|
+
async assign(task) {
|
|
5332
|
+
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
5333
|
+
if (task.subagentId) {
|
|
5334
|
+
const entry = this.manifestEntries.get(task.subagentId);
|
|
5335
|
+
if (entry) entry.taskIds.push(taskWithId.id);
|
|
5336
|
+
}
|
|
5337
|
+
await this.coordinator.assign(taskWithId);
|
|
5338
|
+
return taskWithId.id;
|
|
5339
|
+
}
|
|
5340
|
+
/**
|
|
5341
|
+
* Block until every task id resolves. Returns results in the same
|
|
5342
|
+
* order as the input. If any task hasn't completed by the time this
|
|
5343
|
+
* is called, the promise hangs until it does — pair with a timeout
|
|
5344
|
+
* at the caller if that's a concern. Resolves immediately for ids
|
|
5345
|
+
* whose results were already cached.
|
|
5346
|
+
*/
|
|
5347
|
+
awaitTasks(taskIds) {
|
|
5348
|
+
return Promise.all(taskIds.map((id) => {
|
|
5349
|
+
const cached = this.completed.get(id);
|
|
5350
|
+
if (cached) return cached;
|
|
5351
|
+
const existing = this.taskWaiters.get(id);
|
|
5352
|
+
if (existing) return existing.promise;
|
|
5353
|
+
let resolve4;
|
|
5354
|
+
const promise = new Promise((res) => {
|
|
5355
|
+
resolve4 = res;
|
|
5356
|
+
});
|
|
5357
|
+
this.taskWaiters.set(id, { promise, resolve: resolve4 });
|
|
5358
|
+
return promise;
|
|
5359
|
+
}));
|
|
5360
|
+
}
|
|
5361
|
+
async terminate(subagentId) {
|
|
5362
|
+
await this.coordinator.stop(subagentId);
|
|
5363
|
+
}
|
|
5364
|
+
async terminateAll() {
|
|
5365
|
+
await this.coordinator.stopAll();
|
|
5366
|
+
}
|
|
5367
|
+
status() {
|
|
5368
|
+
return this.coordinator.getStatus();
|
|
5369
|
+
}
|
|
5370
|
+
/**
|
|
5371
|
+
* Subscribe to coordinator events. Currently only `task.completed` is
|
|
5372
|
+
* exposed (the others are internal lifecycle). Returns an unsubscribe
|
|
5373
|
+
* function. External callers (e.g. the CLI's `MultiAgentHost`) use this
|
|
5374
|
+
* to drive their own pending/results tracking without poking the
|
|
5375
|
+
* coordinator directly.
|
|
5376
|
+
*/
|
|
5377
|
+
on(event, handler) {
|
|
5378
|
+
this.coordinator.on(event, handler);
|
|
5379
|
+
return () => {
|
|
5380
|
+
this.coordinator.off(event, handler);
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
5383
|
+
/**
|
|
5384
|
+
* Snapshot of every task that has resolved (success, failed, timeout,
|
|
5385
|
+
* stopped) since the director started. Returned in completion order
|
|
5386
|
+
* via the internal map's iteration order. Used by `/fleet status` to
|
|
5387
|
+
* paint the completed table without reaching into private state.
|
|
5388
|
+
*/
|
|
5389
|
+
completedResults() {
|
|
5390
|
+
return Array.from(this.completed.values());
|
|
5391
|
+
}
|
|
5392
|
+
snapshot() {
|
|
5393
|
+
return this.usage.snapshot();
|
|
5394
|
+
}
|
|
5395
|
+
/**
|
|
5396
|
+
* Compose the leader/director-agent system prompt: fleet preamble +
|
|
5397
|
+
* (optional) roster summary + user base prompt. Pass the result to your
|
|
5398
|
+
* leader Agent's `ctx.systemPrompt` when constructing it.
|
|
5399
|
+
*
|
|
5400
|
+
* `basePrompt` defaults to `config.leaderSystemPrompt` so callers can
|
|
5401
|
+
* use the no-arg form when the multi-agent config already carries it.
|
|
5402
|
+
*/
|
|
5403
|
+
leaderSystemPrompt(basePrompt) {
|
|
5404
|
+
return composeDirectorPrompt({
|
|
5405
|
+
basePrompt: basePrompt ?? this.coordinator.config.leaderSystemPrompt,
|
|
5406
|
+
directorPreamble: this.directorPreamble,
|
|
5407
|
+
rosterSummary: this.roster ? rosterSummaryFromConfigs(this.roster) : void 0
|
|
5408
|
+
});
|
|
5409
|
+
}
|
|
5410
|
+
/**
|
|
5411
|
+
* Compose a subagent's system prompt for a given `SubagentConfig`:
|
|
5412
|
+
* baseline + role + task + per-spawn override. Returned by value — does
|
|
5413
|
+
* not mutate the config. Factories (the user-supplied `AgentFactory`)
|
|
5414
|
+
* should call this when building each subagent's Agent so the bridge
|
|
5415
|
+
* contract, role context, and override are all surfaced.
|
|
5416
|
+
*
|
|
5417
|
+
* When `taskBrief` is omitted the Task section is dropped. Pass the
|
|
5418
|
+
* actual task description here to reinforce it in the system prompt
|
|
5419
|
+
* (the runner already passes it as user input — duplicating in the
|
|
5420
|
+
* system prompt is optional but improves anchoring on small models).
|
|
5421
|
+
*/
|
|
5422
|
+
subagentSystemPrompt(config, taskBrief) {
|
|
5423
|
+
return composeSubagentPrompt({
|
|
5424
|
+
baseline: this.subagentBaseline,
|
|
5425
|
+
role: config.prompt,
|
|
5426
|
+
task: taskBrief,
|
|
5427
|
+
override: config.systemPromptOverride
|
|
5428
|
+
});
|
|
5429
|
+
}
|
|
5430
|
+
/**
|
|
5431
|
+
* Build the tool set the LLM-driven director uses to orchestrate.
|
|
5432
|
+
* Returns an array of `Tool` definitions; register these on the
|
|
5433
|
+
* director's `Agent` to expose `spawn_subagent`, `assign_task`, etc.
|
|
5434
|
+
* Each tool's `execute()` delegates straight to the matching method
|
|
5435
|
+
* above.
|
|
5436
|
+
*
|
|
5437
|
+
* Tools all carry `permission: 'auto'` — the *user* has already
|
|
5438
|
+
* approved running the director when they kicked off the run, so
|
|
5439
|
+
* gating individual orchestration calls behind a confirm prompt
|
|
5440
|
+
* would just be noise. The actual subagent tools they spawn are
|
|
5441
|
+
* still permission-checked normally.
|
|
5442
|
+
*/
|
|
5443
|
+
tools(roster) {
|
|
5444
|
+
const t2 = [
|
|
5445
|
+
makeSpawnTool(this, roster),
|
|
5446
|
+
makeAssignTool(this),
|
|
5447
|
+
makeAwaitTasksTool(this),
|
|
5448
|
+
makeAskTool(this),
|
|
5449
|
+
makeRollUpTool(this),
|
|
5450
|
+
makeTerminateTool(this),
|
|
5451
|
+
makeFleetStatusTool(this),
|
|
5452
|
+
makeFleetUsageTool(this)
|
|
5453
|
+
];
|
|
5454
|
+
return t2;
|
|
5455
|
+
}
|
|
5456
|
+
};
|
|
5457
|
+
function makeSpawnTool(director, roster) {
|
|
5458
|
+
const inputSchema = {
|
|
5459
|
+
type: "object",
|
|
5460
|
+
properties: {
|
|
5461
|
+
role: { type: "string", description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
5462
|
+
name: { type: "string", description: "Display name for the subagent. Required when not using roster." },
|
|
5463
|
+
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
5464
|
+
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
5465
|
+
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
5466
|
+
maxIterations: { type: "number" },
|
|
5467
|
+
maxToolCalls: { type: "number" },
|
|
5468
|
+
maxCostUsd: { type: "number" }
|
|
5469
|
+
},
|
|
5470
|
+
required: []
|
|
5471
|
+
};
|
|
5472
|
+
return {
|
|
5473
|
+
name: "spawn_subagent",
|
|
5474
|
+
description: "Create a new subagent under this director. Returns the subagent id. Use this when you need a worker with a specific provider, model, or role to handle a piece of the plan.",
|
|
5475
|
+
usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
|
|
5476
|
+
permission: "auto",
|
|
5477
|
+
mutating: false,
|
|
5478
|
+
inputSchema,
|
|
5479
|
+
async execute(input) {
|
|
5480
|
+
const i = input ?? {};
|
|
5481
|
+
const role = typeof i.role === "string" ? i.role : void 0;
|
|
5482
|
+
const base = role && roster ? roster[role] : void 0;
|
|
5483
|
+
if (role && !base) {
|
|
5484
|
+
return { error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}` };
|
|
5485
|
+
}
|
|
5486
|
+
const cfg = {
|
|
5487
|
+
...base ?? { name: i.name ?? "subagent" }
|
|
5488
|
+
};
|
|
5489
|
+
if (typeof i.name === "string") cfg.name = i.name;
|
|
5490
|
+
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
5491
|
+
if (typeof i.model === "string") cfg.model = i.model;
|
|
5492
|
+
if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
|
|
5493
|
+
if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
|
|
5494
|
+
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
5495
|
+
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
5496
|
+
const subagentId = await director.spawn(cfg);
|
|
5497
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
|
|
5498
|
+
}
|
|
5499
|
+
};
|
|
5500
|
+
}
|
|
5501
|
+
function makeAssignTool(director) {
|
|
5502
|
+
const inputSchema = {
|
|
5503
|
+
type: "object",
|
|
5504
|
+
properties: {
|
|
5505
|
+
subagentId: { type: "string", description: "Target subagent id. Required." },
|
|
5506
|
+
description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
|
|
5507
|
+
maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
|
|
5508
|
+
timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
|
|
5509
|
+
},
|
|
5510
|
+
required: ["subagentId", "description"]
|
|
5511
|
+
};
|
|
5512
|
+
return {
|
|
5513
|
+
name: "assign_task",
|
|
5514
|
+
description: "Hand a task to a previously spawned subagent. Returns the task id \u2014 pass it to `await_tasks` to block on completion.",
|
|
5515
|
+
permission: "auto",
|
|
5516
|
+
mutating: false,
|
|
5517
|
+
inputSchema,
|
|
5518
|
+
async execute(input) {
|
|
5519
|
+
const i = input;
|
|
5520
|
+
const task = {
|
|
5521
|
+
id: randomUUID(),
|
|
5522
|
+
description: i.description,
|
|
5523
|
+
subagentId: i.subagentId,
|
|
5524
|
+
maxToolCalls: i.maxToolCalls,
|
|
5525
|
+
timeoutMs: i.timeoutMs
|
|
5526
|
+
};
|
|
5527
|
+
const taskId = await director.assign(task);
|
|
5528
|
+
return { taskId, subagentId: i.subagentId };
|
|
5529
|
+
}
|
|
5530
|
+
};
|
|
5531
|
+
}
|
|
5532
|
+
function makeAwaitTasksTool(director) {
|
|
5533
|
+
const inputSchema = {
|
|
5534
|
+
type: "object",
|
|
5535
|
+
properties: {
|
|
5536
|
+
taskIds: {
|
|
5537
|
+
type: "array",
|
|
5538
|
+
items: { type: "string" },
|
|
5539
|
+
description: "One or more task ids returned by `assign_task`. The call blocks until every id resolves."
|
|
5540
|
+
}
|
|
5541
|
+
},
|
|
5542
|
+
required: ["taskIds"]
|
|
5543
|
+
};
|
|
5544
|
+
return {
|
|
5545
|
+
name: "await_tasks",
|
|
5546
|
+
description: "Block until every named task completes. Returns the array of TaskResult \u2014 use this to gather subagent output before deciding the next step.",
|
|
5547
|
+
permission: "auto",
|
|
5548
|
+
mutating: false,
|
|
5549
|
+
inputSchema,
|
|
5550
|
+
async execute(input) {
|
|
5551
|
+
const i = input;
|
|
5552
|
+
const results = await director.awaitTasks(i.taskIds);
|
|
5553
|
+
return { results };
|
|
5554
|
+
}
|
|
5555
|
+
};
|
|
5556
|
+
}
|
|
5557
|
+
function makeAskTool(director) {
|
|
5558
|
+
const inputSchema = {
|
|
5559
|
+
type: "object",
|
|
5560
|
+
properties: {
|
|
5561
|
+
subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
|
|
5562
|
+
question: { type: "string", description: "The question or instruction. Sent as the bridge message payload." },
|
|
5563
|
+
timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
|
|
5564
|
+
},
|
|
5565
|
+
required: ["subagentId", "question"]
|
|
5566
|
+
};
|
|
5567
|
+
return {
|
|
5568
|
+
name: "ask_subagent",
|
|
5569
|
+
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge (or the timeout fires). Use this when you need a one-shot answer without spawning a fresh task.",
|
|
5570
|
+
permission: "auto",
|
|
5571
|
+
mutating: false,
|
|
5572
|
+
inputSchema,
|
|
5573
|
+
async execute(input) {
|
|
5574
|
+
const i = input;
|
|
5575
|
+
try {
|
|
5576
|
+
const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
|
|
5577
|
+
return { ok: true, answer };
|
|
5578
|
+
} catch (err) {
|
|
5579
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
};
|
|
5583
|
+
}
|
|
5584
|
+
function makeRollUpTool(director) {
|
|
5585
|
+
const inputSchema = {
|
|
5586
|
+
type: "object",
|
|
5587
|
+
properties: {
|
|
5588
|
+
taskIds: {
|
|
5589
|
+
type: "array",
|
|
5590
|
+
items: { type: "string" },
|
|
5591
|
+
description: "Completed task ids to aggregate. Pass the ids returned by previous `assign_task` calls."
|
|
5592
|
+
},
|
|
5593
|
+
style: {
|
|
5594
|
+
type: "string",
|
|
5595
|
+
enum: ["markdown", "json"],
|
|
5596
|
+
description: "Output flavor \u2014 markdown (default) for in-prompt summarization, json for structured downstream processing."
|
|
5597
|
+
}
|
|
5598
|
+
},
|
|
5599
|
+
required: ["taskIds"]
|
|
5600
|
+
};
|
|
5601
|
+
return {
|
|
5602
|
+
name: "roll_up",
|
|
5603
|
+
description: "Aggregate completed task results into a single formatted summary. Use this after `await_tasks` to fold subagent outputs back into the director's context before deciding the next step.",
|
|
5604
|
+
permission: "auto",
|
|
5605
|
+
mutating: false,
|
|
5606
|
+
inputSchema,
|
|
5607
|
+
async execute(input) {
|
|
5608
|
+
const i = input;
|
|
5609
|
+
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
5610
|
+
return { summary, count: i.taskIds.length };
|
|
5611
|
+
}
|
|
5612
|
+
};
|
|
5613
|
+
}
|
|
5614
|
+
function makeTerminateTool(director) {
|
|
5615
|
+
const inputSchema = {
|
|
5616
|
+
type: "object",
|
|
5617
|
+
properties: {
|
|
5618
|
+
subagentId: { type: "string", description: "Subagent to abort." }
|
|
5619
|
+
},
|
|
5620
|
+
required: ["subagentId"]
|
|
5621
|
+
};
|
|
5622
|
+
return {
|
|
5623
|
+
name: "terminate_subagent",
|
|
5624
|
+
description: 'Forcibly abort a subagent. Use sparingly \u2014 prefer waiting on the natural budget to expire. The current task (if any) ends with status "stopped".',
|
|
5625
|
+
permission: "auto",
|
|
5626
|
+
mutating: true,
|
|
5627
|
+
inputSchema,
|
|
5628
|
+
async execute(input) {
|
|
5629
|
+
const i = input;
|
|
5630
|
+
await director.terminate(i.subagentId);
|
|
5631
|
+
return { ok: true };
|
|
5632
|
+
}
|
|
5633
|
+
};
|
|
5634
|
+
}
|
|
5635
|
+
function makeFleetStatusTool(director) {
|
|
5636
|
+
return {
|
|
5637
|
+
name: "fleet_status",
|
|
5638
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts, and the running total iteration count. Cheap; call freely.",
|
|
5639
|
+
permission: "auto",
|
|
5640
|
+
mutating: false,
|
|
5641
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
5642
|
+
async execute() {
|
|
5643
|
+
return director.status();
|
|
5644
|
+
}
|
|
5645
|
+
};
|
|
5646
|
+
}
|
|
5647
|
+
function makeFleetUsageTool(director) {
|
|
5648
|
+
return {
|
|
5649
|
+
name: "fleet_usage",
|
|
5650
|
+
description: "Token + cost breakdown across the fleet, per-subagent and totals. Use this to reason about which workers to assign costly tasks to or when to wrap up to stay within budget.",
|
|
5651
|
+
permission: "auto",
|
|
5652
|
+
mutating: false,
|
|
5653
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
5654
|
+
async execute() {
|
|
5655
|
+
return director.snapshot();
|
|
5656
|
+
}
|
|
5657
|
+
};
|
|
5658
|
+
}
|
|
5659
|
+
function makeDirectorSessionFactory(opts) {
|
|
5660
|
+
const runId = opts.directorRunId ?? `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-director`;
|
|
5661
|
+
let store;
|
|
5662
|
+
let dir;
|
|
5663
|
+
if (opts.store) {
|
|
5664
|
+
store = opts.store;
|
|
5665
|
+
dir = opts.sessionsRoot ? path2.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
5666
|
+
} else if (opts.sessionsRoot) {
|
|
5667
|
+
dir = path2.join(opts.sessionsRoot, runId);
|
|
5668
|
+
store = new DefaultSessionStore({ dir });
|
|
5669
|
+
} else {
|
|
5670
|
+
throw new Error(
|
|
5671
|
+
"makeDirectorSessionFactory requires either `store` or `sessionsRoot`"
|
|
5672
|
+
);
|
|
5673
|
+
}
|
|
5674
|
+
return {
|
|
5675
|
+
dir,
|
|
5676
|
+
directorRunId: runId,
|
|
5677
|
+
async createSubagentSession({ subagentId, provider, model, title }) {
|
|
5678
|
+
return store.create({
|
|
5679
|
+
id: subagentId,
|
|
5680
|
+
title: title ?? subagentId,
|
|
5681
|
+
provider: provider ?? "unknown",
|
|
5682
|
+
model: model ?? "unknown"
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5685
|
+
};
|
|
5686
|
+
}
|
|
5687
|
+
|
|
4840
5688
|
// src/defaults/autonomous-runner.ts
|
|
4841
5689
|
var DoneConditionChecker = class {
|
|
4842
5690
|
constructor(condition) {
|
|
@@ -4878,6 +5726,16 @@ var AutonomousRunner = class {
|
|
|
4878
5726
|
stopped = false;
|
|
4879
5727
|
doneChecker;
|
|
4880
5728
|
async run() {
|
|
5729
|
+
const offToolExecuted = this.opts.agent.events?.on?.("tool.executed", () => {
|
|
5730
|
+
this.toolCalls++;
|
|
5731
|
+
});
|
|
5732
|
+
try {
|
|
5733
|
+
return await this.runLoop();
|
|
5734
|
+
} finally {
|
|
5735
|
+
offToolExecuted?.();
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
async runLoop() {
|
|
4881
5739
|
while (!this.stopped) {
|
|
4882
5740
|
const check = this.doneChecker.check({
|
|
4883
5741
|
iterations: this.iterations,
|
|
@@ -4904,7 +5762,6 @@ var AutonomousRunner = class {
|
|
|
4904
5762
|
);
|
|
4905
5763
|
this.iterations++;
|
|
4906
5764
|
this.lastOutput = result.finalText;
|
|
4907
|
-
this.toolCalls++;
|
|
4908
5765
|
if (result.status === "failed" || result.status === "aborted") {
|
|
4909
5766
|
const failedResult = {
|
|
4910
5767
|
status: result.status,
|
|
@@ -6711,7 +7568,7 @@ var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
|
6711
7568
|
async function startMetricsServer(opts) {
|
|
6712
7569
|
const { createServer } = await import('http');
|
|
6713
7570
|
const host = opts.host ?? "127.0.0.1";
|
|
6714
|
-
const
|
|
7571
|
+
const path17 = opts.path ?? "/metrics";
|
|
6715
7572
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
6716
7573
|
const healthRegistry = opts.healthRegistry;
|
|
6717
7574
|
const server = createServer((req, res) => {
|
|
@@ -6721,7 +7578,7 @@ async function startMetricsServer(opts) {
|
|
|
6721
7578
|
return;
|
|
6722
7579
|
}
|
|
6723
7580
|
const url = req.url.split("?")[0];
|
|
6724
|
-
if (url ===
|
|
7581
|
+
if (url === path17) {
|
|
6725
7582
|
let body;
|
|
6726
7583
|
try {
|
|
6727
7584
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -6772,7 +7629,7 @@ async function startMetricsServer(opts) {
|
|
|
6772
7629
|
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
6773
7630
|
return {
|
|
6774
7631
|
port: boundPort,
|
|
6775
|
-
url: `http://${host}:${boundPort}${
|
|
7632
|
+
url: `http://${host}:${boundPort}${path17}`,
|
|
6776
7633
|
close: () => new Promise((resolve4, reject) => {
|
|
6777
7634
|
server.close((err) => err ? reject(err) : resolve4());
|
|
6778
7635
|
})
|
|
@@ -8810,7 +9667,7 @@ var noopSlashCommands = {
|
|
|
8810
9667
|
};
|
|
8811
9668
|
|
|
8812
9669
|
// src/plugin/loader.ts
|
|
8813
|
-
var KERNEL_API_VERSION = "0.1.
|
|
9670
|
+
var KERNEL_API_VERSION = "0.1.8";
|
|
8814
9671
|
function parseSemver(v) {
|
|
8815
9672
|
const parts = v.replace(/^[^0-9]*/, "").split(".").map((s) => Number.parseInt(s, 10) || 0);
|
|
8816
9673
|
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
@@ -9042,6 +9899,6 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
|
|
|
9042
9899
|
});
|
|
9043
9900
|
}
|
|
9044
9901
|
|
|
9045
|
-
export { Agent, AgentError, AutoCompactionMiddleware, AutonomousRunner, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_SPEC_TEMPLATE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, DoneConditionChecker, ENCRYPTED_PREFIX, EventBus, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, RecoveryLock, RunController, SelectiveCompactor, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, allServers, asBlocks, asText, atomicWrite, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, color, compileGlob, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadPlugins, loadProjectModes, loadUserModes, makeAgentSubagentRunner, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
|
|
9902
|
+
export { Agent, AgentError, AutoCompactionMiddleware, AutonomousRunner, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DoneConditionChecker, ENCRYPTED_PREFIX, EventBus, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, RecoveryLock, RunController, SelectiveCompactor, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, allServers, asBlocks, asText, atomicWrite, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadPlugins, loadProjectModes, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
|
|
9046
9903
|
//# sourceMappingURL=index.js.map
|
|
9047
9904
|
//# sourceMappingURL=index.js.map
|