toolroll 0.9.65 → 0.9.68

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 (264) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +20 -2
  3. package/dist/acp-client.js +1 -3
  4. package/dist/agent-board.d.ts +0 -1
  5. package/dist/agent-board.js +0 -1
  6. package/dist/agent-fence.d.ts +69 -2
  7. package/dist/agent-fence.js +294 -11
  8. package/dist/agentconfig.js +4 -1
  9. package/dist/batch-checks.js +3 -2
  10. package/dist/browser/THIRD_PARTY_NOTICES.txt +46 -0
  11. package/dist/browser/terminal.js +134 -23
  12. package/dist/browser/workspace.css +2 -2
  13. package/dist/browser/workspace.js +31 -29
  14. package/dist/builder.d.ts +4 -0
  15. package/dist/builder.js +56 -37
  16. package/dist/canonical-json.d.ts +14 -0
  17. package/dist/canonical-json.js +36 -0
  18. package/dist/chat-actions.js +1 -2
  19. package/dist/chat-admission.d.ts +19 -0
  20. package/dist/chat-admission.js +22 -0
  21. package/dist/chat-channel.js +1 -1
  22. package/dist/chat-delivery.js +0 -1
  23. package/dist/chat-follow.d.ts +17 -0
  24. package/dist/chat-follow.js +32 -0
  25. package/dist/chat-messages.js +0 -1
  26. package/dist/chat-settings-page.d.ts +23 -0
  27. package/dist/chat-settings-page.js +41 -0
  28. package/dist/claim.js +3 -6
  29. package/dist/claude-print.d.ts +12 -0
  30. package/dist/claude-print.js +34 -0
  31. package/dist/cli-http.d.ts +0 -7
  32. package/dist/cli-http.js +0 -21
  33. package/dist/cli.d.ts +3 -10
  34. package/dist/cli.js +15 -24
  35. package/dist/coding-provider.js +2 -1
  36. package/dist/coding-workspace.js +1 -1
  37. package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
  38. package/dist/connect-cli.js +331 -0
  39. package/dist/containment.d.ts +0 -2
  40. package/dist/containment.js +2 -6
  41. package/dist/contracts/chat-actions.d.ts +0 -1
  42. package/dist/contracts/chat-content.d.ts +0 -4
  43. package/dist/contracts/chat-content.js +0 -2
  44. package/dist/contracts/chat-tables.d.ts +0 -1
  45. package/dist/contracts/cli.d.ts +0 -62
  46. package/dist/contracts/cli.js +0 -4
  47. package/dist/contracts/coding-workspace.js +1 -1
  48. package/dist/contracts/console-api.d.ts +1 -0
  49. package/dist/contracts/console-api.js +1 -0
  50. package/dist/contracts/contract.js +1 -1
  51. package/dist/contracts/flow-card.d.ts +0 -1
  52. package/dist/contracts/flow-propose.d.ts +0 -1
  53. package/dist/contracts/flow-send.js +1 -1
  54. package/dist/contracts/gateway-tools.d.ts +0 -1
  55. package/dist/contracts/integration-metadata.js +1 -1
  56. package/dist/contracts/lead-tools.d.ts +0 -1
  57. package/dist/contracts/memory-pass.d.ts +0 -1
  58. package/dist/contracts/memory-pass.js +1 -1
  59. package/dist/contracts/plan.js +1 -1
  60. package/dist/contracts/project-knowledge.js +1 -1
  61. package/dist/contracts/project-skills.d.ts +0 -1
  62. package/dist/contracts/proof.js +1 -1
  63. package/dist/contracts/recipes.js +1 -1
  64. package/dist/contracts/scope.js +1 -1
  65. package/dist/contracts/scout-report.js +1 -1
  66. package/dist/contracts/teams-callback.d.ts +0 -1
  67. package/dist/converse.d.ts +0 -6
  68. package/dist/converse.js +0 -19
  69. package/dist/daemon.js +3 -2
  70. package/dist/decision.d.ts +1 -3
  71. package/dist/deprecations.d.ts +12 -4
  72. package/dist/deprecations.js +27 -4
  73. package/dist/desktop-update-recovery.js +1 -1
  74. package/dist/desktop-update.js +3 -2
  75. package/dist/discord-chat.js +4 -15
  76. package/dist/discord-settings.d.ts +2 -6
  77. package/dist/discord-settings.js +10 -38
  78. package/dist/discord.js +12 -23
  79. package/dist/evidence.d.ts +0 -1
  80. package/dist/evidence.js +0 -3
  81. package/dist/exec.d.ts +0 -9
  82. package/dist/exec.js +3 -81
  83. package/dist/flow-actions.js +0 -3
  84. package/dist/flow-draft.d.ts +1 -3
  85. package/dist/flow-draft.js +15 -28
  86. package/dist/flow-scripts.d.ts +1 -8
  87. package/dist/flow-scripts.js +0 -13
  88. package/dist/flow-send.js +1 -1
  89. package/dist/flow-share.d.ts +0 -2
  90. package/dist/flow-share.js +1 -6
  91. package/dist/flow-steps.js +1 -1
  92. package/dist/flow-triggers.d.ts +0 -1
  93. package/dist/flow-triggers.js +1 -2
  94. package/dist/flow-unattended.js +1 -12
  95. package/dist/flows.js +1 -1
  96. package/dist/health-cli.d.ts +2 -2
  97. package/dist/health-cli.js +1 -1
  98. package/dist/invoke.d.ts +18 -3
  99. package/dist/invoke.js +142 -17
  100. package/dist/is-record.d.ts +2 -0
  101. package/dist/is-record.js +2 -0
  102. package/dist/lead-doors.d.ts +1 -5
  103. package/dist/lead-doors.js +0 -4
  104. package/dist/lead-tools.js +3 -6
  105. package/dist/live-detail.d.ts +0 -1
  106. package/dist/live-detail.js +0 -6
  107. package/dist/live-terminal.d.ts +24 -13
  108. package/dist/live-terminal.js +81 -43
  109. package/dist/live.d.ts +0 -1
  110. package/dist/live.js +1 -2
  111. package/dist/mcp-core.js +0 -2
  112. package/dist/memory-pass.js +1 -1
  113. package/dist/model-catalog.js +1 -1
  114. package/dist/modes.js +1 -12
  115. package/dist/operate.d.ts +2 -0
  116. package/dist/operate.js +436 -745
  117. package/dist/password-bearer.d.ts +0 -2
  118. package/dist/password-bearer.js +0 -4
  119. package/dist/peek-cli.js +2 -1
  120. package/dist/peek.d.ts +2 -26
  121. package/dist/peek.js +2 -168
  122. package/dist/phase-routing.d.ts +0 -4
  123. package/dist/phase-routing.js +2 -21
  124. package/dist/planner-source.d.ts +2 -1
  125. package/dist/planner-source.js +2 -12
  126. package/dist/planner.js +2 -14
  127. package/dist/process-tree.d.ts +0 -2
  128. package/dist/process-tree.js +0 -10
  129. package/dist/project-delete-ui.d.ts +6 -0
  130. package/dist/project-delete-ui.js +20 -1
  131. package/dist/project-delete.d.ts +0 -2
  132. package/dist/project-delete.js +1 -3
  133. package/dist/project-learning.d.ts +0 -14
  134. package/dist/project-skills.d.ts +0 -1
  135. package/dist/project-skills.js +0 -14
  136. package/dist/project-tools.js +2 -10
  137. package/dist/provider.d.ts +0 -17
  138. package/dist/provider.js +0 -26
  139. package/dist/pulls.d.ts +0 -2
  140. package/dist/pulls.js +0 -9
  141. package/dist/release-adapters.d.ts +9 -0
  142. package/dist/release-adapters.js +81 -4
  143. package/dist/release.d.ts +7 -1
  144. package/dist/release.js +20 -7
  145. package/dist/remote-exec.d.ts +3 -2
  146. package/dist/remote-exec.js +3 -2
  147. package/dist/repos.d.ts +0 -5
  148. package/dist/repos.js +2 -10
  149. package/dist/repository-context.d.ts +0 -2
  150. package/dist/repository-context.js +0 -4
  151. package/dist/restart-certification.d.ts +0 -2
  152. package/dist/restart-certification.js +1 -5
  153. package/dist/result-follow-ups.d.ts +1 -4
  154. package/dist/result-follow-ups.js +4 -8
  155. package/dist/result-review.d.ts +0 -2
  156. package/dist/result-review.js +0 -2
  157. package/dist/retention.d.ts +0 -1
  158. package/dist/retention.js +0 -1
  159. package/dist/review-context.d.ts +0 -1
  160. package/dist/review-context.js +1 -12
  161. package/dist/reviewer.d.ts +0 -16
  162. package/dist/reviewer.js +0 -18
  163. package/dist/run-closeout.js +2 -1
  164. package/dist/scope.d.ts +13 -0
  165. package/dist/scope.js +11 -18
  166. package/dist/scout-browser.js +2 -1
  167. package/dist/serve.js +3 -2
  168. package/dist/server/chrome.d.ts +10 -1
  169. package/dist/server/chrome.js +45 -4
  170. package/dist/server/console.css +60 -137
  171. package/dist/server/flows.js +2 -2
  172. package/dist/server/http.d.ts +2 -0
  173. package/dist/server/http.js +5 -0
  174. package/dist/server/pages.js +2 -2
  175. package/dist/server/people-tokens.js +3 -4
  176. package/dist/server/remote.js +1 -6
  177. package/dist/server/render-chat.d.ts +9 -2
  178. package/dist/server/render-chat.js +53 -7
  179. package/dist/server/render-pages.d.ts +28 -3
  180. package/dist/server/render-pages.js +78 -9
  181. package/dist/server/render-results.d.ts +0 -6
  182. package/dist/server/render-results.js +3 -9
  183. package/dist/server/render-tasks.js +6 -6
  184. package/dist/server/route-table.d.ts +0 -4
  185. package/dist/server/route-table.js +1 -6
  186. package/dist/server/settings.js +43 -22
  187. package/dist/server/task-views.js +1 -1
  188. package/dist/server/tasks.js +10 -7
  189. package/dist/setup-guide.d.ts +2 -2
  190. package/dist/skills.d.ts +0 -6
  191. package/dist/skills.js +0 -8
  192. package/dist/slack-chat.d.ts +1 -7
  193. package/dist/slack-chat.js +5 -18
  194. package/dist/slack-settings.d.ts +2 -6
  195. package/dist/slack-settings.js +10 -38
  196. package/dist/slack.js +12 -23
  197. package/dist/sleep.d.ts +2 -0
  198. package/dist/sleep.js +2 -0
  199. package/dist/store.d.ts +11 -74
  200. package/dist/store.js +36 -179
  201. package/dist/structured-output.d.ts +0 -1
  202. package/dist/structured-output.js +0 -1
  203. package/dist/subagent-work.d.ts +0 -2
  204. package/dist/subagent-work.js +0 -2
  205. package/dist/subagents.d.ts +1 -3
  206. package/dist/subagents.js +20 -33
  207. package/dist/subscription-chat.d.ts +1 -3
  208. package/dist/subscription-chat.js +2 -3
  209. package/dist/task-sizing.d.ts +1 -4
  210. package/dist/task-sizing.js +3 -10
  211. package/dist/teams-chat.js +4 -11
  212. package/dist/teams.js +12 -19
  213. package/dist/teardown-git.d.ts +22 -0
  214. package/dist/teardown-git.js +136 -2
  215. package/dist/telegram-lead.js +2 -2
  216. package/dist/telegram.js +1 -1
  217. package/dist/token-notices.d.ts +0 -11
  218. package/dist/token-notices.js +1 -5
  219. package/dist/tool-broker.d.ts +12 -0
  220. package/dist/tool-broker.js +66 -2
  221. package/dist/tool-secret-store.d.ts +0 -3
  222. package/dist/tool-secret-store.js +2 -11
  223. package/dist/toolroll-update.d.ts +0 -2
  224. package/dist/toolroll-update.js +2 -2
  225. package/dist/unfenced-test-support.d.ts +2 -0
  226. package/dist/unfenced-test-support.js +11 -0
  227. package/dist/verification-evidence.d.ts +0 -1
  228. package/dist/verification-evidence.js +0 -1
  229. package/dist/webhooks.d.ts +0 -1
  230. package/dist/webhooks.js +0 -1
  231. package/dist/workspace-ui.d.ts +0 -8
  232. package/dist/workspace-ui.js +1 -6
  233. package/dist/writes-mode.d.ts +9 -0
  234. package/dist/writes-mode.js +8 -0
  235. package/package.json +5 -1
  236. package/dist/contracts/native-census.d.ts +0 -42
  237. package/dist/contracts/native-census.js +0 -36
  238. package/dist/process-recovery-anchor.d.ts +0 -75
  239. package/dist/process-recovery-anchor.js +0 -188
  240. package/dist/process-recovery-coalition.d.ts +0 -25
  241. package/dist/process-recovery-coalition.js +0 -105
  242. package/dist/process-recovery-eligibility.d.ts +0 -179
  243. package/dist/process-recovery-eligibility.js +0 -174
  244. package/dist/process-recovery-native.d.ts +0 -161
  245. package/dist/process-recovery-native.js +0 -595
  246. package/dist/process-recovery-provenance.d.ts +0 -163
  247. package/dist/process-recovery-provenance.js +0 -292
  248. package/dist/process-recovery-services.d.ts +0 -56
  249. package/dist/process-recovery-services.js +0 -191
  250. package/dist/process-recovery-settlement.d.ts +0 -32
  251. package/dist/process-recovery-settlement.js +0 -133
  252. package/dist/process-recovery.d.ts +0 -41
  253. package/dist/process-recovery.js +0 -91
  254. package/dist/session-brief.d.ts +0 -8
  255. package/dist/session-brief.js +0 -24
  256. package/dist/session-cli.d.ts +0 -42
  257. package/dist/session-cli.js +0 -271
  258. package/dist/session-contract.d.ts +0 -183
  259. package/dist/session-contract.js +0 -122
  260. package/dist/session-remote.d.ts +0 -19
  261. package/dist/session-remote.js +0 -86
  262. package/dist/session-service.d.ts +0 -22
  263. package/dist/session-service.js +0 -157
  264. package/dist/team-cli.js +0 -692
