@geminixiang/mikan 1.0.0-beta.13 → 1.0.0-beta.14

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 (275) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +10 -10
  3. package/dist/adapters/discord/bot.d.ts.map +1 -1
  4. package/dist/adapters/discord/bot.js +26 -67
  5. package/dist/adapters/discord/bot.js.map +1 -1
  6. package/dist/adapters/discord/context.d.ts.map +1 -1
  7. package/dist/adapters/discord/context.js +17 -43
  8. package/dist/adapters/discord/context.js.map +1 -1
  9. package/dist/adapters/github/bot.d.ts +69 -1
  10. package/dist/adapters/github/bot.d.ts.map +1 -1
  11. package/dist/adapters/github/bot.js +428 -57
  12. package/dist/adapters/github/bot.js.map +1 -1
  13. package/dist/adapters/github/client.d.ts +32 -1
  14. package/dist/adapters/github/client.d.ts.map +1 -1
  15. package/dist/adapters/github/client.js +79 -0
  16. package/dist/adapters/github/client.js.map +1 -1
  17. package/dist/adapters/github/cloudbuild.d.ts +24 -0
  18. package/dist/adapters/github/cloudbuild.d.ts.map +1 -0
  19. package/dist/adapters/github/cloudbuild.js +71 -0
  20. package/dist/adapters/github/cloudbuild.js.map +1 -0
  21. package/dist/adapters/github/context.d.ts.map +1 -1
  22. package/dist/adapters/github/context.js +34 -31
  23. package/dist/adapters/github/context.js.map +1 -1
  24. package/dist/adapters/github/gcp-auth.d.ts +31 -0
  25. package/dist/adapters/github/gcp-auth.d.ts.map +1 -0
  26. package/dist/adapters/github/gcp-auth.js +196 -0
  27. package/dist/adapters/github/gcp-auth.js.map +1 -0
  28. package/dist/adapters/github/ids.d.ts +9 -0
  29. package/dist/adapters/github/ids.d.ts.map +1 -1
  30. package/dist/adapters/github/ids.js +14 -0
  31. package/dist/adapters/github/ids.js.map +1 -1
  32. package/dist/adapters/github/repo.d.ts +13 -2
  33. package/dist/adapters/github/repo.d.ts.map +1 -1
  34. package/dist/adapters/github/repo.js +80 -2
  35. package/dist/adapters/github/repo.js.map +1 -1
  36. package/dist/adapters/github/tool-pack.d.ts +3 -1
  37. package/dist/adapters/github/tool-pack.d.ts.map +1 -1
  38. package/dist/adapters/github/tool-pack.js +30 -4
  39. package/dist/adapters/github/tool-pack.js.map +1 -1
  40. package/dist/{tools/github-checks.d.ts → adapters/github/tools/checks.d.ts} +4 -2
  41. package/dist/adapters/github/tools/checks.d.ts.map +1 -0
  42. package/dist/{tools/github-checks.js → adapters/github/tools/checks.js} +28 -11
  43. package/dist/adapters/github/tools/checks.js.map +1 -0
  44. package/dist/adapters/github/tools/issue.d.ts +22 -0
  45. package/dist/adapters/github/tools/issue.d.ts.map +1 -0
  46. package/dist/adapters/github/tools/issue.js +59 -0
  47. package/dist/adapters/github/tools/issue.js.map +1 -0
  48. package/dist/{tools/github-pr.d.ts → adapters/github/tools/pr.d.ts} +2 -2
  49. package/dist/adapters/github/tools/pr.d.ts.map +1 -0
  50. package/dist/{tools/github-pr.js → adapters/github/tools/pr.js} +8 -4
  51. package/dist/adapters/github/tools/pr.js.map +1 -0
  52. package/dist/adapters/github/tools/read.d.ts +22 -0
  53. package/dist/adapters/github/tools/read.d.ts.map +1 -0
  54. package/dist/adapters/github/tools/read.js +152 -0
  55. package/dist/adapters/github/tools/read.js.map +1 -0
  56. package/dist/adapters/github/tools/review-reply.d.ts +19 -0
  57. package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
  58. package/dist/adapters/github/tools/review-reply.js +49 -0
  59. package/dist/adapters/github/tools/review-reply.js.map +1 -0
  60. package/dist/adapters/github/tools/sync.d.ts +17 -0
  61. package/dist/adapters/github/tools/sync.d.ts.map +1 -0
  62. package/dist/adapters/github/tools/sync.js +45 -0
  63. package/dist/adapters/github/tools/sync.js.map +1 -0
  64. package/dist/adapters/github/types.d.ts +174 -1
  65. package/dist/adapters/github/types.d.ts.map +1 -1
  66. package/dist/adapters/github/types.js.map +1 -1
  67. package/dist/adapters/intake.d.ts +16 -5
  68. package/dist/adapters/intake.d.ts.map +1 -1
  69. package/dist/adapters/intake.js +79 -22
  70. package/dist/adapters/intake.js.map +1 -1
  71. package/dist/adapters/slack/bot.d.ts +7 -6
  72. package/dist/adapters/slack/bot.d.ts.map +1 -1
  73. package/dist/adapters/slack/bot.js +65 -135
  74. package/dist/adapters/slack/bot.js.map +1 -1
  75. package/dist/adapters/slack/context.d.ts.map +1 -1
  76. package/dist/adapters/slack/context.js +3 -15
  77. package/dist/adapters/slack/context.js.map +1 -1
  78. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  79. package/dist/adapters/slack/response-lifecycle.js +7 -16
  80. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  81. package/dist/adapters/slack/session.d.ts.map +1 -1
  82. package/dist/adapters/slack/session.js +5 -8
  83. package/dist/adapters/slack/session.js.map +1 -1
  84. package/dist/adapters/streaming.d.ts +4 -0
  85. package/dist/adapters/streaming.d.ts.map +1 -1
  86. package/dist/adapters/streaming.js +11 -0
  87. package/dist/adapters/streaming.js.map +1 -1
  88. package/dist/adapters/telegram/bot.d.ts +0 -2
  89. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  90. package/dist/adapters/telegram/bot.js +16 -62
  91. package/dist/adapters/telegram/bot.js.map +1 -1
  92. package/dist/adapters/telegram/context.d.ts.map +1 -1
  93. package/dist/adapters/telegram/context.js +8 -28
  94. package/dist/adapters/telegram/context.js.map +1 -1
  95. package/dist/adapters/types.d.ts +28 -2
  96. package/dist/adapters/types.d.ts.map +1 -1
  97. package/dist/adapters/types.js.map +1 -1
  98. package/dist/agent.d.ts +21 -4
  99. package/dist/agent.d.ts.map +1 -1
  100. package/dist/agent.js +22 -9
  101. package/dist/agent.js.map +1 -1
  102. package/dist/commands/admin.d.ts.map +1 -1
  103. package/dist/commands/admin.js +2 -1
  104. package/dist/commands/admin.js.map +1 -1
  105. package/dist/commands/auto-reply.d.ts.map +1 -1
  106. package/dist/commands/auto-reply.js +2 -6
  107. package/dist/commands/auto-reply.js.map +1 -1
  108. package/dist/commands/extensions.d.ts.map +1 -1
  109. package/dist/commands/extensions.js +2 -1
  110. package/dist/commands/extensions.js.map +1 -1
  111. package/dist/commands/login.d.ts +2 -1
  112. package/dist/commands/login.d.ts.map +1 -1
  113. package/dist/commands/login.js +30 -1
  114. package/dist/commands/login.js.map +1 -1
  115. package/dist/commands/manifest.d.ts +72 -0
  116. package/dist/commands/manifest.d.ts.map +1 -0
  117. package/dist/commands/manifest.js +121 -0
  118. package/dist/commands/manifest.js.map +1 -0
  119. package/dist/commands/model.d.ts.map +1 -1
  120. package/dist/commands/model.js +2 -1
  121. package/dist/commands/model.js.map +1 -1
  122. package/dist/commands/new.d.ts.map +1 -1
  123. package/dist/commands/new.js +2 -1
  124. package/dist/commands/new.js.map +1 -1
  125. package/dist/commands/sandbox.d.ts.map +1 -1
  126. package/dist/commands/sandbox.js +3 -2
  127. package/dist/commands/sandbox.js.map +1 -1
  128. package/dist/commands/text.d.ts +2 -0
  129. package/dist/commands/text.d.ts.map +1 -0
  130. package/dist/commands/text.js +16 -0
  131. package/dist/commands/text.js.map +1 -0
  132. package/dist/commands/types.d.ts +12 -1
  133. package/dist/commands/types.d.ts.map +1 -1
  134. package/dist/commands/types.js.map +1 -1
  135. package/dist/config.d.ts +1 -1
  136. package/dist/config.d.ts.map +1 -1
  137. package/dist/config.js +49 -54
  138. package/dist/config.js.map +1 -1
  139. package/dist/events.d.ts +10 -0
  140. package/dist/events.d.ts.map +1 -1
  141. package/dist/events.js +14 -5
  142. package/dist/events.js.map +1 -1
  143. package/dist/execution-resolver.d.ts.map +1 -1
  144. package/dist/execution-resolver.js +3 -3
  145. package/dist/execution-resolver.js.map +1 -1
  146. package/dist/index.d.ts +1 -1
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js.map +1 -1
  149. package/dist/main.js +25 -5
  150. package/dist/main.js.map +1 -1
  151. package/dist/provisioner.d.ts.map +1 -1
  152. package/dist/provisioner.js +2 -5
  153. package/dist/provisioner.js.map +1 -1
  154. package/dist/runtime/conversation-runtime.d.ts +1 -1
  155. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  156. package/dist/runtime/conversation-runtime.js +230 -37
  157. package/dist/runtime/conversation-runtime.js.map +1 -1
  158. package/dist/runtime/types.d.ts +19 -4
  159. package/dist/runtime/types.d.ts.map +1 -1
  160. package/dist/runtime/types.js.map +1 -1
  161. package/dist/sandbox/cloudflare.d.ts +2 -0
  162. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  163. package/dist/sandbox/cloudflare.js +7 -0
  164. package/dist/sandbox/cloudflare.js.map +1 -1
  165. package/dist/sandbox/container.d.ts +2 -0
  166. package/dist/sandbox/container.d.ts.map +1 -1
  167. package/dist/sandbox/container.js +7 -1
  168. package/dist/sandbox/container.js.map +1 -1
  169. package/dist/sandbox/firecracker.d.ts +2 -0
  170. package/dist/sandbox/firecracker.d.ts.map +1 -1
  171. package/dist/sandbox/firecracker.js +7 -1
  172. package/dist/sandbox/firecracker.js.map +1 -1
  173. package/dist/sandbox/host.d.ts +2 -0
  174. package/dist/sandbox/host.d.ts.map +1 -1
  175. package/dist/sandbox/host.js +11 -0
  176. package/dist/sandbox/host.js.map +1 -1
  177. package/dist/sandbox/identity.d.ts +21 -0
  178. package/dist/sandbox/identity.d.ts.map +1 -0
  179. package/dist/sandbox/identity.js +35 -0
  180. package/dist/sandbox/identity.js.map +1 -0
  181. package/dist/sandbox/types.d.ts +12 -0
  182. package/dist/sandbox/types.d.ts.map +1 -1
  183. package/dist/sandbox/types.js.map +1 -1
  184. package/dist/sandbox/utils.d.ts +11 -0
  185. package/dist/sandbox/utils.d.ts.map +1 -1
  186. package/dist/sandbox/utils.js +35 -0
  187. package/dist/sandbox/utils.js.map +1 -1
  188. package/dist/sessions/agent-memory-file-manager.d.ts.map +1 -1
  189. package/dist/sessions/agent-memory-file-manager.js +16 -19
  190. package/dist/sessions/agent-memory-file-manager.js.map +1 -1
  191. package/dist/sessions/history-line.d.ts +20 -0
  192. package/dist/sessions/history-line.d.ts.map +1 -0
  193. package/dist/sessions/history-line.js +24 -0
  194. package/dist/sessions/history-line.js.map +1 -0
  195. package/dist/sessions/policy.d.ts.map +1 -1
  196. package/dist/sessions/policy.js +4 -3
  197. package/dist/sessions/policy.js.map +1 -1
  198. package/dist/sessions/session-key.d.ts +31 -0
  199. package/dist/sessions/session-key.d.ts.map +1 -0
  200. package/dist/sessions/session-key.js +44 -0
  201. package/dist/sessions/session-key.js.map +1 -0
  202. package/dist/sessions/store.d.ts.map +1 -1
  203. package/dist/sessions/store.js +2 -2
  204. package/dist/sessions/store.js.map +1 -1
  205. package/dist/tools/bash.d.ts +3 -1
  206. package/dist/tools/bash.d.ts.map +1 -1
  207. package/dist/tools/bash.js +24 -17
  208. package/dist/tools/bash.js.map +1 -1
  209. package/dist/tools/edit.d.ts.map +1 -1
  210. package/dist/tools/edit.js +9 -12
  211. package/dist/tools/edit.js.map +1 -1
  212. package/dist/tools/event.d.ts +1 -1
  213. package/dist/tools/event.d.ts.map +1 -1
  214. package/dist/tools/event.js +33 -4
  215. package/dist/tools/event.js.map +1 -1
  216. package/dist/tools/index.js +1 -1
  217. package/dist/tools/index.js.map +1 -1
  218. package/dist/tools/types.d.ts +6 -1
  219. package/dist/tools/types.d.ts.map +1 -1
  220. package/dist/tools/types.js.map +1 -1
  221. package/dist/tools/write.d.ts.map +1 -1
  222. package/dist/tools/write.js +1 -8
  223. package/dist/tools/write.js.map +1 -1
  224. package/dist/types.d.ts +13 -6
  225. package/dist/types.d.ts.map +1 -1
  226. package/dist/types.js.map +1 -1
  227. package/dist/vault/disabled.d.ts +9 -0
  228. package/dist/vault/disabled.d.ts.map +1 -0
  229. package/dist/vault/disabled.js +23 -0
  230. package/dist/vault/disabled.js.map +1 -0
  231. package/dist/vault/index.d.ts.map +1 -1
  232. package/dist/vault/index.js +2 -7
  233. package/dist/vault/index.js.map +1 -1
  234. package/dist/vault/routing.d.ts +1 -0
  235. package/dist/vault/routing.d.ts.map +1 -1
  236. package/dist/vault/routing.js +3 -10
  237. package/dist/vault/routing.js.map +1 -1
  238. package/dist/web/admin/portal.d.ts +27 -0
  239. package/dist/web/admin/portal.d.ts.map +1 -1
  240. package/dist/web/admin/portal.js +87 -126
  241. package/dist/web/admin/portal.js.map +1 -1
  242. package/dist/web/admin/types.d.ts +3 -0
  243. package/dist/web/admin/types.d.ts.map +1 -1
  244. package/dist/web/admin/types.js.map +1 -1
  245. package/dist/web/login/oauth.d.ts +2 -3
  246. package/dist/web/login/oauth.d.ts.map +1 -1
  247. package/dist/web/login/oauth.js +0 -31
  248. package/dist/web/login/oauth.js.map +1 -1
  249. package/dist/web/login/types.d.ts +0 -11
  250. package/dist/web/login/types.d.ts.map +1 -1
  251. package/dist/web/login/types.js.map +1 -1
  252. package/dist/web/server.d.ts.map +1 -1
  253. package/dist/web/server.js +7 -0
  254. package/dist/web/server.js.map +1 -1
  255. package/dist/web/session-view/command.d.ts.map +1 -1
  256. package/dist/web/session-view/command.js +4 -1
  257. package/dist/web/session-view/command.js.map +1 -1
  258. package/dist/web/session-view/portal.d.ts.map +1 -1
  259. package/dist/web/session-view/portal.js +3 -2
  260. package/dist/web/session-view/portal.js.map +1 -1
  261. package/dist/web/session-view/service.d.ts.map +1 -1
  262. package/dist/web/session-view/service.js +2 -1
  263. package/dist/web/session-view/service.js.map +1 -1
  264. package/dist/web/session-view/types.d.ts +2 -1
  265. package/dist/web/session-view/types.d.ts.map +1 -1
  266. package/dist/web/session-view/types.js.map +1 -1
  267. package/package.json +1 -1
  268. package/dist/runtime/agent-run-controller.d.ts +0 -31
  269. package/dist/runtime/agent-run-controller.d.ts.map +0 -1
  270. package/dist/runtime/agent-run-controller.js +0 -195
  271. package/dist/runtime/agent-run-controller.js.map +0 -1
  272. package/dist/tools/github-checks.d.ts.map +0 -1
  273. package/dist/tools/github-checks.js.map +0 -1
  274. package/dist/tools/github-pr.d.ts.map +0 -1
  275. package/dist/tools/github-pr.js.map +0 -1
