@nebutra/agent-runtime 0.2.1 → 0.2.2

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.
Files changed (101) hide show
  1. package/LICENSE +21 -676
  2. package/dist/adapters/index.d.ts +24 -5
  3. package/dist/adapters/index.js +27 -0
  4. package/dist/adapters/index.js.map +1 -1
  5. package/dist/chunk-Q62VKHIT.js +178 -0
  6. package/dist/chunk-Q62VKHIT.js.map +1 -0
  7. package/dist/{chunk-MUF7ZZTO.js → chunk-R5HOSQUW.js} +2 -1
  8. package/dist/{chunk-MUF7ZZTO.js.map → chunk-R5HOSQUW.js.map} +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js +14 -17
  11. package/dist/index.js.map +1 -1
  12. package/dist/orchestration.d.ts +42 -1
  13. package/dist/orchestration.js +7 -3
  14. package/dist/sandbox.js +1 -1
  15. package/package.json +80 -30
  16. package/.turbo/turbo-build.log +0 -130
  17. package/.turbo/turbo-test.log +0 -47
  18. package/.turbo/turbo-typecheck.log +0 -4
  19. package/CHANGELOG.md +0 -250
  20. package/dist/chunk-MX2WL43P.js +0 -90
  21. package/dist/chunk-MX2WL43P.js.map +0 -1
  22. package/examples/pulsar-quickstart.ts +0 -35
  23. package/examples/resume-branch.ts +0 -45
  24. package/examples/subagent-fanout.ts +0 -20
  25. package/src/adapters/dispatcher-sse.test.ts +0 -218
  26. package/src/adapters/dispatcher-sse.ts +0 -222
  27. package/src/adapters/index.ts +0 -18
  28. package/src/adapters/mcp-catalog.test.ts +0 -213
  29. package/src/adapters/mcp-catalog.ts +0 -188
  30. package/src/adapters/prisma-rollout.test.ts +0 -153
  31. package/src/adapters/prisma-rollout.ts +0 -104
  32. package/src/agent-runtime.test.ts +0 -176
  33. package/src/artifact-stream.test.ts +0 -330
  34. package/src/artifact-stream.ts +0 -453
  35. package/src/channel-gateway.test.ts +0 -432
  36. package/src/channel-gateway.ts +0 -357
  37. package/src/cli.ts +0 -49
  38. package/src/code-review.test.ts +0 -501
  39. package/src/code-review.ts +0 -495
  40. package/src/command-suggestions.test.ts +0 -251
  41. package/src/command-suggestions.ts +0 -338
  42. package/src/commands.test.ts +0 -184
  43. package/src/commands.ts +0 -140
  44. package/src/commit-message.test.ts +0 -249
  45. package/src/commit-message.ts +0 -180
  46. package/src/context-compaction.test.ts +0 -522
  47. package/src/context-compaction.ts +0 -438
  48. package/src/definitions.test.ts +0 -78
  49. package/src/definitions.ts +0 -190
  50. package/src/deployment-status.test.ts +0 -215
  51. package/src/deployment-status.ts +0 -227
  52. package/src/design-context.test.ts +0 -195
  53. package/src/design-context.ts +0 -198
  54. package/src/dispatcher.test.ts +0 -234
  55. package/src/dispatcher.ts +0 -189
  56. package/src/durable-turn.test.ts +0 -209
  57. package/src/durable-turn.ts +0 -135
  58. package/src/edit-planner.test.ts +0 -204
  59. package/src/edit-planner.ts +0 -325
  60. package/src/fuzzy-match.test.ts +0 -311
  61. package/src/fuzzy-match.ts +0 -444
  62. package/src/hook-pipeline.test.ts +0 -279
  63. package/src/hook-pipeline.ts +0 -373
  64. package/src/inbound-admission.test.ts +0 -394
  65. package/src/inbound-admission.ts +0 -246
  66. package/src/index.ts +0 -49
  67. package/src/loop.test.ts +0 -161
  68. package/src/loop.ts +0 -211
  69. package/src/mcp-bridge.test.ts +0 -165
  70. package/src/mcp-bridge.ts +0 -81
  71. package/src/memory-provider.test.ts +0 -232
  72. package/src/memory-provider.ts +0 -257
  73. package/src/model.ts +0 -168
  74. package/src/orchestration.test.ts +0 -53
  75. package/src/orchestration.ts +0 -146
  76. package/src/permission-ruleset.test.ts +0 -301
  77. package/src/permission-ruleset.ts +0 -1
  78. package/src/policy.ts +0 -151
  79. package/src/project-repo.test.ts +0 -232
  80. package/src/project-repo.ts +0 -311
  81. package/src/protocol.ts +0 -159
  82. package/src/pulsar.test.ts +0 -156
  83. package/src/pulsar.ts +0 -322
  84. package/src/rollout-store-persistent.test.ts +0 -217
  85. package/src/rollout-store-persistent.ts +0 -166
  86. package/src/rollout.ts +0 -150
  87. package/src/sandbox.ts +0 -113
  88. package/src/session-share.test.ts +0 -360
  89. package/src/session-share.ts +0 -310
  90. package/src/skill-distillation.test.ts +0 -177
  91. package/src/skill-distillation.ts +0 -369
  92. package/src/skills.test.ts +0 -277
  93. package/src/skills.ts +0 -277
  94. package/src/subagents.test.ts +0 -290
  95. package/src/subagents.ts +0 -332
  96. package/src/tools.test.ts +0 -12
  97. package/src/tools.ts +0 -129
  98. package/src/workbench.test.ts +0 -0
  99. package/src/workbench.ts +0 -0
  100. package/tsconfig.json +0 -12
  101. package/tsup.config.ts +0 -36
