@synap-core/cli 1.13.0 → 1.13.1-next.20261102.246dc73

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 (227) hide show
  1. package/README.md +13 -3
  2. package/dist/build-info.json +2 -3
  3. package/dist/commands/agents.js +12 -2
  4. package/dist/commands/agents.js.map +1 -1
  5. package/dist/commands/capability.d.ts +86 -0
  6. package/dist/commands/capability.js +117 -30
  7. package/dist/commands/capability.js.map +1 -1
  8. package/dist/commands/context.js +2 -1
  9. package/dist/commands/context.js.map +1 -1
  10. package/dist/commands/data-extra.d.ts +13 -0
  11. package/dist/commands/data-extra.js +23 -2
  12. package/dist/commands/data-extra.js.map +1 -1
  13. package/dist/commands/doctor.d.ts +18 -0
  14. package/dist/commands/doctor.js +28 -1
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/init.js +4 -2
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/commands/keys-request.d.ts +48 -0
  19. package/dist/commands/keys-request.js +236 -0
  20. package/dist/commands/keys-request.js.map +1 -0
  21. package/dist/commands/keys-request.test.d.ts +1 -0
  22. package/dist/commands/keys-request.test.js +176 -0
  23. package/dist/commands/keys-request.test.js.map +1 -0
  24. package/dist/commands/knowledge.d.ts +42 -5
  25. package/dist/commands/knowledge.js +90 -27
  26. package/dist/commands/knowledge.js.map +1 -1
  27. package/dist/commands/market.d.ts +89 -31
  28. package/dist/commands/market.js +215 -222
  29. package/dist/commands/market.js.map +1 -1
  30. package/dist/commands/mcp.d.ts +1 -1
  31. package/dist/commands/mcp.js +14 -9
  32. package/dist/commands/mcp.js.map +1 -1
  33. package/dist/commands/pods.js +6 -2
  34. package/dist/commands/pods.js.map +1 -1
  35. package/dist/commands/proposals-actions.js +7 -1
  36. package/dist/commands/proposals-actions.js.map +1 -1
  37. package/dist/commands/providers.d.ts +22 -6
  38. package/dist/commands/providers.js +39 -32
  39. package/dist/commands/providers.js.map +1 -1
  40. package/dist/commands/raycast.d.ts +15 -0
  41. package/dist/commands/raycast.js +36 -10
  42. package/dist/commands/raycast.js.map +1 -1
  43. package/dist/commands/status.js +12 -6
  44. package/dist/commands/status.js.map +1 -1
  45. package/dist/commands/statusline.backoff.test.js +13 -3
  46. package/dist/commands/statusline.backoff.test.js.map +1 -1
  47. package/dist/commands/statusline.d.ts +104 -9
  48. package/dist/commands/statusline.js +392 -148
  49. package/dist/commands/statusline.js.map +1 -1
  50. package/dist/commands/statusline.line2.test.d.ts +15 -0
  51. package/dist/commands/statusline.line2.test.js +226 -0
  52. package/dist/commands/statusline.line2.test.js.map +1 -0
  53. package/dist/commands/statusline.needs-you.test.d.ts +1 -0
  54. package/dist/commands/statusline.needs-you.test.js +17 -0
  55. package/dist/commands/statusline.needs-you.test.js.map +1 -0
  56. package/dist/commands/statusline.now.test.d.ts +10 -0
  57. package/dist/commands/statusline.now.test.js +250 -0
  58. package/dist/commands/statusline.now.test.js.map +1 -0
  59. package/dist/commands/tracks.d.ts +1 -0
  60. package/dist/commands/tracks.js +4 -0
  61. package/dist/commands/tracks.js.map +1 -1
  62. package/dist/commands/upload.js +12 -0
  63. package/dist/commands/upload.js.map +1 -1
  64. package/dist/commands/wait.d.ts +124 -0
  65. package/dist/commands/wait.js +384 -0
  66. package/dist/commands/wait.js.map +1 -0
  67. package/dist/commands/wait.test.d.ts +8 -0
  68. package/dist/commands/wait.test.js +308 -0
  69. package/dist/commands/wait.test.js.map +1 -0
  70. package/dist/commands/worker-github-app.d.ts +63 -0
  71. package/dist/commands/worker-github-app.js +161 -0
  72. package/dist/commands/worker-github-app.js.map +1 -0
  73. package/dist/commands/worker-github-app.test.d.ts +1 -0
  74. package/dist/commands/worker-github-app.test.js +102 -0
  75. package/dist/commands/worker-github-app.test.js.map +1 -0
  76. package/dist/commands/worker-github-broker.d.ts +39 -0
  77. package/dist/commands/worker-github-broker.js +106 -0
  78. package/dist/commands/worker-github-broker.js.map +1 -0
  79. package/dist/commands/worker-github-broker.test.d.ts +1 -0
  80. package/dist/commands/worker-github-broker.test.js +90 -0
  81. package/dist/commands/worker-github-broker.test.js.map +1 -0
  82. package/dist/commands/worker-runs.test.d.ts +1 -0
  83. package/dist/commands/worker-runs.test.js +150 -0
  84. package/dist/commands/worker-runs.test.js.map +1 -0
  85. package/dist/commands/worker-worktrees.d.ts +34 -0
  86. package/dist/commands/worker-worktrees.js +87 -0
  87. package/dist/commands/worker-worktrees.js.map +1 -0
  88. package/dist/commands/worker.d.ts +192 -5
  89. package/dist/commands/worker.js +467 -45
  90. package/dist/commands/worker.js.map +1 -1
  91. package/dist/commands/worker.test.js +269 -3
  92. package/dist/commands/worker.test.js.map +1 -1
  93. package/dist/index.js +59 -4
  94. package/dist/index.js.map +1 -1
  95. package/dist/lib/agents-config.d.ts +9 -0
  96. package/dist/lib/agents-config.js +20 -0
  97. package/dist/lib/agents-config.js.map +1 -1
  98. package/dist/lib/atomic-file.d.ts +20 -1
  99. package/dist/lib/atomic-file.js +100 -2
  100. package/dist/lib/atomic-file.js.map +1 -1
  101. package/dist/lib/auth.d.ts +25 -0
  102. package/dist/lib/auth.js +49 -9
  103. package/dist/lib/auth.js.map +1 -1
  104. package/dist/lib/box/job-server.js +4 -0
  105. package/dist/lib/box/job-server.js.map +1 -1
  106. package/dist/lib/bridge-invocation.test.d.ts +1 -0
  107. package/dist/lib/bridge-invocation.test.js +75 -0
  108. package/dist/lib/bridge-invocation.test.js.map +1 -0
  109. package/dist/lib/bridge-status.d.ts +144 -0
  110. package/dist/lib/bridge-status.js +227 -0
  111. package/dist/lib/bridge-status.js.map +1 -0
  112. package/dist/lib/bridge-status.test.d.ts +6 -0
  113. package/dist/lib/bridge-status.test.js +197 -0
  114. package/dist/lib/bridge-status.test.js.map +1 -0
  115. package/dist/lib/client-header.test.js +19 -1
  116. package/dist/lib/client-header.test.js.map +1 -1
  117. package/dist/lib/conversation-headers.d.ts +67 -2
  118. package/dist/lib/conversation-headers.js +122 -6
  119. package/dist/lib/conversation-headers.js.map +1 -1
  120. package/dist/lib/conversation-headers.test.d.ts +1 -0
  121. package/dist/lib/conversation-headers.test.js +78 -0
  122. package/dist/lib/conversation-headers.test.js.map +1 -0
  123. package/dist/lib/credential-class.d.ts +12 -1
  124. package/dist/lib/credential-class.js +20 -0
  125. package/dist/lib/credential-class.js.map +1 -1
  126. package/dist/lib/device-connect.d.ts +45 -0
  127. package/dist/lib/device-connect.js +60 -0
  128. package/dist/lib/device-connect.js.map +1 -1
  129. package/dist/lib/goal-words.js +6 -3
  130. package/dist/lib/goal-words.js.map +1 -1
  131. package/dist/lib/hub-client.d.ts +59 -1
  132. package/dist/lib/hub-client.js +130 -36
  133. package/dist/lib/hub-client.js.map +1 -1
  134. package/dist/lib/key-locations.d.ts +3 -3
  135. package/dist/lib/key-locations.js +3 -39
  136. package/dist/lib/key-locations.js.map +1 -1
  137. package/dist/lib/machine-instance.d.ts +7 -5
  138. package/dist/lib/machine-instance.js +7 -5
  139. package/dist/lib/machine-instance.js.map +1 -1
  140. package/dist/lib/mcp-bridge.d.ts +43 -0
  141. package/dist/lib/mcp-bridge.js +199 -11
  142. package/dist/lib/mcp-bridge.js.map +1 -1
  143. package/dist/lib/mcp-bridge.test.js +194 -2
  144. package/dist/lib/mcp-bridge.test.js.map +1 -1
  145. package/dist/lib/mcp-call.d.ts +5 -1
  146. package/dist/lib/mcp-call.js +7 -6
  147. package/dist/lib/mcp-call.js.map +1 -1
  148. package/dist/lib/mcp-connector-url.test.d.ts +1 -0
  149. package/dist/lib/mcp-connector-url.test.js +13 -0
  150. package/dist/lib/mcp-connector-url.test.js.map +1 -0
  151. package/dist/lib/mcp-snippets.d.ts +16 -0
  152. package/dist/lib/mcp-snippets.js +20 -2
  153. package/dist/lib/mcp-snippets.js.map +1 -1
  154. package/dist/lib/now-line.d.ts +66 -0
  155. package/dist/lib/now-line.js +193 -0
  156. package/dist/lib/now-line.js.map +1 -0
  157. package/dist/lib/now-line.test.d.ts +5 -0
  158. package/dist/lib/now-line.test.js +177 -0
  159. package/dist/lib/now-line.test.js.map +1 -0
  160. package/dist/lib/package-updates.d.ts +66 -0
  161. package/dist/lib/package-updates.js +76 -0
  162. package/dist/lib/package-updates.js.map +1 -0
  163. package/dist/lib/pod.d.ts +0 -4
  164. package/dist/lib/pod.js +0 -7
  165. package/dist/lib/pod.js.map +1 -1
  166. package/dist/lib/register-claude-code-mcp.test.js +19 -0
  167. package/dist/lib/register-claude-code-mcp.test.js.map +1 -1
  168. package/dist/lib/safe-open.d.ts +5 -3
  169. package/dist/lib/safe-open.js +9 -8
  170. package/dist/lib/safe-open.js.map +1 -1
  171. package/dist/lib/self-host.d.ts +17 -0
  172. package/dist/lib/self-host.js +24 -0
  173. package/dist/lib/self-host.js.map +1 -0
  174. package/dist/lib/targets.d.ts +31 -0
  175. package/dist/lib/targets.js +100 -104
  176. package/dist/lib/targets.js.map +1 -1
  177. package/dist/lib/template-lifecycle.d.ts +0 -31
  178. package/dist/lib/template-lifecycle.js +0 -74
  179. package/dist/lib/template-lifecycle.js.map +1 -1
  180. package/dist/lib/term.d.ts +24 -0
  181. package/dist/lib/term.js +52 -0
  182. package/dist/lib/term.js.map +1 -0
  183. package/dist/lib/term.test.d.ts +1 -0
  184. package/dist/lib/term.test.js +29 -0
  185. package/dist/lib/term.test.js.map +1 -0
  186. package/dist/lib/transcript-index.d.ts +94 -0
  187. package/dist/lib/transcript-index.js +321 -0
  188. package/dist/lib/transcript-index.js.map +1 -0
  189. package/dist/lib/transcript-index.test.d.ts +10 -0
  190. package/dist/lib/transcript-index.test.js +200 -0
  191. package/dist/lib/transcript-index.test.js.map +1 -0
  192. package/dist/lib/wait-help.d.ts +11 -0
  193. package/dist/lib/wait-help.js +25 -0
  194. package/dist/lib/wait-help.js.map +1 -0
  195. package/dist/lib/wait-state.d.ts +57 -0
  196. package/dist/lib/wait-state.js +114 -0
  197. package/dist/lib/wait-state.js.map +1 -0
  198. package/package.json +2 -2
  199. package/skills/synap/SKILL.md +81 -44
  200. package/skills/synap/code-skills.md +15 -1
  201. package/skills/synap/concepts.md +36 -26
  202. package/skills/synap/document-embeds.md +15 -1
  203. package/skills/synap/focus-sessions.md +4 -4
  204. package/skills/synap/from-intent.md +2 -2
  205. package/skills/synap/governance-rules.md +2 -0
  206. package/skills/synap/hand-off.md +10 -6
  207. package/skills/synap/reflexes.md +4 -4
  208. package/skills/synap/work-model.md +1 -1
  209. package/skills/synap/writing-titles.md +7 -0
  210. package/skills/synap-app/SKILL.md +2 -2
  211. package/skills/synap-app/overview.md +2 -2
  212. package/skills/synap-market/SKILL.md +1 -1
  213. package/skills/synap-market/governance-and-catalog.md +1 -1
  214. package/skills/synap-schema/SKILL.md +4 -1
  215. package/skills/synap-schema/intent-vocabulary-ssot.md +4 -1
  216. package/dist/commands/template.d.ts +0 -42
  217. package/dist/commands/template.js +0 -191
  218. package/dist/commands/template.js.map +0 -1
  219. package/dist/lib/browser-auth.d.ts +0 -35
  220. package/dist/lib/browser-auth.js +0 -170
  221. package/dist/lib/browser-auth.js.map +0 -1
  222. package/dist/lib/openclaw.d.ts +0 -32
  223. package/dist/lib/openclaw.js +0 -165
  224. package/dist/lib/openclaw.js.map +0 -1
  225. package/dist/lib/seed.d.ts +0 -13
  226. package/dist/lib/seed.js +0 -135
  227. package/dist/lib/seed.js.map +0 -1
