@stigmer/runner 3.1.5 → 3.1.7

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 (53) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/discover-mcp-server.js +23 -0
  3. package/dist/activities/discover-mcp-server.js.map +1 -1
  4. package/dist/activities/execute-cursor/index.js +39 -1
  5. package/dist/activities/execute-cursor/index.js.map +1 -1
  6. package/dist/activities/execute-cursor/workspace-provision.d.ts +23 -4
  7. package/dist/activities/execute-cursor/workspace-provision.js +17 -6
  8. package/dist/activities/execute-cursor/workspace-provision.js.map +1 -1
  9. package/dist/activities/execute-deep-agent/index.js +8 -1
  10. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  11. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +1 -1
  12. package/dist/activities/execute-deep-agent/post-stream.d.ts +1 -1
  13. package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +1 -1
  14. package/dist/activities/execute-deep-agent/streaming-terminal.d.ts +1 -1
  15. package/dist/activities/execute-deep-agent/streaming.d.ts +1 -1
  16. package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +1 -1
  17. package/dist/shared/execution-status-writer.d.ts +31 -0
  18. package/dist/shared/execution-status-writer.js +43 -0
  19. package/dist/shared/execution-status-writer.js.map +1 -0
  20. package/dist/shared/mcp-oauth-detect.d.ts +53 -0
  21. package/dist/shared/mcp-oauth-detect.js +99 -0
  22. package/dist/shared/mcp-oauth-detect.js.map +1 -0
  23. package/dist/shared/workspace/writeback-coordinator.d.ts +120 -0
  24. package/dist/shared/workspace/writeback-coordinator.js +393 -0
  25. package/dist/shared/workspace/writeback-coordinator.js.map +1 -0
  26. package/package.json +2 -2
  27. package/src/activities/discover-mcp-server.ts +25 -0
  28. package/src/activities/execute-cursor/__tests__/workspace-provision.test.ts +23 -15
  29. package/src/activities/execute-cursor/index.ts +41 -1
  30. package/src/activities/execute-cursor/workspace-provision.ts +39 -7
  31. package/src/activities/execute-deep-agent/__tests__/post-stream.test.ts +1 -1
  32. package/src/activities/execute-deep-agent/index.ts +8 -1
  33. package/src/activities/execute-deep-agent/inline-publisher.ts +1 -1
  34. package/src/activities/execute-deep-agent/post-stream.ts +1 -1
  35. package/src/activities/execute-deep-agent/status-builder.ts +1 -1
  36. package/src/activities/execute-deep-agent/streaming-side-effects.ts +1 -1
  37. package/src/activities/execute-deep-agent/streaming-terminal.ts +1 -1
  38. package/src/activities/execute-deep-agent/streaming.ts +1 -1
  39. package/src/activities/execute-deep-agent/v3-status-builder.ts +1 -1
  40. package/src/shared/__tests__/mcp-oauth-detect.test.ts +147 -0
  41. package/src/shared/execution-status-writer.ts +56 -0
  42. package/src/shared/mcp-oauth-detect.ts +112 -0
  43. package/src/shared/workspace/__tests__/writeback-coordinator.integration.test.ts +230 -0
  44. package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +477 -0
  45. package/src/{activities/execute-deep-agent → shared/workspace}/writeback-coordinator.ts +206 -94
  46. package/dist/activities/execute-deep-agent/execution-status-writer.d.ts +0 -17
  47. package/dist/activities/execute-deep-agent/execution-status-writer.js +0 -9
  48. package/dist/activities/execute-deep-agent/execution-status-writer.js.map +0 -1
  49. package/dist/activities/execute-deep-agent/writeback-coordinator.d.ts +0 -71
  50. package/dist/activities/execute-deep-agent/writeback-coordinator.js +0 -299
  51. package/dist/activities/execute-deep-agent/writeback-coordinator.js.map +0 -1
  52. package/src/activities/execute-deep-agent/__tests__/writeback-coordinator.test.ts +0 -426
  53. package/src/activities/execute-deep-agent/execution-status-writer.ts +0 -19
