@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.
- package/LICENSE +21 -676
- package/dist/adapters/index.d.ts +24 -5
- package/dist/adapters/index.js +27 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/chunk-Q62VKHIT.js +178 -0
- package/dist/chunk-Q62VKHIT.js.map +1 -0
- package/dist/{chunk-MUF7ZZTO.js → chunk-R5HOSQUW.js} +2 -1
- package/dist/{chunk-MUF7ZZTO.js.map → chunk-R5HOSQUW.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -17
- package/dist/index.js.map +1 -1
- package/dist/orchestration.d.ts +42 -1
- package/dist/orchestration.js +7 -3
- package/dist/sandbox.js +1 -1
- package/package.json +80 -30
- package/.turbo/turbo-build.log +0 -130
- package/.turbo/turbo-test.log +0 -47
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -250
- package/dist/chunk-MX2WL43P.js +0 -90
- package/dist/chunk-MX2WL43P.js.map +0 -1
- package/examples/pulsar-quickstart.ts +0 -35
- package/examples/resume-branch.ts +0 -45
- package/examples/subagent-fanout.ts +0 -20
- package/src/adapters/dispatcher-sse.test.ts +0 -218
- package/src/adapters/dispatcher-sse.ts +0 -222
- package/src/adapters/index.ts +0 -18
- package/src/adapters/mcp-catalog.test.ts +0 -213
- package/src/adapters/mcp-catalog.ts +0 -188
- package/src/adapters/prisma-rollout.test.ts +0 -153
- package/src/adapters/prisma-rollout.ts +0 -104
- package/src/agent-runtime.test.ts +0 -176
- package/src/artifact-stream.test.ts +0 -330
- package/src/artifact-stream.ts +0 -453
- package/src/channel-gateway.test.ts +0 -432
- package/src/channel-gateway.ts +0 -357
- package/src/cli.ts +0 -49
- package/src/code-review.test.ts +0 -501
- package/src/code-review.ts +0 -495
- package/src/command-suggestions.test.ts +0 -251
- package/src/command-suggestions.ts +0 -338
- package/src/commands.test.ts +0 -184
- package/src/commands.ts +0 -140
- package/src/commit-message.test.ts +0 -249
- package/src/commit-message.ts +0 -180
- package/src/context-compaction.test.ts +0 -522
- package/src/context-compaction.ts +0 -438
- package/src/definitions.test.ts +0 -78
- package/src/definitions.ts +0 -190
- package/src/deployment-status.test.ts +0 -215
- package/src/deployment-status.ts +0 -227
- package/src/design-context.test.ts +0 -195
- package/src/design-context.ts +0 -198
- package/src/dispatcher.test.ts +0 -234
- package/src/dispatcher.ts +0 -189
- package/src/durable-turn.test.ts +0 -209
- package/src/durable-turn.ts +0 -135
- package/src/edit-planner.test.ts +0 -204
- package/src/edit-planner.ts +0 -325
- package/src/fuzzy-match.test.ts +0 -311
- package/src/fuzzy-match.ts +0 -444
- package/src/hook-pipeline.test.ts +0 -279
- package/src/hook-pipeline.ts +0 -373
- package/src/inbound-admission.test.ts +0 -394
- package/src/inbound-admission.ts +0 -246
- package/src/index.ts +0 -49
- package/src/loop.test.ts +0 -161
- package/src/loop.ts +0 -211
- package/src/mcp-bridge.test.ts +0 -165
- package/src/mcp-bridge.ts +0 -81
- package/src/memory-provider.test.ts +0 -232
- package/src/memory-provider.ts +0 -257
- package/src/model.ts +0 -168
- package/src/orchestration.test.ts +0 -53
- package/src/orchestration.ts +0 -146
- package/src/permission-ruleset.test.ts +0 -301
- package/src/permission-ruleset.ts +0 -1
- package/src/policy.ts +0 -151
- package/src/project-repo.test.ts +0 -232
- package/src/project-repo.ts +0 -311
- package/src/protocol.ts +0 -159
- package/src/pulsar.test.ts +0 -156
- package/src/pulsar.ts +0 -322
- package/src/rollout-store-persistent.test.ts +0 -217
- package/src/rollout-store-persistent.ts +0 -166
- package/src/rollout.ts +0 -150
- package/src/sandbox.ts +0 -113
- package/src/session-share.test.ts +0 -360
- package/src/session-share.ts +0 -310
- package/src/skill-distillation.test.ts +0 -177
- package/src/skill-distillation.ts +0 -369
- package/src/skills.test.ts +0 -277
- package/src/skills.ts +0 -277
- package/src/subagents.test.ts +0 -290
- package/src/subagents.ts +0 -332
- package/src/tools.test.ts +0 -12
- package/src/tools.ts +0 -129
- package/src/workbench.test.ts +0 -0
- package/src/workbench.ts +0 -0
- package/tsconfig.json +0 -12
- package/tsup.config.ts +0 -36
package/dist/adapters/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/adapters/index.js
CHANGED
|
@@ -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-
|
|
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;
|
|
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-
|
|
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
|
-
|
|
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
|
|
1369
|
-
|
|
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(/<[^>]
|
|
1574
|
-
const decoded = withoutTags.replace(/&
|
|
1568
|
+
const withoutTags = html.replace(/<[^>]{0,2000}>/g, " ");
|
|
1569
|
+
const decoded = withoutTags.replace(/ /g, " ").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/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,
|