@@ -0,0 +1,196 @@
1
+ import { readFileSync } from "fs";
2
+ import { createSign } from "crypto";
3
+ /**
4
+ * Minimal Google Application Default Credentials provider — enough to mint
5
+ * cloud-platform access tokens on the host without pulling in
6
+ * google-auth-library (this repo hand-rolls its GitHub App JWTs the same way).
7
+ *
8
+ * Supported `GOOGLE_APPLICATION_CREDENTIALS` JSON shapes:
9
+ * - `external_account` (Workload Identity Federation) with a file or url
10
+ * credential source: STS token exchange, plus optional service-account
11
+ * impersonation. AWS/executable sources are rejected with a clear error.
12
+ * - `service_account` key: self-signed JWT assertion grant.
13
+ * - `authorized_user` (gcloud user ADC): refresh-token grant. Local dev.
14
+ */
15
+ const CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
16
+ const OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
17
+ /** Refresh cached tokens this long before expiry — STS/impersonation tokens
18
+ * live ~1h; minting per request would hammer STS in a CI-debug loop. */
19
+ const TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000;
20
+ function base64Url(data) {
21
+ return Buffer.from(data).toString("base64url");
22
+ }
23
+ export class GcpAuthError extends Error {
24
+ constructor(message, options) {
25
+ super(message, options);
26
+ this.name = "GcpAuthError";
27
+ }
28
+ }
29
+ export class GcpTokenProvider {
30
+ constructor(options) {
31
+ this.credentials = null;
32
+ this.cached = null;
33
+ this.inFlight = null;
34
+ this.credentialsPath = options.credentialsPath;
35
+ this.fetchImpl = options.fetchImpl ?? fetch;
36
+ }
37
+ /** A valid cloud-platform access token, cached until near expiry. */
38
+ async getAccessToken() {
39
+ if (this.cached && Date.now() < this.cached.expiresAt - TOKEN_REFRESH_MARGIN_MS) {
40
+ return this.cached.token;
41
+ }
42
+ // Single-flight: concurrent tool calls must not each hit STS.
43
+ this.inFlight ??= this.mintToken().finally(() => {
44
+ this.inFlight = null;
45
+ });
46
+ return this.inFlight;
47
+ }
48
+ loadCredentials() {
49
+ if (this.credentials)
50
+ return this.credentials;
51
+ let parsed;
52
+ try {
53
+ parsed = JSON.parse(readFileSync(this.credentialsPath, "utf-8"));
54
+ }
55
+ catch (err) {
56
+ throw new GcpAuthError(`Cannot read GCP credentials at ${this.credentialsPath}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
57
+ }
58
+ if (!["external_account", "service_account", "authorized_user"].includes(parsed.type)) {
59
+ throw new GcpAuthError(`Unsupported GCP credential type '${parsed.type}' — expected ` +
60
+ `external_account (WIF), service_account, or authorized_user.`);
61
+ }
62
+ this.credentials = parsed;
63
+ return parsed;
64
+ }
65
+ async mintToken() {
66
+ const credentials = this.loadCredentials();
67
+ let minted;
68
+ switch (credentials.type) {
69
+ case "external_account":
70
+ minted = await this.mintExternalAccount(credentials);
71
+ break;
72
+ case "service_account":
73
+ minted = await this.mintServiceAccount(credentials);
74
+ break;
75
+ case "authorized_user":
76
+ minted = await this.mintAuthorizedUser(credentials);
77
+ break;
78
+ }
79
+ this.cached = {
80
+ token: minted.token,
81
+ expiresAt: Date.now() + minted.expiresInSeconds * 1000,
82
+ };
83
+ return minted.token;
84
+ }
85
+ async postForm(url, form, context) {
86
+ const response = await this.fetchImpl(url, {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
89
+ body: new URLSearchParams(form).toString(),
90
+ });
91
+ if (!response.ok) {
92
+ const detail = (await response.text().catch(() => "")).slice(0, 300);
93
+ throw new GcpAuthError(`${context} failed with ${response.status}: ${detail}`);
94
+ }
95
+ return (await response.json());
96
+ }
97
+ /** WIF: read the subject token, exchange at STS, optionally impersonate. */
98
+ async mintExternalAccount(credentials) {
99
+ const subjectToken = await this.readSubjectToken(credentials);
100
+ const sts = await this.postForm(credentials.token_url, {
101
+ grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
102
+ audience: credentials.audience,
103
+ scope: CLOUD_PLATFORM_SCOPE,
104
+ requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
105
+ subject_token: subjectToken,
106
+ subject_token_type: credentials.subject_token_type,
107
+ }, "GCP STS token exchange");
108
+ const stsToken = sts.access_token;
109
+ const stsExpires = sts.expires_in ?? 3600;
110
+ if (!credentials.service_account_impersonation_url) {
111
+ return { token: stsToken, expiresInSeconds: stsExpires };
112
+ }
113
+ const response = await this.fetchImpl(credentials.service_account_impersonation_url, {
114
+ method: "POST",
115
+ headers: {
116
+ "Content-Type": "application/json",
117
+ Authorization: `Bearer ${stsToken}`,
118
+ },
119
+ body: JSON.stringify({ scope: [CLOUD_PLATFORM_SCOPE] }),
120
+ });
121
+ if (!response.ok) {
122
+ const detail = (await response.text().catch(() => "")).slice(0, 300);
123
+ throw new GcpAuthError(`GCP service-account impersonation failed with ${response.status}: ${detail}`);
124
+ }
125
+ const impersonated = (await response.json());
126
+ const expiresInSeconds = Math.max(0, Math.floor((Date.parse(impersonated.expireTime) - Date.now()) / 1000));
127
+ return { token: impersonated.accessToken, expiresInSeconds };
128
+ }
129
+ async readSubjectToken(credentials) {
130
+ const source = credentials.credential_source;
131
+ let raw;
132
+ if (source.file) {
133
+ raw = readFileSync(source.file, "utf-8").trim();
134
+ }
135
+ else if (source.url) {
136
+ const response = await this.fetchImpl(source.url, { headers: source.headers });
137
+ if (!response.ok) {
138
+ throw new GcpAuthError(`WIF credential_source url returned ${response.status} — cannot read subject token`);
139
+ }
140
+ raw = (await response.text()).trim();
141
+ }
142
+ else {
143
+ throw new GcpAuthError("Unsupported WIF credential_source: only file and url sources are supported " +
144
+ "(aws/executable sources are not).");
145
+ }
146
+ if (source.format?.type === "json") {
147
+ const field = source.format.subject_token_field_name ?? "token";
148
+ const parsed = JSON.parse(raw);
149
+ const token = parsed[field];
150
+ if (!token) {
151
+ throw new GcpAuthError(`WIF subject token field '${field}' missing from JSON source`);
152
+ }
153
+ return token;
154
+ }
155
+ return raw;
156
+ }
157
+ /** Service-account key: self-signed RS256 JWT → assertion grant. */
158
+ async mintServiceAccount(credentials) {
159
+ const tokenUrl = credentials.token_uri ?? OAUTH_TOKEN_URL;
160
+ const now = Math.floor(Date.now() / 1000);
161
+ const header = base64Url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
162
+ const payload = base64Url(JSON.stringify({
163
+ iss: credentials.client_email,
164
+ scope: CLOUD_PLATFORM_SCOPE,
165
+ aud: tokenUrl,
166
+ iat: now,
167
+ exp: now + 3600,
168
+ }));
169
+ const signature = createSign("RSA-SHA256")
170
+ .update(`${header}.${payload}`)
171
+ .sign(credentials.private_key)
172
+ .toString("base64url");
173
+ const data = await this.postForm(tokenUrl, {
174
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
175
+ assertion: `${header}.${payload}.${signature}`,
176
+ }, "GCP service-account token grant");
177
+ return {
178
+ token: data.access_token,
179
+ expiresInSeconds: data.expires_in ?? 3600,
180
+ };
181
+ }
182
+ /** gcloud user ADC: refresh-token grant. */
183
+ async mintAuthorizedUser(credentials) {
184
+ const data = await this.postForm(OAUTH_TOKEN_URL, {
185
+ grant_type: "refresh_token",
186
+ client_id: credentials.client_id,
187
+ client_secret: credentials.client_secret,
188
+ refresh_token: credentials.refresh_token,
189
+ }, "GCP user-credential refresh");
190
+ return {
191
+ token: data.access_token,
192
+ expiresInSeconds: data.expires_in ?? 3600,
193
+ };
194
+ }
195
+ }
196
+ //# sourceMappingURL=gcp-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gcp-auth.js","sourceRoot":"","sources":["../../../src/adapters/github/gcp-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"fs\";\nimport { createSign } from \"crypto\";\n\n/**\n * Minimal Google Application Default Credentials provider — enough to mint\n * cloud-platform access tokens on the host without pulling in\n * google-auth-library (this repo hand-rolls its GitHub App JWTs the same way).\n *\n * Supported `GOOGLE_APPLICATION_CREDENTIALS` JSON shapes:\n * - `external_account` (Workload Identity Federation) with a file or url\n * credential source: STS token exchange, plus optional service-account\n * impersonation. AWS/executable sources are rejected with a clear error.\n * - `service_account` key: self-signed JWT assertion grant.\n * - `authorized_user` (gcloud user ADC): refresh-token grant. Local dev.\n */\n\nconst CLOUD_PLATFORM_SCOPE = \"https://www.googleapis.com/auth/cloud-platform\";\nconst OAUTH_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\n\n/** Refresh cached tokens this long before expiry — STS/impersonation tokens\n * live ~1h; minting per request would hammer STS in a CI-debug loop. */\nconst TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000;\n\ninterface ExternalAccountCredentials {\n type: \"external_account\";\n audience: string;\n subject_token_type: string;\n token_url: string;\n service_account_impersonation_url?: string;\n credential_source: {\n file?: string;\n url?: string;\n headers?: Record<string, string>;\n format?: { type?: string; subject_token_field_name?: string };\n };\n}\n\ninterface ServiceAccountCredentials {\n type: \"service_account\";\n client_email: string;\n private_key: string;\n token_uri?: string;\n}\n\ninterface AuthorizedUserCredentials {\n type: \"authorized_user\";\n client_id: string;\n client_secret: string;\n refresh_token: string;\n}\n\ntype AdcCredentials =\n | ExternalAccountCredentials\n | ServiceAccountCredentials\n | AuthorizedUserCredentials;\n\nexport interface GcpTokenProviderOptions {\n /** Path to the ADC JSON (usually $GOOGLE_APPLICATION_CREDENTIALS). */\n credentialsPath: string;\n fetchImpl?: typeof fetch;\n}\n\nfunction base64Url(data: string): string {\n return Buffer.from(data).toString(\"base64url\");\n}\n\nexport class GcpAuthError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"GcpAuthError\";\n }\n}\n\nexport class GcpTokenProvider {\n private readonly credentialsPath: string;\n private readonly fetchImpl: typeof fetch;\n private credentials: AdcCredentials | null = null;\n private cached: { token: string; expiresAt: number } | null = null;\n private inFlight: Promise<string> | null = null;\n\n constructor(options: GcpTokenProviderOptions) {\n this.credentialsPath = options.credentialsPath;\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n /** A valid cloud-platform access token, cached until near expiry. */\n async getAccessToken(): Promise<string> {\n if (this.cached && Date.now() < this.cached.expiresAt - TOKEN_REFRESH_MARGIN_MS) {\n return this.cached.token;\n }\n // Single-flight: concurrent tool calls must not each hit STS.\n this.inFlight ??= this.mintToken().finally(() => {\n this.inFlight = null;\n });\n return this.inFlight;\n }\n\n private loadCredentials(): AdcCredentials {\n if (this.credentials) return this.credentials;\n let parsed: AdcCredentials;\n try {\n parsed = JSON.parse(readFileSync(this.credentialsPath, \"utf-8\")) as AdcCredentials;\n } catch (err) {\n throw new GcpAuthError(\n `Cannot read GCP credentials at ${this.credentialsPath}: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err },\n );\n }\n if (![\"external_account\", \"service_account\", \"authorized_user\"].includes(parsed.type)) {\n throw new GcpAuthError(\n `Unsupported GCP credential type '${(parsed as { type?: string }).type}' — expected ` +\n `external_account (WIF), service_account, or authorized_user.`,\n );\n }\n this.credentials = parsed;\n return parsed;\n }\n\n private async mintToken(): Promise<string> {\n const credentials = this.loadCredentials();\n let minted: { token: string; expiresInSeconds: number };\n switch (credentials.type) {\n case \"external_account\":\n minted = await this.mintExternalAccount(credentials);\n break;\n case \"service_account\":\n minted = await this.mintServiceAccount(credentials);\n break;\n case \"authorized_user\":\n minted = await this.mintAuthorizedUser(credentials);\n break;\n }\n this.cached = {\n token: minted.token,\n expiresAt: Date.now() + minted.expiresInSeconds * 1000,\n };\n return minted.token;\n }\n\n private async postForm(\n url: string,\n form: Record<string, string>,\n context: string,\n ): Promise<Record<string, unknown>> {\n const response = await this.fetchImpl(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(form).toString(),\n });\n if (!response.ok) {\n const detail = (await response.text().catch(() => \"\")).slice(0, 300);\n throw new GcpAuthError(`${context} failed with ${response.status}: ${detail}`);\n }\n return (await response.json()) as Record<string, unknown>;\n }\n\n /** WIF: read the subject token, exchange at STS, optionally impersonate. */\n private async mintExternalAccount(\n credentials: ExternalAccountCredentials,\n ): Promise<{ token: string; expiresInSeconds: number }> {\n const subjectToken = await this.readSubjectToken(credentials);\n const sts = await this.postForm(\n credentials.token_url,\n {\n grant_type: \"urn:ietf:params:oauth:grant-type:token-exchange\",\n audience: credentials.audience,\n scope: CLOUD_PLATFORM_SCOPE,\n requested_token_type: \"urn:ietf:params:oauth:token-type:access_token\",\n subject_token: subjectToken,\n subject_token_type: credentials.subject_token_type,\n },\n \"GCP STS token exchange\",\n );\n const stsToken = sts.access_token as string;\n const stsExpires = (sts.expires_in as number | undefined) ?? 3600;\n if (!credentials.service_account_impersonation_url) {\n return { token: stsToken, expiresInSeconds: stsExpires };\n }\n\n const response = await this.fetchImpl(credentials.service_account_impersonation_url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${stsToken}`,\n },\n body: JSON.stringify({ scope: [CLOUD_PLATFORM_SCOPE] }),\n });\n if (!response.ok) {\n const detail = (await response.text().catch(() => \"\")).slice(0, 300);\n throw new GcpAuthError(\n `GCP service-account impersonation failed with ${response.status}: ${detail}`,\n );\n }\n const impersonated = (await response.json()) as { accessToken: string; expireTime: string };\n const expiresInSeconds = Math.max(\n 0,\n Math.floor((Date.parse(impersonated.expireTime) - Date.now()) / 1000),\n );\n return { token: impersonated.accessToken, expiresInSeconds };\n }\n\n private async readSubjectToken(credentials: ExternalAccountCredentials): Promise<string> {\n const source = credentials.credential_source;\n let raw: string;\n if (source.file) {\n raw = readFileSync(source.file, \"utf-8\").trim();\n } else if (source.url) {\n const response = await this.fetchImpl(source.url, { headers: source.headers });\n if (!response.ok) {\n throw new GcpAuthError(\n `WIF credential_source url returned ${response.status} — cannot read subject token`,\n );\n }\n raw = (await response.text()).trim();\n } else {\n throw new GcpAuthError(\n \"Unsupported WIF credential_source: only file and url sources are supported \" +\n \"(aws/executable sources are not).\",\n );\n }\n if (source.format?.type === \"json\") {\n const field = source.format.subject_token_field_name ?? \"token\";\n const parsed = JSON.parse(raw) as Record<string, string>;\n const token = parsed[field];\n if (!token) {\n throw new GcpAuthError(`WIF subject token field '${field}' missing from JSON source`);\n }\n return token;\n }\n return raw;\n }\n\n /** Service-account key: self-signed RS256 JWT → assertion grant. */\n private async mintServiceAccount(\n credentials: ServiceAccountCredentials,\n ): Promise<{ token: string; expiresInSeconds: number }> {\n const tokenUrl = credentials.token_uri ?? OAUTH_TOKEN_URL;\n const now = Math.floor(Date.now() / 1000);\n const header = base64Url(JSON.stringify({ alg: \"RS256\", typ: \"JWT\" }));\n const payload = base64Url(\n JSON.stringify({\n iss: credentials.client_email,\n scope: CLOUD_PLATFORM_SCOPE,\n aud: tokenUrl,\n iat: now,\n exp: now + 3600,\n }),\n );\n const signature = createSign(\"RSA-SHA256\")\n .update(`${header}.${payload}`)\n .sign(credentials.private_key)\n .toString(\"base64url\");\n const data = await this.postForm(\n tokenUrl,\n {\n grant_type: \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n assertion: `${header}.${payload}.${signature}`,\n },\n \"GCP service-account token grant\",\n );\n return {\n token: data.access_token as string,\n expiresInSeconds: (data.expires_in as number | undefined) ?? 3600,\n };\n }\n\n /** gcloud user ADC: refresh-token grant. */\n private async mintAuthorizedUser(\n credentials: AuthorizedUserCredentials,\n ): Promise<{ token: string; expiresInSeconds: number }> {\n const data = await this.postForm(\n OAUTH_TOKEN_URL,\n {\n grant_type: \"refresh_token\",\n client_id: credentials.client_id,\n client_secret: credentials.client_secret,\n refresh_token: credentials.refresh_token,\n },\n \"GCP user-credential refresh\",\n );\n return {\n token: data.access_token as string,\n expiresInSeconds: (data.expires_in as number | undefined) ?? 3600,\n };\n }\n}\n"]}
@@ -22,4 +22,13 @@ export declare function buildGithubConversationId(ref: GithubConversationRef): s
22
22
  export declare function parseGithubConversationId(conversationId: string): GithubConversationRef;
23
23
  /** Message ts used for the issue/PR body itself (comments use their comment id). */
24
24
  export declare const GITHUB_ISSUE_BODY_TS = "issue";
25
+ /**
26
+ * Message ts for an inline PR review comment: `rc-<id>`. Review-comment ids
27
+ * live in a different id space than issue-comment ids, and several endpoints
28
+ * (reactions, edits) differ by family — the prefix makes routing a review id
29
+ * to an issue-comment endpoint structurally impossible.
30
+ */
31
+ export declare function githubReviewCommentTs(commentId: number): string;
32
+ /** The review-comment id inside an `rc-<id>` ts, or null for other ts kinds. */
33
+ export declare function parseReviewCommentTs(ts: string): number | null;
25
34
  //# sourceMappingURL=ids.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/ids.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAE5E;AAID,wBAAgB,yBAAyB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB,CAYvF;AAED,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,UAAU,CAAC"}
1
+ {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/ids.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAE5E;AAID,wBAAgB,yBAAyB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB,CAYvF;AAED,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D"}
@@ -36,4 +36,18 @@ export function parseGithubConversationId(conversationId) {
36
36
  }
37
37
  /** Message ts used for the issue/PR body itself (comments use their comment id). */
38
38
  export const GITHUB_ISSUE_BODY_TS = "issue";
39
+ /**
40
+ * Message ts for an inline PR review comment: `rc-<id>`. Review-comment ids
41
+ * live in a different id space than issue-comment ids, and several endpoints
42
+ * (reactions, edits) differ by family — the prefix makes routing a review id
43
+ * to an issue-comment endpoint structurally impossible.
44
+ */
45
+ export function githubReviewCommentTs(commentId) {
46
+ return `rc-${commentId}`;
47
+ }
48
+ /** The review-comment id inside an `rc-<id>` ts, or null for other ts kinds. */
49
+ export function parseReviewCommentTs(ts) {
50
+ const match = /^rc-(\d+)$/.exec(ts);
51
+ return match ? Number(match[1]) : null;
52
+ }
39
53
  //# sourceMappingURL=ids.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ids.js","sourceRoot":"","sources":["../../../src/adapters/github/ids.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAA0B;IAClE,OAAO,MAAM,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,uBAAuB,GAAG,iCAAiC,CAAC;AAElE,MAAM,UAAU,yBAAyB,CAAC,cAAsB;IAC9D,0EAA0E;IAC1E,0EAA0E;IAC1E,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,iCAAiC,cAAc,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC7B,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC5B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACzB,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC","sourcesContent":["import type { GithubConversationRef } from \"./types.js\";\n\n/**\n * Conversation id for one issue/PR: `GH_<owner>_<repo>_<number>`.\n *\n * DESIGN.md originally proposed `gh:<owner>/<repo>#<number>`, but conversation\n * ids are used verbatim as a single path segment (LAYOUT.md § Casing) and the\n * conversation dir is bind-mounted with docker's `-v source:target` syntax in\n * image mode — so the id can contain neither `/` nor `:`. `_` is the\n * separator because it is the only inert character that also parses back\n * unambiguously under GitHub's name grammar: owners allow `-` (so `-` would\n * make the owner/repo boundary ambiguous) but never `_`, repos may contain\n * `_` but the trailing number is pure digits — so the first `_` and the last\n * `_` before the digits are always the real boundaries.\n *\n * Owner and repo are lowercased: GitHub names are case-insensitive (and this\n * id has two spelling sources — the GITHUB_REPOS env var and API payloads),\n * so per LAYOUT.md § Casing this is a mikan-derived slug, not a verbatim\n * platform id, and derived slugs are lowercased to keep one issue from\n * mapping to two conversation dirs on case-sensitive filesystems.\n */\nexport function buildGithubConversationId(ref: GithubConversationRef): string {\n return `GH_${ref.owner.toLowerCase()}_${ref.repo.toLowerCase()}_${ref.number}`;\n}\n\nconst CONVERSATION_ID_PATTERN = /^GH_([A-Za-z0-9-]+)_(.+)_(\\d+)$/;\n\nexport function parseGithubConversationId(conversationId: string): GithubConversationRef {\n // The greedy repo group splits at the last `_` followed by digits-to-end,\n // which is always the number boundary (repos may contain `_` themselves).\n const match = CONVERSATION_ID_PATTERN.exec(conversationId);\n if (!match) {\n throw new Error(`Not a GitHub conversation id: ${conversationId}`);\n }\n return {\n owner: match[1].toLowerCase(),\n repo: match[2].toLowerCase(),\n number: Number(match[3]),\n };\n}\n\n/** Message ts used for the issue/PR body itself (comments use their comment id). */\nexport const GITHUB_ISSUE_BODY_TS = \"issue\";\n"]}
1
+ {"version":3,"file":"ids.js","sourceRoot":"","sources":["../../../src/adapters/github/ids.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAA0B;IAClE,OAAO,MAAM,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,uBAAuB,GAAG,iCAAiC,CAAC;AAElE,MAAM,UAAU,yBAAyB,CAAC,cAAsB;IAC9D,0EAA0E;IAC1E,0EAA0E;IAC1E,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,iCAAiC,cAAc,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC7B,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC5B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACzB,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB;IACrD,OAAO,MAAM,SAAS,EAAE,CAAC;AAC3B,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,oBAAoB,CAAC,EAAU;IAC7C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpC,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC","sourcesContent":["import type { GithubConversationRef } from \"./types.js\";\n\n/**\n * Conversation id for one issue/PR: `GH_<owner>_<repo>_<number>`.\n *\n * DESIGN.md originally proposed `gh:<owner>/<repo>#<number>`, but conversation\n * ids are used verbatim as a single path segment (LAYOUT.md § Casing) and the\n * conversation dir is bind-mounted with docker's `-v source:target` syntax in\n * image mode — so the id can contain neither `/` nor `:`. `_` is the\n * separator because it is the only inert character that also parses back\n * unambiguously under GitHub's name grammar: owners allow `-` (so `-` would\n * make the owner/repo boundary ambiguous) but never `_`, repos may contain\n * `_` but the trailing number is pure digits — so the first `_` and the last\n * `_` before the digits are always the real boundaries.\n *\n * Owner and repo are lowercased: GitHub names are case-insensitive (and this\n * id has two spelling sources — the GITHUB_REPOS env var and API payloads),\n * so per LAYOUT.md § Casing this is a mikan-derived slug, not a verbatim\n * platform id, and derived slugs are lowercased to keep one issue from\n * mapping to two conversation dirs on case-sensitive filesystems.\n */\nexport function buildGithubConversationId(ref: GithubConversationRef): string {\n return `GH_${ref.owner.toLowerCase()}_${ref.repo.toLowerCase()}_${ref.number}`;\n}\n\nconst CONVERSATION_ID_PATTERN = /^GH_([A-Za-z0-9-]+)_(.+)_(\\d+)$/;\n\nexport function parseGithubConversationId(conversationId: string): GithubConversationRef {\n // The greedy repo group splits at the last `_` followed by digits-to-end,\n // which is always the number boundary (repos may contain `_` themselves).\n const match = CONVERSATION_ID_PATTERN.exec(conversationId);\n if (!match) {\n throw new Error(`Not a GitHub conversation id: ${conversationId}`);\n }\n return {\n owner: match[1].toLowerCase(),\n repo: match[2].toLowerCase(),\n number: Number(match[3]),\n };\n}\n\n/** Message ts used for the issue/PR body itself (comments use their comment id). */\nexport const GITHUB_ISSUE_BODY_TS = \"issue\";\n\n/**\n * Message ts for an inline PR review comment: `rc-<id>`. Review-comment ids\n * live in a different id space than issue-comment ids, and several endpoints\n * (reactions, edits) differ by family — the prefix makes routing a review id\n * to an issue-comment endpoint structurally impossible.\n */\nexport function githubReviewCommentTs(commentId: number): string {\n return `rc-${commentId}`;\n}\n\n/** The review-comment id inside an `rc-<id>` ts, or null for other ts kinds. */\nexport function parseReviewCommentTs(ts: string): number | null {\n const match = /^rc-(\\d+)$/.exec(ts);\n return match ? Number(match[1]) : null;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { CloneRepoOptions, PushBranchOptions } from "./types.js";
1
+ import type { CloneRepoOptions, PushBranchOptions, SyncRepoOptions, SyncRepoResult } from "./types.js";
2
2
  /**
3
3
  * Branches the bot may push: `pi/<name>`. Everything else — including the
4
4
  * repo's default branch by construction — is refused, so a driven agent can
@@ -8,9 +8,20 @@ export declare const GITHUB_PUSH_BRANCH_PATTERN: RegExp;
8
8
  /**
9
9
  * Shallow-clone a repo into a conversation dir and preconfigure the bot's
10
10
  * commit identity. For PR conversations, additionally fetch the PR head and
11
- * check it out as `pr-<number>` so the agent starts on the code under review.
11
+ * check it out so the agent starts on the code under review — under its real
12
+ * branch name when the caller resolved one, which is what lets the agent push
13
+ * fixes back to the PR itself.
12
14
  */
13
15
  export declare function cloneRepo(options: CloneRepoOptions): Promise<void>;
16
+ /**
17
+ * Refresh a conversation clone from origin. Always fetches; only moves the
18
+ * checkout when that provably cannot lose agent work: the target branch is
19
+ * the one checked out, the tree is clean, and it has no commits of its own
20
+ * (`checkout -B` then handles fast-forward and force-pushed heads alike).
21
+ * Anything else stays fetch-only — FETCH_HEAD is left for the agent, which
22
+ * has full credential-less git inside the sandbox, to merge or rebase itself.
23
+ */
24
+ export declare function syncRepo(options: SyncRepoOptions): Promise<SyncRepoResult>;
14
25
  /**
15
26
  * Push one local branch to origin. Non-force by construction (plain refspec),
16
27
  * and only `pi/*` branches are accepted.
@@ -1 +1 @@
1
- {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAMtE;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAwC,CAAC;AAoBhF;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxE;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1E"}
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACf,MAAM,YAAY,CAAC;AAMpB;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAwC,CAAC;AA0ChF;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBxE;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA8ChF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1E"}
@@ -24,10 +24,30 @@ async function git(args, token) {
24
24
  });
25
25
  return stdout;
26
26
  }
27
+ /**
28
+ * Git itself rejects these ref shapes; the check just keeps an API-supplied
29
+ * branch name that would parse as a git option (leading dash) or an invalid
30
+ * ref out of argv entirely instead of surfacing as a git error mid-clone.
31
+ */
32
+ function isSafeBranchName(name) {
33
+ return (name.length > 0 && !name.startsWith("-") && !name.includes("..") && !/[\s~^:?*[\\]/.test(name));
34
+ }
35
+ /**
36
+ * Local checkout name for a PR conversation: the PR's real head branch when
37
+ * known (same-repo heads only — the caller resolves that), falling back to
38
+ * the synthetic `pr-<n>` for fork PRs, unresolvable heads, and unsafe names.
39
+ * Using the real name is what lets the agent update the PR in place: commit
40
+ * on a `pi/*` head and `github_pr` pushes back to the same branch.
41
+ */
42
+ function prCheckoutName(prNumber, prHeadBranch) {
43
+ return prHeadBranch && isSafeBranchName(prHeadBranch) ? prHeadBranch : `pr-${prNumber}`;
44
+ }
27
45
  /**
28
46
  * Shallow-clone a repo into a conversation dir and preconfigure the bot's
29
47
  * commit identity. For PR conversations, additionally fetch the PR head and
30
- * check it out as `pr-<number>` so the agent starts on the code under review.
48
+ * check it out so the agent starts on the code under review — under its real
49
+ * branch name when the caller resolved one, which is what lets the agent push
50
+ * fixes back to the PR itself.
31
51
  */
32
52
  export async function cloneRepo(options) {
33
53
  await git(["clone", "--depth", "50", options.url, options.dir], options.token);
@@ -35,8 +55,66 @@ export async function cloneRepo(options) {
35
55
  await git(["-C", options.dir, "config", "user.email", options.botEmail]);
36
56
  if (options.prNumber !== undefined) {
37
57
  await git(["-C", options.dir, "fetch", "--depth", "50", "origin", `pull/${options.prNumber}/head`], options.token);
38
- await git(["-C", options.dir, "checkout", "-B", `pr-${options.prNumber}`, "FETCH_HEAD"]);
58
+ await git([
59
+ "-C",
60
+ options.dir,
61
+ "checkout",
62
+ "-B",
63
+ prCheckoutName(options.prNumber, options.prHeadBranch),
64
+ "FETCH_HEAD",
65
+ ]);
66
+ }
67
+ }
68
+ /**
69
+ * Refresh a conversation clone from origin. Always fetches; only moves the
70
+ * checkout when that provably cannot lose agent work: the target branch is
71
+ * the one checked out, the tree is clean, and it has no commits of its own
72
+ * (`checkout -B` then handles fast-forward and force-pushed heads alike).
73
+ * Anything else stays fetch-only — FETCH_HEAD is left for the agent, which
74
+ * has full credential-less git inside the sandbox, to merge or rebase itself.
75
+ */
76
+ export async function syncRepo(options) {
77
+ const { dir, token } = options;
78
+ let refspec;
79
+ let target;
80
+ if (options.branch) {
81
+ refspec = options.branch;
82
+ target = options.branch;
83
+ }
84
+ else if (options.prNumber !== undefined) {
85
+ refspec = `pull/${options.prNumber}/head`;
86
+ target = prCheckoutName(options.prNumber, options.prHeadBranch);
87
+ }
88
+ else if (options.defaultBranch) {
89
+ refspec = options.defaultBranch;
90
+ target = options.defaultBranch;
91
+ }
92
+ else {
93
+ throw new Error("syncRepo needs a branch, a PR number, or the default branch");
94
+ }
95
+ await git(["-C", dir, "fetch", "--depth", "50", "origin", refspec], token);
96
+ const fetchedSha = (await git(["-C", dir, "rev-parse", "FETCH_HEAD"])).trim();
97
+ const dirty = (await git(["-C", dir, "status", "--porcelain"])).trim().length > 0;
98
+ const currentBranch = (await git(["-C", dir, "rev-parse", "--abbrev-ref", "HEAD"])).trim();
99
+ // Clones made before the head branch name was resolvable sit on the
100
+ // synthetic pr-<n>; treat that as the same checkout so a clean sync
101
+ // migrates them to the real name instead of going fetch-only forever.
102
+ const legacyPrBranch = options.prNumber !== undefined ? `pr-${options.prNumber}` : undefined;
103
+ const onTarget = currentBranch === target || (legacyPrBranch !== undefined && currentBranch === legacyPrBranch);
104
+ let localCommits = 0;
105
+ if (onTarget) {
106
+ // Commits unique to HEAD split two ways: ones the agent committed here
107
+ // (the clone's preconfigured bot identity) are its work and must survive;
108
+ // ones committed by others were force-pushed away upstream and are safe
109
+ // to drop. Filter by the bot committer so a rebased PR head still syncs.
110
+ const botEmail = (await git(["-C", dir, "config", "user.email"])).trim();
111
+ localCommits = Number((await git(["-C", dir, "rev-list", "--count", `--committer=${botEmail}`, "FETCH_HEAD..HEAD"])).trim());
112
+ }
113
+ const updatedCheckout = onTarget && !dirty && localCommits === 0;
114
+ if (updatedCheckout) {
115
+ await git(["-C", dir, "checkout", "-B", target, "FETCH_HEAD"]);
39
116
  }
117
+ return { target, fetchedSha, updatedCheckout, dirty, currentBranch, localCommits };
40
118
  }
41
119
  /**
42
120
  * Push one local branch to origin. Non-force by construction (plain refspec),
@@ -1 +1 @@
1
- {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAGjC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,cAAc,GAAG,OAAO,CAAC;AAE/B;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,qCAAqC,CAAC;AAEhF;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACxE,OAAO,CAAC,IAAI,EAAE,yCAAyC,KAAK,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,KAAK,UAAU,GAAG,CAAC,IAAc,EAAE,KAAc;IAC/C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE;QAC7F,OAAO,EAAE,cAAc;QACvB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,GAAG,EAAE;KAClD,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAyB;IACvD,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/E,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzE,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,GAAG,CACP,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,OAAO,CAAC,QAAQ,OAAO,CAAC,EACxF,OAAO,CAAC,KAAK,CACd,CAAC;QACF,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;IAC3F,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAA0B;IACzD,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACvC,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CACb,4BAA4B,MAAM,oDAAoD,CACvF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,MAAM,EAAE,CAAC,CAAC,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,WAAW,MAAM,sDAAsD,CAAC,CAAC;IAC3F,CAAC;IACD,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,MAAM,eAAe,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;AAC/F,CAAC","sourcesContent":["import { execFile } from \"child_process\";\nimport { promisify } from \"util\";\nimport type { CloneRepoOptions, PushBranchOptions } from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst GIT_TIMEOUT_MS = 120_000;\n\n/**\n * Branches the bot may push: `pi/<name>`. Everything else — including the\n * repo's default branch by construction — is refused, so a driven agent can\n * only ever produce reviewable branches, never touch mainline directly.\n */\nexport const GITHUB_PUSH_BRANCH_PATTERN = /^pi\\/[A-Za-z0-9._][A-Za-z0-9._/-]*$/;\n\n/**\n * Pass the ephemeral token as a per-invocation header, never in the remote\n * URL: nothing credential-shaped may end up in `.git/config`, which lives in\n * the conversation dir and is bind-mounted into the sandbox.\n */\nfunction gitAuthArgs(token: string): string[] {\n const basic = Buffer.from(`x-access-token:${token}`).toString(\"base64\");\n return [\"-c\", `http.extraheader=Authorization: basic ${basic}`];\n}\n\nasync function git(args: string[], token?: string): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", [...(token ? gitAuthArgs(token) : []), ...args], {\n timeout: GIT_TIMEOUT_MS,\n env: { ...process.env, GIT_TERMINAL_PROMPT: \"0\" },\n });\n return stdout;\n}\n\n/**\n * Shallow-clone a repo into a conversation dir and preconfigure the bot's\n * commit identity. For PR conversations, additionally fetch the PR head and\n * check it out as `pr-<number>` so the agent starts on the code under review.\n */\nexport async function cloneRepo(options: CloneRepoOptions): Promise<void> {\n await git([\"clone\", \"--depth\", \"50\", options.url, options.dir], options.token);\n await git([\"-C\", options.dir, \"config\", \"user.name\", options.botLogin]);\n await git([\"-C\", options.dir, \"config\", \"user.email\", options.botEmail]);\n if (options.prNumber !== undefined) {\n await git(\n [\"-C\", options.dir, \"fetch\", \"--depth\", \"50\", \"origin\", `pull/${options.prNumber}/head`],\n options.token,\n );\n await git([\"-C\", options.dir, \"checkout\", \"-B\", `pr-${options.prNumber}`, \"FETCH_HEAD\"]);\n }\n}\n\n/**\n * Push one local branch to origin. Non-force by construction (plain refspec),\n * and only `pi/*` branches are accepted.\n */\nexport async function pushBranch(options: PushBranchOptions): Promise<void> {\n const { dir, branch, token } = options;\n if (!GITHUB_PUSH_BRANCH_PATTERN.test(branch)) {\n throw new Error(\n `Refusing to push branch '${branch}': only branches matching pi/<name> may be pushed.`,\n );\n }\n try {\n await git([\"-C\", dir, \"rev-parse\", \"--verify\", \"--quiet\", `refs/heads/${branch}`]);\n } catch {\n throw new Error(`Branch '${branch}' does not exist in the conversation's ./repo clone.`);\n }\n await git([\"-C\", dir, \"push\", \"origin\", `refs/heads/${branch}:refs/heads/${branch}`], token);\n}\n"]}
1
+ {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"child_process\";\nimport { promisify } from \"util\";\nimport type {\n CloneRepoOptions,\n PushBranchOptions,\n SyncRepoOptions,\n SyncRepoResult,\n} from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst GIT_TIMEOUT_MS = 120_000;\n\n/**\n * Branches the bot may push: `pi/<name>`. Everything else — including the\n * repo's default branch by construction — is refused, so a driven agent can\n * only ever produce reviewable branches, never touch mainline directly.\n */\nexport const GITHUB_PUSH_BRANCH_PATTERN = /^pi\\/[A-Za-z0-9._][A-Za-z0-9._/-]*$/;\n\n/**\n * Pass the ephemeral token as a per-invocation header, never in the remote\n * URL: nothing credential-shaped may end up in `.git/config`, which lives in\n * the conversation dir and is bind-mounted into the sandbox.\n */\nfunction gitAuthArgs(token: string): string[] {\n const basic = Buffer.from(`x-access-token:${token}`).toString(\"base64\");\n return [\"-c\", `http.extraheader=Authorization: basic ${basic}`];\n}\n\nasync function git(args: string[], token?: string): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", [...(token ? gitAuthArgs(token) : []), ...args], {\n timeout: GIT_TIMEOUT_MS,\n env: { ...process.env, GIT_TERMINAL_PROMPT: \"0\" },\n });\n return stdout;\n}\n\n/**\n * Git itself rejects these ref shapes; the check just keeps an API-supplied\n * branch name that would parse as a git option (leading dash) or an invalid\n * ref out of argv entirely instead of surfacing as a git error mid-clone.\n */\nfunction isSafeBranchName(name: string): boolean {\n return (\n name.length > 0 && !name.startsWith(\"-\") && !name.includes(\"..\") && !/[\\s~^:?*[\\\\]/.test(name)\n );\n}\n\n/**\n * Local checkout name for a PR conversation: the PR's real head branch when\n * known (same-repo heads only — the caller resolves that), falling back to\n * the synthetic `pr-<n>` for fork PRs, unresolvable heads, and unsafe names.\n * Using the real name is what lets the agent update the PR in place: commit\n * on a `pi/*` head and `github_pr` pushes back to the same branch.\n */\nfunction prCheckoutName(prNumber: number, prHeadBranch?: string): string {\n return prHeadBranch && isSafeBranchName(prHeadBranch) ? prHeadBranch : `pr-${prNumber}`;\n}\n\n/**\n * Shallow-clone a repo into a conversation dir and preconfigure the bot's\n * commit identity. For PR conversations, additionally fetch the PR head and\n * check it out so the agent starts on the code under review — under its real\n * branch name when the caller resolved one, which is what lets the agent push\n * fixes back to the PR itself.\n */\nexport async function cloneRepo(options: CloneRepoOptions): Promise<void> {\n await git([\"clone\", \"--depth\", \"50\", options.url, options.dir], options.token);\n await git([\"-C\", options.dir, \"config\", \"user.name\", options.botLogin]);\n await git([\"-C\", options.dir, \"config\", \"user.email\", options.botEmail]);\n if (options.prNumber !== undefined) {\n await git(\n [\"-C\", options.dir, \"fetch\", \"--depth\", \"50\", \"origin\", `pull/${options.prNumber}/head`],\n options.token,\n );\n await git([\n \"-C\",\n options.dir,\n \"checkout\",\n \"-B\",\n prCheckoutName(options.prNumber, options.prHeadBranch),\n \"FETCH_HEAD\",\n ]);\n }\n}\n\n/**\n * Refresh a conversation clone from origin. Always fetches; only moves the\n * checkout when that provably cannot lose agent work: the target branch is\n * the one checked out, the tree is clean, and it has no commits of its own\n * (`checkout -B` then handles fast-forward and force-pushed heads alike).\n * Anything else stays fetch-only — FETCH_HEAD is left for the agent, which\n * has full credential-less git inside the sandbox, to merge or rebase itself.\n */\nexport async function syncRepo(options: SyncRepoOptions): Promise<SyncRepoResult> {\n const { dir, token } = options;\n let refspec: string;\n let target: string;\n if (options.branch) {\n refspec = options.branch;\n target = options.branch;\n } else if (options.prNumber !== undefined) {\n refspec = `pull/${options.prNumber}/head`;\n target = prCheckoutName(options.prNumber, options.prHeadBranch);\n } else if (options.defaultBranch) {\n refspec = options.defaultBranch;\n target = options.defaultBranch;\n } else {\n throw new Error(\"syncRepo needs a branch, a PR number, or the default branch\");\n }\n\n await git([\"-C\", dir, \"fetch\", \"--depth\", \"50\", \"origin\", refspec], token);\n const fetchedSha = (await git([\"-C\", dir, \"rev-parse\", \"FETCH_HEAD\"])).trim();\n const dirty = (await git([\"-C\", dir, \"status\", \"--porcelain\"])).trim().length > 0;\n const currentBranch = (await git([\"-C\", dir, \"rev-parse\", \"--abbrev-ref\", \"HEAD\"])).trim();\n // Clones made before the head branch name was resolvable sit on the\n // synthetic pr-<n>; treat that as the same checkout so a clean sync\n // migrates them to the real name instead of going fetch-only forever.\n const legacyPrBranch = options.prNumber !== undefined ? `pr-${options.prNumber}` : undefined;\n const onTarget =\n currentBranch === target || (legacyPrBranch !== undefined && currentBranch === legacyPrBranch);\n let localCommits = 0;\n if (onTarget) {\n // Commits unique to HEAD split two ways: ones the agent committed here\n // (the clone's preconfigured bot identity) are its work and must survive;\n // ones committed by others were force-pushed away upstream and are safe\n // to drop. Filter by the bot committer so a rebased PR head still syncs.\n const botEmail = (await git([\"-C\", dir, \"config\", \"user.email\"])).trim();\n localCommits = Number(\n (\n await git([\"-C\", dir, \"rev-list\", \"--count\", `--committer=${botEmail}`, \"FETCH_HEAD..HEAD\"])\n ).trim(),\n );\n }\n\n const updatedCheckout = onTarget && !dirty && localCommits === 0;\n if (updatedCheckout) {\n await git([\"-C\", dir, \"checkout\", \"-B\", target, \"FETCH_HEAD\"]);\n }\n return { target, fetchedSha, updatedCheckout, dirty, currentBranch, localCommits };\n}\n\n/**\n * Push one local branch to origin. Non-force by construction (plain refspec),\n * and only `pi/*` branches are accepted.\n */\nexport async function pushBranch(options: PushBranchOptions): Promise<void> {\n const { dir, branch, token } = options;\n if (!GITHUB_PUSH_BRANCH_PATTERN.test(branch)) {\n throw new Error(\n `Refusing to push branch '${branch}': only branches matching pi/<name> may be pushed.`,\n );\n }\n try {\n await git([\"-C\", dir, \"rev-parse\", \"--verify\", \"--quiet\", `refs/heads/${branch}`]);\n } catch {\n throw new Error(`Branch '${branch}' does not exist in the conversation's ./repo clone.`);\n }\n await git([\"-C\", dir, \"push\", \"origin\", `refs/heads/${branch}:refs/heads/${branch}`], token);\n}\n"]}
@@ -1,7 +1,9 @@
1
1
  import type { PlatformToolPack } from "../../tools/types.js";
2
2
  import type { PlatformGithubOps } from "./types.js";
3
3
  /**
4
- * GitHub host-side capability pack: github_pr + github_checks.
4
+ * GitHub host-side capability pack — one tool per module under `tools/`:
5
+ * github_pr, github_checks, github_review_reply, github_sync, github_read,
6
+ * github_issue.
5
7
  *
6
8
  * These tools never run in the sandbox; they call PlatformGithubOps which
7
9
  * mints short-lived installation tokens on the host. The pack is only
@@ -1 +1 @@
1
- {"version":3,"file":"tool-pack.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/tool-pack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,iBAAiB,GAAG,gBAAgB,CAmB7E"}
1
+ {"version":3,"file":"tool-pack.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/tool-pack.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,iBAAiB,GAAG,gBAAgB,CA0C7E"}
@@ -1,7 +1,13 @@
1
- import { createGithubChecksTool } from "../../tools/github-checks.js";
2
- import { createGithubPrTool } from "../../tools/github-pr.js";
1
+ import { createGithubChecksTool } from "./tools/checks.js";
2
+ import { createGithubPrTool } from "./tools/pr.js";
3
+ import { createGithubIssueTool } from "./tools/issue.js";
4
+ import { createGithubReadTool } from "./tools/read.js";
5
+ import { createGithubReviewReplyTool } from "./tools/review-reply.js";
6
+ import { createGithubSyncTool } from "./tools/sync.js";
3
7
  /**
4
- * GitHub host-side capability pack: github_pr + github_checks.
8
+ * GitHub host-side capability pack — one tool per module under `tools/`:
9
+ * github_pr, github_checks, github_review_reply, github_sync, github_read,
10
+ * github_issue.
5
11
  *
6
12
  * These tools never run in the sandbox; they call PlatformGithubOps which
7
13
  * mints short-lived installation tokens on the host. The pack is only
@@ -11,19 +17,39 @@ import { createGithubPrTool } from "../../tools/github-pr.js";
11
17
  export function createGithubToolPack(ops) {
12
18
  const { tool: githubPrTool, setGithubPrFunction } = createGithubPrTool();
13
19
  const { tool: githubChecksTool, setGithubChecksFunction } = createGithubChecksTool();
20
+ const { tool: githubReviewReplyTool, setGithubReviewReplyFunction } = createGithubReviewReplyTool();
21
+ const { tool: githubSyncTool, setGithubSyncFunction } = createGithubSyncTool();
22
+ const { tool: githubReadTool, setGithubReadFunction } = createGithubReadTool();
23
+ const { tool: githubIssueTool, setGithubIssueFunction } = createGithubIssueTool();
14
24
  return {
15
- tools: [githubPrTool, githubChecksTool],
25
+ tools: [
26
+ githubPrTool,
27
+ githubChecksTool,
28
+ githubReviewReplyTool,
29
+ githubSyncTool,
30
+ githubReadTool,
31
+ githubIssueTool,
32
+ ],
16
33
  bindRun({ conversationId, platformName }) {
17
34
  if (platformName !== "github") {
18
35
  setGithubPrFunction(null);
19
36
  setGithubChecksFunction(null);
37
+ setGithubReviewReplyFunction(null);
38
+ setGithubSyncFunction(null);
39
+ setGithubReadFunction(null);
40
+ setGithubIssueFunction(null);
20
41
  return;
21
42
  }
22
43
  setGithubPrFunction((request) => ops.pushAndCreatePr(conversationId, request));
23
44
  setGithubChecksFunction({
24
45
  getChecks: (branch) => ops.getChecks(conversationId, branch),
25
46
  getJobLog: (jobId) => ops.getJobLog(conversationId, jobId),
47
+ getBuildLog: (buildId) => ops.getBuildLog(conversationId, buildId),
26
48
  });
49
+ setGithubReviewReplyFunction((commentId, body) => ops.replyToReviewThread(conversationId, commentId, body));
50
+ setGithubSyncFunction((branch) => ops.syncRepo(conversationId, branch));
51
+ setGithubReadFunction((request) => ops.readGithub(conversationId, request));
52
+ setGithubIssueFunction((request) => ops.manageIssue(conversationId, request));
27
53
  },
28
54
  };
29
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tool-pack.js","sourceRoot":"","sources":["../../../src/adapters/github/tool-pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAI9D;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAsB;IACzD,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,mBAAmB,EAAE,GAAG,kBAAkB,EAAE,CAAC;IACzE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAErF,OAAO;QACL,KAAK,EAAE,CAAC,YAAY,EAAE,gBAAgB,CAAC;QACvC,OAAO,CAAC,EAAE,cAAc,EAAE,YAAY,EAAE;YACtC,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;gBAC9B,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBAC1B,uBAAuB,CAAC,IAAI,CAAC,CAAC;gBAC9B,OAAO;YACT,CAAC;YACD,mBAAmB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;YAC/E,uBAAuB,CAAC;gBACtB,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC;gBAC5D,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC;aAC3D,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { createGithubChecksTool } from \"../../tools/github-checks.js\";\nimport { createGithubPrTool } from \"../../tools/github-pr.js\";\nimport type { PlatformToolPack } from \"../../tools/types.js\";\nimport type { PlatformGithubOps } from \"./types.js\";\n\n/**\n * GitHub host-side capability pack: github_pr + github_checks.\n *\n * These tools never run in the sandbox; they call PlatformGithubOps which\n * mints short-lived installation tokens on the host. The pack is only\n * constructed when the GitHub bot is configured; bindRun enables the tools\n * only for github-named conversations so multi-platform processes stay safe.\n */\nexport function createGithubToolPack(ops: PlatformGithubOps): PlatformToolPack {\n const { tool: githubPrTool, setGithubPrFunction } = createGithubPrTool();\n const { tool: githubChecksTool, setGithubChecksFunction } = createGithubChecksTool();\n\n return {\n tools: [githubPrTool, githubChecksTool],\n bindRun({ conversationId, platformName }) {\n if (platformName !== \"github\") {\n setGithubPrFunction(null);\n setGithubChecksFunction(null);\n return;\n }\n setGithubPrFunction((request) => ops.pushAndCreatePr(conversationId, request));\n setGithubChecksFunction({\n getChecks: (branch) => ops.getChecks(conversationId, branch),\n getJobLog: (jobId) => ops.getJobLog(conversationId, jobId),\n });\n },\n };\n}\n"]}
1
+ {"version":3,"file":"tool-pack.js","sourceRoot":"","sources":["../../../src/adapters/github/tool-pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAIvD;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAsB;IACzD,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,mBAAmB,EAAE,GAAG,kBAAkB,EAAE,CAAC;IACzE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,GAAG,sBAAsB,EAAE,CAAC;IACrF,MAAM,EAAE,IAAI,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,GACjE,2BAA2B,EAAE,CAAC;IAChC,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,qBAAqB,EAAE,GAAG,oBAAoB,EAAE,CAAC;IAC/E,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,qBAAqB,EAAE,GAAG,oBAAoB,EAAE,CAAC;IAC/E,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,sBAAsB,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAElF,OAAO;QACL,KAAK,EAAE;YACL,YAAY;YACZ,gBAAgB;YAChB,qBAAqB;YACrB,cAAc;YACd,cAAc;YACd,eAAe;SAChB;QACD,OAAO,CAAC,EAAE,cAAc,EAAE,YAAY,EAAE;YACtC,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;gBAC9B,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBAC1B,uBAAuB,CAAC,IAAI,CAAC,CAAC;gBAC9B,4BAA4B,CAAC,IAAI,CAAC,CAAC;gBACnC,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBAC5B,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBAC5B,sBAAsB,CAAC,IAAI,CAAC,CAAC;gBAC7B,OAAO;YACT,CAAC;YACD,mBAAmB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;YAC/E,uBAAuB,CAAC;gBACtB,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC;gBAC5D,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC;gBAC1D,WAAW,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC;aACnE,CAAC,CAAC;YACH,4BAA4B,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CAC/C,GAAG,CAAC,mBAAmB,CAAC,cAAc,EAAE,SAAS,EAAE,IAAI,CAAC,CACzD,CAAC;YACF,qBAAqB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;YACxE,qBAAqB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;YAC5E,sBAAsB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;QAChF,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { createGithubChecksTool } from \"./tools/checks.js\";\nimport { createGithubPrTool } from \"./tools/pr.js\";\nimport { createGithubIssueTool } from \"./tools/issue.js\";\nimport { createGithubReadTool } from \"./tools/read.js\";\nimport { createGithubReviewReplyTool } from \"./tools/review-reply.js\";\nimport { createGithubSyncTool } from \"./tools/sync.js\";\nimport type { PlatformToolPack } from \"../../tools/types.js\";\nimport type { PlatformGithubOps } from \"./types.js\";\n\n/**\n * GitHub host-side capability pack — one tool per module under `tools/`:\n * github_pr, github_checks, github_review_reply, github_sync, github_read,\n * github_issue.\n *\n * These tools never run in the sandbox; they call PlatformGithubOps which\n * mints short-lived installation tokens on the host. The pack is only\n * constructed when the GitHub bot is configured; bindRun enables the tools\n * only for github-named conversations so multi-platform processes stay safe.\n */\nexport function createGithubToolPack(ops: PlatformGithubOps): PlatformToolPack {\n const { tool: githubPrTool, setGithubPrFunction } = createGithubPrTool();\n const { tool: githubChecksTool, setGithubChecksFunction } = createGithubChecksTool();\n const { tool: githubReviewReplyTool, setGithubReviewReplyFunction } =\n createGithubReviewReplyTool();\n const { tool: githubSyncTool, setGithubSyncFunction } = createGithubSyncTool();\n const { tool: githubReadTool, setGithubReadFunction } = createGithubReadTool();\n const { tool: githubIssueTool, setGithubIssueFunction } = createGithubIssueTool();\n\n return {\n tools: [\n githubPrTool,\n githubChecksTool,\n githubReviewReplyTool,\n githubSyncTool,\n githubReadTool,\n githubIssueTool,\n ],\n bindRun({ conversationId, platformName }) {\n if (platformName !== \"github\") {\n setGithubPrFunction(null);\n setGithubChecksFunction(null);\n setGithubReviewReplyFunction(null);\n setGithubSyncFunction(null);\n setGithubReadFunction(null);\n setGithubIssueFunction(null);\n return;\n }\n setGithubPrFunction((request) => ops.pushAndCreatePr(conversationId, request));\n setGithubChecksFunction({\n getChecks: (branch) => ops.getChecks(conversationId, branch),\n getJobLog: (jobId) => ops.getJobLog(conversationId, jobId),\n getBuildLog: (buildId) => ops.getBuildLog(conversationId, buildId),\n });\n setGithubReviewReplyFunction((commentId, body) =>\n ops.replyToReviewThread(conversationId, commentId, body),\n );\n setGithubSyncFunction((branch) => ops.syncRepo(conversationId, branch));\n setGithubReadFunction((request) => ops.readGithub(conversationId, request));\n setGithubIssueFunction((request) => ops.manageIssue(conversationId, request));\n },\n };\n}\n"]}
@@ -1,12 +1,14 @@
1
1
  import type { AgentTool } from "@earendil-works/pi-agent-core";
2
- import type { GithubCheckSummary } from "../adapters/github/types.js";
2
+ import type { GithubCheckSummary } from "../types.js";
3
3
  declare const githubChecksSchema: import("@sinclair/typebox").TObject<{
4
4
  branch: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
5
5
  job_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
6
+ build_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
6
7
  }>;
7
8
  export interface GithubChecksFns {
8
9
  getChecks: (branch?: string) => Promise<GithubCheckSummary[]>;
9
10
  getJobLog: (jobId: number) => Promise<string>;
11
+ getBuildLog: (buildId: string) => Promise<string>;
10
12
  }
11
13
  /**
12
14
  * The `github_checks` tool reads CI check runs (GitHub Actions and other
@@ -19,4 +21,4 @@ export declare function createGithubChecksTool(): {
19
21
  setGithubChecksFunction: (fns: GithubChecksFns | null) => void;
20
22
  };
21
23
  export {};
22
- //# sourceMappingURL=github-checks.d.ts.map
24
+ //# sourceMappingURL=checks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../../../src/adapters/github/tools/checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,QAAA,MAAM,kBAAkB;;;;EAsBtB,CAAC;AA+BH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC9D,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACnD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,IAAI,EAAE,SAAS,CAAC,OAAO,kBAAkB,CAAC,CAAC;IAC3C,uBAAuB,EAAE,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;CAChE,CAgFA"}