@@ -1,12 +1,31 @@
1
+ import { ModelInvoker } from '../loop.js';
1
2
  export { NotificationFrame, SseOptions, createRpcHandler, sseResponse, subscribeNotifications } from './dispatcher-sse.js';
2
3
  export { McpExecutorLike, McpRegistryLike, createMcpClientPort, createMcpServerCatalog } from './mcp-catalog.js';
3
4
  export { PrismaRolloutDelegate, RolloutDelegateResolver, createPrismaRolloutPersistence } from './prisma-rollout.js';
4
- import '../dispatcher.js';
5
- import '../protocol.js';
6
- import 'zod';
7
5
  import '../model.js';
6
+ import '../policy.js';
7
+ import 'zod';
8
+ import '../protocol.js';
9
+ import '../rollout.js';
10
+ import '../tools.js';
11
+ import '../dispatcher.js';
8
12
  import '@nebutra/mcp';
9
13
  import '../mcp-bridge.js';
10
- import '../tools.js';
11
14
  import '../rollout-store-persistent.js';
12
- import '../rollout.js';
15
+
16
+ /**
17
+ * AgentsModelInvoker — the ONE bridge that makes the substrate's `runTurn` use
18
+ * the real provider stack. Implements `ModelInvoker` over @nebutra/agents
19
+ * `generateText` (which resolves the provider + model and runs the AI SDK).
20
+ * Each round sends the running history at the round's `config.model` — which is
21
+ * how a per-node model (resolved from a node's NodeModelSpec) reaches the
22
+ * provider.
23
+ *
24
+ * Tools are NOT passed here: the runtime's own ToolRegistry/loop owns tool
25
+ * dispatch; this round is a plain text completion. Errors propagate to the loop
26
+ * (which records them on the rollout), not swallowed.
27
+ */
28
+
29
+ declare function createAgentsModelInvoker(): ModelInvoker;
30
+
31
+ export { createAgentsModelInvoker };
@@ -11,7 +11,34 @@ import {
11
11
  createPrismaRolloutPersistence
12
12
  } from "../chunk-57W3AR43.js";
13
13
  import "../chunk-424PT5DM.js";
