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
@@ -18,6 +18,7 @@ import { existsSync, readdirSync, readlinkSync, realpathSync } from "node:fs";
18
18
  import { hostname } from "node:os";
19
19
  import { basename, sep } from "node:path";
20
20
  import { ownedProcessCount, runOwnerTag } from "./exec.js";
21
+ import { sleep as defaultSleep } from "./sleep.js";
21
22
  /** How long a process has after SIGTERM before SIGKILL. */
22
23
  export const STOP_GRACE_MS = 10_000;
23
24
  /** `ps` reports age in whole seconds: a process born this close after the run saw its pid is still that process. */
@@ -86,7 +87,7 @@ export async function stopProcesses(targets, options = {}) {
86
87
  const graceMs = options.graceMs ?? STOP_GRACE_MS;
87
88
  const census = options.census ?? (() => processCensus());
88
89
  const signal = options.signal ?? ((pid, name) => process.kill(pid, name));
89
- const sleep = options.sleep ?? (ms => new Promise(done => setTimeout(done, ms)));
90
+ const sleep = options.sleep ?? defaultSleep;
90
91
  const send = (rows, name) => {
91
92
  for (const row of rows) {
92
93
  try {
package/dist/scope.d.ts CHANGED
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import { isAcpProfile, type AcpProviderId } from "./acp-agents.js";
4
4
  export { isAcpProfile };
5
5
  import { type NetworkProfile } from "./egress.js";
6
+ import { type WritesMode } from "./writes-mode.js";
6
7
  import { type EvidenceKind } from "./contracts/acceptance-terms.js";
7
8
  import { type AcceptanceCriterion, type AcceptanceProblem, type ScopeTerms } from "./contracts/scope.js";
8
9
  import type { Store, Mutation } from "./store.js";
@@ -74,6 +75,9 @@ export type ClaudeProfile = {
74
75
  /** What the build may reach (egress.ts), resolved when the scope was written; absent on approvals sealed before
75
76
  * network policies, which run narrowed (egress.ts legacyNetwork). */
76
77
  network?: NetworkProfile;
78
+ /** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
79
+ * write allowlists, which keep writing anywhere their fence allows. */
80
+ writes?: WritesMode;
77
81
  };
78
82
  export type CodexShapedProfile = {
79
83
  provider: "codex" | "openrouter";
@@ -91,6 +95,9 @@ export type CodexShapedProfile = {
91
95
  repairTimeoutSeconds: number;
92
96
  repairModel: string;
93
97
  network?: NetworkProfile;
98
+ /** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
99
+ * write allowlists, which keep writing anywhere their fence allows. */
100
+ writes?: WritesMode;
94
101
  };
95
102
  export type GeminiProfile = {
96
103
  provider: "gemini";
@@ -110,6 +117,9 @@ export type GeminiProfile = {
110
117
  repairTimeoutSeconds: number;
111
118
  repairModel: string;
112
119
  network?: NetworkProfile;
120
+ /** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
121
+ * write allowlists, which keep writing anywhere their fence allows. */
122
+ writes?: WritesMode;
113
123
  };
114
124
  /**
115
125
  * An agent over the Agent Client Protocol (acp-agents.ts, docs/plans/acp-adapter.md).
@@ -130,6 +140,9 @@ export type AcpProfile = {
130
140
  repairIdleTimeoutSeconds: number;
131
141
  repairModel: string;
132
142
  network?: NetworkProfile;
143
+ /** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
144
+ * write allowlists, which keep writing anywhere their fence allows. */
145
+ writes?: WritesMode;
133
146
  };
134
147
  export type ExecutionProfile = ClaudeProfile | CodexShapedProfile | GeminiProfile | AcpProfile;
135
148
  /** Whether a profile's clock is a no-progress window (an ACP profile has both clocks; its overall one is a deadline). */
package/dist/scope.js CHANGED
@@ -11,6 +11,7 @@ import { claimActor, currentActor } from "./actor.js";
11
11
  import { parseLeadToken } from "./api-tokens.js";
12
12
  import { isAcpProfile, isAcpProvider } from "./acp-agents.js";
13
13
  import { levelOfProfile } from "./policy.js";
14
+ import { canonicalJson } from "./canonical-json.js";
14
15
  export { isAcpProfile };
15
16
  /**
16
17
  * What a task is allowed to become, agreed before anything builds it.
@@ -39,6 +40,7 @@ export { isAcpProfile };
39
40
  */
40
41
  import { createHash, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
41
42
  import { networkFromValue, networkWords } from "./egress.js";
43
+ import { isWritesMode, writesWords } from "./writes-mode.js";
42
44
  import { z } from "zod";
43
45
  import {} from "./contracts/acceptance-terms.js";
44
46
  import { parseContract } from "./contracts/contract.js";
@@ -132,19 +134,6 @@ export function watchdogsOf(profile, timeoutMs, repair = false) {
132
134
  return profile.timeoutKind === "idle" ? { idleTimeoutMs: timeoutMs } : { timeoutMs };
133
135
  }
134
136
  export const PROFILE_DIGEST_VERSION = 2;
135
- /** Deterministic JSON: object keys sorted recursively, arrays in order. */
136
- function canonicalJson(value) {
137
- if (Array.isArray(value))
138
- return `[${value.map(canonicalJson).join(",")}]`;
139
- if (value !== null && typeof value === "object") {
140
- const entries = Object.entries(value)
141
- .filter(([, v]) => v !== undefined)
142
- .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
143
- .map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
144
- return `{${entries.join(",")}}`;
145
- }
146
- return JSON.stringify(value);
147
- }
148
137
  /** The stored snapshot bytes: version embedded IN the snapshot (finding 17). */
149
138
  export function canonicalProfileJson(profile) {
150
139
  return canonicalJson({ digestVersion: PROFILE_DIGEST_VERSION, profile });
@@ -362,9 +351,12 @@ export function profileFromJson(json) {
362
351
  const network = p["network"] === undefined ? undefined : networkFromValue(p["network"]);
363
352
  if (network === null)
364
353
  return null;
365
- const withNetwork = network === undefined ? {} : { network };
354
+ const writes = p["writes"];
355
+ if (writes !== undefined && !isWritesMode(writes))
356
+ return null;
357
+ const withNetwork = { ...(network === undefined ? {} : { network }), ...(writes === undefined ? {} : { writes }) };
366
358
  if (p["provider"] === "claude") {
367
- if (exactKeys(p, [...PROVIDER_KEYS, "permissionArgv"], ["timeoutKind", "network"]) &&
359
+ if (exactKeys(p, [...PROVIDER_KEYS, "permissionArgv"], ["timeoutKind", "network", "writes"]) &&
368
360
  str(p["model"]) &&
369
361
  (p["permissionArgv"] === "auto" || p["permissionArgv"] === "acceptEdits" || p["permissionArgv"] === "bypassPermissions") &&
370
362
  num(p["maxTurns"]) && num(p["repairMaxTurns"]) &&
@@ -386,7 +378,7 @@ export function profileFromJson(json) {
386
378
  return null;
387
379
  }
388
380
  if (p["provider"] === "codex" || p["provider"] === "openrouter") {
389
- if (exactKeys(p, [...PROVIDER_KEYS, "sandboxMode"], ["timeoutKind", "network"]) &&
381
+ if (exactKeys(p, [...PROVIDER_KEYS, "sandboxMode"], ["timeoutKind", "network", "writes"]) &&
390
382
  str(p["model"]) &&
391
383
  (p["sandboxMode"] === "workspace-write" || p["sandboxMode"] === "danger-full-access") &&
392
384
  p["maxTurns"] === "unsupported" && p["repairMaxTurns"] === "unsupported" &&
@@ -408,7 +400,7 @@ export function profileFromJson(json) {
408
400
  return null;
409
401
  }
410
402
  if (p["provider"] === "gemini") {
411
- if (exactKeys(p, [...PROVIDER_KEYS, "approvalArgv"], ["timeoutKind", "network"]) &&
403
+ if (exactKeys(p, [...PROVIDER_KEYS, "approvalArgv"], ["timeoutKind", "network", "writes"]) &&
412
404
  str(p["model"]) &&
413
405
  (p["approvalArgv"] === "auto_edit" || p["approvalArgv"] === "yolo") &&
414
406
  p["maxTurns"] === "unsupported" && p["repairMaxTurns"] === "unsupported" &&
@@ -430,7 +422,7 @@ export function profileFromJson(json) {
430
422
  return null;
431
423
  }
432
424
  if (typeof p["provider"] === "string" && isAcpProvider(p["provider"])) {
433
- if (exactKeys(p, [...PROVIDER_KEYS, "permission", "idleTimeoutSeconds", "repairIdleTimeoutSeconds"], ["network"]) &&
425
+ if (exactKeys(p, [...PROVIDER_KEYS, "permission", "idleTimeoutSeconds", "repairIdleTimeoutSeconds"], ["network", "writes"]) &&
434
426
  str(p["model"]) &&
435
427
  p["permission"] === "full" &&
436
428
  p["maxTurns"] === "unsupported" && p["repairMaxTurns"] === "unsupported" &&
@@ -1278,6 +1270,7 @@ export function describeScope(scope, readiness = NO_READINESS) {
1278
1270
  ...scopeRouteWords(scope, readiness),
1279
1271
  // What the build may reach (egress.ts), part of what the yes agrees to.
1280
1272
  ...(scope.profile === null || scope.profile === undefined ? [] : [` network ${networkWords(scope.profile.network ?? null).replace(/^Network: /, "")}`]),
1273
+ ...(scope.profile === null || scope.profile === undefined ? [] : [` writes ${writesWords(scope.profile.writes)}`]),
1281
1274
  ` reference ${scope.digest}`,
1282
1275
  ` approved ${describeApproval(approval)}`,
1283
1276
  ];
@@ -16,6 +16,7 @@ import { request as httpRequest } from "node:http";
16
16
  import { connect, createServer } from "node:net";
17
17
  import { readdirSync, rmSync } from "node:fs";
18
18
  import { join } from "node:path";
19
+ import { sleep } from "./sleep.js";
19
20
  /** The public page the check opens through the scout's proxy. */
20
21
  export const PREFLIGHT_PAGE = "https://example.com/";
21
22
  const PREFLIGHT_TIMEOUT_MS = 60_000;
@@ -90,7 +91,7 @@ async function serveBrowser(launch, options, deadline, late) {
90
91
  };
91
92
  // First runs download the pinned tool; it listens once it is ready.
92
93
  while (stopped === null && Date.now() < deadline && !(await answers(port)))
93
- await new Promise(done => setTimeout(done, 250));
94
+ await sleep(250);
94
95
  if (stopped !== null || Date.now() >= deadline) {
95
96
  const reason = stopped ?? late;
96
97
  await close();
package/dist/serve.js CHANGED
@@ -151,7 +151,7 @@ import { loadConsoleUrl } from "./webhooks.js";
151
151
  import { reviewFactsOf } from "./workspace-ui.js";
152
152
  import { WorktreePool } from "./worktree.js";
153
153
  export function createDecisionServer(options) {
154
- const { guided, projectFamilyPeek, familiesInView, familyTasksInView, revisionDestination, earlierLiveVersions, familyHistory, freshAssignment, workRowOf, taskListPane, runIsLive, runIsTaskResult, revisionViewOf, taskViewData, routeViewOf, taskChatFocus, pullRequestTargetOf, taskPullRequestOf, failureOf, explainAttempt, runChecksHere, taskScreen, armTaskResume, planViewOf, planContractViewOf, revisionDocOf, revisionLedgerOf, progressOf, resultDetailOf } = createTaskViews({
154
+ const { projectFamilyPeek, familiesInView, familyTasksInView, revisionDestination, runIsLive, runIsTaskResult, revisionViewOf, taskViewData, routeViewOf, taskChatFocus, pullRequestTargetOf, failureOf, explainAttempt, taskScreen, armTaskResume, planViewOf, planContractViewOf, revisionDocOf, revisionLedgerOf, resultDetailOf } = createTaskViews({
155
155
  get store() { return store; },
156
156
  get projectCounts() { return projectCounts; },
157
157
  get admissionList() { return admissionList; },
@@ -170,7 +170,7 @@ export function createDecisionServer(options) {
170
170
  get dockedConversation() { return dockedConversation; },
171
171
  get liveRefreshSeconds() { return liveRefreshSeconds; },
172
172
  });
173
- const { authenticateApprover, authenticateCredential, authorizeMutation, projectOf, resolveRouteProject, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible } = createSharedGuards({
173
+ const { authenticateApprover, authenticateCredential, authorizeMutation, projectOf, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible } = createSharedGuards({
174
174
  get store() { return store; },
175
175
  get liveCeiling() { return liveCeiling; },
176
176
  get codingProjects() { return codingProjects; },
@@ -447,6 +447,7 @@ export function createDecisionServer(options) {
447
447
  builds: projectBuildsOf(repo, who),
448
448
  access: projectAccessOf(repo),
449
449
  network: { setting: store.projectNetwork(repo), canChange: who.via === "cookie" && who.role === "approver" && !store.isDemo() && store.accountCanAccess(who.name, repo) },
450
+ writes: { setting: store.projectWrites(repo), canChange: who.via === "cookie" && who.role === "approver" && !store.isDemo() && store.accountCanAccess(who.name, repo) },
450
451
  liveView: (() => {
451
452
  const commands = liveCommandsShown(store, repo);
452
453
  const canChange = who.via === "cookie" && who.role === "approver" && !store.isDemo() && store.accountCanAccess(who.name, repo);
@@ -128,7 +128,9 @@ export type Chrome = {
128
128
  * at the sink like every page, fetched same-origin — and the nonce'd CSP
129
129
  * refuses to execute anything the region could smuggle. Nothing reads on a timer.
130
130
  */
131
- export declare function regionScript(regionId: string, fragmentName: string, path?: string): string;
131
+ export declare function regionScript(regionId: string, fragmentName: string, path?: string, options?: {
132
+ animate?: boolean;
133
+ }): string;
132
134
  /**
133
135
  * The live transcript's DEDICATED poller (arc 1 §4): a JSON byte-offset
134
136
  * protocol, not an HTML fragment — the response is raw sanitized text and
@@ -297,3 +299,10 @@ export declare function safePrUrl(url: string | null): string | null;
297
299
  export declare function shortDigest(digest: string): Html;
298
300
  /** One display line, bounded — turn text is data, never layout. */
299
301
  export declare function oneLineOf(text: string, cap: number): string;
302
+ /**
303
+ * The board's clocks tick in the page: each <time data-tick> counts up from
304
+ * its datetime, measured against the server's own now (the board's data-now)
305
+ * so a skewed device clock cannot run them backwards. The server-rendered
306
+ * minutes are the answer without script.
307
+ */
308
+ export declare function boardTickScript(regionId?: string): string;
@@ -161,7 +161,7 @@ export const WORKSPACE_STYLE = styleAsset(STYLE + BRAND_MARK_CSS + INBOX_TABS_CS
161
161
  * at the sink like every page, fetched same-origin — and the nonce'd CSP
162
162
  * refuses to execute anything the region could smuggle. Nothing reads on a timer.
163
163
  */
164
- export function regionScript(regionId, fragmentName, path) {
164
+ export function regionScript(regionId, fragmentName, path, options = {}) {
165
165
  // Where the fragment lives: the page's own URL by default; an explicit
166
166
  // path when a page embeds another entity's region (the task page embeds
167
167
  // the live run's peek, slice 1c — no proxy route exists for it).
@@ -184,7 +184,7 @@ export function regionScript(regionId, fragmentName, path) {
184
184
  `function keep(){var data={lanes:[],focus:null,pager:-1,fold:{}};` +
185
185
  `region.querySelectorAll("details.lane").forEach(function(d){var k=laneKey(d);if(k)data.fold[k]=d.open;});` +
186
186
  `var act=document.activeElement;` +
187
- `if(act&&region.contains(act)&&act.matches&&act.matches("a.row, a.lane-card"))data.focus=act.getAttribute("href");` +
187
+ `if(act&&region.contains(act)&&act.matches&&act.matches("a.row, a.lane-card, a.worker-job")){data.focus=act.getAttribute("href");data.focusKind=act.matches("a.worker-job")?"a.worker-job":"a.row, a.lane-card";}` +
188
188
  `var board=region.querySelector(".board");` +
189
189
  `if(board&&board.scrollLeft>0){var lanes=board.querySelectorAll(".lane");` +
190
190
  `var bc=board.getBoundingClientRect();var mid=bc.left+bc.width/2;var best=-1,bd=1e9;` +
@@ -208,17 +208,41 @@ export function regionScript(regionId, fragmentName, path) {
208
208
  `if(board){var lanes=board.querySelectorAll(".lane");var at=Math.min(data.pager,lanes.length-1);` +
209
209
  `if(at>=0){var br=board.getBoundingClientRect();var lr=lanes[at].getBoundingClientRect();` +
210
210
  `board.scrollLeft=board.scrollLeft+(lr.left+lr.width/2)-(br.left+br.width/2);}}}` +
211
- `if(data.focus!==null){var links=region.querySelectorAll("a.row, a.lane-card");` +
211
+ `if(data.focus!==null){var links=region.querySelectorAll(data.focusKind);` +
212
212
  `for(var i=0;i<links.length;i++){if(links[i].getAttribute("href")===data.focus){links[i].focus({preventScroll:true});break;}}}}` +
213
213
  `function tell(){if(!stamp||stopped)return;var s=Math.round((Date.now()-last)/1000);` +
214
214
  `stamp.textContent=failed?"stale — retrying ("+s+"s old)":"updated "+s+"s ago";}` +
215
215
  `setInterval(tell,1000);` +
216
+ // The animated swap (the live board): a card that changed lane slides there and a new one drops in, through one
217
+ // same-document view transition keyed by each card's data-vt; a worker whose builds changed flashes once
218
+ // (data-flash). Never under reduced motion, in a hidden tab, or where the browser lacks view transitions — there
219
+ // the swap is the plain instant one. Names are set only for the swap and cleared after it.
220
+ `var animate=${options.animate === true ? "true" : "false"};` +
221
+ // A name spells the whole key in hex — two tasks can never share one, so a card's identity never jumps to another.
222
+ `function vtName(v){var n="so-c";for(var i=0;i<v.length;i++){n+=("000"+v.charCodeAt(i).toString(16)).slice(-4);}return n;}` +
223
+ `function nameAll(){var seen={};region.querySelectorAll("[data-vt]").forEach(function(e){var n=vtName(e.getAttribute("data-vt"));if(seen[n])return;seen[n]=1;e.style.setProperty("view-transition-name",n);});}` +
224
+ `function flashes(){var m={};region.querySelectorAll("[data-flash]").forEach(function(e){m[e.getAttribute("data-flash")]=e.getAttribute("data-flash-value");});return m;}` +
225
+ `function apply(t){var before=animate?flashes():{};var kept=keep();region.innerHTML=t;restore(kept);` +
226
+ `if(animate)region.querySelectorAll("[data-flash]").forEach(function(e){var k=e.getAttribute("data-flash"),v=e.getAttribute("data-flash-value");if(k in before&&before[k]!==v&&v!=="")e.classList.add("flash");});}` +
227
+ // The swap resolves when its transition has finished, and the next fetch waits for it (refresh holds busy), so
228
+ // one transition runs at a time; each cleans up only its own (the generation), never a later one's.
229
+ `var generation=0;` +
230
+ `function swap(t){var calm=!window.matchMedia||matchMedia("(prefers-reduced-motion: reduce)").matches;` +
231
+ `if(!animate||calm||document.hidden||typeof document.startViewTransition!=="function"){apply(t);return Promise.resolve();}` +
232
+ // Bounded: a transition that never settles (or never runs its update) is let go after 1.5 s — the fragment is
233
+ // applied exactly once either way, so the board can never stop refreshing behind an animation.
234
+ `var root=document.documentElement;var mine=++generation;var applied=false;var settled=false;nameAll();root.classList.add("so-swap");` +
235
+ `var once=function(){if(applied)return false;applied=true;apply(t);return true;};` +
236
+ `var end=function(){if(settled)return;settled=true;once();if(mine!==generation)return;root.classList.remove("so-swap");region.querySelectorAll("[data-vt]").forEach(function(e){e.style.removeProperty("view-transition-name");});};` +
237
+ `var vt;try{vt=document.startViewTransition(function(){if(once())nameAll();});}catch(e){end();return Promise.resolve();}` +
238
+ `return new Promise(function(done){var timer=setTimeout(function(){end();done();},1500);` +
239
+ `var fin=function(){clearTimeout(timer);end();done();};try{Promise.resolve(vt&&vt.finished).then(fin,fin);}catch(e){fin();}});}` +
216
240
  `function refresh(){if(stopped)return;if(busy){again=true;return;}busy=true;again=false;` +
217
241
  `var q=location.search?location.search+"&fragment="+${JSON.stringify(fragmentName)}:"?fragment="+${JSON.stringify(fragmentName)};` +
218
242
  `fetch(${target},{redirect:"manual",cache:"no-store"})` +
219
243
  `.then(function(r){if(r.type==="opaqueredirect"||r.status===401||r.status===403){location.href="/login";return null;}` +
220
244
  `return r.ok?r.text():null;})` +
221
- `.then(function(t){if(t){var kept=keep();region.innerHTML=t;restore(kept);last=Date.now();failed=false;}else{failed=true;}})` +
245
+ `.then(function(t){if(t){return swap(t).then(function(){last=Date.now();failed=false;});}failed=true;})` +
222
246
  `.catch(function(){failed=true;})` +
223
247
  // A fragment that marks itself final stops: a finished or abandoned build is not fetched again.
224
248
  `.then(function(){busy=false;tell();if(region.querySelector("[data-region-stop]")){stopped=true;if(stamp)stamp.textContent="";return;}if(again)refresh();});}` +
@@ -760,3 +784,20 @@ export function oneLineOf(text, cap) {
760
784
  const flat = text.replace(/\s+/g, " ").trim();
761
785
  return flat.length <= cap ? flat : `${flat.slice(0, cap - 1)}…`;
762
786
  }
787
+ /**
788
+ * The board's clocks tick in the page: each <time data-tick> counts up from
789
+ * its datetime, measured against the server's own now (the board's data-now)
790
+ * so a skewed device clock cannot run them backwards. The server-rendered
791
+ * minutes are the answer without script.
792
+ */
793
+ export function boardTickScript(regionId = "board-region") {
794
+ return (`(function(){var seen=null,offset=0;var region=document.getElementById(${JSON.stringify(regionId)});` +
795
+ `function fmt(s){if(s<0)s=0;var h=Math.floor(s/3600),m=Math.floor(s%3600/60),x=s%60;return h>0?h+"h "+(m<10?"0":"")+m+"m":m+":"+(x<10?"0":"")+x;}` +
796
+ `function run(){var scope=region||document;var anchor=scope.querySelector("[data-now]");var v=anchor?anchor.getAttribute("data-now"):null;` +
797
+ `if(v!==null&&v!==seen){seen=v;var n=Number(v);offset=isFinite(n)?n-Date.now():0;}` +
798
+ `var now=Date.now()+offset;scope.querySelectorAll("time[data-tick]").forEach(function(t){var at=Date.parse(t.getAttribute("datetime")||"");if(isNaN(at))return;var text=fmt(Math.floor((now-at)/1000));if(t.textContent!==text)t.textContent=text;});}` +
799
+ // A swapped region brings back the server's minutes, so a swap (the region's children replaced — never a clock's
800
+ // own text) ticks at once and no clock flickers back to them. Hidden tabs do not tick.
801
+ `run();setInterval(function(){if(!document.hidden)run();},1000);document.addEventListener("visibilitychange",function(){if(!document.hidden)run();});` +
802
+ `if(region&&typeof MutationObserver==="function")new MutationObserver(run).observe(region,{childList:true});})();`);
803
+ }