@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,330 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import {
3
- type ActionPorts,
4
- ActionRunner,
5
- ArtifactStreamParser,
6
- type PlanAction,
7
- stripFencedWrapper,
8
- unescapeEntities,
9
- } from "./artifact-stream";
10
-
11
- // ── Pure helpers ─────────────────────────────────────────────────────────────
12
-
13
- describe("stripFencedWrapper", () => {
14
- it("strips a single ```lang fenced wrapper", () => {
15
- expect(stripFencedWrapper("```ts\nconst a = 1;\n```")).toBe("const a = 1;");
16
- });
17
-
18
- it("strips a bare ``` fence", () => {
19
- expect(stripFencedWrapper("```\nhello\n```")).toBe("hello");
20
- });
21
-
22
- it("leaves unfenced content untouched", () => {
23
- expect(stripFencedWrapper("plain text")).toBe("plain text");
24
- });
25
-
26
- it("does not strip inner fences (single wrapper only)", () => {
27
- const input = "```md\nuse ```code``` here\n```";
28
- expect(stripFencedWrapper(input)).toBe("use ```code``` here");
29
- });
30
- });
31
-
32
- describe("unescapeEntities", () => {
33
- it("unescapes < and >", () => {
34
- expect(unescapeEntities("a &lt;tag&gt; b")).toBe("a <tag> b");
35
- });
36
-
37
- it("is a no-op without entities", () => {
38
- expect(unescapeEntities("nothing here")).toBe("nothing here");
39
- });
40
- });
41
-
42
- // ── Parser ───────────────────────────────────────────────────────────────────
43
-
44
- function collector() {
45
- const events: string[] = [];
46
- return {
47
- events,
48
- onArtifactOpen: vi.fn((e) => events.push(`open:${e.artifactId}:${e.title}`)),
49
- onArtifactClose: vi.fn((e) => events.push(`close:${e.artifactId}`)),
50
- onActionOpen: vi.fn((e) => events.push(`aopen:${e.actionId}:${e.action.type}`)),
51
- onActionStream: vi.fn((e) => events.push(`astream:${e.actionId}`)),
52
- onActionClose: vi.fn((e) => events.push(`aclose:${e.actionId}:${e.action.type}`)),
53
- };
54
- }
55
-
56
- const CTX = { tenantId: "org_123" };
57
-
58
- describe("ArtifactStreamParser", () => {
59
- it("parses a single-action artifact in one chunk", () => {
60
- const c = collector();
61
- const p = new ArtifactStreamParser(c);
62
- p.parse(
63
- "m1",
64
- CTX,
65
- `<artifact id="a1" title="Build">` +
66
- `<action type="file" filePath="src/x.ts">const x = 1;</action></artifact>`,
67
- );
68
-
69
- expect(c.onArtifactOpen).toHaveBeenCalledWith(
70
- expect.objectContaining({
71
- tenantId: "org_123",
72
- messageId: "m1",
73
- artifactId: "a1",
74
- title: "Build",
75
- }),
76
- );
77
- expect(c.onActionClose).toHaveBeenCalledWith(
78
- expect.objectContaining({
79
- tenantId: "org_123",
80
- action: { type: "file", filePath: "src/x.ts", content: "const x = 1;" },
81
- }),
82
- );
83
- expect(c.onArtifactClose).toHaveBeenCalledTimes(1);
84
- });
85
-
86
- it("handles a tag split across two chunks without dup emit", () => {
87
- const c = collector();
88
- const p = new ArtifactStreamParser(c);
89
- p.parse("m1", CTX, `<artifact id="a1" title="Sp`);
90
- p.parse("m1", CTX, `lit"><action type="shell">npm i</acti`);
91
- p.parse("m1", CTX, `on></artifact>`);
92
-
93
- expect(c.onArtifactOpen).toHaveBeenCalledTimes(1);
94
- expect(c.onArtifactOpen).toHaveBeenCalledWith(
95
- expect.objectContaining({ artifactId: "a1", title: "Split" }),
96
- );
97
- expect(c.onActionClose).toHaveBeenCalledTimes(1);
98
- expect(c.onActionClose).toHaveBeenCalledWith(
99
- expect.objectContaining({ action: { type: "shell", content: "npm i" } }),
100
- );
101
- expect(c.onArtifactClose).toHaveBeenCalledTimes(1);
102
- });
103
-
104
- it("streams partial content via onActionStream then final via onActionClose", () => {
105
- const c = collector();
106
- const p = new ArtifactStreamParser(c);
107
- p.parse("m1", CTX, `<artifact id="a1" title="T"><action type="file" filePath="f">par`);
108
- p.parse("m1", CTX, `tial</action></artifact>`);
109
-
110
- expect(c.onActionStream).toHaveBeenCalled();
111
- expect(c.onActionClose).toHaveBeenCalledWith(
112
- expect.objectContaining({ action: { type: "file", filePath: "f", content: "partial" } }),
113
- );
114
- });
115
-
116
- it("strips a fenced wrapper and unescapes entities in action content", () => {
117
- const c = collector();
118
- const p = new ArtifactStreamParser(c);
119
- p.parse(
120
- "m1",
121
- CTX,
122
- '<artifact id="a1" title="T"><action type="file" filePath="f">' +
123
- "```ts\nconst t = &lt;X&gt;();\n```" +
124
- "</action></artifact>",
125
- );
126
-
127
- expect(c.onActionClose).toHaveBeenCalledWith(
128
- expect.objectContaining({
129
- action: { type: "file", filePath: "f", content: "const t = <X>();" },
130
- }),
131
- );
132
- });
133
-
134
- it("preserves order of multiple actions in one artifact", () => {
135
- const c = collector();
136
- const p = new ArtifactStreamParser(c);
137
- p.parse(
138
- "m1",
139
- CTX,
140
- `<artifact id="a1" title="Multi">` +
141
- `<action type="file" filePath="a.ts">A</action>` +
142
- `<action type="shell">npm test</action>` +
143
- `<action type="start">npm run dev</action>` +
144
- `</artifact>`,
145
- );
146
-
147
- const opens = c.events.filter((e) => e.startsWith("aclose:"));
148
- expect(opens).toEqual(["aclose:0:file", "aclose:1:shell", "aclose:2:start"]);
149
- });
150
-
151
- it("parses a data action with operation + projectId", () => {
152
- const c = collector();
153
- const p = new ArtifactStreamParser(c);
154
- p.parse(
155
- "m1",
156
- CTX,
157
- `<artifact id="a1" title="T"><action type="data" operation="migration" projectId="p1">SQL</action></artifact>`,
158
- );
159
-
160
- expect(c.onActionClose).toHaveBeenCalledWith(
161
- expect.objectContaining({
162
- action: { type: "data", operation: "migration", projectId: "p1", content: "SQL" },
163
- }),
164
- );
165
- });
166
-
167
- it("isolates state per messageId", () => {
168
- const c = collector();
169
- const p = new ArtifactStreamParser(c);
170
- p.parse("m1", CTX, `<artifact id="a1" title="One">`);
171
- p.parse("m2", CTX, `<artifact id="b1" title="Two">`);
172
- p.parse("m1", CTX, `<action type="shell">x</action></artifact>`);
173
- p.parse("m2", CTX, `<action type="shell">y</action></artifact>`);
174
-
175
- expect(c.onArtifactOpen).toHaveBeenCalledWith(expect.objectContaining({ artifactId: "a1" }));
176
- expect(c.onArtifactOpen).toHaveBeenCalledWith(expect.objectContaining({ artifactId: "b1" }));
177
- expect(c.onActionClose).toHaveBeenCalledTimes(2);
178
- });
179
-
180
- it("fails closed on empty tenantId", () => {
181
- const c = collector();
182
- const p = new ArtifactStreamParser(c);
183
- expect(() => p.parse("m1", { tenantId: "" }, '<artifact id="a" title="T">')).toThrow();
184
- });
185
- });
186
-
187
- // ── Runner ───────────────────────────────────────────────────────────────────
188
-
189
- function ports() {
190
- const p = {
191
- writeFile: vi.fn(async (_a: PlanAction, _c: { tenantId: string }) => undefined),
192
- runShell: vi.fn(async (_a: PlanAction, _c: { tenantId: string }) => ({
193
- exitCode: 0,
194
- output: "ok",
195
- })),
196
- dataOp: vi.fn(async (_a: PlanAction, _c: { tenantId: string }) => undefined),
197
- };
198
- return p as typeof p & ActionPorts;
199
- }
200
-
201
- const fileAction: PlanAction = { type: "file", filePath: "a.ts", content: "x" };
202
- const shellAction: PlanAction = { type: "shell", content: "npm i" };
203
-
204
- describe("ActionRunner — state machine", () => {
205
- it("transitions pending -> running -> complete", async () => {
206
- const r = new ActionRunner(ports(), CTX);
207
- r.register("0", fileAction);
208
- expect(r.get("0").status).toBe("pending");
209
- await r.runAction("0");
210
- expect(r.get("0").status).toBe("complete");
211
- expect(r.get("0").executed).toBe(true);
212
- });
213
-
214
- it("delegates file write to injected port (no in-proc exec)", async () => {
215
- const p = ports();
216
- const r = new ActionRunner(p, CTX);
217
- r.register("0", fileAction);
218
- await r.runAction("0");
219
- expect(p.writeFile).toHaveBeenCalledWith(
220
- fileAction,
221
- expect.objectContaining({ tenantId: "org_123" }),
222
- );
223
- });
224
-
225
- it("delegates shell to injected port", async () => {
226
- const p = ports();
227
- const r = new ActionRunner(p, CTX);
228
- r.register("0", shellAction);
229
- await r.runAction("0");
230
- expect(p.runShell).toHaveBeenCalledWith(
231
- shellAction,
232
- expect.objectContaining({ tenantId: "org_123" }),
233
- );
234
- });
235
-
236
- it("delegates data action to dataOp port", async () => {
237
- const p = ports();
238
- const r = new ActionRunner(p, CTX);
239
- const dataAction: PlanAction = { type: "data", operation: "query", content: "SELECT 1" };
240
- r.register("0", dataAction);
241
- await r.runAction("0");
242
- expect(p.dataOp).toHaveBeenCalledWith(
243
- dataAction,
244
- expect.objectContaining({ tenantId: "org_123" }),
245
- );
246
- });
247
-
248
- it("transitions to failed when a port throws", async () => {
249
- const p = ports();
250
- p.runShell.mockRejectedValueOnce(new Error("boom"));
251
- const r = new ActionRunner(p, CTX);
252
- r.register("0", shellAction);
253
- await r.runAction("0");
254
- expect(r.get("0").status).toBe("failed");
255
- expect(r.get("0").error).toContain("boom");
256
- });
257
-
258
- it("throws on illegal transition (run an already complete action)", async () => {
259
- const r = new ActionRunner(ports(), CTX);
260
- r.register("0", fileAction);
261
- await r.runAction("0");
262
- await expect(r.runAction("0")).rejects.toThrow();
263
- });
264
-
265
- it("abort allowed from pending", () => {
266
- const r = new ActionRunner(ports(), CTX);
267
- r.register("0", fileAction);
268
- r.abort("0");
269
- expect(r.get("0").status).toBe("aborted");
270
- });
271
-
272
- it("abort rejected from complete", async () => {
273
- const r = new ActionRunner(ports(), CTX);
274
- r.register("0", fileAction);
275
- await r.runAction("0");
276
- expect(() => r.abort("0")).toThrow();
277
- });
278
- });
279
-
280
- describe("ActionRunner — queue", () => {
281
- it("runs actions in submission order", async () => {
282
- const p = ports();
283
- const order: string[] = [];
284
- p.writeFile.mockImplementation(async (a: PlanAction) => {
285
- order.push((a as { filePath: string }).filePath);
286
- });
287
- const r = new ActionRunner(p, CTX);
288
- r.register("0", { type: "file", filePath: "first", content: "" });
289
- r.register("1", { type: "file", filePath: "second", content: "" });
290
- await r.runQueue();
291
- expect(order).toEqual(["first", "second"]);
292
- });
293
-
294
- it("a failed required action halts the queue", async () => {
295
- const p = ports();
296
- p.runShell.mockRejectedValueOnce(new Error("fail"));
297
- const r = new ActionRunner(p, CTX);
298
- r.register("0", shellAction);
299
- r.register("1", fileAction);
300
- await r.runQueue();
301
- expect(r.get("0").status).toBe("failed");
302
- expect(r.get("1").status).toBe("pending");
303
- expect(p.writeFile).not.toHaveBeenCalled();
304
- });
305
-
306
- it("continueOnError keeps draining the queue", async () => {
307
- const p = ports();
308
- p.runShell.mockRejectedValueOnce(new Error("fail"));
309
- const r = new ActionRunner(p, CTX);
310
- r.register("0", shellAction);
311
- r.register("1", fileAction);
312
- await r.runQueue({ continueOnError: true });
313
- expect(r.get("0").status).toBe("failed");
314
- expect(r.get("1").status).toBe("complete");
315
- });
316
- });
317
-
318
- describe("ActionRunner — tenant isolation", () => {
319
- it("fails closed on empty tenantId", () => {
320
- expect(() => new ActionRunner(ports(), { tenantId: "" })).toThrow();
321
- });
322
-
323
- it("cross-tenant action map access is impossible", () => {
324
- const a = new ActionRunner(ports(), { tenantId: "org_a" });
325
- a.register("0", fileAction);
326
- const b = new ActionRunner(ports(), { tenantId: "org_b" });
327
- expect(() => b.get("0")).toThrow();
328
- expect(a.get("0").status).toBe("pending");
329
- });
330
- });