@nebutra/agent-runtime 0.2.0 → 0.2.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/.turbo/turbo-build.log +65 -50
- package/.turbo/turbo-test.log +39 -36
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +12 -15
- package/README.md +2 -0
- package/dist/adapters/dispatcher-sse.js +1 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/mcp-catalog.js +1 -0
- package/dist/adapters/prisma-rollout.js +1 -0
- package/dist/chunk-424PT5DM.js +23 -0
- package/dist/chunk-424PT5DM.js.map +1 -0
- package/dist/{chunk-NN7DATXA.js → chunk-4Y25ZTKI.js} +3 -3
- package/dist/chunk-4Y25ZTKI.js.map +1 -0
- package/dist/{chunk-BJBBR3QA.js → chunk-D4YAPLOW.js} +4 -4
- package/dist/chunk-D4YAPLOW.js.map +1 -0
- package/dist/{chunk-ZMYX5VBU.js → chunk-GQZKYWFT.js} +23 -7
- package/dist/chunk-GQZKYWFT.js.map +1 -0
- package/dist/chunk-KCNN4QUQ.js +255 -0
- package/dist/chunk-KCNN4QUQ.js.map +1 -0
- package/dist/chunk-MX2WL43P.js +90 -0
- package/dist/chunk-MX2WL43P.js.map +1 -0
- package/dist/{chunk-PGGWSUTM.js → chunk-NI4EDT4T.js} +2 -2
- package/dist/chunk-NI4EDT4T.js.map +1 -0
- package/dist/{chunk-5N4644PB.js → chunk-SD2ZJ7XG.js} +4 -4
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.js +1 -0
- package/dist/definitions.js +1 -0
- package/dist/dispatcher.js +1 -0
- package/dist/durable-turn.js +3 -2
- package/dist/hook-pipeline.js +1 -0
- package/dist/index.d.ts +8 -85
- package/dist/index.js +237 -119
- package/dist/index.js.map +1 -1
- package/dist/loop.d.ts +2 -2
- package/dist/loop.js +3 -2
- package/dist/mcp-bridge.d.ts +3 -3
- package/dist/mcp-bridge.js +3 -2
- package/dist/model.js +1 -0
- package/dist/orchestration.d.ts +43 -0
- package/dist/orchestration.js +12 -0
- package/dist/orchestration.js.map +1 -0
- package/dist/policy.js +1 -0
- package/dist/protocol.js +1 -0
- package/dist/pulsar.d.ts +78 -0
- package/dist/pulsar.js +17 -0
- package/dist/pulsar.js.map +1 -0
- package/dist/rollout-store-persistent.js +1 -0
- package/dist/rollout.js +1 -0
- package/dist/sandbox.js +1 -0
- package/dist/skills.js +2 -1
- package/dist/subagents.js +1 -0
- package/dist/tools.d.ts +4 -3
- package/dist/tools.js +5 -3
- package/examples/pulsar-quickstart.ts +35 -0
- package/examples/resume-branch.ts +45 -0
- package/examples/subagent-fanout.ts +20 -0
- package/package.json +12 -5
- package/src/cli.ts +49 -0
- package/src/context-compaction.ts +6 -2
- package/src/index.ts +2 -0
- package/src/loop.ts +2 -2
- package/src/mcp-bridge.ts +8 -3
- package/src/orchestration.test.ts +53 -0
- package/src/orchestration.ts +146 -0
- package/src/permission-ruleset.ts +1 -200
- package/src/pulsar.test.ts +156 -0
- package/src/pulsar.ts +322 -0
- package/src/skills.ts +30 -8
- package/src/tools.test.ts +12 -0
- package/src/tools.ts +5 -2
- package/tsup.config.ts +4 -1
- package/dist/chunk-BJBBR3QA.js.map +0 -1
- package/dist/chunk-NN7DATXA.js.map +0 -1
- package/dist/chunk-PGGWSUTM.js.map +0 -1
- package/dist/chunk-ZMYX5VBU.js.map +0 -1
- /package/dist/{chunk-5N4644PB.js.map → chunk-SD2ZJ7XG.js.map} +0 -0
|
@@ -3,20 +3,36 @@ import {
|
|
|
3
3
|
} from "./chunk-7BUOF367.js";
|
|
4
4
|
|
|
5
5
|
// src/skills.ts
|
|
6
|
+
import { estimateTokens as estimatePrimitiveTokens } from "@nebutra/ai-primitives";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
var FIRST_PARTY_TIERS = /* @__PURE__ */ new Set(["bundled", "builtin"]);
|
|
9
|
+
var REGEX_META_CHARS = /* @__PURE__ */ new Set([
|
|
10
|
+
".",
|
|
11
|
+
"+",
|
|
12
|
+
"?",
|
|
13
|
+
"^",
|
|
14
|
+
"$",
|
|
15
|
+
"{",
|
|
16
|
+
"}",
|
|
17
|
+
"(",
|
|
18
|
+
")",
|
|
19
|
+
"|",
|
|
20
|
+
"[",
|
|
21
|
+
"]",
|
|
22
|
+
"\\"
|
|
23
|
+
]);
|
|
8
24
|
var isFirstParty = (s) => FIRST_PARTY_TIERS.has(s.sourceTier);
|
|
25
|
+
var estimateListingTokens = (text) => estimatePrimitiveTokens(text, { correction: 1 });
|
|
9
26
|
var listingOptionsSchema = z.object({
|
|
10
27
|
maxListingDescChars: z.number().int().positive().default(250),
|
|
11
28
|
contextWindowTokens: z.number().int().positive().default(2e5),
|
|
12
29
|
budgetPercent: z.number().positive().max(1).default(0.01),
|
|
13
30
|
touchedPaths: z.array(z.string()).default([])
|
|
14
31
|
});
|
|
15
|
-
var estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
16
32
|
function globToRegExp(glob) {
|
|
17
33
|
let out = "^";
|
|
18
34
|
for (let i = 0; i < glob.length; i++) {
|
|
19
|
-
const c = glob
|
|
35
|
+
const c = glob.charAt(i);
|
|
20
36
|
if (c === "*") {
|
|
21
37
|
if (glob[i + 1] === "*") {
|
|
22
38
|
out += ".*";
|
|
@@ -24,7 +40,7 @@ function globToRegExp(glob) {
|
|
|
24
40
|
} else {
|
|
25
41
|
out += "[^/]*";
|
|
26
42
|
}
|
|
27
|
-
} else if (
|
|
43
|
+
} else if (REGEX_META_CHARS.has(c)) {
|
|
28
44
|
out += `\\${c}`;
|
|
29
45
|
} else {
|
|
30
46
|
out += c;
|
|
@@ -74,7 +90,7 @@ function buildSkillListing(resolver, ctx, options = {}) {
|
|
|
74
90
|
firstParty: true
|
|
75
91
|
};
|
|
76
92
|
entries.push(entry);
|
|
77
|
-
used +=
|
|
93
|
+
used += estimateListingTokens(renderEntry(entry));
|
|
78
94
|
continue;
|
|
79
95
|
}
|
|
80
96
|
const full = {
|
|
@@ -84,7 +100,7 @@ function buildSkillListing(resolver, ctx, options = {}) {
|
|
|
84
100
|
degraded: false,
|
|
85
101
|
firstParty: false
|
|
86
102
|
};
|
|
87
|
-
const fullCost =
|
|
103
|
+
const fullCost = estimateListingTokens(renderEntry(full));
|
|
88
104
|
if (used + fullCost <= budget) {
|
|
89
105
|
entries.push(full);
|
|
90
106
|
used += fullCost;
|
|
@@ -97,7 +113,7 @@ function buildSkillListing(resolver, ctx, options = {}) {
|
|
|
97
113
|
degraded: true,
|
|
98
114
|
firstParty: false
|
|
99
115
|
};
|
|
100
|
-
const nameCost =
|
|
116
|
+
const nameCost = estimateListingTokens(renderEntry(nameOnly));
|
|
101
117
|
if (used + nameCost <= budget) {
|
|
102
118
|
entries.push(nameOnly);
|
|
103
119
|
used += nameCost;
|
|
@@ -132,4 +148,4 @@ export {
|
|
|
132
148
|
buildSkillListing,
|
|
133
149
|
expandSkill
|
|
134
150
|
};
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
151
|
+
//# sourceMappingURL=chunk-GQZKYWFT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/skills.ts"],"sourcesContent":["/**\n * Skills system (WRAP — progressive-disclosure capability).\n *\n * Faithful re-expression of the source harness's two-phase skill model:\n *\n * Phase 1 — Listing (cheap, always in context): a budget-bounded catalog\n * exposing only {name, description, whenToUse} per skill. First-party\n * skills (bundled/builtin tier) are never truncated and never dropped;\n * lower-tier skills degrade to names-only, then drop, as the context\n * budget is exhausted. Path-activated skills stay hidden until a touched\n * file matches one of their globs.\n *\n * Phase 2 — Invocation (lazy, on demand): expand the chosen skill's body\n * into turn messages, with arg/var substitution and a merged tool\n * allowlist contributed to the permission context.\n *\n * A skill IS a `Definition` — the same tenant-scoped layered resolver backs\n * the command registry and the subagent registry (see ./definitions).\n *\n * SECURITY: this re-expression intentionally drops the source's body-side\n * command-substitution (the upstream \"!`…`\" inline-shell facility). Skill\n * bodies are inert text; they NEVER trigger host shell execution here. All\n * entry points require a non-empty `tenantId` and fail closed; cross-tenant\n * inputs are rejected by the underlying `DefinitionResolver`. Pure data/logic\n * — no host filesystem access (resource base is an injected indirection).\n */\n\nimport { estimateTokens as estimatePrimitiveTokens } from \"@nebutra/ai-primitives\";\nimport { z } from \"zod\";\nimport {\n type Definition,\n type DefinitionResolver,\n type ResolveContext,\n substituteArguments,\n} from \"./definitions\";\n\n/** A skill is just a resolvable `Definition`. */\nexport type SkillRecord = Definition;\n\n/** Tiers treated as first-party (never truncated, never dropped). */\nconst FIRST_PARTY_TIERS: ReadonlySet<string> = new Set([\"bundled\", \"builtin\"]);\nconst REGEX_META_CHARS = new Set([\n \".\",\n \"+\",\n \"?\",\n \"^\",\n \"$\",\n \"{\",\n \"}\",\n \"(\",\n \")\",\n \"|\",\n \"[\",\n \"]\",\n \"\\\\\",\n]);\n\nconst isFirstParty = (s: SkillRecord): boolean => FIRST_PARTY_TIERS.has(s.sourceTier);\n\n/**\n * Listing budgets preserve the catalog degradation contract: very tight lower-\n * tier budgets should still degrade to names-only before dropping. Context\n * compaction uses the corrected 1.3 estimate; this listing surface deliberately\n * uses the shared primitive with no correction.\n */\nconst estimateListingTokens = (text: string): number =>\n estimatePrimitiveTokens(text, { correction: 1 });\n\n// ── Phase 1: listing ─────────────────────────────────────────────────────────\n\n/** One catalog entry — only the cheap, always-in-context surface. */\nexport interface SkillListingEntry {\n readonly name: string;\n readonly description: string;\n readonly whenToUse?: string | undefined;\n /** True when this entry was degraded to names-only by the budget. */\n readonly degraded: boolean;\n readonly firstParty: boolean;\n}\n\nexport interface SkillListing {\n readonly entries: readonly SkillListingEntry[];\n readonly text: string;\n}\n\nconst listingOptionsSchema = z.object({\n maxListingDescChars: z.number().int().positive().default(250),\n contextWindowTokens: z.number().int().positive().default(200_000),\n budgetPercent: z.number().positive().max(1).default(0.01),\n touchedPaths: z.array(z.string()).default([]),\n});\nexport type SkillListingOptions = z.input<typeof listingOptionsSchema>;\n\n/** Minimal glob: `**` = any chars, `*` = any chars except `/`, else exact. */\nfunction globToRegExp(glob: string): RegExp {\n let out = \"^\";\n for (let i = 0; i < glob.length; i++) {\n const c = glob.charAt(i);\n if (c === \"*\") {\n if (glob[i + 1] === \"*\") {\n out += \".*\";\n i++;\n } else {\n out += \"[^/]*\";\n }\n } else if (REGEX_META_CHARS.has(c)) {\n out += `\\\\${c}`;\n } else {\n out += c;\n }\n }\n return new RegExp(`${out}$`);\n}\n\nconst pathMatches = (globs: readonly string[], touched: readonly string[]): boolean =>\n globs.some((g) => {\n const re = globToRegExp(g);\n return touched.some((p) => re.test(p));\n });\n\n/** Stable order: first-party first, then by slug — deterministic output. */\nfunction orderSkills(skills: readonly SkillRecord[]): SkillRecord[] {\n return [...skills].sort((a, b) => {\n const fa = isFirstParty(a) ? 0 : 1;\n const fb = isFirstParty(b) ? 0 : 1;\n if (fa !== fb) return fa - fb;\n return a.slug.localeCompare(b.slug);\n });\n}\n\nconst truncate = (s: string, max: number): string => (s.length <= max ? s : s.slice(0, max));\n\nfunction renderEntry(e: SkillListingEntry): string {\n const head = e.whenToUse ? `- ${e.name} — ${e.whenToUse}` : `- ${e.name}`;\n return e.description ? `${head}\\n ${e.description}` : head;\n}\n\n/**\n * Phase 1 — build the always-in-context skill catalog. Pure & deterministic.\n *\n * Budget = `contextWindowTokens * budgetPercent`. First-party skills are\n * emitted in full and do NOT consume budget against being dropped. Each\n * remaining skill is tried at full description; if it would overflow it is\n * degraded to names-only; if even that overflows it is dropped. Skills in\n * `ctx.loadedSlugs` and unactivated path-gated skills are suppressed.\n */\nexport function buildSkillListing(\n resolver: DefinitionResolver<SkillRecord>,\n ctx: ResolveContext,\n options: SkillListingOptions = {},\n): SkillListing {\n if (!ctx.tenantId) throw new Error(\"buildSkillListing: tenantId is required\");\n const opts = listingOptionsSchema.parse(options);\n\n const loaded = ctx.loadedSlugs ?? new Set<string>();\n const visible = resolver\n .resolve(ctx)\n .filter((s) => !loaded.has(s.slug))\n .filter((s) => {\n const paths = s.frontmatter.paths;\n return paths.length === 0 || pathMatches(paths, opts.touchedPaths);\n });\n\n const budget = Math.max(1, Math.floor(opts.contextWindowTokens * opts.budgetPercent));\n\n const entries: SkillListingEntry[] = [];\n let used = 0;\n\n for (const s of orderSkills(visible)) {\n const fp = isFirstParty(s);\n const fm = s.frontmatter;\n const fullDesc = fp ? fm.description : truncate(fm.description, opts.maxListingDescChars);\n\n if (fp) {\n const entry: SkillListingEntry = {\n name: fm.name,\n description: fm.description,\n whenToUse: fm.whenToUse,\n degraded: false,\n firstParty: true,\n };\n entries.push(entry);\n used += estimateListingTokens(renderEntry(entry));\n continue;\n }\n\n const full: SkillListingEntry = {\n name: fm.name,\n description: fullDesc,\n whenToUse: fm.whenToUse,\n degraded: false,\n firstParty: false,\n };\n const fullCost = estimateListingTokens(renderEntry(full));\n if (used + fullCost <= budget) {\n entries.push(full);\n used += fullCost;\n continue;\n }\n\n const nameOnly: SkillListingEntry = {\n name: fm.name,\n description: \"\",\n whenToUse: undefined,\n degraded: true,\n firstParty: false,\n };\n const nameCost = estimateListingTokens(renderEntry(nameOnly));\n if (used + nameCost <= budget) {\n entries.push(nameOnly);\n used += nameCost;\n }\n // else: drop entirely.\n }\n\n const text = entries.map(renderEntry).join(\"\\n\");\n return { entries, text };\n}\n\n// ── Phase 2: invocation ──────────────────────────────────────────────────────\n\nexport interface SkillMessage {\n readonly role: \"system\" | \"user\";\n readonly content: string;\n}\n\nexport interface ExpandedSkill {\n readonly messages: readonly SkillMessage[];\n readonly allowedTools: readonly string[];\n readonly modelOverride?: string | undefined;\n readonly effort?: \"low\" | \"medium\" | \"high\" | undefined;\n readonly executionMode: \"inline\" | \"fork\";\n}\n\nexport interface ExpandContext {\n readonly tenantId: string;\n readonly args: Record<string, string>;\n readonly vars: Record<string, string>;\n /** Lazy body fetch — host supplies; called only here, only once. */\n readonly bodyLoader: (bodyRef: string) => Promise<string>;\n /** Host-injected resource-base indirection (NO host FS access here). */\n readonly skillResourceBase: (slug: string) => string;\n}\n\n/**\n * Phase 2 — expand a skill into turn messages. Body is loaded lazily via the\n * injected `bodyLoader` (never during listing). A `Base directory…` line is\n * prepended via the injected `skillResourceBase` indirection. Args + vars are\n * substituted with `substituteArguments`. The skill's `allowedTools` are\n * returned for the caller to merge into the permission context — this never\n * mutates global state and never executes anything from the body.\n */\nexport async function expandSkill(skill: SkillRecord, ctx: ExpandContext): Promise<ExpandedSkill> {\n if (!ctx.tenantId) throw new Error(\"expandSkill: tenantId is required\");\n if (skill.tenantId !== ctx.tenantId) {\n throw new Error(\"expandSkill: cross-tenant skill access rejected\");\n }\n\n const rawBody = await ctx.bodyLoader(skill.bodyRef);\n const body = substituteArguments(rawBody, ctx.args, ctx.vars);\n const baseLine = `Base directory for this skill: ${ctx.skillResourceBase(skill.slug)}`;\n\n const messages: readonly SkillMessage[] = [\n { role: \"system\", content: baseLine },\n { role: \"user\", content: body },\n ];\n\n const allowedTools = [...skill.frontmatter.allowedTools];\n\n return {\n messages,\n allowedTools,\n modelOverride: skill.frontmatter.model,\n effort: skill.frontmatter.effort,\n executionMode: skill.frontmatter.executionMode,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runTurn
|
|
3
|
+
} from "./chunk-D4YAPLOW.js";
|
|
4
|
+
import {
|
|
5
|
+
replay
|
|
6
|
+
} from "./chunk-37BBB2P2.js";
|
|
7
|
+
import {
|
|
8
|
+
RuntimeToolRegistry
|
|
9
|
+
} from "./chunk-4Y25ZTKI.js";
|
|
10
|
+
import {
|
|
11
|
+
isTurnTerminal
|
|
12
|
+
} from "./chunk-6EGG2OZC.js";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_APPROVAL_POLICY
|
|
15
|
+
} from "./chunk-CGRCUKGT.js";
|
|
16
|
+
|
|
17
|
+
// src/pulsar.ts
|
|
18
|
+
function newId(prefix) {
|
|
19
|
+
return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`;
|
|
20
|
+
}
|
|
21
|
+
function requireNonEmpty(value, label) {
|
|
22
|
+
const trimmed = value.trim();
|
|
23
|
+
if (!trimmed) {
|
|
24
|
+
throw new Error(`${label} is required. Suggestion: pass a non-empty ${label}.`);
|
|
25
|
+
}
|
|
26
|
+
return trimmed;
|
|
27
|
+
}
|
|
28
|
+
function recordedEvents(lines) {
|
|
29
|
+
return lines.flatMap((line) => line.type === "event" ? [line.event] : []);
|
|
30
|
+
}
|
|
31
|
+
function mapItemToEvent(item) {
|
|
32
|
+
switch (item.type) {
|
|
33
|
+
case "agent_message":
|
|
34
|
+
case "reasoning":
|
|
35
|
+
return "llm_call";
|
|
36
|
+
case "file_change":
|
|
37
|
+
return "content_write";
|
|
38
|
+
case "command_execution":
|
|
39
|
+
case "mcp_tool_call":
|
|
40
|
+
return "tool_call";
|
|
41
|
+
case "error":
|
|
42
|
+
case "todo_list":
|
|
43
|
+
case "web_search":
|
|
44
|
+
return "tool_call";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function affectedPaths(item) {
|
|
48
|
+
return item.type === "file_change" ? item.changes.map((change) => change.path) : [];
|
|
49
|
+
}
|
|
50
|
+
var PulsarThread = class {
|
|
51
|
+
id;
|
|
52
|
+
playId;
|
|
53
|
+
tenantId;
|
|
54
|
+
#deps;
|
|
55
|
+
#input;
|
|
56
|
+
#maxSteps;
|
|
57
|
+
#mode;
|
|
58
|
+
#events;
|
|
59
|
+
constructor(input) {
|
|
60
|
+
this.id = input.id;
|
|
61
|
+
this.playId = input.playId;
|
|
62
|
+
this.tenantId = input.tenantId;
|
|
63
|
+
this.#deps = input.deps;
|
|
64
|
+
this.#input = input.userInput;
|
|
65
|
+
this.#mode = input.mode;
|
|
66
|
+
this.#maxSteps = input.maxSteps;
|
|
67
|
+
}
|
|
68
|
+
async *subscribe() {
|
|
69
|
+
if (this.#events) {
|
|
70
|
+
for (const event of this.#events) yield event;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const events = [];
|
|
74
|
+
const emit = async (event) => {
|
|
75
|
+
events.push(event);
|
|
76
|
+
if (event.type === "item.completed") {
|
|
77
|
+
await this.#mirrorItem(event.item);
|
|
78
|
+
}
|
|
79
|
+
return event;
|
|
80
|
+
};
|
|
81
|
+
if (this.#mode === "resume") {
|
|
82
|
+
const lines = await this.#deps.store.read(this.tenantId, this.id);
|
|
83
|
+
const projection = replay(lines);
|
|
84
|
+
const recorded = recordedEvents(lines);
|
|
85
|
+
const last = recorded.at(-1);
|
|
86
|
+
if (projection && last && isTurnTerminal(last)) {
|
|
87
|
+
this.#events = recorded;
|
|
88
|
+
for (const event of recorded) yield event;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
yield await emit({ type: "thread.started", threadId: this.id });
|
|
93
|
+
await this.#deps.store.append({
|
|
94
|
+
tenantId: this.tenantId,
|
|
95
|
+
threadId: this.id,
|
|
96
|
+
type: "event",
|
|
97
|
+
event: { type: "thread.started", threadId: this.id },
|
|
98
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
99
|
+
});
|
|
100
|
+
const stream = runTurn(this.#input, {
|
|
101
|
+
tenantId: this.tenantId,
|
|
102
|
+
threadId: this.id,
|
|
103
|
+
config: this.#deps.config,
|
|
104
|
+
approvalPolicy: this.#deps.approvalPolicy,
|
|
105
|
+
model: this.#deps.model,
|
|
106
|
+
tools: this.#deps.tools,
|
|
107
|
+
store: this.#deps.store,
|
|
108
|
+
approvalGate: this.#deps.approvalGate,
|
|
109
|
+
ruleEvaluator: () => "allow",
|
|
110
|
+
...this.#maxSteps !== void 0 ? { maxSteps: this.#maxSteps } : {}
|
|
111
|
+
});
|
|
112
|
+
for await (const event of stream) {
|
|
113
|
+
yield await emit(event);
|
|
114
|
+
}
|
|
115
|
+
this.#events = events;
|
|
116
|
+
}
|
|
117
|
+
async #mirrorItem(item) {
|
|
118
|
+
const eventLog = this.#deps.eventLog;
|
|
119
|
+
if (!eventLog) return;
|
|
120
|
+
const parent = Pulsar.itemCommitIds.get(`${this.id}::__last`) ?? null;
|
|
121
|
+
const commitId = await eventLog.commit({
|
|
122
|
+
traceId: this.id,
|
|
123
|
+
kind: mapItemToEvent(item),
|
|
124
|
+
affected: affectedPaths(item),
|
|
125
|
+
parent
|
|
126
|
+
});
|
|
127
|
+
Pulsar.itemCommitIds.set(`${this.id}:${item.id}`, commitId);
|
|
128
|
+
Pulsar.itemCommitIds.set(`${this.id}::__last`, commitId);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
var PulsarBuilder = class {
|
|
132
|
+
#tenantId;
|
|
133
|
+
#config;
|
|
134
|
+
#model;
|
|
135
|
+
#tools = new RuntimeToolRegistry();
|
|
136
|
+
#store;
|
|
137
|
+
#approvalGate;
|
|
138
|
+
#approvalPolicy = DEFAULT_APPROVAL_POLICY;
|
|
139
|
+
#eventLog;
|
|
140
|
+
withTenant(tenantId) {
|
|
141
|
+
this.#tenantId = requireNonEmpty(tenantId, "tenantId");
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
withConfig(config) {
|
|
145
|
+
this.#config = config;
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
withModel(model) {
|
|
149
|
+
this.#model = model;
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
withTools(tools) {
|
|
153
|
+
this.#tools = tools;
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
withRolloutStore(store) {
|
|
157
|
+
this.#store = store;
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
withApprovalGate(approvalGate) {
|
|
161
|
+
this.#approvalGate = approvalGate;
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
withApprovalPolicy(policy) {
|
|
165
|
+
this.#approvalPolicy = policy;
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
withEventLog(eventLog) {
|
|
169
|
+
this.#eventLog = eventLog;
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
build() {
|
|
173
|
+
if (!this.#tenantId) throw new Error("tenantId is required. Suggestion: call withTenant().");
|
|
174
|
+
if (!this.#config) throw new Error("config is required. Suggestion: call withConfig().");
|
|
175
|
+
if (!this.#model) throw new Error("model is required. Suggestion: call withModel().");
|
|
176
|
+
if (!this.#store) {
|
|
177
|
+
throw new Error("rollout store is required. Suggestion: call withRolloutStore().");
|
|
178
|
+
}
|
|
179
|
+
if (!this.#approvalGate) {
|
|
180
|
+
throw new Error("approval gate is required. Suggestion: call withApprovalGate().");
|
|
181
|
+
}
|
|
182
|
+
const deps = {
|
|
183
|
+
tenantId: this.#tenantId,
|
|
184
|
+
config: this.#config,
|
|
185
|
+
model: this.#model,
|
|
186
|
+
tools: this.#tools,
|
|
187
|
+
store: this.#store,
|
|
188
|
+
approvalGate: this.#approvalGate,
|
|
189
|
+
approvalPolicy: this.#approvalPolicy,
|
|
190
|
+
...this.#eventLog !== void 0 ? { eventLog: this.#eventLog } : {}
|
|
191
|
+
};
|
|
192
|
+
return new Pulsar(deps);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var Pulsar = class _Pulsar {
|
|
196
|
+
static itemCommitIds = /* @__PURE__ */ new Map();
|
|
197
|
+
#deps;
|
|
198
|
+
constructor(deps) {
|
|
199
|
+
this.#deps = deps;
|
|
200
|
+
}
|
|
201
|
+
static builder() {
|
|
202
|
+
return new PulsarBuilder();
|
|
203
|
+
}
|
|
204
|
+
async startPlay(playId, input, options = {}) {
|
|
205
|
+
const threadId = options.threadId ?? newId("thread");
|
|
206
|
+
await this.#deps.store.append({
|
|
207
|
+
tenantId: this.#deps.tenantId,
|
|
208
|
+
threadId,
|
|
209
|
+
type: "session_meta",
|
|
210
|
+
config: this.#deps.config,
|
|
211
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
212
|
+
});
|
|
213
|
+
return new PulsarThread({
|
|
214
|
+
id: threadId,
|
|
215
|
+
playId: requireNonEmpty(playId, "playId"),
|
|
216
|
+
tenantId: this.#deps.tenantId,
|
|
217
|
+
deps: this.#deps,
|
|
218
|
+
mode: "start",
|
|
219
|
+
userInput: input,
|
|
220
|
+
...options.maxSteps !== void 0 ? { maxSteps: options.maxSteps } : {}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async resume(threadId) {
|
|
224
|
+
return new PulsarThread({
|
|
225
|
+
id: requireNonEmpty(threadId, "threadId"),
|
|
226
|
+
playId: "resume",
|
|
227
|
+
tenantId: this.#deps.tenantId,
|
|
228
|
+
deps: this.#deps,
|
|
229
|
+
mode: "resume",
|
|
230
|
+
userInput: ""
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async branchFromItem(threadId, itemId, name) {
|
|
234
|
+
const eventLog = this.#deps.eventLog;
|
|
235
|
+
if (!eventLog) {
|
|
236
|
+
throw new Error("event-log is required. Suggestion: call withEventLog() before branching.");
|
|
237
|
+
}
|
|
238
|
+
const commitId = _Pulsar.itemCommitIds.get(
|
|
239
|
+
`${requireNonEmpty(threadId, "threadId")}:${requireNonEmpty(itemId, "itemId")}`
|
|
240
|
+
);
|
|
241
|
+
if (!commitId) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"item has no event-log commit. Suggestion: subscribe to the thread before branching."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return eventLog.branchFrom(commitId, requireNonEmpty(name, "branch name"));
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export {
|
|
251
|
+
PulsarThread,
|
|
252
|
+
PulsarBuilder,
|
|
253
|
+
Pulsar
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=chunk-KCNN4QUQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pulsar.ts"],"sourcesContent":["import { type ApprovalGate, type ModelInvoker, runTurn } from \"./loop\";\nimport type { ThreadEvent, ThreadItem, TurnConfig } from \"./model\";\nimport { isTurnTerminal } from \"./model\";\nimport type { ApprovalPolicy } from \"./policy\";\nimport { DEFAULT_APPROVAL_POLICY } from \"./policy\";\nimport type { RolloutLine, RolloutStore } from \"./rollout\";\nimport { replay } from \"./rollout\";\nimport { RuntimeToolRegistry } from \"./tools\";\n\ntype EventLogKind = \"llm_call\" | \"tool_call\" | \"sub_agent_call\" | \"content_write\";\n\nexport interface PulsarEventInput {\n readonly traceId: string;\n readonly kind: EventLogKind;\n readonly affected: readonly string[];\n readonly parent: string | null;\n readonly snapshot?: Record<string, string>;\n}\n\nexport interface PulsarBranchRecord {\n readonly name: string;\n readonly from: string;\n readonly at: string;\n}\n\nexport interface PulsarEventLogPort {\n commit(event: PulsarEventInput): Promise<string>;\n branchFrom(id: string, name: string): Promise<PulsarBranchRecord>;\n}\n\nexport interface StartThreadOptions {\n readonly threadId?: string;\n readonly maxSteps?: number;\n}\n\nexport interface PulsarDeps {\n readonly tenantId: string;\n readonly config: TurnConfig;\n readonly model: ModelInvoker;\n readonly tools: RuntimeToolRegistry;\n readonly store: RolloutStore;\n readonly approvalGate: ApprovalGate;\n readonly approvalPolicy: ApprovalPolicy;\n readonly eventLog?: PulsarEventLogPort;\n}\n\nfunction newId(prefix: string): string {\n return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`;\n}\n\nfunction requireNonEmpty(value: string, label: string): string {\n const trimmed = value.trim();\n if (!trimmed) {\n throw new Error(`${label} is required. Suggestion: pass a non-empty ${label}.`);\n }\n return trimmed;\n}\n\nfunction recordedEvents(lines: readonly RolloutLine[]): ThreadEvent[] {\n return lines.flatMap((line) => (line.type === \"event\" ? [line.event] : []));\n}\n\nfunction mapItemToEvent(item: ThreadItem): PulsarEventInput[\"kind\"] {\n switch (item.type) {\n case \"agent_message\":\n case \"reasoning\":\n return \"llm_call\";\n case \"file_change\":\n return \"content_write\";\n case \"command_execution\":\n case \"mcp_tool_call\":\n return \"tool_call\";\n case \"error\":\n case \"todo_list\":\n case \"web_search\":\n return \"tool_call\";\n }\n}\n\nfunction affectedPaths(item: ThreadItem): readonly string[] {\n return item.type === \"file_change\" ? item.changes.map((change) => change.path) : [];\n}\n\nexport class PulsarThread {\n readonly id: string;\n readonly playId: string;\n readonly tenantId: string;\n readonly #deps: PulsarDeps;\n readonly #input: string;\n readonly #maxSteps: number | undefined;\n readonly #mode: \"start\" | \"resume\";\n #events: ThreadEvent[] | undefined;\n\n constructor(input: {\n readonly id: string;\n readonly playId: string;\n readonly tenantId: string;\n readonly deps: PulsarDeps;\n readonly mode: \"start\" | \"resume\";\n readonly userInput: string;\n readonly maxSteps?: number;\n }) {\n this.id = input.id;\n this.playId = input.playId;\n this.tenantId = input.tenantId;\n this.#deps = input.deps;\n this.#input = input.userInput;\n this.#mode = input.mode;\n this.#maxSteps = input.maxSteps;\n }\n\n async *subscribe(): AsyncGenerator<ThreadEvent> {\n if (this.#events) {\n for (const event of this.#events) yield event;\n return;\n }\n\n const events: ThreadEvent[] = [];\n const emit = async (event: ThreadEvent): Promise<ThreadEvent> => {\n events.push(event);\n if (event.type === \"item.completed\") {\n await this.#mirrorItem(event.item);\n }\n return event;\n };\n\n if (this.#mode === \"resume\") {\n const lines = await this.#deps.store.read(this.tenantId, this.id);\n const projection = replay(lines);\n const recorded = recordedEvents(lines);\n const last = recorded.at(-1);\n if (projection && last && isTurnTerminal(last)) {\n this.#events = recorded;\n for (const event of recorded) yield event;\n return;\n }\n }\n\n yield await emit({ type: \"thread.started\", threadId: this.id });\n await this.#deps.store.append({\n tenantId: this.tenantId,\n threadId: this.id,\n type: \"event\",\n event: { type: \"thread.started\", threadId: this.id },\n at: new Date().toISOString(),\n });\n\n const stream = runTurn(this.#input, {\n tenantId: this.tenantId,\n threadId: this.id,\n config: this.#deps.config,\n approvalPolicy: this.#deps.approvalPolicy,\n model: this.#deps.model,\n tools: this.#deps.tools,\n store: this.#deps.store,\n approvalGate: this.#deps.approvalGate,\n ruleEvaluator: () => \"allow\",\n ...(this.#maxSteps !== undefined ? { maxSteps: this.#maxSteps } : {}),\n });\n\n for await (const event of stream) {\n yield await emit(event);\n }\n this.#events = events;\n }\n\n async #mirrorItem(item: ThreadItem): Promise<void> {\n const eventLog = this.#deps.eventLog;\n if (!eventLog) return;\n const parent = Pulsar.itemCommitIds.get(`${this.id}::__last`) ?? null;\n const commitId = await eventLog.commit({\n traceId: this.id,\n kind: mapItemToEvent(item),\n affected: affectedPaths(item),\n parent,\n });\n Pulsar.itemCommitIds.set(`${this.id}:${item.id}`, commitId);\n Pulsar.itemCommitIds.set(`${this.id}::__last`, commitId);\n }\n}\n\nexport class PulsarBuilder {\n #tenantId?: string;\n #config?: TurnConfig;\n #model?: ModelInvoker;\n #tools: RuntimeToolRegistry = new RuntimeToolRegistry();\n #store?: RolloutStore;\n #approvalGate?: ApprovalGate;\n #approvalPolicy: ApprovalPolicy = DEFAULT_APPROVAL_POLICY;\n #eventLog?: PulsarEventLogPort;\n\n withTenant(tenantId: string): this {\n this.#tenantId = requireNonEmpty(tenantId, \"tenantId\");\n return this;\n }\n\n withConfig(config: TurnConfig): this {\n this.#config = config;\n return this;\n }\n\n withModel(model: ModelInvoker): this {\n this.#model = model;\n return this;\n }\n\n withTools(tools: RuntimeToolRegistry): this {\n this.#tools = tools;\n return this;\n }\n\n withRolloutStore(store: RolloutStore): this {\n this.#store = store;\n return this;\n }\n\n withApprovalGate(approvalGate: ApprovalGate): this {\n this.#approvalGate = approvalGate;\n return this;\n }\n\n withApprovalPolicy(policy: ApprovalPolicy): this {\n this.#approvalPolicy = policy;\n return this;\n }\n\n withEventLog(eventLog: PulsarEventLogPort): this {\n this.#eventLog = eventLog;\n return this;\n }\n\n build(): Pulsar {\n if (!this.#tenantId) throw new Error(\"tenantId is required. Suggestion: call withTenant().\");\n if (!this.#config) throw new Error(\"config is required. Suggestion: call withConfig().\");\n if (!this.#model) throw new Error(\"model is required. Suggestion: call withModel().\");\n if (!this.#store) {\n throw new Error(\"rollout store is required. Suggestion: call withRolloutStore().\");\n }\n if (!this.#approvalGate) {\n throw new Error(\"approval gate is required. Suggestion: call withApprovalGate().\");\n }\n const deps: PulsarDeps = {\n tenantId: this.#tenantId,\n config: this.#config,\n model: this.#model,\n tools: this.#tools,\n store: this.#store,\n approvalGate: this.#approvalGate,\n approvalPolicy: this.#approvalPolicy,\n ...(this.#eventLog !== undefined ? { eventLog: this.#eventLog } : {}),\n };\n return new Pulsar(deps);\n }\n}\n\nexport class Pulsar {\n static readonly itemCommitIds = new Map<string, string>();\n readonly #deps: PulsarDeps;\n\n constructor(deps: PulsarDeps) {\n this.#deps = deps;\n }\n\n static builder(): PulsarBuilder {\n return new PulsarBuilder();\n }\n\n async startPlay(\n playId: string,\n input: string,\n options: StartThreadOptions = {},\n ): Promise<PulsarThread> {\n const threadId = options.threadId ?? newId(\"thread\");\n await this.#deps.store.append({\n tenantId: this.#deps.tenantId,\n threadId,\n type: \"session_meta\",\n config: this.#deps.config,\n at: new Date().toISOString(),\n });\n return new PulsarThread({\n id: threadId,\n playId: requireNonEmpty(playId, \"playId\"),\n tenantId: this.#deps.tenantId,\n deps: this.#deps,\n mode: \"start\",\n userInput: input,\n ...(options.maxSteps !== undefined ? { maxSteps: options.maxSteps } : {}),\n });\n }\n\n async resume(threadId: string): Promise<PulsarThread> {\n return new PulsarThread({\n id: requireNonEmpty(threadId, \"threadId\"),\n playId: \"resume\",\n tenantId: this.#deps.tenantId,\n deps: this.#deps,\n mode: \"resume\",\n userInput: \"\",\n });\n }\n\n async branchFromItem(\n threadId: string,\n itemId: string,\n name: string,\n ): Promise<PulsarBranchRecord> {\n const eventLog = this.#deps.eventLog;\n if (!eventLog) {\n throw new Error(\"event-log is required. Suggestion: call withEventLog() before branching.\");\n }\n const commitId = Pulsar.itemCommitIds.get(\n `${requireNonEmpty(threadId, \"threadId\")}:${requireNonEmpty(itemId, \"itemId\")}`,\n );\n if (!commitId) {\n throw new Error(\n \"item has no event-log commit. Suggestion: subscribe to the thread before branching.\",\n );\n }\n return eventLog.branchFrom(commitId, requireNonEmpty(name, \"branch name\"));\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/orchestration.ts
|
|
2
|
+
function fail(message, suggestion) {
|
|
3
|
+
throw new Error(`${message}. Suggestion: ${suggestion}`);
|
|
4
|
+
}
|
|
5
|
+
function validateBrief(brief) {
|
|
6
|
+
if (!brief.id.trim()) fail("brief.id is required", "Use a stable role or task id.");
|
|
7
|
+
if (!brief.objective.trim()) {
|
|
8
|
+
fail("brief.objective is required", "Write a concrete objective before dispatching.");
|
|
9
|
+
}
|
|
10
|
+
if (brief.allowedTools.length === 0) {
|
|
11
|
+
fail("brief.allowedTools is required", "Constrain each subagent to an explicit tool scope.");
|
|
12
|
+
}
|
|
13
|
+
if (brief.boundaries.length === 0) {
|
|
14
|
+
fail("brief.boundaries is required", "State at least one boundary for the worker.");
|
|
15
|
+
}
|
|
16
|
+
if (brief.budget.tokenLimit <= 0 || brief.budget.durationMs <= 0 || brief.budget.costUsd < 0) {
|
|
17
|
+
fail("brief.budget is invalid", "Set positive token/time caps and a non-negative cost cap.");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function dependencySet(briefs) {
|
|
21
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const brief of briefs) {
|
|
23
|
+
for (const dep of brief.dependsOn ?? []) dependencies.add(dep);
|
|
24
|
+
}
|
|
25
|
+
return dependencies;
|
|
26
|
+
}
|
|
27
|
+
function topologicalOrder(briefs) {
|
|
28
|
+
const byId = new Map(briefs.map((brief) => [brief.id, brief]));
|
|
29
|
+
const visited = /* @__PURE__ */ new Set();
|
|
30
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
31
|
+
const ordered = [];
|
|
32
|
+
const visit = (brief) => {
|
|
33
|
+
if (visited.has(brief.id)) return;
|
|
34
|
+
if (visiting.has(brief.id)) {
|
|
35
|
+
fail(
|
|
36
|
+
`subagent dependency cycle includes '${brief.id}'`,
|
|
37
|
+
"Remove the cycle or collapse the dependent work into one sequential brief."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
visiting.add(brief.id);
|
|
41
|
+
for (const dep of brief.dependsOn ?? []) {
|
|
42
|
+
const dependency = byId.get(dep);
|
|
43
|
+
if (dependency) visit(dependency);
|
|
44
|
+
}
|
|
45
|
+
visiting.delete(brief.id);
|
|
46
|
+
visited.add(brief.id);
|
|
47
|
+
ordered.push(brief);
|
|
48
|
+
};
|
|
49
|
+
for (const brief of briefs) visit(brief);
|
|
50
|
+
return ordered;
|
|
51
|
+
}
|
|
52
|
+
function planSubagentDispatch(briefs, options = {}) {
|
|
53
|
+
if (briefs.length === 0) {
|
|
54
|
+
fail("at least one brief is required", "Create a concrete worker brief before dispatching.");
|
|
55
|
+
}
|
|
56
|
+
for (const brief of briefs) validateBrief(brief);
|
|
57
|
+
const dependencies = dependencySet(briefs);
|
|
58
|
+
if ((options.strategy ?? "auto") === "fanout" && dependencies.size > 0) {
|
|
59
|
+
fail(
|
|
60
|
+
"fan-out cannot run briefs that depend on each other",
|
|
61
|
+
"Use sequential dispatch for dependent work or split independent briefs only."
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const order = topologicalOrder(briefs);
|
|
65
|
+
const strategy = options.strategy === "fanout" || options.strategy === "auto" && dependencies.size === 0 ? "fanout" : "sequential";
|
|
66
|
+
return { strategy, order };
|
|
67
|
+
}
|
|
68
|
+
async function fanOutSubagents(briefs, run) {
|
|
69
|
+
const plan = planSubagentDispatch(briefs, { strategy: "fanout" });
|
|
70
|
+
return Promise.all(plan.order.map((brief) => run(brief)));
|
|
71
|
+
}
|
|
72
|
+
function costReport(results) {
|
|
73
|
+
return {
|
|
74
|
+
totalInputTokens: results.reduce((sum, result) => sum + result.usage.inputTokens, 0),
|
|
75
|
+
totalOutputTokens: results.reduce((sum, result) => sum + result.usage.outputTokens, 0),
|
|
76
|
+
totalReasoningTokens: results.reduce(
|
|
77
|
+
(sum, result) => sum + result.usage.reasoningOutputTokens,
|
|
78
|
+
0
|
|
79
|
+
),
|
|
80
|
+
maxDurationMs: results.reduce((max, result) => Math.max(max, result.durationMs), 0),
|
|
81
|
+
subagents: results.length
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
planSubagentDispatch,
|
|
87
|
+
fanOutSubagents,
|
|
88
|
+
costReport
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=chunk-MX2WL43P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/orchestration.ts"],"sourcesContent":["import type { TurnUsage } from \"./model\";\n\nexport interface BudgetCap {\n readonly durationMs: number;\n readonly costUsd: number;\n readonly tokenLimit: number;\n}\n\nexport interface Brief {\n readonly id: string;\n readonly objective: string;\n readonly outputFormat: Record<string, unknown>;\n readonly allowedTools: readonly string[];\n readonly contextRefs: readonly string[];\n readonly boundaries: readonly string[];\n readonly budget: BudgetCap;\n readonly dependsOn?: readonly string[];\n}\n\nexport type DispatchStrategy = \"auto\" | \"sequential\" | \"fanout\";\n\nexport interface DispatchPlan {\n readonly strategy: Exclude<DispatchStrategy, \"auto\">;\n readonly order: readonly Brief[];\n}\n\nexport interface DispatchPlanOptions {\n readonly strategy?: DispatchStrategy;\n}\n\nexport interface SubagentResult {\n readonly briefId: string;\n readonly output: unknown;\n readonly usage: TurnUsage;\n readonly durationMs: number;\n}\n\nexport interface SubagentCostReport {\n readonly totalInputTokens: number;\n readonly totalOutputTokens: number;\n readonly totalReasoningTokens: number;\n readonly maxDurationMs: number;\n readonly subagents: number;\n}\n\nfunction fail(message: string, suggestion: string): never {\n throw new Error(`${message}. Suggestion: ${suggestion}`);\n}\n\nfunction validateBrief(brief: Brief): void {\n if (!brief.id.trim()) fail(\"brief.id is required\", \"Use a stable role or task id.\");\n if (!brief.objective.trim()) {\n fail(\"brief.objective is required\", \"Write a concrete objective before dispatching.\");\n }\n if (brief.allowedTools.length === 0) {\n fail(\"brief.allowedTools is required\", \"Constrain each subagent to an explicit tool scope.\");\n }\n if (brief.boundaries.length === 0) {\n fail(\"brief.boundaries is required\", \"State at least one boundary for the worker.\");\n }\n if (brief.budget.tokenLimit <= 0 || brief.budget.durationMs <= 0 || brief.budget.costUsd < 0) {\n fail(\"brief.budget is invalid\", \"Set positive token/time caps and a non-negative cost cap.\");\n }\n}\n\nfunction dependencySet(briefs: readonly Brief[]): Set<string> {\n const dependencies = new Set<string>();\n for (const brief of briefs) {\n for (const dep of brief.dependsOn ?? []) dependencies.add(dep);\n }\n return dependencies;\n}\n\nfunction topologicalOrder(briefs: readonly Brief[]): Brief[] {\n const byId = new Map(briefs.map((brief) => [brief.id, brief]));\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const ordered: Brief[] = [];\n\n const visit = (brief: Brief): void => {\n if (visited.has(brief.id)) return;\n if (visiting.has(brief.id)) {\n fail(\n `subagent dependency cycle includes '${brief.id}'`,\n \"Remove the cycle or collapse the dependent work into one sequential brief.\",\n );\n }\n visiting.add(brief.id);\n for (const dep of brief.dependsOn ?? []) {\n const dependency = byId.get(dep);\n if (dependency) visit(dependency);\n }\n visiting.delete(brief.id);\n visited.add(brief.id);\n ordered.push(brief);\n };\n\n for (const brief of briefs) visit(brief);\n return ordered;\n}\n\nexport function planSubagentDispatch(\n briefs: readonly Brief[],\n options: DispatchPlanOptions = {},\n): DispatchPlan {\n if (briefs.length === 0) {\n fail(\"at least one brief is required\", \"Create a concrete worker brief before dispatching.\");\n }\n for (const brief of briefs) validateBrief(brief);\n\n const dependencies = dependencySet(briefs);\n if ((options.strategy ?? \"auto\") === \"fanout\" && dependencies.size > 0) {\n fail(\n \"fan-out cannot run briefs that depend on each other\",\n \"Use sequential dispatch for dependent work or split independent briefs only.\",\n );\n }\n\n const order = topologicalOrder(briefs);\n const strategy =\n options.strategy === \"fanout\" || (options.strategy === \"auto\" && dependencies.size === 0)\n ? \"fanout\"\n : \"sequential\";\n return { strategy, order };\n}\n\nexport async function fanOutSubagents(\n briefs: readonly Brief[],\n run: (brief: Brief) => Promise<SubagentResult>,\n): Promise<readonly SubagentResult[]> {\n const plan = planSubagentDispatch(briefs, { strategy: \"fanout\" });\n return Promise.all(plan.order.map((brief) => run(brief)));\n}\n\nexport function costReport(results: readonly SubagentResult[]): SubagentCostReport {\n return {\n totalInputTokens: results.reduce((sum, result) => sum + result.usage.inputTokens, 0),\n totalOutputTokens: results.reduce((sum, result) => sum + result.usage.outputTokens, 0),\n totalReasoningTokens: results.reduce(\n (sum, result) => sum + result.usage.reasoningOutputTokens,\n 0,\n ),\n maxDurationMs: results.reduce((max, result) => Math.max(max, result.durationMs), 0),\n subagents: results.length,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
adaptMcpTool
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4Y25ZTKI.js";
|
|
4
4
|
|
|
5
5
|
// src/mcp-bridge.ts
|
|
6
6
|
import { z } from "zod";
|
|
@@ -30,4 +30,4 @@ async function activateMcpTools(registry, catalog, client, ctx) {
|
|
|
30
30
|
export {
|
|
31
31
|
activateMcpTools
|
|
32
32
|
};
|
|
33
|
-
//# sourceMappingURL=chunk-
|
|
33
|
+
//# sourceMappingURL=chunk-NI4EDT4T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp-bridge.ts"],"sourcesContent":["/**\n * MCP activation bridge (WRAP — capability #9, activation seam).\n *\n * Concrete bridge that registers external MCP-server tools into the runtime's\n * uniform tool model. It does NOT import `@nebutra/mcp` (WIP / do-not-import):\n * instead it defines minimal injectable ports so callers wire `@nebutra/mcp`\n * (`serverRegistry` + `mcpClient`) without this package taking a hard dep.\n *\n * Tenant-scoped by construction: the catalog port is always queried with the\n * caller's `{ tenantId, plan }`, so a tenant only ever sees its own MCP\n * servers/tools. Fail-closed on missing tenant.\n */\n\nimport { z } from \"zod\";\n\nimport {\n adaptMcpTool,\n type McpClientLike,\n type RuntimeToolRegistry,\n type ToolDefinition,\n} from \"./tools\";\n\n/**\n * Port over an MCP server catalog (satisfied by `@nebutra/mcp`'s\n * `serverRegistry` + plan middleware). Returns only the tools visible to the\n * given tenant/plan — visibility/plan-gating is the port's responsibility.\n */\nexport interface McpServerCatalogPort {\n listTools(ctx: {\n tenantId: string;\n plan?: string;\n }): Promise<{ server: string; definition: ToolDefinition }[]>;\n}\n\nconst ctxSchema = z.object({\n tenantId: z.string().trim().min(1, \"tenantId is required (fail-closed)\"),\n plan: z.string().min(1).optional(),\n});\n\nexport interface ActivateMcpToolsResult {\n /** Tool names newly registered into the registry, in catalog order. */\n readonly registered: readonly string[];\n /** Tool names skipped because the name was already registered. */\n readonly skipped: readonly string[];\n}\n\n/**\n * List the tenant/plan-visible MCP tools, adapt each via {@link adaptMcpTool},\n * and register them into the {@link RuntimeToolRegistry}. A tool whose name is already\n * registered is skipped (reported, never thrown). Empty/blank tenantId fails\n * closed before any catalog call.\n */\nexport async function activateMcpTools(\n registry: RuntimeToolRegistry,\n catalog: McpServerCatalogPort,\n client: McpClientLike,\n ctx: { tenantId: string; plan?: string },\n): Promise<ActivateMcpToolsResult> {\n const scope = ctxSchema.parse(ctx);\n\n const entries = await catalog.listTools(\n scope.plan === undefined\n ? { tenantId: scope.tenantId }\n : { tenantId: scope.tenantId, plan: scope.plan },\n );\n\n const registered: string[] = [];\n const skipped: string[] = [];\n\n for (const { server, definition } of entries) {\n if (registry.list().some((r) => r.definition.name === definition.name)) {\n skipped.push(definition.name);\n continue;\n }\n const adapted = adaptMcpTool(server, definition, client);\n registry.register(adapted.definition, adapted.handler, adapted.origin);\n registered.push(definition.name);\n }\n\n return { registered, skipped };\n}\n"],"mappings":";;;;;AAaA,SAAS,SAAS;AAqBlB,IAAM,YAAY,EAAE,OAAO;AAAA,EACzB,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,GAAG,oCAAoC;AAAA,EACvE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AACnC,CAAC;AAeD,eAAsB,iBACpB,UACA,SACA,QACA,KACiC;AACjC,QAAM,QAAQ,UAAU,MAAM,GAAG;AAEjC,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,MAAM,SAAS,SACX,EAAE,UAAU,MAAM,SAAS,IAC3B,EAAE,UAAU,MAAM,UAAU,MAAM,MAAM,KAAK;AAAA,EACnD;AAEA,QAAM,aAAuB,CAAC;AAC9B,QAAM,UAAoB,CAAC;AAE3B,aAAW,EAAE,QAAQ,WAAW,KAAK,SAAS;AAC5C,QAAI,SAAS,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,WAAW,IAAI,GAAG;AACtE,cAAQ,KAAK,WAAW,IAAI;AAC5B;AAAA,IACF;AACA,UAAM,UAAU,aAAa,QAAQ,YAAY,MAAM;AACvD,aAAS,SAAS,QAAQ,YAAY,QAAQ,SAAS,QAAQ,MAAM;AACrE,eAAW,KAAK,WAAW,IAAI;AAAA,EACjC;AAEA,SAAO,EAAE,YAAY,QAAQ;AAC/B;","names":[]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isTurnTerminal
|
|
3
|
-
} from "./chunk-6EGG2OZC.js";
|
|
4
1
|
import {
|
|
5
2
|
replay
|
|
6
3
|
} from "./chunk-37BBB2P2.js";
|
|
4
|
+
import {
|
|
5
|
+
isTurnTerminal
|
|
6
|
+
} from "./chunk-6EGG2OZC.js";
|
|
7
7
|
|
|
8
8
|
// src/durable-turn.ts
|
|
9
9
|
import { z } from "zod";
|
|
@@ -64,4 +64,4 @@ function createDurableTurn(deps) {
|
|
|
64
64
|
export {
|
|
65
65
|
createDurableTurn
|
|
66
66
|
};
|
|
67
|
-
//# sourceMappingURL=chunk-
|
|
67
|
+
//# sourceMappingURL=chunk-SD2ZJ7XG.js.map
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/cli.ts
|
|
2
|
+
import { readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
3
|
+
var command = process.argv[2] ?? "doctor";
|
|
4
|
+
if (command === "doctor") {
|
|
5
|
+
process.stdout.write(
|
|
6
|
+
`${JSON.stringify(
|
|
7
|
+
{
|
|
8
|
+
capability: "agent-runtime",
|
|
9
|
+
ok: true,
|
|
10
|
+
checks: ["thread-turn-item", "rollout-store", "pulsar-facade", "subagent-briefs"]
|
|
11
|
+
},
|
|
12
|
+
null,
|
|
13
|
+
2
|
|
14
|
+
)}
|
|
15
|
+
`
|
|
16
|
+
);
|
|
17
|
+
} else if (command === "debug" || command === "replay") {
|
|
18
|
+
const threadId = process.argv[3];
|
|
19
|
+
process.stdout.write(
|
|
20
|
+
`${JSON.stringify(
|
|
21
|
+
{
|
|
22
|
+
capability: "agent-runtime",
|
|
23
|
+
threadId,
|
|
24
|
+
entries: await readCapabilityDebug("agent-runtime", { limit: 20 }),
|
|
25
|
+
suggestion: "Use a RolloutStore-backed host to replay full thread events; this CLI reports local debug lines."
|
|
26
|
+
},
|
|
27
|
+
null,
|
|
28
|
+
2
|
|
29
|
+
)}
|
|
30
|
+
`
|
|
31
|
+
);
|
|
32
|
+
} else if (command === "subagent-trace" || command === "subagent-cost") {
|
|
33
|
+
const threadId = process.argv[3];
|
|
34
|
+
process.stdout.write(
|
|
35
|
+
`${JSON.stringify(
|
|
36
|
+
{
|
|
37
|
+
capability: "agent-runtime",
|
|
38
|
+
command,
|
|
39
|
+
threadId,
|
|
40
|
+
entries: await readCapabilityDebug("agent-runtime", { limit: 20 })
|
|
41
|
+
},
|
|
42
|
+
null,
|
|
43
|
+
2
|
|
44
|
+
)}
|
|
45
|
+
`
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
process.stderr.write(`Unknown agent-runtime command: ${command}
|
|
49
|
+
`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\n\nconst command = process.argv[2] ?? \"doctor\";\n\nif (command === \"doctor\") {\n process.stdout.write(\n `${JSON.stringify(\n {\n capability: \"agent-runtime\",\n ok: true,\n checks: [\"thread-turn-item\", \"rollout-store\", \"pulsar-facade\", \"subagent-briefs\"],\n },\n null,\n 2,\n )}\\n`,\n );\n} else if (command === \"debug\" || command === \"replay\") {\n const threadId = process.argv[3];\n process.stdout.write(\n `${JSON.stringify(\n {\n capability: \"agent-runtime\",\n threadId,\n entries: await readCapabilityDebug(\"agent-runtime\", { limit: 20 }),\n suggestion:\n \"Use a RolloutStore-backed host to replay full thread events; this CLI reports local debug lines.\",\n },\n null,\n 2,\n )}\\n`,\n );\n} else if (command === \"subagent-trace\" || command === \"subagent-cost\") {\n const threadId = process.argv[3];\n process.stdout.write(\n `${JSON.stringify(\n {\n capability: \"agent-runtime\",\n command,\n threadId,\n entries: await readCapabilityDebug(\"agent-runtime\", { limit: 20 }),\n },\n null,\n 2,\n )}\\n`,\n );\n} else {\n process.stderr.write(`Unknown agent-runtime command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";AAAA,SAAS,2BAA2B;AAEpC,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AAEnC,IAAI,YAAY,UAAU;AACxB,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK;AAAA,MACN;AAAA,QACE,YAAY;AAAA,QACZ,IAAI;AAAA,QACJ,QAAQ,CAAC,oBAAoB,iBAAiB,iBAAiB,iBAAiB;AAAA,MAClF;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA;AAAA,EACH;AACF,WAAW,YAAY,WAAW,YAAY,UAAU;AACtD,QAAM,WAAW,QAAQ,KAAK,CAAC;AAC/B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK;AAAA,MACN;AAAA,QACE,YAAY;AAAA,QACZ;AAAA,QACA,SAAS,MAAM,oBAAoB,iBAAiB,EAAE,OAAO,GAAG,CAAC;AAAA,QACjE,YACE;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA;AAAA,EACH;AACF,WAAW,YAAY,oBAAoB,YAAY,iBAAiB;AACtE,QAAM,WAAW,QAAQ,KAAK,CAAC;AAC/B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK;AAAA,MACN;AAAA,QACE,YAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA,SAAS,MAAM,oBAAoB,iBAAiB,EAAE,OAAO,GAAG,CAAC;AAAA,MACnE;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA;AAAA,EACH;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,kCAAkC,OAAO;AAAA,CAAI;AAClE,UAAQ,WAAW;AACrB;","names":[]}
|
package/dist/commands.js
CHANGED
package/dist/definitions.js
CHANGED
package/dist/dispatcher.js
CHANGED
package/dist/durable-turn.js
CHANGED