@@ -1,426 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { create } from "@bufbuild/protobuf";
3
- import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
4
- import { WorkspaceWriteBackPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
5
- import { GitWriteBackMode } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
6
- import { StatusBuilder } from "../status-builder.js";
7
- import {
8
- WriteBackCoordinator,
9
- parseGithubRepo,
10
- extractGithubToken,
11
- } from "../writeback-coordinator.js";
12
- import type { WorkspaceBackend, ProvisionResult } from "../../../shared/workspace/types.js";
13
- import { SourceType } from "../../../shared/workspace/types.js";
14
- import {
15
- AGENT_GIT_AUTHOR_NAME,
16
- AGENT_GIT_AUTHOR_EMAIL,
17
- } from "../../../shared/workspace/git-identity.js";
18
-
19
- function makeStatusBuilder(): StatusBuilder {
20
- return new StatusBuilder("exec-wb-test", create(AgentExecutionStatusSchema, {}));
21
- }
22
-
23
- function makeProvisionResult(overrides: Partial<ProvisionResult> = {}): ProvisionResult {
24
- return {
25
- rootDir: "/workspace/my-app",
26
- sourceType: SourceType.GIT_REPO,
27
- consumedKeys: [],
28
- workspaceDescription: "test",
29
- entryName: "my-app",
30
- gitMetadata: {
31
- repoUrl: "https://ghp_TOKEN@github.com/acme/my-app.git",
32
- branch: "main",
33
- baseCommit: "abc123",
34
- gitCredentialsConfigured: true,
35
- },
36
- ...overrides,
37
- };
38
- }
39
-
40
- function makeWorkspaceEntry(name: string, writeBackMode: GitWriteBackMode = GitWriteBackMode.GIT_WRITE_BACK_BRANCH_AND_PR) {
41
- return {
42
- name,
43
- source: {
44
- source: {
45
- case: "gitRepo" as const,
46
- value: { writeBackMode },
47
- },
48
- },
49
- $typeName: "ai.stigmer.agentic.session.v1.WorkspaceEntry" as const,
50
- } as any;
51
- }
52
-
53
- function mockWorkspaceBackend(responses: Record<string, string> = {}): WorkspaceBackend {
54
- const defaultResponses: Record<string, string> = {
55
- "git diff --stat": " 1 file changed, 1 insertion(+)",
56
- "git diff --cached --stat": "",
57
- "git ls-files --others --exclude-standard": "",
58
- "git checkout -b": "",
59
- "git add -A": "",
60
- "commit -m": "",
61
- "git rev-parse HEAD": "abc123def456",
62
- "git push -u origin": "",
63
- "git push": "",
64
- "git diff --stat main...HEAD": " 1 file changed",
65
- ...responses,
66
- };
67
-
68
- return {
69
- rootDir: "/workspace",
70
- execute: vi.fn(async (cmd: string) => {
71
- for (const [pattern, response] of Object.entries(defaultResponses)) {
72
- if (cmd.includes(pattern)) return response;
73
- }
74
- return "";
75
- }),
76
- readFile: vi.fn(),
77
- writeFile: vi.fn(),
78
- writeFileBuffer: vi.fn(),
79
- exists: vi.fn(),
80
- };
81
- }
82
-
83
- const originalFetch = globalThis.fetch;
84
-
85
- describe("WriteBackCoordinator", () => {
86
- let sb: StatusBuilder;
87
-
88
- beforeEach(() => {
89
- sb = makeStatusBuilder();
90
- globalThis.fetch = vi.fn(async () =>
91
- new Response(
92
- JSON.stringify({ html_url: "https://github.com/acme/my-app/pull/42", number: 42 }),
93
- { status: 201 },
94
- ),
95
- ) as typeof fetch;
96
- });
97
-
98
- afterEach(() => {
99
- globalThis.fetch = originalFetch;
100
- });
101
-
102
- it("filters out non-git workspace entries", () => {
103
- const coord = new WriteBackCoordinator({
104
- statusWriter: sb,
105
- executionId: "exec-123",
106
- provisionResults: [makeProvisionResult({ sourceType: SourceType.LOCAL_PATH })],
107
- workspaceEntries: [makeWorkspaceEntry("my-app")],
108
- workspaceBackend: mockWorkspaceBackend(),
109
- });
110
-
111
- expect(coord.hasEligibleEntries).toBe(false);
112
- });
113
-
114
- it("filters out entries without git credentials", () => {
115
- const coord = new WriteBackCoordinator({
116
- statusWriter: sb,
117
- executionId: "exec-123",
118
- provisionResults: [makeProvisionResult({
119
- gitMetadata: {
120
- repoUrl: "https://github.com/acme/my-app.git",
121
- branch: "main",
122
- baseCommit: "abc",
123
- gitCredentialsConfigured: false,
124
- },
125
- })],
126
- workspaceEntries: [makeWorkspaceEntry("my-app")],
127
- workspaceBackend: mockWorkspaceBackend(),
128
- });
129
-
130
- expect(coord.hasEligibleEntries).toBe(false);
131
- });
132
-
133
- it("accepts entries with UNSPECIFIED write-back mode (platform decides)", () => {
134
- const coord = new WriteBackCoordinator({
135
- statusWriter: sb,
136
- executionId: "exec-123",
137
- provisionResults: [makeProvisionResult()],
138
- workspaceEntries: [makeWorkspaceEntry("my-app", GitWriteBackMode.GIT_WRITE_BACK_MODE_UNSPECIFIED)],
139
- workspaceBackend: mockWorkspaceBackend(),
140
- });
141
-
142
- expect(coord.hasEligibleEntries).toBe(true);
143
- });
144
-
145
- it("performs full incremental cycle: branch -> commit -> push -> PR", async () => {
146
- const backend = mockWorkspaceBackend();
147
- const coord = new WriteBackCoordinator({
148
- statusWriter: sb,
149
- executionId: "exec-12345678rest",
150
- provisionResults: [makeProvisionResult()],
151
- workspaceEntries: [makeWorkspaceEntry("my-app")],
152
- workspaceBackend: backend,
153
- });
154
-
155
- await coord.onFileModified("src/main.ts");
156
-
157
- const wbs = sb.currentStatus.workspaceWriteBacks;
158
- expect(wbs).toHaveLength(1);
159
- expect(wbs[0].branchName).toBe("stigmer/exec-123");
160
- expect(wbs[0].baseBranch).toBe("main");
161
- expect(wbs[0].commitSha).toBe("abc123def456");
162
- expect(wbs[0].pullRequestUrl).toBe("https://github.com/acme/my-app/pull/42");
163
- expect(wbs[0].pullRequestNumber).toBe(42);
164
- expect(wbs[0].phase).toBe(WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_PR_CREATED);
165
-
166
- const executeCalls = (backend.execute as ReturnType<typeof vi.fn>).mock.calls;
167
- const commands = executeCalls.map((c: any) => c[0] as string);
168
- expect(commands.some((c: string) => c.includes("git checkout -b stigmer/exec-123"))).toBe(true);
169
- expect(commands.some((c: string) => c.includes("git add -A"))).toBe(true);
170
- expect(commands.some((c: string) => c.includes("commit -m"))).toBe(true);
171
- expect(commands.some((c: string) => c.includes("git push -u origin"))).toBe(true);
172
- });
173
-
174
- it("commits with the agent identity pinned via -c flags", async () => {
175
- const backend = mockWorkspaceBackend();
176
- const coord = new WriteBackCoordinator({
177
- statusWriter: sb,
178
- executionId: "exec-12345678rest",
179
- provisionResults: [makeProvisionResult()],
180
- workspaceEntries: [makeWorkspaceEntry("my-app")],
181
- workspaceBackend: backend,
182
- });
183
-
184
- await coord.onFileModified("src/main.ts");
185
-
186
- const executeCalls = (backend.execute as ReturnType<typeof vi.fn>).mock.calls;
187
- const commands = executeCalls.map((c: any) => c[0] as string);
188
- const commitCommand = commands.find((c: string) => c.includes("git") && c.includes("commit -m"));
189
- expect(commitCommand,
190
- "commit must not depend on ambient git identity — the cloud sandbox has none",
191
- ).toBeDefined();
192
- expect(commitCommand).toContain(`-c user.name='${AGENT_GIT_AUTHOR_NAME}'`);
193
- expect(commitCommand).toContain(`-c user.email='${AGENT_GIT_AUTHOR_EMAIL}'`);
194
- expect(commitCommand).toContain('commit -m "agent changes (1)"');
195
- });
196
-
197
- it("skips when there are no changes", async () => {
198
- const backend = mockWorkspaceBackend({
199
- "git diff --stat": "",
200
- "git ls-files --others --exclude-standard": "",
201
- });
202
-
203
- const coord = new WriteBackCoordinator({
204
- statusWriter: sb,
205
- executionId: "exec-123",
206
- provisionResults: [makeProvisionResult()],
207
- workspaceEntries: [makeWorkspaceEntry("my-app")],
208
- workspaceBackend: backend,
209
- });
210
-
211
- await coord.onFileModified("src/main.ts");
212
-
213
- expect(sb.currentStatus.workspaceWriteBacks).toHaveLength(0);
214
- });
215
-
216
- it("on second call, commits to existing branch without re-creating", async () => {
217
- const backend = mockWorkspaceBackend();
218
- const coord = new WriteBackCoordinator({
219
- statusWriter: sb,
220
- executionId: "exec-12345678rest",
221
- provisionResults: [makeProvisionResult()],
222
- workspaceEntries: [makeWorkspaceEntry("my-app")],
223
- workspaceBackend: backend,
224
- });
225
-
226
- await coord.onFileModified("first.ts");
227
- await coord.onFileModified("second.ts");
228
-
229
- const executeCalls = (backend.execute as ReturnType<typeof vi.fn>).mock.calls;
230
- const commands = executeCalls.map((c: any) => c[0] as string);
231
- const checkoutCalls = commands.filter((c: string) => c.includes("git checkout -b"));
232
- expect(checkoutCalls).toHaveLength(1);
233
-
234
- const pushCalls = commands.filter((c: string) => c.includes("git push"));
235
- expect(pushCalls.length).toBeGreaterThanOrEqual(2);
236
- });
237
-
238
- it("sets FAILED phase on git error after mutation started", async () => {
239
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
240
- const backend = mockWorkspaceBackend({
241
- "git checkout -b": "",
242
- });
243
- (backend.execute as ReturnType<typeof vi.fn>).mockImplementation(async (cmd: string) => {
244
- if (cmd.includes("git diff --stat") && !cmd.includes("...HEAD")) return " changed";
245
- if (cmd.includes("git diff --cached")) return "";
246
- if (cmd.includes("git checkout -b")) return "";
247
- if (cmd.includes("git add")) return "";
248
- if (cmd.includes("commit -m")) throw new Error("commit failed: lock");
249
- return "";
250
- });
251
-
252
- const coord = new WriteBackCoordinator({
253
- statusWriter: sb,
254
- executionId: "exec-123",
255
- provisionResults: [makeProvisionResult()],
256
- workspaceEntries: [makeWorkspaceEntry("my-app")],
257
- workspaceBackend: backend,
258
- });
259
-
260
- await coord.onFileModified("src/main.ts");
261
-
262
- const wbs = sb.currentStatus.workspaceWriteBacks;
263
- expect(wbs).toHaveLength(1);
264
- expect(wbs[0].phase).toBe(WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_FAILED);
265
- expect(wbs[0].error).toContain("commit failed");
266
- warnSpy.mockRestore();
267
- });
268
-
269
- it("finalize catches remaining uncommitted changes", async () => {
270
- const backend = mockWorkspaceBackend();
271
- const coord = new WriteBackCoordinator({
272
- statusWriter: sb,
273
- executionId: "exec-12345678rest",
274
- provisionResults: [makeProvisionResult()],
275
- workspaceEntries: [makeWorkspaceEntry("my-app")],
276
- workspaceBackend: backend,
277
- });
278
-
279
- await coord.finalize();
280
-
281
- expect(sb.currentStatus.workspaceWriteBacks).toHaveLength(1);
282
- expect(sb.currentStatus.workspaceWriteBacks[0].phase).toBe(
283
- WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_PR_CREATED,
284
- );
285
- });
286
-
287
- it("resolves path to single entry without prefix matching", async () => {
288
- const backend = mockWorkspaceBackend();
289
- const coord = new WriteBackCoordinator({
290
- statusWriter: sb,
291
- executionId: "exec-12345678rest",
292
- provisionResults: [makeProvisionResult()],
293
- workspaceEntries: [makeWorkspaceEntry("my-app")],
294
- workspaceBackend: backend,
295
- });
296
-
297
- await coord.onFileModified("any/path/file.ts");
298
-
299
- expect(sb.currentStatus.workspaceWriteBacks).toHaveLength(1);
300
- });
301
-
302
- it("resolves path to correct entry in multi-entry workspace", async () => {
303
- const backend = mockWorkspaceBackend();
304
- const coord = new WriteBackCoordinator({
305
- statusWriter: sb,
306
- executionId: "exec-12345678rest",
307
- provisionResults: [
308
- makeProvisionResult({ entryName: "frontend", rootDir: "/workspace/frontend" }),
309
- makeProvisionResult({ entryName: "backend", rootDir: "/workspace/backend" }),
310
- ],
311
- workspaceEntries: [
312
- makeWorkspaceEntry("frontend"),
313
- makeWorkspaceEntry("backend"),
314
- ],
315
- workspaceBackend: backend,
316
- });
317
-
318
- await coord.onFileModified("frontend/src/app.tsx");
319
-
320
- const wbs = sb.currentStatus.workspaceWriteBacks;
321
- expect(wbs).toHaveLength(1);
322
- expect(wbs[0].workspaceEntryName).toBe("frontend");
323
- });
324
-
325
- it("ignores paths outside eligible entries in multi-entry mode", async () => {
326
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
327
- const backend = mockWorkspaceBackend();
328
- const coord = new WriteBackCoordinator({
329
- statusWriter: sb,
330
- executionId: "exec-123",
331
- provisionResults: [
332
- makeProvisionResult({ entryName: "frontend", rootDir: "/workspace/frontend" }),
333
- ],
334
- workspaceEntries: [makeWorkspaceEntry("frontend")],
335
- workspaceBackend: backend,
336
- });
337
-
338
- const coord2 = new WriteBackCoordinator({
339
- statusWriter: sb,
340
- executionId: "exec-123",
341
- provisionResults: [
342
- makeProvisionResult({ entryName: "fe", rootDir: "/workspace/fe" }),
343
- makeProvisionResult({ entryName: "be", rootDir: "/workspace/be" }),
344
- ],
345
- workspaceEntries: [makeWorkspaceEntry("fe"), makeWorkspaceEntry("be")],
346
- workspaceBackend: backend,
347
- });
348
-
349
- await coord2.onFileModified("unknown/file.ts");
350
- expect(sb.currentStatus.workspaceWriteBacks).toHaveLength(0);
351
- warnSpy.mockRestore();
352
- });
353
-
354
- it("PR creation failure sets FAILED phase", async () => {
355
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
356
- globalThis.fetch = vi.fn(async () =>
357
- new Response(JSON.stringify({ message: "Forbidden" }), { status: 403 }),
358
- ) as typeof fetch;
359
-
360
- const backend = mockWorkspaceBackend();
361
- const coord = new WriteBackCoordinator({
362
- statusWriter: sb,
363
- executionId: "exec-12345678rest",
364
- provisionResults: [makeProvisionResult()],
365
- workspaceEntries: [makeWorkspaceEntry("my-app")],
366
- workspaceBackend: backend,
367
- });
368
-
369
- await coord.onFileModified("src/main.ts");
370
-
371
- const wbs = sb.currentStatus.workspaceWriteBacks;
372
- expect(wbs).toHaveLength(1);
373
- expect(wbs[0].phase).toBe(WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_FAILED);
374
- expect(wbs[0].error).toContain("GitHub API error");
375
- warnSpy.mockRestore();
376
- });
377
- });
378
-
379
- describe("parseGithubRepo", () => {
380
- it("parses HTTPS URL with .git suffix", () => {
381
- const result = parseGithubRepo("https://github.com/acme/my-app.git");
382
- expect(result).toEqual({ owner: "acme", repo: "my-app" });
383
- });
384
-
385
- it("parses HTTPS URL without .git suffix", () => {
386
- const result = parseGithubRepo("https://github.com/acme/my-app");
387
- expect(result).toEqual({ owner: "acme", repo: "my-app" });
388
- });
389
-
390
- it("parses HTTPS URL with token", () => {
391
- const result = parseGithubRepo("https://ghp_token@github.com/acme/my-app.git");
392
- expect(result).toEqual({ owner: "acme", repo: "my-app" });
393
- });
394
-
395
- it("parses SSH URL", () => {
396
- const result = parseGithubRepo("git@github.com:acme/my-app.git");
397
- expect(result).toEqual({ owner: "acme", repo: "my-app" });
398
- });
399
-
400
- it("throws for non-GitHub URLs", () => {
401
- expect(() => parseGithubRepo("https://gitlab.com/acme/my-app.git"))
402
- .toThrow("Cannot parse GitHub owner/repo");
403
- });
404
- });
405
-
406
- describe("extractGithubToken", () => {
407
- afterEach(() => {
408
- delete process.env.GITHUB_TOKEN;
409
- });
410
-
411
- it("extracts token from HTTPS URL", () => {
412
- const token = extractGithubToken("https://ghp_abc123@github.com/acme/app.git");
413
- expect(token).toBe("ghp_abc123");
414
- });
415
-
416
- it("falls back to GITHUB_TOKEN env var", () => {
417
- process.env.GITHUB_TOKEN = "env-token";
418
- const token = extractGithubToken("https://github.com/acme/app.git");
419
- expect(token).toBe("env-token");
420
- });
421
-
422
- it("throws when no token available", () => {
423
- expect(() => extractGithubToken("https://github.com/acme/app.git"))
424
- .toThrow("Cannot extract GitHub token");
425
- });
426
- });
@@ -1,19 +0,0 @@
1
- /**
2
- * Shared interface for proto mutation consumed by side-effect classes
3
- * (InlinePublisher, WriteBackCoordinator) and streaming loops.
4
- *
5
- * Both v2 StatusBuilder and V3StatusBuilder implement this interface,
6
- * decoupling side-effect classes from the specific builder version.
7
- */
8
-
9
- import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
10
- import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
11
- import type { WorkspaceWriteBack } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
12
-
13
- export interface ExecutionStatusWriter {
14
- readonly currentStatus: AgentExecutionStatus;
15
- readonly forceNextUpdate: boolean;
16
- clearForceFlag(): void;
17
- addArtifact(artifact: ExecutionArtifact): void;
18
- addWriteBack(wb: WorkspaceWriteBack): void;
19
- }