14
+
15
+ // src/adapters/agents-model-invoker.ts
16
+ import { generateText } from "@nebutra/agents";
17
+ function toRole(role) {
18
+ return role === "assistant" || role === "system" ? role : "user";
19
+ }
20
+ function createAgentsModelInvoker() {
21
+ return {
22
+ async invoke(request) {
23
+ const messages = request.history.map((m) => ({
24
+ role: toRole(m.role),
25
+ content: m.content
26
+ }));
27
+ const result = await generateText(messages, { model: request.config.model });
28
+ const text = result.text ?? "";
29
+ const usage = result.usage;
30
+ return {
31
+ emissions: text.length > 0 ? [{ kind: "text", text }] : [],
32
+ usage: {
33
+ inputTokens: usage?.inputTokens ?? 0,
34
+ outputTokens: usage?.outputTokens ?? 0
35
+ }
36
+ };
37
+ }
38
+ };
39
+ }
14
40
  export {
41
+ createAgentsModelInvoker,
15
42
  createMcpClientPort,
16
43
  createMcpServerCatalog,
17
44
  createPrismaRolloutPersistence,
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/agents-model-invoker.ts"],"sourcesContent":["/**\n * AgentsModelInvoker — the ONE bridge that makes the substrate's `runTurn` use\n * the real provider stack. Implements `ModelInvoker` over @nebutra/agents\n * `generateText` (which resolves the provider + model and runs the AI SDK).\n * Each round sends the running history at the round's `config.model` — which is\n * how a per-node model (resolved from a node's NodeModelSpec) reaches the\n * provider.\n *\n * Tools are NOT passed here: the runtime's own ToolRegistry/loop owns tool\n * dispatch; this round is a plain text completion. Errors propagate to the loop\n * (which records them on the rollout), not swallowed.\n */\n\nimport { generateText } from \"@nebutra/agents\";\nimport type { ModelInvoker, ModelRoundRequest, ModelRoundResult } from \"../loop\";\n\ntype Role = \"user\" | \"assistant\" | \"system\";\n\nfunction toRole(role: string): Role {\n return role === \"assistant\" || role === \"system\" ? role : \"user\";\n}\n\nexport function createAgentsModelInvoker(): ModelInvoker {\n return {\n async invoke(request: ModelRoundRequest): Promise<ModelRoundResult> {\n const messages = request.history.map((m) => ({\n role: toRole(m.role),\n content: m.content,\n }));\n\n const result = await generateText(messages, { model: request.config.model });\n\n const text = result.text ?? \"\";\n const usage = result.usage;\n return {\n emissions: text.length > 0 ? [{ kind: \"text\", text }] : [],\n usage: {\n inputTokens: usage?.inputTokens ?? 0,\n outputTokens: usage?.outputTokens ?? 0,\n },\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAaA,SAAS,oBAAoB;AAK7B,SAAS,OAAO,MAAoB;AAClC,SAAO,SAAS,eAAe,SAAS,WAAW,OAAO;AAC5D;AAEO,SAAS,2BAAyC;AACvD,SAAO;AAAA,IACL,MAAM,OAAO,SAAuD;AAClE,YAAM,WAAW,QAAQ,QAAQ,IAAI,CAAC,OAAO;AAAA,QAC3C,MAAM,OAAO,EAAE,IAAI;AAAA,QACnB,SAAS,EAAE;AAAA,MACb,EAAE;AAEF,YAAM,SAAS,MAAM,aAAa,UAAU,EAAE,OAAO,QAAQ,OAAO,MAAM,CAAC;AAE3E,YAAM,OAAO,OAAO,QAAQ;AAC5B,YAAM,QAAQ,OAAO;AACrB,aAAO;AAAA,QACL,WAAW,KAAK,SAAS,IAAI,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,IAAI,CAAC;AAAA,QACzD,OAAO;AAAA,UACL,aAAa,OAAO,eAAe;AAAA,UACnC,cAAc,OAAO,gBAAgB;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,178 @@
1
+ // src/orchestration.ts
2
+ import { hasCycleFrom } from "@nebutra/graph-model";
3
+ import { z } from "zod";
4
+ var budgetCapSchema = z.object({
5
+ durationMs: z.number().positive(),
6
+ costUsd: z.number().nonnegative(),
7
+ tokenLimit: z.number().positive()
8
+ });
9
+ var briefSchema = z.object({
10
+ id: z.string().trim().min(1),
11
+ objective: z.string().trim().min(1),
12
+ outputFormat: z.record(z.string(), z.unknown()),
13
+ allowedTools: z.array(z.string()).min(1),
14
+ contextRefs: z.array(z.string()),
15
+ boundaries: z.array(z.string()).min(1),
16
+ budget: budgetCapSchema,
17
+ dependsOn: z.array(z.string()).optional(),
18
+ model: z.object({
19
+ id: z.string().trim().min(1).optional(),
20
+ reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]).optional(),
21
+ modality: z.enum(["text", "image", "video", "audio", "embedding"]).optional(),
22
+ capabilities: z.object({
23
+ vision: z.boolean().optional(),
24
+ toolCall: z.boolean().optional(),
25
+ reasoning: z.boolean().optional()
26
+ }).optional()
27
+ }).optional()
28
+ });
29
+ function fail(message, suggestion) {
30
+ throw new Error(`${message}. Suggestion: ${suggestion}`);
31
+ }
32
+ var BRIEF_FIELD_FAILURE = {
33
+ id: { message: "brief.id is required", suggestion: "Use a stable role or task id." },
34
+ objective: {
35
+ message: "brief.objective is required",
36
+ suggestion: "Write a concrete objective before dispatching."
37
+ },
38
+ outputFormat: {
39
+ message: "brief.outputFormat is invalid",
40
+ suggestion: "Provide an output schema object the worker must conform to."
41
+ },
42
+ allowedTools: {
43
+ message: "brief.allowedTools is required",
44
+ suggestion: "Constrain each subagent to an explicit tool scope."
45
+ },
46
+ contextRefs: {
47
+ message: "brief.contextRefs is invalid",
48
+ suggestion: "Pass an array of context references (may be empty)."
49
+ },
50
+ boundaries: {
51
+ message: "brief.boundaries is required",
52
+ suggestion: "State at least one boundary for the worker."
53
+ },
54
+ budget: {
55
+ message: "brief.budget is invalid",
56
+ suggestion: "Set positive token/time caps and a non-negative cost cap."
57
+ },
58
+ dependsOn: {
59
+ message: "brief.dependsOn is invalid",
60
+ suggestion: "List dependency brief ids as an array of strings."
61
+ },
62
+ model: {
63
+ message: "brief.model is invalid",
64
+ suggestion: "Use a preset alias or 'provider/model' id, or omit to inherit the run default."
65
+ }
66
+ };
67
+ function validateBrief(brief) {
68
+ const result = briefSchema.safeParse(brief);
69
+ if (result.success) return;
70
+ const topLevelField = result.error.issues[0]?.path[0];
71
+ const failure = typeof topLevelField === "string" ? BRIEF_FIELD_FAILURE[topLevelField] : void 0;
72
+ if (failure) fail(failure.message, failure.suggestion);
73
+ fail("brief is invalid", "Provide every required brief field with valid values.");
74
+ }
75
+ function dependencySet(briefs) {
76
+ const dependencies = /* @__PURE__ */ new Set();
77
+ for (const brief of briefs) {
78
+ for (const dep of brief.dependsOn ?? []) dependencies.add(dep);
79
+ }
80
+ return dependencies;
81
+ }
82
+ function briefEdges(briefs) {
83
+ const ids = new Set(briefs.map((brief) => brief.id));
84
+ const edges = [];
85
+ for (const brief of briefs) {
86
+ for (const dep of brief.dependsOn ?? []) {
87
+ if (ids.has(dep)) edges.push({ from: dep, to: brief.id });
88
+ }
89
+ }
90
+ return edges;
91
+ }
92
+ function assertAcyclic(briefs, edges) {
93
+ for (const brief of briefs) {
94
+ if (hasCycleFrom(edges, brief.id)) {
95
+ fail(
96
+ `subagent dependency cycle includes '${brief.id}'`,
97
+ "Remove the cycle or collapse the dependent work into one sequential brief."
98
+ );
99
+ }
100
+ }
101
+ }
102
+ function planWaves(briefs) {
103
+ const edges = briefEdges(briefs);
104
+ assertAcyclic(briefs, edges);
105
+ const byId = new Map(briefs.map((brief) => [brief.id, brief]));
106
+ const done = /* @__PURE__ */ new Set();
107
+ const waves = [];
108
+ while (done.size < briefs.length) {
109
+ const wave = [];
110
+ for (const brief of briefs) {
111
+ if (done.has(brief.id)) continue;
112
+ const deps = (brief.dependsOn ?? []).filter((dep) => byId.has(dep));
113
+ if (deps.every((dep) => done.has(dep))) wave.push(brief);
114
+ }
115
+ if (wave.length === 0) {
116
+ fail(
117
+ "subagent dependencies cannot be resolved into waves",
118
+ "Ensure every dependsOn id references a brief in the set."
119
+ );
120
+ }
121
+ for (const brief of wave) done.add(brief.id);
122
+ waves.push(wave);
123
+ }
124
+ return waves;
125
+ }
126
+ function topologicalOrder(briefs) {
127
+ return planWaves(briefs).flat();
128
+ }
129
+ function planSubagentDispatch(briefs, options = {}) {
130
+ if (briefs.length === 0) {
131
+ fail("at least one brief is required", "Create a concrete worker brief before dispatching.");
132
+ }
133
+ for (const brief of briefs) validateBrief(brief);
134
+ const dependencies = dependencySet(briefs);
135
+ if ((options.strategy ?? "auto") === "fanout" && dependencies.size > 0) {
136
+ fail(
137
+ "fan-out cannot run briefs that depend on each other",
138
+ "Use sequential dispatch for dependent work or split independent briefs only."
139
+ );
140
+ }
141
+ const order = topologicalOrder(briefs);
142
+ const strategy = options.strategy === "fanout" || options.strategy === "auto" && dependencies.size === 0 ? "fanout" : "sequential";
143
+ return { strategy, order };
144
+ }
145
+ async function fanOutSubagents(briefs, run) {
146
+ const plan = planSubagentDispatch(briefs, { strategy: "fanout" });
147
+ return Promise.all(plan.order.map((brief) => run(brief)));
148
+ }
149
+ async function runAgentWaves(briefs, run) {
150
+ for (const brief of briefs) validateBrief(brief);
151
+ const results = [];
152
+ for (const wave of planWaves(briefs)) {
153
+ const waveResults = await Promise.all(wave.map((brief) => run(brief)));
154
+ results.push(...waveResults);
155
+ }
156
+ return results;
157
+ }
158
+ function costReport(results) {
159
+ return {
160
+ totalInputTokens: results.reduce((sum, result) => sum + result.usage.inputTokens, 0),
161
+ totalOutputTokens: results.reduce((sum, result) => sum + result.usage.outputTokens, 0),
162
+ totalReasoningTokens: results.reduce(
163
+ (sum, result) => sum + result.usage.reasoningOutputTokens,
164
+ 0
165
+ ),
166
+ maxDurationMs: results.reduce((max, result) => Math.max(max, result.durationMs), 0),
167
+ subagents: results.length
168
+ };
169
+ }
170
+
171
+ export {
172
+ planWaves,
173
+ planSubagentDispatch,
174
+ fanOutSubagents,
175
+ runAgentWaves,
176
+ costReport
177
+ };
178
+ //# sourceMappingURL=chunk-Q62VKHIT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/orchestration.ts"],"sourcesContent":["import { type GraphEdge, hasCycleFrom } from \"@nebutra/graph-model\";\nimport { z } from \"zod\";\nimport type { TurnUsage } from \"./model\";\n\nexport interface BudgetCap {\n readonly durationMs: number;\n readonly costUsd: number;\n readonly tokenLimit: number;\n}\n\n/** Thinking/reasoning effort for a node (maps to provider reasoning controls). */\nexport type ReasoningEffort = \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\n/** Output modality a node produces. Mirrors @nebutra/ai-providers ModelModality. */\nexport type ModelModality = \"text\" | \"image\" | \"video\" | \"audio\" | \"embedding\";\n\n/**\n * Per-node model selection — the full model meta-info a node can declare. ALL\n * fields optional; an unset field inherits the run-level default\n * (WorkflowDefinition.defaultModel / runTurn config). The executor resolves\n * these HINTS against @nebutra/ai-providers/catalog (listModelsByModality /\n * resolveFrontierModel) to a concrete model — so a node can either PIN one\n * (`id`) or DESCRIBE what it needs (\"the latest fast text model with vision\").\n */\nexport interface NodeModelSpec {\n /** Explicit pin: preset alias (`flagship`/`reasoning`/`fast`) or `provider/model` id. */\n readonly id?: string;\n /** Thinking effort: `low` → cheap/quick, `xhigh` → deepest reasoning. */\n readonly reasoningEffort?: ReasoningEffort;\n /** Output modality — selects a model of this modality when `id` is unset. */\n readonly modality?: ModelModality;\n /** Capabilities the selected model MUST have (e.g. a node needing image input). */\n readonly capabilities?: {\n readonly vision?: boolean;\n readonly toolCall?: boolean;\n readonly reasoning?: boolean;\n };\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 /** Per-node model meta-info (id/preset · thinking effort · modality · capabilities). */\n readonly model?: NodeModelSpec;\n}\n\n/**\n * Runtime shape + semantic validation for {@link Brief}. The public type keeps\n * its `readonly` immutability contract (see project immutability rules), so we\n * validate against this schema at the boundary rather than deriving the type\n * from it. Each rule mirrors a tailored DX suggestion surfaced via {@link fail}.\n */\nconst budgetCapSchema = z.object({\n durationMs: z.number().positive(),\n costUsd: z.number().nonnegative(),\n tokenLimit: z.number().positive(),\n});\n\nconst briefSchema = z.object({\n id: z.string().trim().min(1),\n objective: z.string().trim().min(1),\n outputFormat: z.record(z.string(), z.unknown()),\n allowedTools: z.array(z.string()).min(1),\n contextRefs: z.array(z.string()),\n boundaries: z.array(z.string()).min(1),\n budget: budgetCapSchema,\n dependsOn: z.array(z.string()).optional(),\n model: z\n .object({\n id: z.string().trim().min(1).optional(),\n reasoningEffort: z.enum([\"low\", \"medium\", \"high\", \"xhigh\"]).optional(),\n modality: z.enum([\"text\", \"image\", \"video\", \"audio\", \"embedding\"]).optional(),\n capabilities: z\n .object({\n vision: z.boolean().optional(),\n toolCall: z.boolean().optional(),\n reasoning: z.boolean().optional(),\n })\n .optional(),\n })\n .optional(),\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\n/**\n * Per-top-level-field DX messages, keyed by the failing schema path. Keeps the\n * exact \"message. Suggestion: …\" surface the callers (and tests) rely on while\n * the heavy lifting (shape + bounds) moves into {@link briefSchema}.\n */\nconst BRIEF_FIELD_FAILURE: Record<string, { message: string; suggestion: string }> = {\n id: { message: \"brief.id is required\", suggestion: \"Use a stable role or task id.\" },\n objective: {\n message: \"brief.objective is required\",\n suggestion: \"Write a concrete objective before dispatching.\",\n },\n outputFormat: {\n message: \"brief.outputFormat is invalid\",\n suggestion: \"Provide an output schema object the worker must conform to.\",\n },\n allowedTools: {\n message: \"brief.allowedTools is required\",\n suggestion: \"Constrain each subagent to an explicit tool scope.\",\n },\n contextRefs: {\n message: \"brief.contextRefs is invalid\",\n suggestion: \"Pass an array of context references (may be empty).\",\n },\n boundaries: {\n message: \"brief.boundaries is required\",\n suggestion: \"State at least one boundary for the worker.\",\n },\n budget: {\n message: \"brief.budget is invalid\",\n suggestion: \"Set positive token/time caps and a non-negative cost cap.\",\n },\n dependsOn: {\n message: \"brief.dependsOn is invalid\",\n suggestion: \"List dependency brief ids as an array of strings.\",\n },\n model: {\n message: \"brief.model is invalid\",\n suggestion: \"Use a preset alias or 'provider/model' id, or omit to inherit the run default.\",\n },\n};\n\nfunction validateBrief(brief: Brief): void {\n const result = briefSchema.safeParse(brief);\n if (result.success) return;\n\n const topLevelField = result.error.issues[0]?.path[0];\n const failure =\n typeof topLevelField === \"string\" ? BRIEF_FIELD_FAILURE[topLevelField] : undefined;\n if (failure) fail(failure.message, failure.suggestion);\n fail(\"brief is invalid\", \"Provide every required brief field with valid values.\");\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\n/** Dependency edges (dep → brief) over the brief set; refs to unknown ids drop. */\nfunction briefEdges(briefs: readonly Brief[]): GraphEdge[] {\n const ids = new Set(briefs.map((brief) => brief.id));\n const edges: GraphEdge[] = [];\n for (const brief of briefs) {\n for (const dep of brief.dependsOn ?? []) {\n if (ids.has(dep)) edges.push({ from: dep, to: brief.id });\n }\n }\n return edges;\n}\n\n/**\n * Cycle guard via the SINGLE repo-wide cycle-detection algorithm\n * (`@nebutra/graph-model` `hasCycleFrom`). No private DFS cycle-walker lives here.\n */\nfunction assertAcyclic(briefs: readonly Brief[], edges: readonly GraphEdge[]): void {\n for (const brief of briefs) {\n if (hasCycleFrom(edges, 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 }\n}\n\n/**\n * Dependency-resolved execution WAVES — the agent node-topology scheduler. Each\n * wave is the set of briefs whose dependencies are satisfied by earlier waves;\n * waves run in order, briefs within a wave run in parallel. Input order is\n * preserved within a wave.\n */\nexport function planWaves(briefs: readonly Brief[]): readonly (readonly Brief[])[] {\n const edges = briefEdges(briefs);\n assertAcyclic(briefs, edges);\n\n const byId = new Map(briefs.map((brief) => [brief.id, brief]));\n const done = new Set<string>();\n const waves: Brief[][] = [];\n\n while (done.size < briefs.length) {\n const wave: Brief[] = [];\n for (const brief of briefs) {\n if (done.has(brief.id)) continue;\n const deps = (brief.dependsOn ?? []).filter((dep) => byId.has(dep));\n if (deps.every((dep) => done.has(dep))) wave.push(brief);\n }\n if (wave.length === 0) {\n // assertAcyclic already ruled out cycles; only a logic bug reaches here.\n fail(\n \"subagent dependencies cannot be resolved into waves\",\n \"Ensure every dependsOn id references a brief in the set.\",\n );\n }\n for (const brief of wave) done.add(brief.id);\n waves.push(wave);\n }\n return waves;\n}\n\nfunction topologicalOrder(briefs: readonly Brief[]): Brief[] {\n return planWaves(briefs).flat();\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\n/**\n * Execute briefs as a dependency graph: each wave runs in parallel, waves run\n * sequentially. The node-topology execution path — `fanOutSubagents` is the\n * degenerate single-wave (all-independent) case of this.\n */\nexport async function runAgentWaves(\n briefs: readonly Brief[],\n run: (brief: Brief) => Promise<SubagentResult>,\n): Promise<readonly SubagentResult[]> {\n for (const brief of briefs) validateBrief(brief);\n const results: SubagentResult[] = [];\n for (const wave of planWaves(briefs)) {\n const waveResults = await Promise.all(wave.map((brief) => run(brief)));\n results.push(...waveResults);\n }\n return results;\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":[]}
@@ -18,6 +18,7 @@ var SandboxDelegationError = class extends Error {
18
18
  this.status = status;
19
19
  this.name = "SandboxDelegationError";
20
20
  }
