@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,453 +0,0 @@
1
- /**
2
- * Streaming artifact/action protocol.
3
- *
4
- * Faithful re-expression of an open-source web-app-builder's streamed-plan
5
- * DSL: the model emits text containing `<artifact>` blocks that wrap an
6
- * ordered list of `<action>` blocks. This module provides:
7
- *
8
- * 1. A neutral `PlanAction` discriminated union (file / shell / start /
9
- * build / data).
10
- * 2. An incremental, chunk-boundary-safe `ArtifactStreamParser`.
11
- * 3. A delegating `ActionRunner` state machine (no in-process exec, no
12
- * host FS — all side effects go through injected ports).
13
- *
14
- * Multi-tenant, fail-closed: a `tenantId` is mandatory at every entry point;
15
- * an empty tenant throws, and per-tenant runner state is isolated.
16
- */
17
-
18
- import { z } from "zod";
19
-
20
- // ── Action union ─────────────────────────────────────────────────────────────
21
-
22
- export interface FileAction {
23
- readonly type: "file";
24
- readonly filePath: string;
25
- readonly content: string;
26
- }
27
-
28
- export interface ShellAction {
29
- readonly type: "shell";
30
- readonly content: string;
31
- }
32
-
33
- export interface StartAction {
34
- readonly type: "start";
35
- readonly content: string;
36
- }
37
-
38
- export interface BuildAction {
39
- readonly type: "build";
40
- readonly content: string;
41
- }
42
-
43
- export interface DataAction {
44
- readonly type: "data";
45
- readonly operation: "migration" | "query";
46
- readonly filePath?: string | undefined;
47
- readonly projectId?: string | undefined;
48
- readonly content: string;
49
- }
50
-
51
- export type PlanAction = FileAction | ShellAction | StartAction | BuildAction | DataAction;
52
-
53
- const DataActionSchema = z.object({
54
- type: z.literal("data"),
55
- operation: z.union([z.literal("migration"), z.literal("query")]),
56
- filePath: z.string().optional(),
57
- projectId: z.string().optional(),
58
- content: z.string(),
59
- });
60
-
61
- // ── Tenant context ───────────────────────────────────────────────────────────
62
-
63
- export interface TenantContext {
64
- readonly tenantId: string;
65
- }
66
-
67
- function assertTenant(ctx: TenantContext): string {
68
- const tenantId = ctx?.tenantId;
69
- if (typeof tenantId !== "string" || tenantId.trim().length === 0) {
70
- throw new Error("artifact-stream: tenantId is mandatory and must be non-empty (fail-closed)");
71
- }
72
- return tenantId;
73
- }
74
-
75
- // ── Pure helpers ─────────────────────────────────────────────────────────────
76
-
77
- /**
78
- * Strip a single ```lang ... ``` fenced wrapper if (and only if) the trimmed
79
- * content opens with a fence on its own line and ends with a closing fence.
80
- * Inner fences are preserved (single-wrapper semantics).
81
- */
82
- export function stripFencedWrapper(input: string): string {
83
- const trimmed = input.trim();
84
- const opener = /^```[^\n]*\n/;
85
- const match = opener.exec(trimmed);
86
- if (match === null || !trimmed.endsWith("```")) {
87
- return input;
88
- }
89
- const body = trimmed.slice(match[0].length, trimmed.length - 3);
90
- return body.replace(/\n$/, "");
91
- }
92
-
93
- /** Unescape the `&lt;` / `&gt;` entities the model emits inside action bodies. */
94
- export function unescapeEntities(input: string): string {
95
- return input.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
96
- }
97
-
98
- function normalizeActionContent(raw: string): string {
99
- return unescapeEntities(stripFencedWrapper(raw));
100
- }
101
-
102
- // ── Parser ───────────────────────────────────────────────────────────────────
103
-
104
- export interface ArtifactEvent {
105
- readonly tenantId: string;
106
- readonly messageId: string;
107
- readonly artifactId: string;
108
- readonly title: string;
109
- }
110
-
111
- export interface ActionEvent {
112
- readonly tenantId: string;
113
- readonly messageId: string;
114
- readonly artifactId: string;
115
- readonly actionId: string;
116
- readonly action: PlanAction;
117
- }
118
-
119
- export interface ArtifactStreamCallbacks {
120
- readonly onArtifactOpen?: ((e: ArtifactEvent) => void) | undefined;
121
- readonly onArtifactClose?: ((e: ArtifactEvent) => void) | undefined;
122
- readonly onActionOpen?: ((e: ActionEvent) => void) | undefined;
123
- readonly onActionStream?: ((e: ActionEvent) => void) | undefined;
124
- readonly onActionClose?: ((e: ActionEvent) => void) | undefined;
125
- }
126
-
127
- interface ParseState {
128
- buffer: string;
129
- position: number;
130
- insideArtifact: boolean;
131
- insideAction: boolean;
132
- artifactCounter: number;
133
- currentArtifact?: { id: string; title: string } | undefined;
134
- currentAction?: { actionId: string; attrs: Record<string, string> } | undefined;
135
- streamedLength: number;
136
- opened: boolean;
137
- }
138
-
139
- function createState(): ParseState {
140
- return {
141
- buffer: "",
142
- position: 0,
143
- insideArtifact: false,
144
- insideAction: false,
145
- artifactCounter: 0,
146
- streamedLength: 0,
147
- opened: false,
148
- };
149
- }
150
-
151
- function parseAttrs(tag: string): Record<string, string> {
152
- const attrs: Record<string, string> = {};
153
- const re = /(\w+)="([^"]*)"/g;
154
- let m: RegExpExecArray | null = re.exec(tag);
155
- while (m !== null) {
156
- attrs[m[1] as string] = m[2] as string;
157
- m = re.exec(tag);
158
- }
159
- return attrs;
160
- }
161
-
162
- function buildAction(attrs: Record<string, string>, content: string): PlanAction {
163
- const type = attrs.type;
164
- const normalized = normalizeActionContent(content);
165
- switch (type) {
166
- case "file":
167
- return { type: "file", filePath: attrs.filePath ?? "", content: normalized };
168
- case "shell":
169
- return { type: "shell", content: normalized };
170
- case "start":
171
- return { type: "start", content: normalized };
172
- case "build":
173
- return { type: "build", content: normalized };
174
- case "data": {
175
- const candidate: Record<string, unknown> = {
176
- type: "data",
177
- operation: attrs.operation === "query" ? "query" : "migration",
178
- content: normalized,
179
- };
180
- if (attrs.filePath !== undefined) candidate.filePath = attrs.filePath;
181
- if (attrs.projectId !== undefined) candidate.projectId = attrs.projectId;
182
- return DataActionSchema.parse(candidate) as DataAction;
183
- }
184
- default:
185
- throw new Error(`artifact-stream: unknown action type "${String(type)}"`);
186
- }
187
- }
188
-
189
- /**
190
- * Incremental parser. State is keyed by `messageId` so independent streams
191
- * never bleed into each other; a tag split across a chunk boundary is
192
- * resumed from `position` and never re-emitted.
193
- */
194
- export class ArtifactStreamParser {
195
- private readonly cb: ArtifactStreamCallbacks;
196
- private readonly states = new Map<string, ParseState>();
197
-
198
- constructor(callbacks: ArtifactStreamCallbacks) {
199
- this.cb = callbacks;
200
- }
201
-
202
- parse(messageId: string, ctx: TenantContext, chunk: string): void {
203
- const tenantId = assertTenant(ctx);
204
- const state = this.states.get(messageId) ?? createState();
205
- this.states.set(messageId, state);
206
- state.buffer += chunk;
207
- this.drain(messageId, tenantId, state);
208
- }
209
-
210
- private drain(messageId: string, tenantId: string, s: ParseState): void {
211
- // Loop while we can make forward progress; stop when the next needed
212
- // token is incomplete (split across the next chunk boundary).
213
- for (;;) {
214
- const rest = s.buffer.slice(s.position);
215
-
216
- if (!s.insideArtifact) {
217
- const open = rest.indexOf("<artifact");
218
- if (open === -1) return;
219
- const end = rest.indexOf(">", open);
220
- if (end === -1) return; // tag continues in a later chunk
221
- const tag = rest.slice(open, end + 1);
222
- const attrs = parseAttrs(tag);
223
- s.position += end + 1;
224
- s.insideArtifact = true;
225
- s.currentArtifact = { id: attrs.id ?? "", title: attrs.title ?? "" };
226
- this.cb.onArtifactOpen?.({
227
- tenantId,
228
- messageId,
229
- artifactId: s.currentArtifact.id,
230
- title: s.currentArtifact.title,
231
- });
232
- continue;
233
- }
234
-
235
- if (!s.insideAction) {
236
- const close = rest.indexOf("</artifact>");
237
- const open = rest.indexOf("<action");
238
- if (open === -1 && close === -1) return;
239
- if (close !== -1 && (open === -1 || close < open)) {
240
- s.position += close + "</artifact>".length;
241
- const art = s.currentArtifact;
242
- s.insideArtifact = false;
243
- s.currentArtifact = undefined;
244
- if (art !== undefined) {
245
- this.cb.onArtifactClose?.({
246
- tenantId,
247
- messageId,
248
- artifactId: art.id,
249
- title: art.title,
250
- });
251
- }
252
- continue;
253
- }
254
- const end = rest.indexOf(">", open);
255
- if (end === -1) return; // open tag split across chunks
256
- const tag = rest.slice(open, end + 1);
257
- const attrs = parseAttrs(tag);
258
- const actionId = String(s.artifactCounter);
259
- s.artifactCounter += 1;
260
- s.position += end + 1;
261
- s.insideAction = true;
262
- s.streamedLength = 0;
263
- s.currentAction = { actionId, attrs };
264
- const art = s.currentArtifact;
265
- if (
266
- art !== undefined &&
267
- (attrs.type === "file" ||
268
- attrs.type === "shell" ||
269
- attrs.type === "start" ||
270
- attrs.type === "build" ||
271
- attrs.type === "data")
272
- ) {
273
- this.cb.onActionOpen?.({
274
- tenantId,
275
- messageId,
276
- artifactId: art.id,
277
- actionId,
278
- action: buildAction(attrs, ""),
279
- });
280
- }
281
- continue;
282
- }
283
-
284
- // Inside an action: look for its close tag.
285
- const closeIdx = rest.indexOf("</action>");
286
- const act = s.currentAction;
287
- const art = s.currentArtifact;
288
- if (closeIdx === -1) {
289
- // Partial content — stream the newly-seen portion (raw, un-normalized
290
- // since the closing fence may not have arrived yet).
291
- const partial = rest;
292
- if (partial.length > s.streamedLength && act !== undefined && art !== undefined) {
293
- s.streamedLength = partial.length;
294
- this.cb.onActionStream?.({
295
- tenantId,
296
- messageId,
297
- artifactId: art.id,
298
- actionId: act.actionId,
299
- action: buildAction(act.attrs, partial),
300
- });
301
- }
302
- return;
303
- }
304
-
305
- const content = rest.slice(0, closeIdx);
306
- s.position += closeIdx + "</action>".length;
307
- s.insideAction = false;
308
- if (act !== undefined && art !== undefined) {
309
- this.cb.onActionClose?.({
310
- tenantId,
311
- messageId,
312
- artifactId: art.id,
313
- actionId: act.actionId,
314
- action: buildAction(act.attrs, content),
315
- });
316
- }
317
- s.currentAction = undefined;
318
- }
319
- }
320
- }
321
-
322
- // ── Runner ───────────────────────────────────────────────────────────────────
323
-
324
- export type ActionStatus = "pending" | "running" | "complete" | "aborted" | "failed";
325
-
326
- export interface ActionState {
327
- readonly action: PlanAction;
328
- readonly status: ActionStatus;
329
- readonly executed: boolean;
330
- readonly error?: string | undefined;
331
- }
332
-
333
- export interface ActionPorts {
334
- readonly writeFile: (action: FileAction, ctx: TenantContext) => Promise<void>;
335
- readonly runShell: (
336
- action: ShellAction | StartAction | BuildAction,
337
- ctx: TenantContext,
338
- ) => Promise<{ exitCode: number; output: string }>;
339
- readonly dataOp: (action: DataAction, ctx: TenantContext) => Promise<void>;
340
- }
341
-
342
- export interface RunQueueOptions {
343
- readonly continueOnError?: boolean | undefined;
344
- }
345
-
346
- function setStatus(
347
- state: ActionState,
348
- status: ActionStatus,
349
- patch: { executed?: boolean; error?: string } = {},
350
- ): ActionState {
351
- return {
352
- action: state.action,
353
- status,
354
- executed: patch.executed ?? state.executed,
355
- error: patch.error ?? state.error,
356
- };
357
- }
358
-
359
- const ABORTABLE: ReadonlySet<ActionStatus> = new Set(["pending", "running"]);
360
-
361
- export class ActionRunner {
362
- private readonly ports: ActionPorts;
363
- private readonly ctx: TenantContext;
364
- private readonly tenantId: string;
365
- private readonly map = new Map<string, ActionState>();
366
- private readonly order: string[] = [];
367
-
368
- constructor(ports: ActionPorts, ctx: TenantContext) {
369
- this.tenantId = assertTenant(ctx);
370
- this.ports = ports;
371
- this.ctx = { tenantId: this.tenantId };
372
- }
373
-
374
- register(actionId: string, action: PlanAction): void {
375
- if (this.map.has(actionId)) {
376
- throw new Error(`artifact-stream: action "${actionId}" already registered`);
377
- }
378
- this.map.set(actionId, { action, status: "pending", executed: false });
379
- this.order.push(actionId);
380
- }
381
-
382
- get(actionId: string): ActionState {
383
- const state = this.map.get(actionId);
384
- if (state === undefined) {
385
- throw new Error(
386
- `artifact-stream: action "${actionId}" not found for tenant "${this.tenantId}"`,
387
- );
388
- }
389
- return state;
390
- }
391
-
392
- abort(actionId: string): void {
393
- const state = this.get(actionId);
394
- if (!ABORTABLE.has(state.status)) {
395
- throw new Error(
396
- `artifact-stream: cannot abort action "${actionId}" from status "${state.status}"`,
397
- );
398
- }
399
- this.map.set(actionId, setStatus(state, "aborted"));
400
- }
401
-
402
- async runAction(actionId: string): Promise<void> {
403
- const state = this.get(actionId);
404
- if (state.status !== "pending") {
405
- throw new Error(
406
- `artifact-stream: illegal transition for "${actionId}": ${state.status} -> running`,
407
- );
408
- }
409
- this.map.set(actionId, setStatus(state, "running"));
410
- try {
411
- await this.dispatch(state.action);
412
- this.map.set(actionId, setStatus(this.get(actionId), "complete", { executed: true }));
413
- } catch (error) {
414
- const message = error instanceof Error ? error.message : String(error);
415
- this.map.set(
416
- actionId,
417
- setStatus(this.get(actionId), "failed", { executed: true, error: message }),
418
- );
419
- }
420
- }
421
-
422
- async runQueue(options: RunQueueOptions = {}): Promise<void> {
423
- const continueOnError = options.continueOnError ?? false;
424
- for (const actionId of this.order) {
425
- const state = this.get(actionId);
426
- if (state.status !== "pending") continue;
427
- await this.runAction(actionId);
428
- if (this.get(actionId).status === "failed" && !continueOnError) {
429
- return;
430
- }
431
- }
432
- }
433
-
434
- private async dispatch(action: PlanAction): Promise<void> {
435
- switch (action.type) {
436
- case "file":
437
- await this.ports.writeFile(action, this.ctx);
438
- return;
439
- case "shell":
440
- case "start":
441
- case "build":
442
- await this.ports.runShell(action, this.ctx);
443
- return;
444
- case "data":
445
- await this.ports.dataOp(action, this.ctx);
446
- return;
447
- default: {
448
- const exhaustive: never = action;
449
- throw new Error(`artifact-stream: unhandled action ${JSON.stringify(exhaustive)}`);
450
- }
451
- }
452
- }
453
- }