@nebutra/agent-runtime 0.2.0 → 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 (137) hide show
  1. package/LICENSE +21 -676
  2. package/README.md +2 -0
  3. package/dist/adapters/dispatcher-sse.js +1 -0
  4. package/dist/adapters/index.d.ts +24 -5
  5. package/dist/adapters/index.js +28 -0
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/adapters/mcp-catalog.js +1 -0
  8. package/dist/adapters/prisma-rollout.js +1 -0
  9. package/dist/chunk-424PT5DM.js +23 -0
  10. package/dist/chunk-424PT5DM.js.map +1 -0
  11. package/dist/{chunk-NN7DATXA.js → chunk-4Y25ZTKI.js} +3 -3
  12. package/dist/chunk-4Y25ZTKI.js.map +1 -0
  13. package/dist/{chunk-BJBBR3QA.js → chunk-D4YAPLOW.js} +4 -4
  14. package/dist/chunk-D4YAPLOW.js.map +1 -0
  15. package/dist/{chunk-ZMYX5VBU.js → chunk-GQZKYWFT.js} +23 -7
  16. package/dist/chunk-GQZKYWFT.js.map +1 -0
  17. package/dist/chunk-KCNN4QUQ.js +255 -0
  18. package/dist/chunk-KCNN4QUQ.js.map +1 -0
  19. package/dist/{chunk-PGGWSUTM.js → chunk-NI4EDT4T.js} +2 -2
  20. package/dist/chunk-NI4EDT4T.js.map +1 -0
  21. package/dist/chunk-Q62VKHIT.js +178 -0
  22. package/dist/chunk-Q62VKHIT.js.map +1 -0
  23. package/dist/{chunk-MUF7ZZTO.js → chunk-R5HOSQUW.js} +2 -1
  24. package/dist/{chunk-MUF7ZZTO.js.map → chunk-R5HOSQUW.js.map} +1 -1
  25. package/dist/{chunk-5N4644PB.js → chunk-SD2ZJ7XG.js} +4 -4
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +52 -0
  28. package/dist/cli.js.map +1 -0
  29. package/dist/commands.js +1 -0
  30. package/dist/definitions.js +1 -0
  31. package/dist/dispatcher.js +1 -0
  32. package/dist/durable-turn.js +3 -2
  33. package/dist/hook-pipeline.js +1 -0
  34. package/dist/index.d.ts +8 -85
  35. package/dist/index.js +249 -134
  36. package/dist/index.js.map +1 -1
  37. package/dist/loop.d.ts +2 -2
  38. package/dist/loop.js +3 -2
  39. package/dist/mcp-bridge.d.ts +3 -3
  40. package/dist/mcp-bridge.js +3 -2
  41. package/dist/model.js +1 -0
  42. package/dist/orchestration.d.ts +84 -0
  43. package/dist/orchestration.js +16 -0
  44. package/dist/orchestration.js.map +1 -0
  45. package/dist/policy.js +1 -0
  46. package/dist/protocol.js +1 -0
  47. package/dist/pulsar.d.ts +78 -0
  48. package/dist/pulsar.js +17 -0
  49. package/dist/pulsar.js.map +1 -0
  50. package/dist/rollout-store-persistent.js +1 -0
  51. package/dist/rollout.js +1 -0
  52. package/dist/sandbox.js +2 -1
  53. package/dist/skills.js +2 -1
  54. package/dist/subagents.js +1 -0
  55. package/dist/tools.d.ts +4 -3
  56. package/dist/tools.js +5 -3
  57. package/package.json +84 -27
  58. package/.turbo/turbo-build.log +0 -115
  59. package/.turbo/turbo-test.log +0 -44
  60. package/.turbo/turbo-typecheck.log +0 -4
  61. package/CHANGELOG.md +0 -253
  62. package/dist/chunk-BJBBR3QA.js.map +0 -1
  63. package/dist/chunk-NN7DATXA.js.map +0 -1
  64. package/dist/chunk-PGGWSUTM.js.map +0 -1
  65. package/dist/chunk-ZMYX5VBU.js.map +0 -1
  66. package/src/adapters/dispatcher-sse.test.ts +0 -218
  67. package/src/adapters/dispatcher-sse.ts +0 -222
  68. package/src/adapters/index.ts +0 -18
  69. package/src/adapters/mcp-catalog.test.ts +0 -213
  70. package/src/adapters/mcp-catalog.ts +0 -188
  71. package/src/adapters/prisma-rollout.test.ts +0 -153
  72. package/src/adapters/prisma-rollout.ts +0 -104
  73. package/src/agent-runtime.test.ts +0 -176
  74. package/src/artifact-stream.test.ts +0 -330
  75. package/src/artifact-stream.ts +0 -453
  76. package/src/channel-gateway.test.ts +0 -432
  77. package/src/channel-gateway.ts +0 -357
  78. package/src/code-review.test.ts +0 -501
  79. package/src/code-review.ts +0 -495
  80. package/src/command-suggestions.test.ts +0 -251
  81. package/src/command-suggestions.ts +0 -338
  82. package/src/commands.test.ts +0 -184
  83. package/src/commands.ts +0 -140
  84. package/src/commit-message.test.ts +0 -249
  85. package/src/commit-message.ts +0 -180
  86. package/src/context-compaction.test.ts +0 -522
  87. package/src/context-compaction.ts +0 -434
  88. package/src/definitions.test.ts +0 -78
  89. package/src/definitions.ts +0 -190
  90. package/src/deployment-status.test.ts +0 -215
  91. package/src/deployment-status.ts +0 -227
  92. package/src/design-context.test.ts +0 -195
  93. package/src/design-context.ts +0 -198
  94. package/src/dispatcher.test.ts +0 -234
  95. package/src/dispatcher.ts +0 -189
  96. package/src/durable-turn.test.ts +0 -209
  97. package/src/durable-turn.ts +0 -135
  98. package/src/edit-planner.test.ts +0 -204
  99. package/src/edit-planner.ts +0 -325
  100. package/src/fuzzy-match.test.ts +0 -311
  101. package/src/fuzzy-match.ts +0 -444
  102. package/src/hook-pipeline.test.ts +0 -279
  103. package/src/hook-pipeline.ts +0 -373
  104. package/src/inbound-admission.test.ts +0 -394
  105. package/src/inbound-admission.ts +0 -246
  106. package/src/index.ts +0 -47
  107. package/src/loop.test.ts +0 -161
  108. package/src/loop.ts +0 -211
  109. package/src/mcp-bridge.test.ts +0 -165
  110. package/src/mcp-bridge.ts +0 -76
  111. package/src/memory-provider.test.ts +0 -232
  112. package/src/memory-provider.ts +0 -257
  113. package/src/model.ts +0 -168
  114. package/src/permission-ruleset.test.ts +0 -301
  115. package/src/permission-ruleset.ts +0 -200
  116. package/src/policy.ts +0 -151
  117. package/src/project-repo.test.ts +0 -232
  118. package/src/project-repo.ts +0 -311
  119. package/src/protocol.ts +0 -159
  120. package/src/rollout-store-persistent.test.ts +0 -217
  121. package/src/rollout-store-persistent.ts +0 -166
  122. package/src/rollout.ts +0 -150
  123. package/src/sandbox.ts +0 -113
  124. package/src/session-share.test.ts +0 -360
  125. package/src/session-share.ts +0 -310
  126. package/src/skill-distillation.test.ts +0 -177
  127. package/src/skill-distillation.ts +0 -369
  128. package/src/skills.test.ts +0 -277
  129. package/src/skills.ts +0 -255
  130. package/src/subagents.test.ts +0 -290
  131. package/src/subagents.ts +0 -332
  132. package/src/tools.ts +0 -126
  133. package/src/workbench.test.ts +0 -0
  134. package/src/workbench.ts +0 -0
  135. package/tsconfig.json +0 -12
  136. package/tsup.config.ts +0 -33
  137. /package/dist/{chunk-5N4644PB.js.map → chunk-SD2ZJ7XG.js.map} +0 -0
@@ -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
- }