21
+ status;
21
22
  };
22
23
  function createHttpSandbox(baseUrl, fetchImpl = fetch) {
23
24
  const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/sandbox/exec`;
@@ -54,4 +55,4 @@ export {
54
55
  createHttpSandbox,
55
56
  assertSafePosture
56
57
  };
57
- //# sourceMappingURL=chunk-MUF7ZZTO.js.map
58
+ //# sourceMappingURL=chunk-R5HOSQUW.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sandbox.ts"],"sourcesContent":["/**\n * External-sandbox delegation seam (PORT — capability #11).\n *\n * Governance decision (dual-track, ExternalSandbox posture): this multi-tenant\n * web runtime NEVER executes untrusted code itself and introduces no isolation\n * infrastructure. It only:\n * 1. carries the capability policy (see ./policy),\n * 2. delegates execution to an external isolator behind this interface\n * (a self-hosted kernel sidecar — Track B — or any other executor),\n * 3. records the outcome as a `command_execution` item (see ./model).\n *\n * The upstream OS enforcers (Seatbelt / Landlock / bubblewrap / Windows\n * restricted token) are deliberately NOT ported — they are single-host and\n * out of scope for a multi-tenant web product.\n */\n\nimport type { CapabilityPolicy } from \"./policy\";\n\nexport interface SandboxExecRequest {\n /** Mandatory tenant scope — every delegated exec is tenant-bound. */\n readonly tenantId: string;\n readonly threadId: string;\n readonly command: string;\n /** The capabilities the external isolator must honor. */\n readonly capabilityPolicy: CapabilityPolicy;\n /** Optional inputs the executor should materialize before running. */\n readonly inputs?: Readonly<Record<string, string>>;\n}\n\nexport interface SandboxExecResult {\n readonly exitCode: number;\n readonly aggregatedOutput: string;\n /** Identifier of the isolator that actually ran the command. */\n readonly executedOn: string;\n}\n\n/**\n * The only thing Track A depends on for execution. Implemented by a decoupled\n * isolator over the ./protocol contract; never implemented in-process here.\n */\nexport interface ExternalSandbox {\n exec(request: SandboxExecRequest): Promise<SandboxExecResult>;\n}\n\n/**\n * Fail-closed default. Wiring this in production requires a real\n * {@link ExternalSandbox}; absent one, untrusted code never runs — by design.\n */\nexport class NoExecutorConfiguredError extends Error {\n constructor() {\n super(\n \"No ExternalSandbox configured. This runtime never executes untrusted \" +\n \"code in-process; delegate to a decoupled isolator (Track B).\",\n );\n this.name = \"NoExecutorConfiguredError\";\n }\n}\n\nexport const REFUSING_SANDBOX: ExternalSandbox = {\n async exec(): Promise<SandboxExecResult> {\n throw new NoExecutorConfiguredError();\n },\n};\n\nexport class SandboxDelegationError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n this.name = \"SandboxDelegationError\";\n }\n}\n\n/**\n * The concrete Track-B coupling: delegate execution over HTTP to the decoupled\n * Rust isolator (`backends/rust/sandbox`, `POST /api/v1/sandbox/exec`). The\n * isolator is fail-closed; a non-2xx (e.g. 403 refusal) is surfaced as an\n * error and never coerced into a fabricated result.\n */\nexport function createHttpSandbox(\n baseUrl: string,\n fetchImpl: typeof fetch = fetch,\n): ExternalSandbox {\n const endpoint = `${baseUrl.replace(/\\/$/, \"\")}/api/v1/sandbox/exec`;\n return {\n async exec(request: SandboxExecRequest): Promise<SandboxExecResult> {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(request),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new SandboxDelegationError(\n `Isolator refused execution (${response.status}): ${detail}`,\n response.status,\n );\n }\n return (await response.json()) as SandboxExecResult;\n },\n };\n}\n\n/** Guard: reject the most dangerous posture unless explicitly opted in. */\nexport function assertSafePosture(policy: CapabilityPolicy, allowDanger = false): void {\n if (policy.kind === \"danger_full_access\" && !allowDanger) {\n throw new Error(\n \"danger_full_access capability policy refused: not permitted for \" +\n \"multi-tenant delegation without explicit opt-in.\",\n );\n }\n}\n"],"mappings":";AAgDO,IAAM,4BAAN,cAAwC,MAAM;AAAA,EACnD,cAAc;AACZ;AAAA,MACE;AAAA,IAEF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,mBAAoC;AAAA,EAC/C,MAAM,OAAmC;AACvC,UAAM,IAAI,0BAA0B;AAAA,EACtC;AACF;AAEO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACE,SACS,QACT;AACA,UAAM,OAAO;AAFJ;AAGT,SAAK,OAAO;AAAA,EACd;AACF;AAQO,SAAS,kBACd,SACA,YAA0B,OACT;AACjB,QAAM,WAAW,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAC9C,SAAO;AAAA,IACL,MAAM,KAAK,SAAyD;AAClE,YAAM,WAAW,MAAM,UAAU,UAAU;AAAA,QACzC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,MAC9B,CAAC;AACD,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,SAAS,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACnD,cAAM,IAAI;AAAA,UACR,+BAA+B,SAAS,MAAM,MAAM,MAAM;AAAA,UAC1D,SAAS;AAAA,QACX;AAAA,MACF;AACA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA,EACF;AACF;AAGO,SAAS,kBAAkB,QAA0B,cAAc,OAAa;AACrF,MAAI,OAAO,SAAS,wBAAwB,CAAC,aAAa;AACxD,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/sandbox.ts"],"sourcesContent":["/**\n * External-sandbox delegation seam (PORT — capability #11).\n *\n * Governance decision (dual-track, ExternalSandbox posture): this multi-tenant\n * web runtime NEVER executes untrusted code itself and introduces no isolation\n * infrastructure. It only:\n * 1. carries the capability policy (see ./policy),\n * 2. delegates execution to an external isolator behind this interface\n * (a self-hosted kernel sidecar — Track B — or any other executor),\n * 3. records the outcome as a `command_execution` item (see ./model).\n *\n * The upstream OS enforcers (Seatbelt / Landlock / bubblewrap / Windows\n * restricted token) are deliberately NOT ported — they are single-host and\n * out of scope for a multi-tenant web product.\n */\n\nimport type { CapabilityPolicy } from \"./policy\";\n\nexport interface SandboxExecRequest {\n /** Mandatory tenant scope — every delegated exec is tenant-bound. */\n readonly tenantId: string;\n readonly threadId: string;\n readonly command: string;\n /** The capabilities the external isolator must honor. */\n readonly capabilityPolicy: CapabilityPolicy;\n /** Optional inputs the executor should materialize before running. */\n readonly inputs?: Readonly<Record<string, string>>;\n}\n\nexport interface SandboxExecResult {\n readonly exitCode: number;\n readonly aggregatedOutput: string;\n /** Identifier of the isolator that actually ran the command. */\n readonly executedOn: string;\n}\n\n/**\n * The only thing Track A depends on for execution. Implemented by a decoupled\n * isolator over the ./protocol contract; never implemented in-process here.\n */\nexport interface ExternalSandbox {\n exec(request: SandboxExecRequest): Promise<SandboxExecResult>;\n}\n\n/**\n * Fail-closed default. Wiring this in production requires a real\n * {@link ExternalSandbox}; absent one, untrusted code never runs — by design.\n */\nexport class NoExecutorConfiguredError extends Error {\n constructor() {\n super(\n \"No ExternalSandbox configured. This runtime never executes untrusted \" +\n \"code in-process; delegate to a decoupled isolator (Track B).\",\n );\n this.name = \"NoExecutorConfiguredError\";\n }\n}\n\nexport const REFUSING_SANDBOX: ExternalSandbox = {\n async exec(): Promise<SandboxExecResult> {\n throw new NoExecutorConfiguredError();\n },\n};\n\nexport class SandboxDelegationError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n this.name = \"SandboxDelegationError\";\n }\n}\n\n/**\n * The concrete Track-B coupling: delegate execution over HTTP to the decoupled\n * Rust isolator (`backends/rust/sandbox`, `POST /api/v1/sandbox/exec`). The\n * isolator is fail-closed; a non-2xx (e.g. 403 refusal) is surfaced as an\n * error and never coerced into a fabricated result.\n */\nexport function createHttpSandbox(\n baseUrl: string,\n fetchImpl: typeof fetch = fetch,\n): ExternalSandbox {\n const endpoint = `${baseUrl.replace(/\\/$/, \"\")}/api/v1/sandbox/exec`;\n return {\n async exec(request: SandboxExecRequest): Promise<SandboxExecResult> {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(request),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new SandboxDelegationError(\n `Isolator refused execution (${response.status}): ${detail}`,\n response.status,\n );\n }\n return (await response.json()) as SandboxExecResult;\n },\n };\n}\n\n/** Guard: reject the most dangerous posture unless explicitly opted in. */\nexport function assertSafePosture(policy: CapabilityPolicy, allowDanger = false): void {\n if (policy.kind === \"danger_full_access\" && !allowDanger) {\n throw new Error(\n \"danger_full_access capability policy refused: not permitted for \" +\n \"multi-tenant delegation without explicit opt-in.\",\n );\n }\n}\n"],"mappings":";AAgDO,IAAM,4BAAN,cAAwC,MAAM;AAAA,EACnD,cAAc;AACZ;AAAA,MACE;AAAA,IAEF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,mBAAoC;AAAA,EAC/C,MAAM,OAAmC;AACvC,UAAM,IAAI,0BAA0B;AAAA,EACtC;AACF;AAEO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACE,SACS,QACT;AACA,UAAM,OAAO;AAFJ;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EAJW;AAKb;AAQO,SAAS,kBACd,SACA,YAA0B,OACT;AACjB,QAAM,WAAW,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAC9C,SAAO;AAAA,IACL,MAAM,KAAK,SAAyD;AAClE,YAAM,WAAW,MAAM,UAAU,UAAU;AAAA,QACzC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,MAC9B,CAAC;AACD,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,SAAS,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACnD,cAAM,IAAI;AAAA,UACR,+BAA+B,SAAS,MAAM,MAAM,MAAM;AAAA,UAC1D,SAAS;AAAA,QACX;AAAA,MACF;AACA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA,EACF;AACF;AAGO,SAAS,kBAAkB,QAA0B,cAAc,OAAa;AACrF,MAAI,OAAO,SAAS,wBAAwB,CAAC,aAAa;AACxD,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export { FunctionTransport, HookConfig, HookContext, HookEvent, HookExitResult,
8
8
  export { ApprovalGate, ModelEmission, ModelInvoker, ModelRoundRequest, ModelRoundResult, RuleEvaluator, RunTurnDeps, runTurn } from './loop.js';
9
9
  export { ActivateMcpToolsResult, McpServerCatalogPort, activateMcpTools } from './mcp-bridge.js';
10
10
  export { AgentMessageItem, CommandExecutionItem, ErrorItem, FileChangeItem, FileUpdateChange, McpToolCallItem, PatchApplyStatus, PatchChangeKind, ReasoningItem, RunStatus, ThreadError, ThreadEvent, ThreadEventType, ThreadItem, ThreadItemType, TodoEntry, TodoListItem, TurnConfig, TurnConfigOverrides, TurnUsage, WebSearchItem, isTurnTerminal, mergeTurnConfig } from './model.js';
11
- export { Brief, BudgetCap, DispatchPlan, DispatchPlanOptions, DispatchStrategy, SubagentCostReport, SubagentResult, costReport, fanOutSubagents, planSubagentDispatch } from './orchestration.js';
11
+ export { Brief, BudgetCap, DispatchPlan, DispatchPlanOptions, DispatchStrategy, ModelModality, NodeModelSpec, ReasoningEffort, SubagentCostReport, SubagentResult, costReport, fanOutSubagents, planSubagentDispatch, planWaves, runAgentWaves } from './orchestration.js';
12
12
  export * from '@nebutra/execution-policy';
13
13
  export { ApprovalPolicy, CapabilityPolicy, DEFAULT_APPROVAL_POLICY, DEFAULT_CAPABILITY_POLICY, DENIED, GranularApprovalConfig, NetworkPolicyAmendment, ReviewDecision, RuleAmendment, RuleDecision, WritableRoot, approvalPolicySchema, capabilityPolicySchema, granularApprovalConfigSchema, isApproval, resolveRuleDecision, writableRootSchema } from './policy.js';
14
14
  export { METHOD_REGISTRY, MethodName, MethodSpec, NOTIFICATIONS, NotificationName, RequestEnvelope, ScopeRule, SerializationScope, ServerRequest, requestEnvelopeSchema, resolveScope, scopeKey } from './protocol.js';
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  SandboxDelegationError,
14
14
  assertSafePosture,
15
15
  createHttpSandbox
16
- } from "./chunk-MUF7ZZTO.js";
16
+ } from "./chunk-R5HOSQUW.js";
17
17
  import {
18
18
  buildSkillListing,
19
19
  expandSkill
@@ -71,8 +71,10 @@ import {
71
71
  import {
72
72
  costReport,
73
73
  fanOutSubagents,
74
- planSubagentDispatch
75
- } from "./chunk-MX2WL43P.js";
74
+ planSubagentDispatch,
75
+ planWaves,
76
+ runAgentWaves
77
+ } from "./chunk-Q62VKHIT.js";
76
78
  import {
77
79
  DEFAULT_APPROVAL_POLICY,
78
80
  DEFAULT_CAPABILITY_POLICY,
@@ -246,6 +248,7 @@ __export(src_exports, {
246
248
  parseJsonl: () => parseJsonl,
247
249
  planEdit: () => planEdit,
248
250
  planSubagentDispatch: () => planSubagentDispatch,
251
+ planWaves: () => planWaves,
249
252
  prepareDispatch: () => prepareDispatch,
250
253
  rankByFuzzy: () => rankByFuzzy,
251
254
  rankSuggestions: () => rankSuggestions,
@@ -262,6 +265,7 @@ __export(src_exports, {
262
265
  resolveSessionKey: () => resolveSessionKey,
263
266
  resolveSubagentTools: () => resolveSubagentTools,
264
267
  restore: () => restore,
268
+ runAgentWaves: () => runAgentWaves,
265
269
  runHooks: () => runHooks,
266
270
  runReview: () => runReview,
267
271
  runTurn: () => runTurn,
@@ -1268,6 +1272,7 @@ async function generateCommitMessage(args) {
1268
1272
  import {
1269
1273
  estimateTokens as estimatePrimitiveTokens
1270
1274
  } from "@nebutra/ai-primitives";
1275
+ import pLimit from "p-limit";
1271
1276
  import { z as z6 } from "zod";
1272
1277
  var TOKEN_CORRECTION = 1.3;
1273
1278
  var BUDGET_RATIO = 0.6;
@@ -1365,18 +1370,8 @@ function buildPrompt(rendered) {
1365
1370
  ].join("\n");
1366
1371
  }
1367
1372
  async function mapLimited(items, worker) {
1368
- const results = new Array(items.length);
1369
- let cursor = 0;
1370
- async function run() {
1371
- while (cursor < items.length) {
1372
- const index = cursor;
1373
- cursor += 1;
1374
- results[index] = await worker(items[index], index);
1375
- }
1376
- }
1377
- const lanes = Math.min(CONCURRENCY, items.length);
1378
- await Promise.all(Array.from({ length: lanes }, () => run()));
1379
- return results;
1373
+ const limit = pLimit(CONCURRENCY);
1374
+ return limit.map(items, worker);
1380
1375
  }
1381
1376
  async function summarizeChunks(chunks, summarize) {
1382
1377
  return mapLimited(chunks, async (chunk) => {
@@ -1570,8 +1565,8 @@ var DEFAULT_FORMATS = ["markdown", "screenshot", "branding"];
1570
1565
  var DEFAULT_MAX_CHARS = 4e3;
1571
1566
  var COLOR_RE = /^(#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})|(?:rgb|rgba|hsl|hsla)\([^)]+\))$/i;
1572
1567
  function stripTags(html) {
1573
- const withoutTags = html.replace(/<[^>]*>/g, " ");
1574
- const decoded = withoutTags.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
1568
+ const withoutTags = html.replace(/<[^>]{0,2000}>/g, " ");
1569
+ const decoded = withoutTags.replace(/&nbsp;/g, " ").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
1575
1570
  return decoded.replace(/\s+/g, " ").trim();
1576
1571
  }
1577
1572
  function dedupe(values) {
@@ -3196,6 +3191,7 @@ export {
3196
3191
  parseJsonl,
3197
3192
  planEdit,
3198
3193
  planSubagentDispatch,
3194
+ planWaves,
3199
3195
  prepareDispatch,
3200
3196
  rankByFuzzy,
3201
3197
  rankSuggestions,
@@ -3212,6 +3208,7 @@ export {
3212
3208
  resolveSessionKey,
3213
3209
  resolveSubagentTools,
3214
3210
  restore,
3211
+ runAgentWaves,
3215
3212
  runHooks,
3216
3213
  runReview,
3217
3214
  runTurn,