@@ -0,0 +1,102 @@
1
+ import { createVerify, generateKeyPairSync } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { describe, expect, it } from "vitest";
6
+ import { createInstallationTokenSource, githubAppFilesTooOpen, githubRunEnv, loadGithubAppConfig, parseEnvFile, signAppJwt, } from "./worker-github-app.js";
7
+ const { privateKey, publicKey } = generateKeyPairSync("rsa", {
8
+ modulusLength: 2048,
9
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
10
+ publicKeyEncoding: { type: "spki", format: "pem" },
11
+ });
12
+ const tmp = () => fs.mkdtempSync(path.join(os.tmpdir(), "gh-app-"));
13
+ describe("signAppJwt", () => {
14
+ it("is a RS256 JWT for the app id that its public key verifies, valid under 10 minutes", () => {
15
+ const now = Date.parse("2026-10-28T12:00:00Z");
16
+ const [h, b, s] = signAppJwt("123", privateKey, now).split(".");
17
+ expect(JSON.parse(Buffer.from(h, "base64url").toString())).toEqual({ alg: "RS256", typ: "JWT" });
18
+ const body = JSON.parse(Buffer.from(b, "base64url").toString());
19
+ expect(body.iss).toBe("123");
20
+ expect(body.exp - body.iat).toBeLessThanOrEqual(600);
21
+ expect(createVerify("RSA-SHA256").update(`${h}.${b}`).verify(publicKey, Buffer.from(s, "base64url"))).toBe(true);
22
+ });
23
+ });
24
+ describe("loadGithubAppConfig", () => {
25
+ it("no file is undefined (the caller warns), never a throw", () => {
26
+ expect(loadGithubAppConfig(path.join(tmp(), "none.env"))).toBeUndefined();
27
+ });
28
+ it("reads the id, the key by path and the installation id", () => {
29
+ const d = tmp();
30
+ fs.writeFileSync(path.join(d, "k.pem"), privateKey, { mode: 0o600 });
31
+ fs.writeFileSync(path.join(d, "a.env"), `# the Synap Agent App\nGITHUB_APP_ID=42\nGITHUB_APP_PRIVATE_KEY_FILE="${path.join(d, "k.pem")}"\nGITHUB_APP_INSTALLATION_ID=7\n`, { mode: 0o600 });
32
+ const cfg = loadGithubAppConfig(path.join(d, "a.env"));
33
+ expect(cfg).toMatchObject({ appId: "42", installationId: "7" });
34
+ expect(cfg?.privateKey).toContain("BEGIN PRIVATE KEY");
35
+ expect(githubAppFilesTooOpen(path.join(d, "a.env"))).toEqual([]);
36
+ });
37
+ it("a file that is there but unusable THROWS — never a silent fall back to a person's login", () => {
38
+ const d = tmp();
39
+ fs.writeFileSync(path.join(d, "a.env"), "GITHUB_APP_ID=42\n");
40
+ expect(() => loadGithubAppConfig(path.join(d, "a.env"))).toThrow(/GITHUB_APP_PRIVATE_KEY_FILE/);
41
+ fs.writeFileSync(path.join(d, "b.env"), `GITHUB_APP_ID=42\nGITHUB_APP_PRIVATE_KEY_FILE=${path.join(d, "missing.pem")}\n`);
42
+ expect(() => loadGithubAppConfig(path.join(d, "b.env"))).toThrow(/could not be read/);
43
+ });
44
+ it("names a key or env file that others can read", () => {
45
+ const d = tmp();
46
+ fs.writeFileSync(path.join(d, "k.pem"), privateKey, { mode: 0o644 });
47
+ fs.writeFileSync(path.join(d, "a.env"), `GITHUB_APP_ID=1\nGITHUB_APP_PRIVATE_KEY_FILE=${path.join(d, "k.pem")}\n`, { mode: 0o600 });
48
+ expect(githubAppFilesTooOpen(path.join(d, "a.env"))).toEqual([path.join(d, "k.pem")]);
49
+ });
50
+ it("parseEnvFile ignores comments and strips quotes", () => {
51
+ expect(parseEnvFile("# A=1\nB='x y'\nexport C=3")).toEqual({ B: "x y", C: "3" });
52
+ });
53
+ });
54
+ describe("createInstallationTokenSource", () => {
55
+ const T0 = Date.parse("2026-10-28T12:00:00Z");
56
+ const fakeFetch = (log, opts = {}) => (async (url, init) => {
57
+ log.push(`${init.method} ${url.replace("https://api.github.com", "")}`);
58
+ expect(init.headers.Authorization).toMatch(/^Bearer [\w-]+\.[\w-]+\.[\w-]+$/);
59
+ const ok = (opts.status ?? 200) < 400;
60
+ if (url.endsWith("/app/installations"))
61
+ return { ok, status: opts.status ?? 200, json: async () => opts.installations ?? [{ id: 9 }] };
62
+ return {
63
+ ok,
64
+ status: opts.status ?? 200,
65
+ json: async () => ({ token: `ghs_${log.length}`, expires_at: new Date(T0 + 3600_000).toISOString() }),
66
+ };
67
+ });
68
+ it("finds the single installation, mints a token, and reuses it until 5 min before it ends", async () => {
69
+ const log = [];
70
+ let t = T0;
71
+ const src = createInstallationTokenSource({ appId: "1", privateKey }, { fetchImpl: fakeFetch(log), now: () => t });
72
+ const a = await src.get();
73
+ expect(log).toEqual(["GET /app/installations", "POST /app/installations/9/access_tokens"]);
74
+ t = T0 + 50 * 60_000;
75
+ expect(await src.get()).toBe(a);
76
+ expect(log).toHaveLength(2);
77
+ t = T0 + 56 * 60_000;
78
+ expect(await src.get()).not.toBe(a);
79
+ expect(log).toHaveLength(3);
80
+ });
81
+ it("two installations and no id is an error, not a guess", async () => {
82
+ const src = createInstallationTokenSource({ appId: "1", privateKey }, { fetchImpl: fakeFetch([], { installations: [{ id: 1 }, { id: 2 }] }), now: () => T0 });
83
+ await expect(src.get()).rejects.toThrow(/GITHUB_APP_INSTALLATION_ID/);
84
+ });
85
+ it("a refused call throws (the worker then does not launch)", async () => {
86
+ const src = createInstallationTokenSource({ appId: "1", privateKey, installationId: "9" }, { fetchImpl: fakeFetch([], { status: 401 }), now: () => T0 });
87
+ await expect(src.get()).rejects.toThrow(/401/);
88
+ });
89
+ });
90
+ describe("githubRunEnv", () => {
91
+ const g = { token: "ghs_x", sock: "/s", binDir: "/b", gitHelper: "/b/git-credential-synap", ghConfigDir: "/c" };
92
+ it("puts the helpers first on PATH, gh on an empty config dir, and resets the credential helpers first", () => {
93
+ const e = githubRunEnv(g, "/usr/bin:/bin");
94
+ expect(e.PATH).toBe("/b:/usr/bin:/bin");
95
+ expect(e.GH_CONFIG_DIR).toBe("/c");
96
+ expect(e.SYNAP_GH_SOCK).toBe("/s");
97
+ expect(e.GH_TOKEN).toBe("ghs_x");
98
+ expect([e.GIT_CONFIG_KEY_0, e.GIT_CONFIG_VALUE_0]).toEqual(["credential.helper", ""]);
99
+ expect(e.GIT_CONFIG_VALUE_1).toBe("!/b/git-credential-synap");
100
+ });
101
+ });
102
+ //# sourceMappingURL=worker-github-app.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-github-app.test.js","sourceRoot":"","sources":["../../src/commands/worker-github-app.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,UAAU,GACX,MAAM,wBAAwB,CAAC;AAEhC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,mBAAmB,CAAC,KAAK,EAAE;IAC3D,aAAa,EAAE,IAAI;IACnB,kBAAkB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;IACpD,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE;CACnD,CAAC,CAAC;AAEH,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;AAEpE,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,oFAAoF,EAAE,GAAG,EAAE;QAC5F,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC/C,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QACjG,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EACrB,yEAAyE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,mCAAmC,EACjI,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;QACF,MAAM,GAAG,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACvD,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,yFAAyF,EAAE,GAAG,EAAE;QACjG,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,oBAAoB,CAAC,CAAC;QAC9D,MAAM,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;QAChG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,iDAAiD,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;QAC1H,MAAM,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,gDAAgD,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACpI,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,YAAY,CAAC,4BAA4B,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,CAAC,GAAa,EAAE,OAAuD,EAAE,EAAE,EAAE,CAC7F,CAAC,KAAK,EAAE,GAAW,EAAE,IAAyD,EAAE,EAAE;QAChF,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC;QAC9E,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC;QACtC,IAAI,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACvI,OAAO;YACL,EAAE;YACF,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,GAAG;YAC1B,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,IAAI,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;SACtG,CAAC;IACJ,CAAC,CAA4B,CAAC;IAEhC,EAAE,CAAC,wFAAwF,EAAE,KAAK,IAAI,EAAE;QACtG,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,6BAA6B,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACnH,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,wBAAwB,EAAE,yCAAyC,CAAC,CAAC,CAAC;QAC3F,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,GAAG,GAAG,6BAA6B,CACvC,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,EAC1B,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CACvF,CAAC;QACF,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,GAAG,GAAG,6BAA6B,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACzJ,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,yBAAyB,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAChH,EAAE,CAAC,oGAAoG,EAAE,GAAG,EAAE;QAC5G,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;QAC3C,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,CAAC;QACtF,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Keeps a long run on its GitHub identity.
3
+ *
4
+ * An installation token lasts 1 h; a static GH_TOKEN would stop working under
5
+ * a longer run. So a run's `gh` and `git` ask the WORKER for a token each time
6
+ * they need one, over an owner-only unix socket, and the worker mints or
7
+ * reuses one from the App key (worker-github-app.ts). The key stays in the
8
+ * worker process; a run is handed tokens, never the key, and its environment
9
+ * carries neither the key file's path nor a long-lived secret.
10
+ *
11
+ * <binDir>/gh `gh` with GH_TOKEN set to a fresh token
12
+ * <binDir>/git-credential-synap git credential helper (https pushes)
13
+ * <binDir>/synap-gh-token asks the broker, prints the token
14
+ *
15
+ * The runs' PATH starts with <binDir>. An absolute /usr/bin/gh still finds no
16
+ * login: the run's GH_CONFIG_DIR is an empty folder (the person's hosts.yml is
17
+ * never read).
18
+ */
19
+ import type { InstallationTokenSource } from "./worker-github-app.js";
20
+ export interface TokenBroker {
21
+ /** The unix socket a run's helpers connect to (SYNAP_GH_SOCK). */
22
+ sock: string;
23
+ ready: Promise<void>;
24
+ close(): void;
25
+ }
26
+ /** One request = one connection: the answer is `OK <token>\n` or `ERR <message>\n`. */
27
+ export declare function startTokenBroker(source: InstallationTokenSource): TokenBroker;
28
+ /** The first `name` on PATH that is not inside `exclude`. */
29
+ export declare function findOnPath(name: string, searchPath: string | undefined, exclude: string): string | undefined;
30
+ /**
31
+ * Writes the three helpers. `realGh`: the `gh` to call (undefined: no `gh`
32
+ * wrapper, the box has none). Returns the helper paths.
33
+ */
34
+ export declare function writeGithubHelpers(binDir: string, o?: {
35
+ realGh?: string;
36
+ nodeBin?: string;
37
+ }): {
38
+ gitHelper: string;
39
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Keeps a long run on its GitHub identity.
3
+ *
4
+ * An installation token lasts 1 h; a static GH_TOKEN would stop working under
5
+ * a longer run. So a run's `gh` and `git` ask the WORKER for a token each time
6
+ * they need one, over an owner-only unix socket, and the worker mints or
7
+ * reuses one from the App key (worker-github-app.ts). The key stays in the
8
+ * worker process; a run is handed tokens, never the key, and its environment
9
+ * carries neither the key file's path nor a long-lived secret.
10
+ *
11
+ * <binDir>/gh `gh` with GH_TOKEN set to a fresh token
12
+ * <binDir>/git-credential-synap git credential helper (https pushes)
13
+ * <binDir>/synap-gh-token asks the broker, prints the token
14
+ *
15
+ * The runs' PATH starts with <binDir>. An absolute /usr/bin/gh still finds no
16
+ * login: the run's GH_CONFIG_DIR is an empty folder (the person's hosts.yml is
17
+ * never read).
18
+ */
19
+ import fs from "node:fs";
20
+ import net from "node:net";
21
+ import os from "node:os";
22
+ import path from "node:path";
23
+ import { writeSecretFile } from "../lib/atomic-file.js";
24
+ /** One request = one connection: the answer is `OK <token>\n` or `ERR <message>\n`. */
25
+ export function startTokenBroker(source) {
26
+ // Under the tmp dir, not the state dir: a unix socket path is limited to ~104 bytes.
27
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "synap-gh-"));
28
+ fs.chmodSync(dir, 0o700);
29
+ const sock = path.join(dir, "s");
30
+ const server = net.createServer((conn) => {
31
+ source.get().then((t) => conn.end(`OK ${t}\n`), (err) => conn.end(`ERR ${err.message.replace(/\s+/g, " ")}\n`));
32
+ conn.on("error", () => conn.destroy());
33
+ });
34
+ server.unref(); // never what keeps the worker process alive
35
+ const ready = new Promise((resolve, reject) => {
36
+ server.once("error", reject);
37
+ server.listen(sock, () => {
38
+ fs.chmodSync(sock, 0o600);
39
+ resolve();
40
+ });
41
+ });
42
+ return {
43
+ sock,
44
+ ready,
45
+ close: () => {
46
+ server.close();
47
+ fs.rmSync(dir, { recursive: true, force: true });
48
+ },
49
+ };
50
+ }
51
+ const sh = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
52
+ /** The first `name` on PATH that is not inside `exclude`. */
53
+ export function findOnPath(name, searchPath, exclude) {
54
+ for (const d of (searchPath ?? "").split(path.delimiter)) {
55
+ if (!d || path.resolve(d) === path.resolve(exclude))
56
+ continue;
57
+ const f = path.join(d, name);
58
+ try {
59
+ fs.accessSync(f, fs.constants.X_OK);
60
+ if (fs.statSync(f).isFile())
61
+ return f;
62
+ }
63
+ catch {
64
+ /* not here */
65
+ }
66
+ }
67
+ return undefined;
68
+ }
69
+ /**
70
+ * Writes the three helpers. `realGh`: the `gh` to call (undefined: no `gh`
71
+ * wrapper, the box has none). Returns the helper paths.
72
+ */
73
+ export function writeGithubHelpers(binDir, o = {}) {
74
+ fs.mkdirSync(binDir, { recursive: true, mode: 0o700 });
75
+ const node = o.nodeBin ?? process.execPath;
76
+ const tokenBin = path.join(binDir, "synap-gh-token");
77
+ // writeSecretFile chmods after writing: a helper left by an earlier run is tightened too.
78
+ const put = (name, body) => writeSecretFile(path.join(binDir, name), body, { mode: 0o700 });
79
+ put("synap-gh-token", `#!${node}
80
+ const net = require("node:net");
81
+ const c = net.connect(process.env.SYNAP_GH_SOCK || "");
82
+ let buf = "";
83
+ c.on("data", (d) => (buf += d));
84
+ c.on("error", (e) => { console.error("synap-gh-token: " + e.message); process.exit(1); });
85
+ c.on("end", () => {
86
+ const m = /^OK (.+)\\n$/.exec(buf);
87
+ if (!m) { console.error("synap-gh-token: " + (buf.trim() || "no answer")); process.exit(1); }
88
+ process.stdout.write(m[1]);
89
+ });
90
+ `);
91
+ put("git-credential-synap", `#!/bin/sh
92
+ # git credential helper: only "get" answers; the token is asked for now, never stored.
93
+ [ "$1" = get ] || exit 0
94
+ cat >/dev/null
95
+ t=$(${sh(tokenBin)}) || exit 1
96
+ printf 'username=x-access-token\\npassword=%s\\n' "$t"
97
+ `);
98
+ if (o.realGh) {
99
+ put("gh", `#!/bin/sh
100
+ t=$(${sh(tokenBin)}) || exit 1
101
+ GH_TOKEN="$t" GITHUB_TOKEN="$t" exec ${sh(o.realGh)} "$@"
102
+ `);
103
+ }
104
+ return { gitHelper: path.join(binDir, "git-credential-synap") };
105
+ }
106
+ //# sourceMappingURL=worker-github-broker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-github-broker.js","sourceRoot":"","sources":["../../src/commands/worker-github-broker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,90 @@
1
+ import { execFile } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { generateKeyPairSync } from "node:crypto";
6
+ import { afterEach, describe, expect, it } from "vitest";
7
+ import { createInstallationTokenSource } from "./worker-github-app.js";
8
+ import { findOnPath, startTokenBroker, writeGithubHelpers } from "./worker-github-broker.js";
9
+ const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048, privateKeyEncoding: { type: "pkcs8", format: "pem" }, publicKeyEncoding: { type: "spki", format: "pem" } });
10
+ const T0 = Date.parse("2026-10-28T12:00:00Z");
11
+ let t = T0;
12
+ let minted = 0;
13
+ const fakeFetch = (async (url) => {
14
+ if (url.endsWith("/app/installations"))
15
+ return { ok: true, status: 200, json: async () => [{ id: 9 }] };
16
+ minted++;
17
+ return { ok: true, status: 200, json: async () => ({ token: `ghs_${minted}`, expires_at: new Date(t + 3600_000).toISOString() }) };
18
+ });
19
+ const run = (bin, args, env, input) => new Promise((resolve) => {
20
+ const c = execFile(bin, args, { env }, (err, stdout, stderr) => resolve({ code: err ? (err.code ?? 1) : 0, stdout, stderr }));
21
+ // A helper may exit without reading its stdin (the git helper on `store`):
22
+ // the write then fails with EPIPE, which is the child's choice, not a test failure.
23
+ c.stdin?.on("error", () => { });
24
+ c.stdin?.end(input ?? "");
25
+ });
26
+ const closers = [];
27
+ afterEach(() => closers.splice(0).forEach((c) => c()));
28
+ const setup = async (source = createInstallationTokenSource({ appId: "1", privateKey }, { fetchImpl: fakeFetch, now: () => t })) => {
29
+ t = T0;
30
+ minted = 0;
31
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "bk-"));
32
+ const realGh = path.join(dir, "real-gh");
33
+ fs.writeFileSync(realGh, '#!/bin/sh\necho "gh-as:$GH_TOKEN args:$*"\n', { mode: 0o755 });
34
+ const binDir = path.join(dir, "bin");
35
+ const { gitHelper } = writeGithubHelpers(binDir, { realGh });
36
+ const broker = startTokenBroker(source);
37
+ closers.push(() => broker.close());
38
+ await broker.ready;
39
+ const env = { SYNAP_GH_SOCK: broker.sock, PATH: `${binDir}:/usr/bin:/bin` };
40
+ return { binDir, gitHelper, env, broker };
41
+ };
42
+ describe("the GitHub helpers a run uses", () => {
43
+ it("gh runs with a token that is still valid after the first hour (the clock is stubbed)", async () => {
44
+ const { binDir, env } = await setup();
45
+ const first = await run(path.join(binDir, "gh"), ["pr", "list"], env);
46
+ expect(first.stdout.trim()).toBe("gh-as:ghs_1 args:pr list");
47
+ t = T0 + 30 * 60_000;
48
+ expect((await run(path.join(binDir, "gh"), [], env)).stdout).toContain("ghs_1"); // reused, not re-minted
49
+ t = T0 + 61 * 60_000; // past the first token's end
50
+ const later = await run(path.join(binDir, "gh"), ["pr", "list"], env);
51
+ expect(later.stdout.trim()).toBe("gh-as:ghs_2 args:pr list");
52
+ });
53
+ it("the git credential helper answers `get` with a fresh token and nothing for other operations", async () => {
54
+ const { gitHelper, env } = await setup();
55
+ const get = await run(gitHelper, ["get"], env, "protocol=https\nhost=github.com\n\n");
56
+ expect(get.stdout).toBe("username=x-access-token\npassword=ghs_1\n");
57
+ t = T0 + 61 * 60_000;
58
+ expect((await run(gitHelper, ["get"], env, "host=github.com\n\n")).stdout).toContain("password=ghs_2");
59
+ expect((await run(gitHelper, ["store"], env, "x\n")).stdout).toBe("");
60
+ });
61
+ it("a broker that cannot mint makes gh fail without calling the real gh (no fall back to another login)", async () => {
62
+ const { binDir, env } = await setup({
63
+ get: async () => {
64
+ throw new Error("GitHub answered 401");
65
+ },
66
+ });
67
+ const r = await run(path.join(binDir, "gh"), ["pr", "list"], env);
68
+ expect(r.code).not.toBe(0);
69
+ expect(r.stdout).not.toContain("gh-as");
70
+ expect(r.stderr).toContain("401");
71
+ });
72
+ it("the key is never handed over: neither helper nor answer carries it", async () => {
73
+ const { binDir, env } = await setup();
74
+ const out = (await run(path.join(binDir, "gh"), [], env)).stdout;
75
+ expect(out).not.toContain("PRIVATE KEY");
76
+ for (const f of fs.readdirSync(binDir))
77
+ expect(fs.readFileSync(path.join(binDir, f), "utf8")).not.toContain("PRIVATE KEY");
78
+ });
79
+ });
80
+ describe("findOnPath", () => {
81
+ it("skips the helpers' own folder, so the wrapper never calls itself", () => {
82
+ const a = fs.mkdtempSync(path.join(os.tmpdir(), "fp-"));
83
+ const b = fs.mkdtempSync(path.join(os.tmpdir(), "fp-"));
84
+ for (const d of [a, b])
85
+ fs.writeFileSync(path.join(d, "gh"), "#!/bin/sh\n", { mode: 0o755 });
86
+ expect(findOnPath("gh", `${a}:${b}`, a)).toBe(path.join(b, "gh"));
87
+ expect(findOnPath("nope", `${a}:${b}`, a)).toBeUndefined();
88
+ });
89
+ });
90
+ //# sourceMappingURL=worker-github-broker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-github-broker.test.js","sourceRoot":"","sources":["../../src/commands/worker-github-broker.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The worker end to end with a fake `claude`: each code run gets its own tree
3
+ * folder, runs as the App token, and finished runs' trees are swept.
4
+ */
5
+ import { execFileSync } from "node:child_process";
6
+ import fs from "node:fs";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
10
+ import { createWorker } from "./worker.js";
11
+ import { sweepTreeFolder } from "./worker-worktrees.js";
12
+ const git = (cwd, ...a) => execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", ...a], { cwd, encoding: "utf8" });
13
+ let home;
14
+ let oldHome;
15
+ let root;
16
+ let repo;
17
+ let out;
18
+ let claude;
19
+ const workers = [];
20
+ /** The nearest CLAUDE.md on the path above `dir` (Claude Code reads every CLAUDE.md on its cwd's parent path). */
21
+ const claudeMdOnParentPath = (dir) => {
22
+ for (let d = dir; d !== path.dirname(d); d = path.dirname(d))
23
+ if (fs.existsSync(path.join(d, "CLAUDE.md")))
24
+ return path.join(d, "CLAUDE.md");
25
+ return undefined;
26
+ };
27
+ const SIDS = {
28
+ aaaaaaaa: "aaaaaaaa-0000-4000-8000-000000000001", // clean worktree
29
+ bbbbbbbb: "bbbbbbbb-0000-4000-8000-000000000002", // uncommitted change
30
+ cccccccc: "cccccccc-0000-4000-8000-000000000003", // unpushed commit
31
+ };
32
+ beforeEach(() => {
33
+ oldHome = process.env.HOME;
34
+ home = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "wk-home-")));
35
+ process.env.HOME = home;
36
+ root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "wk-root-")));
37
+ out = path.join(root, "out");
38
+ fs.mkdirSync(out);
39
+ const origin = path.join(root, "origin.git");
40
+ git(root, "init", "-q", "--bare", origin);
41
+ repo = path.join(root, "code", "repo");
42
+ fs.mkdirSync(repo, { recursive: true });
43
+ fs.writeFileSync(path.join(root, "code", "CLAUDE.md"), "the safety rules");
44
+ git(repo, "init", "-q", "-b", "main");
45
+ fs.writeFileSync(path.join(repo, "f"), "1");
46
+ git(repo, "add", "f");
47
+ git(repo, "commit", "-q", "-m", "init");
48
+ git(repo, "remote", "add", "origin", origin);
49
+ git(repo, "push", "-q", "origin", "main");
50
+ git(repo, "fetch", "-q", "origin");
51
+ claude = path.join(root, "fake-claude.sh");
52
+ // $2 is the prompt; the run makes its worktree in its own cwd, as the prompt tells it.
53
+ fs.writeFileSync(claude, `#!/bin/sh
54
+ sid=$(printf '%s' "$2" | sed -n 's/^Session: \\(........\\).*/\\1/p')
55
+ echo "$sid|$PWD|$GH_TOKEN|$VERCEL_TOKEN" >> "${out}/runs.log"
56
+ git -C "${repo}" worktree add -q --no-track "$PWD/repo" -b "devbox/$sid-x" origin/main
57
+ case "$sid" in
58
+ bbbbbbbb) echo dirty > "$PWD/repo/dirty" ;;
59
+ cccccccc) echo c > "$PWD/repo/c" && git -C "$PWD/repo" add c && git -C "$PWD/repo" -c user.email=t@t -c user.name=t commit -q -m c ;;
60
+ esac
61
+ sleep 1
62
+ `, { mode: 0o755 });
63
+ });
64
+ afterEach(() => {
65
+ process.env.HOME = oldHome;
66
+ delete process.env.VERCEL_TOKEN;
67
+ for (const w of workers.splice(0))
68
+ w.stop();
69
+ });
70
+ const handed = (...ids) => JSON.stringify({ startHere: { pendingReview: { count: 0 }, handedToYou: { count: ids.length, items: ids.map((id) => ({ id, goal: "g" })) } } });
71
+ const sessionText = JSON.stringify({ session: { id: "s", goal: "g" }, outcomes: { status: "ok", outcomes: [] } });
72
+ const mk = (ids, deps = {}) => {
73
+ const lines = [];
74
+ const worker = createWorker({ agent: "w1-test", cwd: path.join(root, "code"), claudeBin: claude, maxParallel: "3" }, {
75
+ mcp: {
76
+ call: async (tool) => tool === "synap_orient" ? { isError: false, text: handed(...ids) } : { isError: false, text: sessionText },
77
+ },
78
+ podUrl: "http://pod.test",
79
+ apiKey: "k",
80
+ selfAgentId: "self",
81
+ say: (l) => lines.push(l),
82
+ ...deps,
83
+ });
84
+ workers.push(worker);
85
+ return { worker, lines };
86
+ };
87
+ describe("a code run gets a tree folder of its own", () => {
88
+ it("two runs at once work in different folders, as the App token; clean trees go, unpushed or dirty ones stay and are named", async () => {
89
+ process.env.VERCEL_TOKEN = "vercel-secret";
90
+ const { worker, lines } = mk(Object.values(SIDS), { githubToken: { get: async () => "ghs_app_token" } });
91
+ expect((await worker.tick()).launched).toBe(3);
92
+ await worker.waitForChildren();
93
+ const runs = fs.readFileSync(path.join(out, "runs.log"), "utf8").trim().split("\n").map((l) => l.split("|"));
94
+ const cwds = runs.map((r) => r[1]);
95
+ expect(new Set(cwds).size).toBe(3);
96
+ for (const [sid, cwd, token, vercel] of runs) {
97
+ expect(vercel).toBe(""); // the worker's own VERCEL_TOKEN never reaches a run
98
+ expect(cwd).toBe(path.join(root, "code", ".worker-trees", sid));
99
+ expect(claudeMdOnParentPath(cwd)).toBe(path.join(root, "code", "CLAUDE.md"));
100
+ expect(token).toBe("ghs_app_token");
101
+ }
102
+ const trees = path.join(root, "code", ".worker-trees");
103
+ expect(fs.existsSync(path.join(trees, "aaaaaaaa"))).toBe(false); // clean: removed with its folder
104
+ expect(fs.existsSync(path.join(trees, "bbbbbbbb", "repo", "dirty"))).toBe(true);
105
+ expect(fs.existsSync(path.join(trees, "cccccccc", "repo", "c"))).toBe(true);
106
+ expect(worker.state.runs[SIDS.aaaaaaaa].keptTrees).toBeUndefined();
107
+ expect(worker.state.runs[SIDS.bbbbbbbb].result).toMatch(/KEPT.*uncommitted changes/);
108
+ expect(worker.state.runs[SIDS.cccccccc].result).toMatch(/KEPT.*unpushed commits/);
109
+ expect(lines.join("\n")).not.toContain("ghs_app_token");
110
+ });
111
+ it("the same hand-off is not launched twice, before or after its tree is swept", async () => {
112
+ const { worker } = mk([SIDS.aaaaaaaa], { githubToken: { get: async () => "t" } });
113
+ expect((await worker.tick()).launched).toBe(1);
114
+ expect((await worker.tick()).launched).toBe(0); // still running
115
+ await worker.waitForChildren();
116
+ expect((await worker.tick()).launched).toBe(0); // done, tree swept
117
+ expect(fs.readFileSync(path.join(out, "runs.log"), "utf8").trim().split("\n")).toHaveLength(1);
118
+ });
119
+ it("without the App files it still runs, and says LOUDLY whose login it uses", async () => {
120
+ const { worker, lines } = mk([SIDS.aaaaaaaa]);
121
+ expect((await worker.tick()).launched).toBe(1);
122
+ await worker.waitForChildren();
123
+ expect(lines.filter((l) => l.includes("WARNING") && l.includes("environment's GitHub login"))).toHaveLength(1);
124
+ });
125
+ it("a failing App token does not launch the run, and does not record it (retried next tick)", async () => {
126
+ const { worker, lines } = mk([SIDS.aaaaaaaa], {
127
+ githubToken: {
128
+ get: async () => {
129
+ throw new Error("GitHub POST … answered 401");
130
+ },
131
+ },
132
+ });
133
+ expect((await worker.tick()).launched).toBe(1); // counted as attempted; nothing ran
134
+ expect(worker.state.runs[SIDS.aaaaaaaa]).toBeUndefined();
135
+ expect(fs.existsSync(path.join(out, "runs.log"))).toBe(false);
136
+ expect(lines.join("\n")).toMatch(/no GitHub App token.*not launching/);
137
+ });
138
+ });
139
+ describe("sweepTreeFolder", () => {
140
+ it("an unreadable status keeps the folder (never deletes on doubt)", async () => {
141
+ const folder = path.join(root, "tree");
142
+ fs.mkdirSync(path.join(folder, "repo"), { recursive: true });
143
+ fs.writeFileSync(path.join(folder, "repo", ".git"), "gitdir: nowhere");
144
+ const r = await sweepTreeFolder(folder, async () => ({ code: 128, stdout: "" }));
145
+ expect(r.removed).toEqual([]);
146
+ expect(r.kept[0].why).toBe("git status failed");
147
+ expect(fs.existsSync(path.join(folder, "repo"))).toBe(true);
148
+ });
149
+ });
150
+ //# sourceMappingURL=worker-runs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-runs.test.js","sourceRoot":"","sources":["../../src/commands/worker-runs.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,GAAG,CAAW,EAAE,EAAE,CAC1C,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAEtG,IAAI,IAAY,CAAC;AACjB,IAAI,OAA2B,CAAC;AAChC,IAAI,IAAY,CAAC;AACjB,IAAI,IAAY,CAAC;AACjB,IAAI,GAAW,CAAC;AAChB,IAAI,MAAc,CAAC;AACnB,MAAM,OAAO,GAA4B,EAAE,CAAC;AAE5C,kHAAkH;AAClH,MAAM,oBAAoB,GAAG,CAAC,GAAW,EAAsB,EAAE;IAC/D,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC7I,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG;IACX,QAAQ,EAAE,sCAAsC,EAAE,iBAAiB;IACnE,QAAQ,EAAE,sCAAsC,EAAE,qBAAqB;IACvE,QAAQ,EAAE,sCAAsC,EAAE,kBAAkB;CACrE,CAAC;AAEF,UAAU,CAAC,GAAG,EAAE;IACd,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAC3B,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3E,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IACxB,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3E,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC7C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAC3E,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACtB,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACnC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC3C,uFAAuF;IACvF,EAAE,CAAC,aAAa,CACd,MAAM,EACN;;+CAE2C,GAAG;UACxC,IAAI;;;;;;CAMb,EACG,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC;IAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,CAAC,GAAG,GAAa,EAAE,EAAE,CAClC,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAClJ,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAElH,MAAM,EAAE,GAAG,CAAC,GAAa,EAAE,OAAqD,EAAE,EAAE,EAAE;IACpF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,YAAY,CACzB,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,EACvF;QACE,GAAG,EAAE;YACH,IAAI,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE,CAC3B,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE;SAC7G;QACD,MAAM,EAAE,iBAAiB;QACzB,MAAM,EAAE,GAAG;QACX,WAAW,EAAE,MAAM;QACnB,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACzB,GAAG,IAAI;KACR,CACF,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC,CAAC;AAEF,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACxD,EAAE,CAAC,yHAAyH,EAAE,KAAK,IAAI,EAAE;QACvI,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,eAAe,CAAC;QAC3C,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;QACzG,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;QAE/B,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7G,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,oDAAoD;YAC7E,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC,CAAC;YAChE,MAAM,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;YAC7E,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;QACvD,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,iCAAiC;QAClG,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChF,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5E,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,aAAa,EAAE,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACrF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;QAClF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAClF,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB;QAChE,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;QAC/B,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB;QACnE,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACjG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACjH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yFAAyF,EAAE,KAAK,IAAI,EAAE;QACvG,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;YAC5C,WAAW,EAAE;gBACX,GAAG,EAAE,KAAK,IAAI,EAAE;oBACd,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBAChD,CAAC;aACF;SACF,CAAC,CAAC;QACH,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,oCAAoC;QACpF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACzD,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,CAAC,CAAC;QACvE,MAAM,CAAC,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACjF,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAChD,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * One tree folder per code run, so two runs at once never share a checkout
3
+ * (finding f94c0287: both worked in the one clone under `--cwd`).
4
+ *
5
+ * The worker's `--cwd` is usually a folder OF repos, not a repo, so the worker
6
+ * cannot create the worktree itself: it gives the run an empty folder
7
+ * (`<state>/trees/<short id>`) as its cwd, the prompt tells the run to make its
8
+ * worktrees there (`git -C <cwd>/<repo> worktree add <folder>/<repo> …`), and
9
+ * when the run ends the worker sweeps the folder: a worktree with no
10
+ * uncommitted change and no commit that is not on a remote is removed; any
11
+ * other is KEPT, and named in the run's result.
12
+ */
13
+ export interface GitResult {
14
+ code: number;
15
+ stdout: string;
16
+ }
17
+ export type GitFn = (cwd: string, args: string[]) => Promise<GitResult>;
18
+ export declare const defaultGit: GitFn;
19
+ export declare function treeFolderFor(treesRoot: string, sessionId: string): string;
20
+ /** The run's own folder under the trees root; created owner-only. */
21
+ export declare function ensureTreeFolder(folder: string): void;
22
+ export interface SweepResult {
23
+ removed: string[];
24
+ /** Worktrees left in place, each with why. */
25
+ kept: {
26
+ dir: string;
27
+ why: string;
28
+ }[];
29
+ }
30
+ /**
31
+ * Removes the run's clean worktrees. A failed check is a reason to KEEP, never
32
+ * "clean": an unreadable status must not delete work.
33
+ */
34
+ export declare function sweepTreeFolder(folder: string, git?: GitFn): Promise<SweepResult>;