package/dist/subagents.js CHANGED
@@ -17,21 +17,17 @@
17
17
  * spending — those stay with people (or a signed hands-off mode).
18
18
  */
19
19
  import { Buffer } from "node:buffer";
20
- import { mkdtempSync, rmSync } from "node:fs";
21
- import { tmpdir } from "node:os";
22
- import { join } from "node:path";
23
20
  import { strictJsonParse } from "./converse.js";
24
21
  import { scanForSecrets } from "./evidence.js";
25
22
  import { run } from "./exec.js";
23
+ import { CLAUDE_NO_TOOLS_ARGS, CLAUDE_SIGNED_OUT, claudeJsonBody, claudeModelArgs, inScratchDir } from "./claude-print.js";
26
24
  import { ALL_CREDENTIAL_ENV } from "./provider.js";
27
25
  import { readTurnAnswer, TURN_MODEL_SCHEMA } from "./contracts/subagent-turn.js";
28
26
  import { LIMITS } from "./decision.js";
29
27
  import { overruns, TEXT_LIMITS } from "./text-limits.js";
30
28
  export const SOUL_CHARS = 12_000;
31
- const HANDLE = /^[a-z0-9][a-z0-9-]{0,31}$/;
32
29
  /** A handle from a name: "Maya" → "maya". */
33
30
  export const handleOf = (name) => name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "subagent";
34
- export const isHandle = (value) => typeof value === "string" && HANDLE.test(value);
35
31
  /** A soul file read: its name and role (front matter) and its sections. */
36
32
  export function parseSoul(text) {
37
33
  const normal = text.replace(/\r\n?/g, "\n").trim();
@@ -220,36 +216,27 @@ export function readTurn(value, context) {
220
216
  export const TURN_TIMEOUT_MS = 180_000;
221
217
  /** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
222
218
  export function claudeTurnRunner(runner = run) {
223
- return async (request) => {
224
- const dir = mkdtempSync(join(tmpdir(), "standing-orders-subagent-"));
219
+ return request => inScratchDir("standing-orders-subagent-", async (dir) => {
225
220
  const started = Date.now();
226
- try {
227
- const result = await runner("claude", [
228
- "-p", "--output-format", "json", "--json-schema", JSON.stringify(TURN_MODEL_SCHEMA), "--safe-mode", "--no-session-persistence", "--tools", "", "--permission-mode", "dontAsk",
229
- "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
230
- ...(request.model === "default" ? [] : ["--model", request.model]),
231
- ], { cwd: dir, stdin: request.prompt, timeoutMs: request.timeoutMs, maxBuffer: 512 * 1024, omitEnv: ALL_CREDENTIAL_ENV, processGroup: true });
232
- if (result.notFound)
233
- return { ok: false, said: "Claude isn't installed on this computer." };
234
- if (result.timedOut)
235
- return { ok: false, said: "Claude took too long to decide." };
236
- const parsed = strictJsonParse(Buffer.from(result.stdout, "utf8"), 512 * 1024, 12);
237
- const body = parsed.ok && typeof parsed.value === "object" && parsed.value !== null && !Array.isArray(parsed.value) ? parsed.value : null;
238
- if (result.code !== 0 || body === null || body["is_error"] === true || body["subtype"] !== "success") {
239
- const why = typeof body?.["subtype"] === "string" && body["subtype"] !== "success" ? ` (${String(body["subtype"]).replace(/_/g, " ").slice(0, 60)})` : result.code !== 0 ? ` (exit ${result.code})` : "";
240
- return { ok: false, said: /not logged in|login|authenticat/i.test(`${result.stdout}${result.stderr}`) ? "Claude isn't signed in on this computer." : `Claude couldn't decide${why}.` };
241
- }
242
- const cost = costOf(body);
243
- if (body["structured_output"] !== undefined && body["structured_output"] !== null)
244
- return { ok: true, value: body["structured_output"], ms: Date.now() - started, ...cost };
245
- const text = typeof body["result"] === "string" ? body["result"].trim().replace(/^```(?:json)?\n?|\n?```$/g, "") : "";
246
- const inner = strictJsonParse(Buffer.from(text, "utf8"), 64 * 1024, 8);
247
- return inner.ok ? { ok: true, value: inner.value, ms: Date.now() - started, ...cost } : { ok: false, said: "Claude's answer wasn't a decision.", ...cost };
221
+ const result = await runner("claude", [
222
+ "-p", "--output-format", "json", "--json-schema", JSON.stringify(TURN_MODEL_SCHEMA), ...CLAUDE_NO_TOOLS_ARGS, ...claudeModelArgs(request.model),
223
+ ], { cwd: dir, stdin: request.prompt, timeoutMs: request.timeoutMs, maxBuffer: 512 * 1024, omitEnv: ALL_CREDENTIAL_ENV, processGroup: true });
224
+ if (result.notFound)
225
+ return { ok: false, said: "Claude isn't installed on this computer." };
226
+ if (result.timedOut)
227
+ return { ok: false, said: "Claude took too long to decide." };
228
+ const body = claudeJsonBody(result.stdout, 512 * 1024);
229
+ if (result.code !== 0 || body === null || body["is_error"] === true || body["subtype"] !== "success") {
230
+ const why = typeof body?.["subtype"] === "string" && body["subtype"] !== "success" ? ` (${String(body["subtype"]).replace(/_/g, " ").slice(0, 60)})` : result.code !== 0 ? ` (exit ${result.code})` : "";
231
+ return { ok: false, said: CLAUDE_SIGNED_OUT.test(`${result.stdout}${result.stderr}`) ? "Claude isn't signed in on this computer." : `Claude couldn't decide${why}.` };
248
232
  }
249
- finally {
250
- rmSync(dir, { recursive: true, force: true });
251
- }
252
- };
233
+ const cost = costOf(body);
234
+ if (body["structured_output"] !== undefined && body["structured_output"] !== null)
235
+ return { ok: true, value: body["structured_output"], ms: Date.now() - started, ...cost };
236
+ const text = typeof body["result"] === "string" ? body["result"].trim().replace(/^```(?:json)?\n?|\n?```$/g, "") : "";
237
+ const inner = strictJsonParse(Buffer.from(text, "utf8"), 64 * 1024, 8);
238
+ return inner.ok ? { ok: true, value: inner.value, ms: Date.now() - started, ...cost } : { ok: false, said: "Claude's answer wasn't a decision.", ...cost };
239
+ });
253
240
  }
254
241
  /** What the CLI says a turn cost, and its tokens (cache reads and writes count as input). */
255
242
  function costOf(body) {
@@ -1,5 +1,5 @@
1
1
  import { type LeadHistoryMessage, type LeadProviderAnswer, type LeadToolSchema } from "./converse.js";
2
- import { run, type ExecResult } from "./exec.js";
2
+ import { type CommandRunner } from "./claude-print.js";
3
3
  import type { SubscriptionChatProviderId } from "./store.js";
4
4
  export type SubscriptionLeadRequest = {
5
5
  provider: SubscriptionChatProviderId;
@@ -27,7 +27,6 @@ export type SubscriptionLeadRunner = (request: SubscriptionLeadRequest) => Promi
27
27
  ok: false;
28
28
  problem: string;
29
29
  }>;
30
- type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
31
30
  /** The model-visible request. Tool results are already redacted by leadView. */
32
31
  export declare function composeSubscriptionLeadPrompt(request: Omit<SubscriptionLeadRequest, "provider" | "model" | "timeoutMs" | "signal">): string;
33
32
  /** Strictly turn the harness's structured output into the provider-neutral answer. */
@@ -49,4 +48,3 @@ export declare function performSubscriptionLeadRequest(request: SubscriptionLead
49
48
  ok: false;
50
49
  problem: string;
51
50
  }>;
52
- export {};
@@ -16,6 +16,7 @@ import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
17
17
  import { LEAD_MAX_CALLS_PER_STEP, LEAD_STEP_TEXT_CAP_BYTES, LEAD_TOOL_CALL_CAP_BYTES, LEAD_TOOL_CALL_ID_CAP_BYTES, strictJsonParse, tokenCount, } from "./converse.js";
18
18
  import { run } from "./exec.js";
19
+ import { CLAUDE_NO_TOOLS_ARGS, claudeModelArgs } from "./claude-print.js";
19
20
  import { claudeStreamReader } from "./lead-progress.js";
20
21
  import { ALL_CREDENTIAL_ENV } from "./provider.js";
21
22
  import { claudeLimitsOf, noteLimits } from "./provider-limits.js";
@@ -215,9 +216,7 @@ export async function performSubscriptionLeadRequest(request, runner = run) {
215
216
  // Streaming (someone is watching): the same answer, delivered as
216
217
  // events whose final `result` line is the whole structured reply.
217
218
  "-p", ...(streaming ? ["--output-format", "stream-json", "--verbose", "--include-partial-messages"] : ["--output-format", "json"]), "--json-schema", JSON.stringify(schema),
218
- "--safe-mode", "--no-session-persistence", "--tools", "", "--permission-mode", "dontAsk",
219
- "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
220
- ...(request.model === "default" ? [] : ["--model", request.model]),
219
+ ...CLAUDE_NO_TOOLS_ARGS, ...claudeModelArgs(request.model),
221
220
  ];
222
221
  }
223
222
  const result = await runner(command, args, {
@@ -1,5 +1,5 @@
1
1
  import { type SizeAnswer } from "./contracts/task-sizing.js";
2
- import { run, type ExecResult } from "./exec.js";
2
+ import { type CommandRunner } from "./claude-print.js";
3
3
  import { type TaskSizing } from "./phase-routing.js";
4
4
  import type { Store } from "./store.js";
5
5
  /** The classifier's whole budget, from asking to answer. */
@@ -24,7 +24,6 @@ export declare function planningSignals(input: SizingInput): boolean;
24
24
  export declare function heuristicSizing(input: SizingInput, why?: string | null): TaskSizing;
25
25
  /** What the classifier reads: the task as data, bounded, keys blanked. */
26
26
  export declare function sizingText(input: SizingInput): string;
27
- type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
28
27
  /** A small Claude model on this computer's sign-in: no tools, no MCP servers, no repository, credential keys stripped.
29
28
  * The abort ends it: a late answer never keeps a command (or its process) waiting. */
30
29
  export declare function claudeSizer(runner?: CommandRunner, model?: string, budgetMs?: number): Sizer;
@@ -41,8 +40,6 @@ export declare function classifyTask(input: SizingInput, sizer: Sizer | null, bu
41
40
  /** Install the process's classifier for filings (the CLI, the console, the worker); returns how to remove it. Unset,
42
41
  * filings keep the description's size — tests never spend a turn. */
43
42
  export declare function installFilingSizer(sizer: Sizer, budgetMs?: number): () => void;
44
- /** Whether filings in this process are classified. */
45
- export declare function filingSizerInstalled(): boolean;
46
43
  /**
47
44
  * After a filing: ask the classifier (within the budget) and apply its size
48
45
  * to the still-unapproved task. Runs in the background; the filing already
@@ -15,15 +15,14 @@
15
15
  * The task text is data to the classifier, never instructions, and all it
16
16
  * can return is one of three sizes, a yes/no, and a short reason.
17
17
  */
18
- import { Buffer } from "node:buffer";
19
18
  import { randomUUID } from "node:crypto";
20
19
  import { mkdtempSync, rmSync } from "node:fs";
21
20
  import { tmpdir } from "node:os";
22
21
  import { join } from "node:path";
23
22
  import { readSizeAnswer, SIZING_MODEL_SCHEMA } from "./contracts/task-sizing.js";
24
- import { strictJsonParse } from "./converse.js";
25
23
  import { redactSecretLines, scanForSecrets } from "./evidence.js";
26
24
  import { run, terminateOwnedProcesses } from "./exec.js";
25
+ import { CLAUDE_NO_TOOLS_ARGS, claudeJsonBody } from "./claude-print.js";
27
26
  import { JEV_MODEL, JEV_URL } from "./flow-sort.js";
28
27
  import { readProviderKey } from "./keys.js";
29
28
  import {} from "./phase-routing.js";
@@ -93,13 +92,11 @@ export function claudeSizer(runner = run, model = SIZING_CLAUDE_MODEL, budgetMs
93
92
  sizingText(input),
94
93
  ].join("\n");
95
94
  const result = await runner("claude", [
96
- "-p", "--output-format", "json", "--json-schema", JSON.stringify(SIZING_MODEL_SCHEMA), "--safe-mode", "--no-session-persistence", "--tools", "", "--permission-mode", "dontAsk",
97
- "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--model", model,
95
+ "-p", "--output-format", "json", "--json-schema", JSON.stringify(SIZING_MODEL_SCHEMA), ...CLAUDE_NO_TOOLS_ARGS, "--model", model,
98
96
  ], { cwd: dir, stdin: prompt, timeoutMs: budgetMs, maxBuffer: 256 * 1024, omitEnv: ALL_CREDENTIAL_ENV, processGroup: true, owner, beforeSpawn: () => !signal.aborted });
99
97
  if (signal.aborted || result.notFound || result.timedOut || result.code !== 0)
100
98
  return null;
101
- const parsed = strictJsonParse(Buffer.from(result.stdout, "utf8"), 256 * 1024, 12);
102
- const body = parsed.ok && typeof parsed.value === "object" && parsed.value !== null && !Array.isArray(parsed.value) ? parsed.value : null;
99
+ const body = claudeJsonBody(result.stdout, 256 * 1024);
103
100
  if (body === null || body["is_error"] === true || body["subtype"] !== "success")
104
101
  return null;
105
102
  return answerOf(body["structured_output"]);
@@ -184,10 +181,6 @@ export function installFilingSizer(sizer, budgetMs = SIZING_BUDGET_MS) {
184
181
  return () => { installed = previous; budget = previousBudget; };
185
182
  }
186
183
  let budget = SIZING_BUDGET_MS;
187
- /** Whether filings in this process are classified. */
188
- export function filingSizerInstalled() {
189
- return installed !== null;
190
- }
191
184
  /**
192
185
  * After a filing: ask the classifier (within the budget) and apply its size
193
186
  * to the still-unapproved task. Runs in the background; the filing already
@@ -6,12 +6,11 @@ import { chatAskButtons } from "./chat-ask.js";
6
6
  import { ChatDeliveryError, ChatState, chatHash, partContent } from "./chat-delivery-state.js";
7
7
  import { channelAccess, chatObject as object, heldForWatch, planChatNotifications, planRoomMessages, processChatEvent, splitChatText } from "./chat-delivery.js";
8
8
  import { PLATFORM_LIMITS } from "./text-limits.js";
9
- import { roomCommand } from "./chat-rooms.js";
10
9
  import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
11
10
  import { renderReply } from "./reply-shape.js";
12
11
  import { armedCardText, armedYesLabel, proposalLink, proposalOutcomeText, proposalPreview } from "./chat-channel.js";
13
12
  import { chatFlowButtons } from "./chat-flow.js";
14
- import { channelInbox } from "./chat-inbox.js";
13
+ import { admitChatEvent } from "./chat-admission.js";
15
14
  import { chatResultHref } from "./chat-controls.js";
16
15
  import { TeamsError } from "./teams-api.js";
17
16
  import { readTeamsSubmit } from "./contracts/teams-callback.js";
@@ -113,16 +112,10 @@ export function receiveTeams(state, identity, raw, serviceUrlClaim, now) {
113
112
  ...(Array.isArray(activity.attachments) && activity.attachments.length > 0 ? { unsupported: "Incoming files are not supported yet. Describe the request in a message; saved result screenshots open from their links." } : {}) };
114
113
  eventId = chatHash(`${identity.installation}:message:${id}`);
115
114
  }
116
- const words = "text" in payload ? payload.text ?? "" : "";
117
- // v89: a channel that feeds a flow takes anyone's message (as a card, with no say over anything); "flow 12" connects one.
118
- const inbox = isRoom && kind === "message" ? channelInbox(state.store, "teams", identity.installation, channel, words) : { watched: false, command: false };
119
- const roomish = isRoom && (state.room(identity.installation, channel) !== null || (kind === "message" && roomCommand(words) !== null) || inbox.watched || inbox.command);
120
- if (isRoom && !roomish)
121
- return false;
122
- const binding = state.bindingFor(identity.installation, member);
123
- const open = inbox.watched && !inbox.command;
124
- if (kind === "pair" ? !!binding || isRoom : !open && (!binding || !state.live(binding) || (binding.channel !== channel && !roomish)))
115
+ const admitted = admitChatEvent(state, "teams", identity.installation, { kind, member, channel, isRoom, payload });
116
+ if (admitted === null)
125
117
  return false;
118
+ const { binding } = admitted;
126
119
  return state.store.transact(() => {
127
120
  state.setMeta(identity.installation, serviceKey(channel), activity.serviceUrl, now);
128
121
  return state.enqueue({
package/dist/teams.js CHANGED
@@ -4,7 +4,7 @@ import { adapterPolicy } from "./server/route-policy.js";
4
4
  * has no client-initiated socket, so inbound activities arrive over HTTPS
5
5
  * and everything else runs from the database like the other channels. */
6
6
  import { randomBytes } from "node:crypto";
7
- import { setTimeout as sleep } from "node:timers/promises";
7
+ import { abortablePause, workChatQueue } from "./chat-follow.js";
8
8
  import { ChatState } from "./chat-delivery-state.js";
9
9
  import { chatObject as object } from "./chat-delivery.js";
10
10
  import { loadTeamsCredentials, teamsAccessToken, teamsApi, verifyTeamsToken, TeamsError } from "./teams-api.js";
@@ -78,10 +78,7 @@ export async function handleTeamsHttp(request, response, options) {
78
78
  /** The worker-side loop: a lease per installation, one pass a second. */
79
79
  export async function followTeams(options) {
80
80
  const owner = randomBytes(16).toString("hex"), state = new ChatState(options.store, "teams");
81
- const pause = async (ms) => { try {
82
- await sleep(ms, undefined, { signal: options.signal });
83
- }
84
- catch { /* shutdown */ } };
81
+ const pause = abortablePause(options.signal);
85
82
  while (!options.signal.aborted) {
86
83
  const credentials = loadTeamsCredentials(options.dir);
87
84
  if (!credentials || !state.lease(credentials.installation, owner, new Date())) {
@@ -110,20 +107,16 @@ export async function followTeams(options) {
110
107
  await pause(30_000);
111
108
  continue;
112
109
  }
113
- while (same()) {
114
- if (state.retryAt(credentials.installation) > new Date().toISOString()) {
115
- await pause(1000);
116
- continue;
117
- }
118
- await processTeamsEvent(chat);
119
- if (same())
120
- await planTeamsRooms(chat);
121
- if (same())
122
- await deliverTeamsPart(chat);
123
- if (same() && options.notifications())
124
- await planTeamsNotifications(chat);
125
- await pause(1000);
126
- }
110
+ await workChatQueue({
111
+ same,
112
+ ready: () => !(state.retryAt(credentials.installation) > new Date().toISOString()),
113
+ pause,
114
+ notifications: () => options.notifications(),
115
+ process: () => processTeamsEvent(chat),
116
+ rooms: () => planTeamsRooms(chat),
117
+ deliver: () => deliverTeamsPart(chat),
118
+ notify: () => planTeamsNotifications(chat),
119
+ });
127
120
  }
128
121
  catch {
129
122
  if (alive)
@@ -8,7 +8,29 @@ export declare const REFUSED_CODE = 128;
8
8
  /** The command-running setting a `git config --list --show-scope -z` listing sets, or null. Only local and worktree
9
9
  * scope count: teardown commands never read system or global config. */
10
10
  export declare function commandSetting(listing: string): string | null;
11
+ /** The `-c` settings that carry the operator's own values (CARRIED_VALUES, LFS_FILTER) from a global/system listing. */
12
+ export declare function carriedSettings(listing: string): string[];
11
13
  /** Why a teardown command was refused, or null when `result` isn't a refusal. */
12
14
  export declare function refusal(result: ExecResult): string | null;
13
15
  /** The one way teardown runs git, including the target checkout immediately before removal. */
14
16
  export declare function teardownGit(runner?: Runner): Git;
17
+ /**
18
+ * Git on a build's checkout, as the plane runs it outside every fence (staging, committing and inspecting the build's
19
+ * work): the same fixed hardening as teardown (no hooks, fsmonitor, ext transport, credential helpers or automatic
20
+ * maintenance, no submodule recursion, no signing), refused outright when the checkout's own config includes a file
21
+ * or names a command — a filter, an external diff, a textconv, a pager — which a build could have planted. It reads no
22
+ * global or system config: only their values (identity, line endings, a standard git-lfs install) are carried over.
23
+ * It never enters a submodule or nested repository (whose config it never read).
24
+ */
25
+ /**
26
+ * Whether a checkout's own git config (its repository's config and its worktree's config.worktree) includes another
27
+ * file, read without git: every git command follows includes as it starts, so an include of the build's could point
28
+ * git at any file, a pipe that never ends among them. Each file is read only as the regular file it is, bounded; one
29
+ * that can't be read so counts as including.
30
+ */
31
+ export declare function checkoutIncludes(checkout: string): boolean;
32
+ /** Whether the checkout's .lfsconfig (read as git parses it, that file only) sets any git-lfs key that could name a
33
+ * program (anything but INERT_LFS). A file that
34
+ * isn't a plain one, or doesn't parse, counts as naming one. */
35
+ export declare function lfsExtensionIn(checkout: string): boolean;
36
+ export declare function checkoutGit(inner?: Runner): Runner;
@@ -10,7 +10,8 @@
10
10
  * any of them the command never runs. The refusal is a failed git result, so every caller's existing doubt applies and
11
11
  * nothing is removed.
12
12
  */
13
- import { existsSync, readdirSync } from "node:fs";
13
+ import { spawnSync } from "node:child_process";
14
+ import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
14
15
  import { join, resolve } from "node:path";
15
16
  import { run } from "./exec.js";
16
17
  import { ENV, ENV_ALLOWLIST } from "./remote-main.js";
@@ -31,6 +32,10 @@ const COMMAND_KEYS = [
31
32
  "diff.*.command", "diff.*.textconv", "merge.*.driver", "gpg.program", "gpg.*.program", "uploadpack.packobjectshook",
32
33
  "remote.*.uploadpack", "remote.*.receivepack", "remote.*.vcs", "pager.*", "include.path", "includeif.*.path",
33
34
  ].map(key => new RegExp(`^${key.replace(/\./g, "\\.").replace(/\*/g, ".+")}$`));
35
+ /** git-lfs settings that name no program (where it fetches, what, whether it checks locks, how it signs in); any other
36
+ * `lfs.*` key in a checkout's own config could (extensions, custom and standalone transfer agents), so it's refused. */
37
+ const INERT_LFS = /^lfs\.(url|pushurl|fetchinclude|fetchexclude|locksverify|repositoryformatversion|.+\.access|.+\.locksverify|setlockablereadonly|skipdownloaderrors|concurrenttransfers)$/;
38
+ const lfsCommand = (key) => key.startsWith("lfs.") && !INERT_LFS.test(key);
34
39
  /** core.fsmonitor as a boolean is git's own daemon; anything else is a command to run. */
35
40
  const BOOLEAN = /^(|true|false|yes|no|on|off|-?\d+)$/i;
36
41
  /** The command-running setting a `git config --list --show-scope -z` listing sets, or null. Only local and worktree
@@ -45,11 +50,40 @@ export function commandSetting(listing) {
45
50
  const newline = entry.indexOf("\n");
46
51
  const key = (newline < 0 ? entry : entry.slice(0, newline)).toLowerCase();
47
52
  const value = newline < 0 ? "" : entry.slice(newline + 1);
48
- if (key === "core.fsmonitor" ? !BOOLEAN.test(value.trim()) : COMMAND_KEYS.some(pattern => pattern.test(key)))
53
+ if (key === "core.fsmonitor" ? !BOOLEAN.test(value.trim()) : COMMAND_KEYS.some(pattern => pattern.test(key)) || lfsCommand(key))
49
54
  return key;
50
55
  }
51
56
  return null;
52
57
  }
58
+ /**
59
+ * What the plane's git on a build's checkout keeps of the operator's own (global and system) configuration: values,
60
+ * never commands — identity, line endings, ignore and case settings — and git-lfs's filters only as git-lfs itself
61
+ * installs them, exactly. Everything else there (filters, diff drivers, fsmonitor, signing and its key commands,
62
+ * whatever an include brings in) is left out, so nothing in it can run a file of the checkout's.
63
+ */
64
+ const CARRIED_VALUES = new Set(["user.name", "user.email", "core.autocrlf", "core.eol", "core.safecrlf", "core.excludesfile", "core.ignorecase",
65
+ "core.precomposeunicode", "core.quotepath", "core.filemode", "core.symlinks", "core.longpaths"]);
66
+ const LFS_FILTER = {
67
+ "filter.lfs.clean": "git-lfs clean -- %f", "filter.lfs.smudge": "git-lfs smudge -- %f", "filter.lfs.process": "git-lfs filter-process", "filter.lfs.required": "true",
68
+ };
69
+ /** The `-c` settings that carry the operator's own values (CARRIED_VALUES, LFS_FILTER) from a global/system listing. */
70
+ export function carriedSettings(listing) {
71
+ const fields = listing.split("\0");
72
+ const carried = new Map();
73
+ for (let at = 0; at + 1 < fields.length; at += 2) {
74
+ if (fields[at] !== "global" && fields[at] !== "system")
75
+ continue;
76
+ const entry = fields[at + 1];
77
+ const newline = entry.indexOf("\n");
78
+ if (newline < 0)
79
+ continue;
80
+ const key = entry.slice(0, newline).toLowerCase();
81
+ const value = entry.slice(newline + 1);
82
+ if (CARRIED_VALUES.has(key) || LFS_FILTER[key] === value)
83
+ carried.set(key, value);
84
+ }
85
+ return [...carried].flatMap(([key, value]) => ["-c", `${key}=${value}`]);
86
+ }
53
87
  /** Why a teardown command was refused, or null when `result` isn't a refusal. */
54
88
  export function refusal(result) {
55
89
  return result.code === REFUSED_CODE && result.stderr.startsWith(REFUSED_PREFIX) ? result.stderr.slice(REFUSED_PREFIX.length).trim() : null;
@@ -117,3 +151,103 @@ export function teardownGit(runner = run) {
117
151
  return runner("git", [...TEARDOWN_FLAGS, ...safeArgs], options(cwd));
118
152
  };
119
153
  }
154
+ /**
155
+ * Git on a build's checkout, as the plane runs it outside every fence (staging, committing and inspecting the build's
156
+ * work): the same fixed hardening as teardown (no hooks, fsmonitor, ext transport, credential helpers or automatic
157
+ * maintenance, no submodule recursion, no signing), refused outright when the checkout's own config includes a file
158
+ * or names a command — a filter, an external diff, a textconv, a pager — which a build could have planted. It reads no
159
+ * global or system config: only their values (identity, line endings, a standard git-lfs install) are carried over.
160
+ * It never enters a submodule or nested repository (whose config it never read).
161
+ */
162
+ /**
163
+ * Whether a checkout's own git config (its repository's config and its worktree's config.worktree) includes another
164
+ * file, read without git: every git command follows includes as it starts, so an include of the build's could point
165
+ * git at any file, a pipe that never ends among them. Each file is read only as the regular file it is, bounded; one
166
+ * that can't be read so counts as including.
167
+ */
168
+ export function checkoutIncludes(checkout) {
169
+ const regular = (path) => {
170
+ try {
171
+ const held = lstatSync(path);
172
+ if (!held.isFile() || held.size > 1024 * 1024)
173
+ return null;
174
+ return readFileSync(path, "utf8");
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ };
180
+ let gitdir = join(checkout, ".git");
181
+ try {
182
+ if (!lstatSync(gitdir).isDirectory()) {
183
+ // As git reads a .git file: "gitdir: " then the path, one line; anything else (more lines, stray space) refused.
184
+ const text = (regular(gitdir) ?? "").replace(/\r?\n$/, "");
185
+ if (!text.startsWith("gitdir: ") || /[\r\n]/.test(text))
186
+ return true;
187
+ const pointer = text.slice("gitdir: ".length);
188
+ if (pointer === "" || pointer !== pointer.trim())
189
+ return true;
190
+ gitdir = resolve(checkout, pointer);
191
+ }
192
+ }
193
+ catch {
194
+ return true;
195
+ }
196
+ const common = (() => { const named = regular(join(gitdir, "commondir"))?.trim(); return named === undefined || named === "" ? gitdir : resolve(gitdir, named); })();
197
+ for (const path of [join(common, "config"), join(gitdir, "config.worktree")]) {
198
+ if (!existsSync(path) && path.endsWith("config.worktree"))
199
+ continue;
200
+ const text = regular(path);
201
+ if (text === null || /^\s*\[\s*include(if)?\b/im.test(text))
202
+ return true;
203
+ // And as git itself parses it, includes not followed (only this one regular file, from a neutral folder, with no
204
+ // other config read): an include git sees by any spelling is refused.
205
+ const parsed = spawnSync("git", ["config", "--file", path, "--no-includes", "--name-only", "--list", "-z"], { cwd: "/", encoding: "utf8", timeout: 5_000, env: { PATH: process.env["PATH"] ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", HOME: "/nonexistent" } });
206
+ if (parsed.status !== 0 || parsed.stdout.split("\0").some(name => /^(include\.path|includeif\..*\.path)$/i.test(name)))
207
+ return true;
208
+ }
209
+ return false;
210
+ }
211
+ /** Whether the checkout's .lfsconfig (read as git parses it, that file only) sets any git-lfs key that could name a
212
+ * program (anything but INERT_LFS). A file that
213
+ * isn't a plain one, or doesn't parse, counts as naming one. */
214
+ export function lfsExtensionIn(checkout) {
215
+ const path = join(checkout, ".lfsconfig");
216
+ try {
217
+ if (!lstatSync(path).isFile())
218
+ return true;
219
+ }
220
+ catch {
221
+ return false;
222
+ }
223
+ const parsed = spawnSync("git", ["config", "--file", path, "--no-includes", "--name-only", "--list", "-z"], { cwd: "/", encoding: "utf8", timeout: 5_000, env: { PATH: process.env["PATH"] ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", HOME: "/nonexistent" } });
224
+ return parsed.status !== 0 || parsed.stdout.split("\0").filter(Boolean).some(name => lfsCommand(name.toLowerCase()) || /^include(if)?\./i.test(name));
225
+ }
226
+ export function checkoutGit(inner = run) {
227
+ return async (file, args, options) => {
228
+ if (file !== "git")
229
+ return inner(file, args, options);
230
+ const cwd = options?.cwd;
231
+ if (cwd === undefined || checkoutIncludes(cwd))
232
+ return refused("the checkout's git config includes another file, or couldn't be read as a plain file");
233
+ // git-lfs also reads .lfsconfig from the checkout: one that names an extension (a command it would run) is refused.
234
+ if (lfsExtensionIn(cwd))
235
+ return refused("the checkout's .lfsconfig sets a git-lfs key that can run a command");
236
+ // The checkout's own config may name no command.
237
+ const own = await inner("git", ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], options);
238
+ if (own.code !== 0)
239
+ return refused("its config couldn't be read");
240
+ const key = commandSetting(own.stdout);
241
+ if (key !== null)
242
+ return refused(`the checkout's git config sets ${key}, which can run a command`);
243
+ // The operator's own config contributes values only (carriedSettings); git then reads no global or system config.
244
+ const carried = carriedSettings(own.stdout);
245
+ const env = { ...(options?.env ?? {}), GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null" };
246
+ // Never into a submodule or nested repository, whose own config this never read: status and diff skip them.
247
+ const safeArgs = [...args];
248
+ const command = safeArgs.findIndex(arg => arg === "status" || arg === "diff");
249
+ if (command >= 0 && !safeArgs.includes("--ignore-submodules=all"))
250
+ safeArgs.splice(command + 1, 0, "--ignore-submodules=all");
251
+ return inner("git", [...TEARDOWN_FLAGS, "-c", "diff.ignoreSubmodules=all", "-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false", ...carried, ...safeArgs], { ...options, env });
252
+ };
253
+ }
@@ -1,5 +1,5 @@
1
1
  import { CHAT_ACTIONS, sharedActionAllowsChallenge, sharedActionPayload } from "./chat-actions.js";
2
- import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
2
+ import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
3
3
  export { channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps } from "./chat-channel.js";
4
4
  /**
5
5
  * The paired phone as one more way to use the same assistant.
@@ -23,7 +23,7 @@ export { channelRepos as telegramConversationRepos, resolveChannelLead as resolv
23
23
  * the console and CLI state), and the door re-proves everything again.
24
24
  */
25
25
  import { createHash, randomBytes } from "node:crypto";
26
- import { LEAD_MESSAGE_MAX_CHARS, leadFailureText, runLeadTurn } from "./lead.js";
26
+ import { leadFailureText, runLeadTurn } from "./lead.js";
27
27
  import { confirmLeadProposal, dismissLeadProposal } from "./lead-doors.js";
28
28
  import { ceilingDigestOf, verifyApproverStanding } from "./principal.js";
29
29
  import { LEAD_ASK_OTHER } from "./store.js";
package/dist/telegram.js CHANGED
@@ -27,7 +27,7 @@ import { chmodSync, readFileSync, rmSync, writeFileSync } from "node:fs";
27
27
  import { join } from "node:path";
28
28
  import { closedQuestionWords, validateNote } from "./decision.js";
29
29
  import { isShotsKind, resultShotsFor } from "./result-shots.js";
30
- import { isLifecycleNotification, isTelegramProgressNotification, RESULT_SHOTS_KIND, TELEGRAM_HOLD_REASONS } from "./store.js";
30
+ import { isLifecycleNotification, isTelegramProgressNotification, TELEGRAM_HOLD_REASONS } from "./store.js";
31
31
  import { telegramProgressCard } from "./telegram-progress.js";
32
32
  import { enqueueEveningDigests, finishedView, isTaskFact, joinsBatch, needsPerson, quietCardView } from "./chat-quiet.js";
33
33
  import { BATCH_MS, chatText, chatTitle, factLinkLabel, mentions, nameTelegramBot } from "./chat-voice.js";
@@ -1,10 +1,3 @@
1
- /**
2
- * API token notices (v111): 7 days and 1 day before a token expires, its person is told once each, in their own chats
3
- * and browsers (a personal notification), and the ledger keeps it. Only the nearest notice newly due is sent, so a
4
- * token made with a day left says so once. Replaced and revoked tokens get none. The same pass records the end of each
5
- * rotated token whose overlap has passed; it already signs no one in (tokenLive), so a late pass grants nothing.
6
- */
7
- import { tokenLive } from "./api-tokens.js";
8
1
  import type { Store } from "./store.js";
9
2
  export declare const TOKEN_NOTICE_KIND = "api-token-expiry";
10
3
  export declare function tokenNoticePass(store: Store, now: Date): {
@@ -14,9 +7,5 @@ export declare function tokenNoticePass(store: Store, now: Date): {
14
7
  }[];
15
8
  ended: number;
16
9
  };
17
- /** Whether a token's console card warns that it expires soon (within the first notice's days). */
18
- export declare function expiresSoon(token: Parameters<typeof tokenLive>[0] & {
19
- replacedBy: string | null;
20
- }, now: number): boolean;
21
10
  /** The loop beside the console: a pass a minute. */
22
11
  export declare function startTokenNotices(store: Store, everyMs?: number): () => void;
@@ -4,7 +4,7 @@
4
4
  * token made with a day left says so once. Replaced and revoked tokens get none. The same pass records the end of each
5
5
  * rotated token whose overlap has passed; it already signs no one in (tokenLive), so a late pass grants nothing.
6
6
  */
7
- import { expiryNoticeDue, EXPIRY_NOTICE_DAYS, tokenLive } from "./api-tokens.js";
7
+ import { expiryNoticeDue, EXPIRY_NOTICE_DAYS } from "./api-tokens.js";
8
8
  export const TOKEN_NOTICE_KIND = "api-token-expiry";
9
9
  export function tokenNoticePass(store, now) {
10
10
  const ended = store.endRotatedApiTokens(now);
@@ -32,10 +32,6 @@ export function tokenNoticePass(store, now) {
32
32
  }
33
33
  return { sent, ended };
34
34
  }
35
- /** Whether a token's console card warns that it expires soon (within the first notice's days). */
36
- export function expiresSoon(token, now) {
37
- return expiryNoticeDue(token, now) !== null;
38
- }
39
35
  /** The loop beside the console: a pass a minute. */
40
36
  export function startTokenNotices(store, everyMs = 60_000) {
41
37
  let stopped = false;
@@ -11,7 +11,13 @@ export type BrokerOptions = {
11
11
  omitEnv?: readonly string[];
12
12
  /** Toolroll's own settings for a local server (the run's isolated database), passed as they are. */
13
13
  pass?: Readonly<Record<string, string>>;
14
+ /** Where a local server starts: a folder the build can't write (never its checkout). */
14
15
  cwd?: string;
16
+ /** Folders the build writes (its checkout, its caches): a server whose command, or a path it's given, resolves inside
17
+ * one is never started, since it would run the build's own bytes outside the build's limits. */
18
+ untrusted?: readonly string[];
19
+ /** Why a local server may not start unwrapped (no fence for it here), read when it would; null when it may. */
20
+ unwrapped?: () => string | null;
15
21
  /** How a local server is started inside its run's fence, read when it starts (the fence is fixed after the tools). */
16
22
  wrap?: () => ((file: string, args: readonly string[]) => {
17
23
  file: string;
@@ -35,5 +41,11 @@ export declare const SERVER_ENV: readonly string[];
35
41
  /** A local server's environment: the allowlisted names from `base` (and its LC_* locale), less `omit`, then `pass`,
36
42
  * then the tool's own values. */
37
43
  export declare function serverEnv(base: NodeJS.ProcessEnv, omit: readonly string[], pass: Readonly<Record<string, string>>, values: Readonly<Record<string, string>>): NodeJS.ProcessEnv;
44
+ /**
45
+ * The first of a server's command and path-like arguments that resolves inside an untrusted folder (a build's checkout
46
+ * or caches), or null. The command is resolved as spawn would: a path against `cwd`, a bare name through PATH; an
47
+ * argument counts when it names an existing path (relative to `cwd`) or is written as one (`./x`, `../x`, `/x`).
48
+ */
49
+ export declare function workspaceBytes(command: string, args: readonly string[], cwd: string | undefined, path: string | undefined, untrusted: readonly string[]): string | null;
38
50
  /** Start one launch's broker on a loopback port. */
39
51
  export declare function startToolBroker(tools: readonly BrokeredTool[], options?: BrokerOptions): Promise<ToolBroker>;