toolroll 0.9.67 → 0.9.69

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 (250) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/acp-client.js +1 -3
  3. package/dist/agent-board.d.ts +0 -1
  4. package/dist/agent-board.js +0 -1
  5. package/dist/agent-fence.d.ts +58 -0
  6. package/dist/agent-fence.js +214 -19
  7. package/dist/agentconfig.d.ts +1 -1
  8. package/dist/agentconfig.js +6 -2
  9. package/dist/batch-checks.js +14 -26
  10. package/dist/browser/workspace.css +1 -1
  11. package/dist/browser/workspace.js +31 -29
  12. package/dist/builder.d.ts +9 -0
  13. package/dist/builder.js +30 -27
  14. package/dist/canonical-json.d.ts +14 -0
  15. package/dist/canonical-json.js +36 -0
  16. package/dist/chat-actions.js +1 -2
  17. package/dist/chat-admission.d.ts +19 -0
  18. package/dist/chat-admission.js +22 -0
  19. package/dist/chat-channel.js +1 -1
  20. package/dist/chat-delivery.js +0 -1
  21. package/dist/chat-follow.d.ts +17 -0
  22. package/dist/chat-follow.js +32 -0
  23. package/dist/chat-messages.js +0 -1
  24. package/dist/chat-settings-page.d.ts +23 -0
  25. package/dist/chat-settings-page.js +41 -0
  26. package/dist/check-access.d.ts +54 -0
  27. package/dist/check-access.js +80 -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 +39 -5
  99. package/dist/invoke.js +181 -24
  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.js +2 -1
  108. package/dist/live.d.ts +0 -1
  109. package/dist/live.js +1 -2
  110. package/dist/mcp-core.js +0 -2
  111. package/dist/memory-pass.js +1 -1
  112. package/dist/model-catalog.js +1 -1
  113. package/dist/modes.js +1 -12
  114. package/dist/operate.d.ts +4 -0
  115. package/dist/operate.js +456 -750
  116. package/dist/password-bearer.d.ts +0 -2
  117. package/dist/password-bearer.js +0 -4
  118. package/dist/peek-cli.js +2 -1
  119. package/dist/peek.d.ts +2 -26
  120. package/dist/peek.js +2 -168
  121. package/dist/phase-routing.d.ts +0 -4
  122. package/dist/phase-routing.js +2 -21
  123. package/dist/planner-source.d.ts +2 -1
  124. package/dist/planner-source.js +2 -12
  125. package/dist/planner.js +2 -14
  126. package/dist/process-tree.d.ts +0 -2
  127. package/dist/process-tree.js +0 -10
  128. package/dist/project-delete-ui.d.ts +16 -0
  129. package/dist/project-delete-ui.js +60 -1
  130. package/dist/project-delete.d.ts +0 -2
  131. package/dist/project-delete.js +1 -3
  132. package/dist/project-learning.d.ts +0 -14
  133. package/dist/project-skills.d.ts +0 -1
  134. package/dist/project-skills.js +0 -14
  135. package/dist/project-tools.js +2 -10
  136. package/dist/provider.d.ts +0 -17
  137. package/dist/provider.js +0 -26
  138. package/dist/pulls.d.ts +0 -2
  139. package/dist/pulls.js +0 -9
  140. package/dist/release-adapters.d.ts +3 -0
  141. package/dist/release-adapters.js +15 -5
  142. package/dist/remote-exec.d.ts +3 -2
  143. package/dist/remote-exec.js +3 -2
  144. package/dist/repos.d.ts +0 -5
  145. package/dist/repos.js +2 -10
  146. package/dist/repository-context.d.ts +0 -2
  147. package/dist/repository-context.js +0 -4
  148. package/dist/restart-certification.d.ts +0 -2
  149. package/dist/restart-certification.js +1 -5
  150. package/dist/result-follow-ups.d.ts +1 -4
  151. package/dist/result-follow-ups.js +11 -21
  152. package/dist/result-review.d.ts +0 -2
  153. package/dist/result-review.js +0 -2
  154. package/dist/retention.d.ts +0 -1
  155. package/dist/retention.js +0 -1
  156. package/dist/review-context.d.ts +0 -1
  157. package/dist/review-context.js +1 -12
  158. package/dist/reviewer.d.ts +0 -16
  159. package/dist/reviewer.js +0 -18
  160. package/dist/run-closeout.js +2 -1
  161. package/dist/scope.d.ts +9 -0
  162. package/dist/scope.js +13 -18
  163. package/dist/scout-browser.js +2 -1
  164. package/dist/serve.js +11 -2
  165. package/dist/server/console.css +5 -133
  166. package/dist/server/flows.js +2 -2
  167. package/dist/server/http.d.ts +4 -0
  168. package/dist/server/http.js +5 -0
  169. package/dist/server/pages.js +2 -2
  170. package/dist/server/people-tokens.js +3 -4
  171. package/dist/server/remote.js +1 -6
  172. package/dist/server/render-chat.d.ts +9 -2
  173. package/dist/server/render-chat.js +53 -7
  174. package/dist/server/render-results.d.ts +3 -6
  175. package/dist/server/render-results.js +4 -9
  176. package/dist/server/render-tasks.js +6 -6
  177. package/dist/server/route-table.d.ts +0 -4
  178. package/dist/server/route-table.js +1 -6
  179. package/dist/server/settings.js +60 -23
  180. package/dist/server/task-views.js +1 -1
  181. package/dist/server/tasks.js +5 -3
  182. package/dist/setup-guide.d.ts +2 -2
  183. package/dist/skills.d.ts +0 -6
  184. package/dist/skills.js +0 -8
  185. package/dist/slack-chat.d.ts +1 -7
  186. package/dist/slack-chat.js +5 -18
  187. package/dist/slack-settings.d.ts +2 -6
  188. package/dist/slack-settings.js +10 -38
  189. package/dist/slack.js +12 -23
  190. package/dist/sleep.d.ts +2 -0
  191. package/dist/sleep.js +2 -0
  192. package/dist/store.d.ts +29 -75
  193. package/dist/store.js +89 -180
  194. package/dist/structured-output.d.ts +0 -1
  195. package/dist/structured-output.js +0 -1
  196. package/dist/subagent-work.d.ts +0 -2
  197. package/dist/subagent-work.js +0 -2
  198. package/dist/subagents.d.ts +1 -3
  199. package/dist/subagents.js +20 -33
  200. package/dist/subscription-chat.d.ts +1 -3
  201. package/dist/subscription-chat.js +2 -3
  202. package/dist/surface.js +2 -1
  203. package/dist/task-sizing.d.ts +1 -4
  204. package/dist/task-sizing.js +3 -10
  205. package/dist/teams-chat.js +4 -11
  206. package/dist/teams.js +12 -19
  207. package/dist/telegram-lead.js +2 -2
  208. package/dist/telegram.js +1 -1
  209. package/dist/token-notices.d.ts +0 -11
  210. package/dist/token-notices.js +1 -5
  211. package/dist/tool-secret-store.d.ts +0 -3
  212. package/dist/tool-secret-store.js +2 -11
  213. package/dist/toolroll-update.d.ts +0 -2
  214. package/dist/toolroll-update.js +2 -2
  215. package/dist/verification-evidence.d.ts +0 -1
  216. package/dist/verification-evidence.js +0 -1
  217. package/dist/webhooks.d.ts +0 -1
  218. package/dist/webhooks.js +0 -1
  219. package/dist/workspace-ui.d.ts +0 -8
  220. package/dist/workspace-ui.js +1 -6
  221. package/package.json +1 -1
  222. package/dist/contracts/native-census.d.ts +0 -42
  223. package/dist/contracts/native-census.js +0 -36
  224. package/dist/process-recovery-anchor.d.ts +0 -75
  225. package/dist/process-recovery-anchor.js +0 -188
  226. package/dist/process-recovery-coalition.d.ts +0 -25
  227. package/dist/process-recovery-coalition.js +0 -105
  228. package/dist/process-recovery-eligibility.d.ts +0 -179
  229. package/dist/process-recovery-eligibility.js +0 -174
  230. package/dist/process-recovery-native.d.ts +0 -161
  231. package/dist/process-recovery-native.js +0 -595
  232. package/dist/process-recovery-provenance.d.ts +0 -163
  233. package/dist/process-recovery-provenance.js +0 -292
  234. package/dist/process-recovery-services.d.ts +0 -56
  235. package/dist/process-recovery-services.js +0 -191
  236. package/dist/process-recovery-settlement.d.ts +0 -32
  237. package/dist/process-recovery-settlement.js +0 -133
  238. package/dist/process-recovery.d.ts +0 -41
  239. package/dist/process-recovery.js +0 -91
  240. package/dist/session-brief.d.ts +0 -8
  241. package/dist/session-brief.js +0 -24
  242. package/dist/session-cli.d.ts +0 -42
  243. package/dist/session-cli.js +0 -271
  244. package/dist/session-contract.d.ts +0 -183
  245. package/dist/session-contract.js +0 -122
  246. package/dist/session-remote.d.ts +0 -19
  247. package/dist/session-remote.js +0 -86
  248. package/dist/session-service.d.ts +0 -22
  249. package/dist/session-service.js +0 -157
  250. package/dist/team-cli.js +0 -692
@@ -11,7 +11,7 @@ import { handlersOf } from './handler-registry.js';
11
11
  import { closedQuestionWords } from "../decision.js";
12
12
  import { DEPRECATED_PAGE } from "../deprecations.js";
13
13
  /** tasks handlers, moved without changing their route bodies. */
14
- import { html, htmlString, joinHtml, postForm, textHtml } from "../html.js";
14
+ import { html, htmlString, joinHtml, textHtml } from "../html.js";
15
15
  import { createHash, randomBytes, randomUUID } from "node:crypto";
16
16
  import { lstatSync, realpathSync } from "node:fs";
17
17
  import {} from "node:http";
@@ -40,7 +40,7 @@ import { isTaskSize, PHASES as ROUTE_PHASES } from "../phase-routing.js";
40
40
  import { applyModeToNewFiling, authorizePlanUnderMode } from "../plan-auto.js";
41
41
  import { milestonesOf, parseExecutionPlanDocument } from "../plan.js";
42
42
  import { levelOfProfile } from "../policy.js";
43
- import { authorizedProject, canonicalProject, projectName, rowVisible } from "../project.js";
43
+ import { authorizedProject, canonicalProject, projectName, } from "../project.js";
44
44
  import { fileTaskProposal } from "../proposal.js";
45
45
  import { isProviderId, PROVIDER_IDS, validateSpec } from "../provider.js";
46
46
  import { completeAndOpenPullRequest, mergeAsPerson } from '../pull-request-flow.js';
@@ -69,7 +69,7 @@ import { ACCEPT_ANYWAY_NEEDS_REASON, editorFileHref, proofBundleView, rankReview
69
69
  import { approvalFormDigest, approveRefusalWords, checkProgressHtml, consentDoorOf, decisionPage, nonceHashOf, PermissionsWouldChange, requirementsFromEditor, resumeDigestOf, taskOf } from "./render-tasks.js";
70
70
  import {} from "./session.js";
71
71
  export function createTasksHandlers(runtime) {
72
- const { store, peekSay, peekCache, PEEK_CACHE_TTL_MS, peekInFlight, PEEK_GLOBAL_INFLIGHT, peekBySession, PEEK_SESSION_INFLIGHT, clock, peekName, PEEK_FRAGMENT_BYTES, peekEvict, evidenceRoot, sendScreen, chromeFor, visible, consumeApprovalNonce, authenticateApprover, options, mintApprovalNonce, taskScreen, unscopedMode, admissionList, planViewOf, revisionViewOf, runIsTaskResult, leadPrincipal, familyOf, armTaskResume, runIsLive, revisionLedgerOf, resultDetailOf, reviewFactsFor, taskRepoOf, runVisible, restricted, codingActorAllowed, codingProjectAllowed, managedRepos, routeViewOf, workAccess, firstRunStepsNow, revisionDocOf, failureOf, dockedConversation, planContractViewOf, familiesInView, explainAttempt, taskChatFocus, familyTasksInView, taskRooms, identify, liveCeiling, projectOf, ceiling, bustBadge, revisionDestination } = runtime;
72
+ const { store, peekSay, peekCache, PEEK_CACHE_TTL_MS, peekInFlight, PEEK_GLOBAL_INFLIGHT, peekBySession, PEEK_SESSION_INFLIGHT, clock, peekName, PEEK_FRAGMENT_BYTES, peekEvict, evidenceRoot, sendScreen, chromeFor, visible, consumeApprovalNonce, authenticateApprover, options, mintApprovalNonce, taskScreen, unscopedMode, admissionList, planViewOf, revisionViewOf, runIsTaskResult, leadPrincipal, familyOf, armTaskResume, runIsLive, revisionLedgerOf, resultDetailOf, reviewFactsFor, taskRepoOf, runVisible, restricted, codingActorAllowed, codingProjectAllowed, managedRepos, routeViewOf, workAccess, firstRunStepsNow, revisionDocOf, failureOf, dockedConversation, planContractViewOf, familiesInView, explainAttempt, taskChatFocus, familyTasksInView, liveCeiling, projectOf, ceiling, bustBadge, revisionDestination } = runtime;
73
73
  /** A row admitted the address but its path parameter does not parse: the console's unknown page. */
74
74
  function noPage(who, response) {
75
75
  return refuse(response, who, 404, "There's no page at this address.", "/chat");
@@ -2846,6 +2846,8 @@ export function createTasksHandlers(runtime) {
2846
2846
  } })(),
2847
2847
  // What its network was and what its proxy saw (egress.ts), display only.
2848
2848
  network: store.runNetwork(run.id),
2849
+ // How far its checks reached (check-access.ts), display only.
2850
+ checkAccess: store.runCheckAccess(run.id),
2849
2851
  reviewRetry: store.reviewRetryStateOf(run.id),
2850
2852
  review: reviewFactsFor(run.id),
2851
2853
  notes: store.notesForRun(run.id),
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
15
15
  } | null;
16
16
  /** Browser installation may write only this known file inside its project. */
17
17
  export declare function previewProjectInstructions(repo: string): {
18
- content?: never;
19
18
  ok: false;
20
19
  message: string;
21
20
  plan?: never;
21
+ content?: never;
22
22
  fingerprint?: never;
23
23
  installed?: never;
24
24
  } | {
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
30
30
  installed: boolean;
31
31
  };
32
32
  export declare function addProjectInstructions(repo: string, fingerprint: string): {
33
- content?: never;
34
33
  ok: false;
35
34
  message: string;
36
35
  plan?: never;
36
+ content?: never;
37
37
  fingerprint?: never;
38
38
  installed?: never;
39
39
  } | import("./skills.js").InstallResult;
package/dist/skills.d.ts CHANGED
@@ -33,12 +33,6 @@ export declare const CONTEXT_END = "<!-- standing-orders:end -->";
33
33
  export declare function skillContent(): string;
34
34
  /** The AGENTS.md block, marker-fenced so reinstalls replace only themselves. */
35
35
  export declare function contextBlock(): string;
36
- /**
37
- * Whether a repository has the Toolroll skill: a SKILL.md in the current
38
- * folder, or in the one installs before the rename used. The setup guide
39
- * and the console's first-run checklist both ask this, so they agree.
40
- */
41
- export declare function projectSkillInstalled(repo: string): boolean;
42
36
  export type InstallPlan = {
43
37
  skillPath: string;
44
38
  skillAction: "create" | "replace" | "refuse-foreign";
package/dist/skills.js CHANGED
@@ -72,14 +72,6 @@ in the plane: read \`skills get knowledge\`, search it with
72
72
  merge anything yourself.
73
73
  ${CONTEXT_END}`;
74
74
  }
75
- /**
76
- * Whether a repository has the Toolroll skill: a SKILL.md in the current
77
- * folder, or in the one installs before the rename used. The setup guide
78
- * and the console's first-run checklist both ask this, so they agree.
79
- */
80
- export function projectSkillInstalled(repo) {
81
- return [SKILL_DIR, LEGACY_SKILL_DIR].some(dir => existsSync(join(repo, dir, SKILL_FILE)));
82
- }
83
75
  /** What install would do, computed without writing anything. */
84
76
  export function planInstall(repo, writeContext) {
85
77
  const skillPath = join(repo, SKILL_DIR, SKILL_FILE);
@@ -1,4 +1,4 @@
1
- import { ChatState, type ChatBinding, type ChatContent, type ChatEvent } from "./chat-delivery-state.js";
1
+ import { ChatState } from "./chat-delivery-state.js";
2
2
  import { uploadSlackBytes, type SlackApi, type SlackIdentity } from "./slack-api.js";
3
3
  import { type Store } from "./store.js";
4
4
  import type { SubscriptionLeadRunner } from "./subscription-chat.js";
@@ -19,12 +19,6 @@ export type SlackChatOptions = {
19
19
  /** Synchronous receipt before ACK. Never stores response_url, raw tokens, or a pairing code. */
20
20
  export declare function receiveSlack(state: ChatState, identity: SlackIdentity, type: string, raw: unknown, now: Date): boolean;
21
21
  export declare const processSlackEvent: (options: SlackChatOptions) => Promise<boolean>;
22
- export declare const applySlackAction: (options: SlackChatOptions, event: ChatEvent, binding: ChatBinding, repos: readonly string[]) => {
23
- merge: import("./chat-decide.js").ChatMerge;
24
- part: number;
25
- shown: string;
26
- content: ChatContent;
27
- } | null;
28
22
  export declare const planSlackNotifications: (options: SlackChatOptions) => Promise<void>;
29
23
  export declare const planSlackRooms: (options: SlackChatOptions) => Promise<void>;
30
24
  export declare function slackBlocks(text: string, buttons?: Record<string, unknown>[]): Record<string, unknown>[];
@@ -2,9 +2,8 @@ import { chatFlowButtons } from "./chat-flow.js";
2
2
  import { refuseResultShots, resultShotsPruned, shotWaitsUntil } from "./result-shots.js";
3
3
  import { chatQuestionButtons } from "./subagent-question.js";
4
4
  import { chatAskButtons } from "./chat-ask.js";
5
- import { channelInbox } from "./chat-inbox.js";
6
- import { roomCommand } from "./chat-rooms.js";
7
- import { processChatEvent, applyChatAction, planChatNotifications, channelAccess, planRoomMessages, heldForWatch, } from "./chat-delivery.js";
5
+ import { admitChatEvent } from "./chat-admission.js";
6
+ import { processChatEvent, planChatNotifications, channelAccess, planRoomMessages, heldForWatch, } from "./chat-delivery.js";
8
7
  /** Slack is a transport for the same saved assistant and confirmation doors. */
9
8
  import { proposalPreview, proposalLink, proposalOutcomeText, armedCardText, armedYesLabel, } from "./chat-channel.js";
10
9
  import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
@@ -100,21 +99,10 @@ export function receiveSlack(state, identity, type, raw, now) {
100
99
  // A channel or private group is a room: accepted only while it follows a
101
100
  // conversation, or for the `/team` words that make it follow one.
102
101
  const isRoom = !slackId(channel, "D");
103
- const words = "text" in payload ? payload.text ?? "" : "";
104
- // v89: a channel that feeds a flow takes anyone's message (as a card, with no say over anything); "flow 12" connects one.
105
- const inbox = isRoom && kind === "message" ? channelInbox(state.store, "slack", identity.installation, String(channel), words) : { watched: false, command: false };
106
- const roomish = isRoom && (state.room(identity.installation, String(channel)) !== null || (kind === "message" && roomCommand(words) !== null) || inbox.watched || inbox.command);
107
- if (isRoom && !roomish)
108
- return false;
109
- const binding = state.bindingFor(identity.installation, member);
110
- const open = inbox.watched && !inbox.command;
111
- if (kind !== "pair" && !open &&
112
- (!binding ||
113
- !state.live(binding) ||
114
- (binding.channel !== channel && !roomish)))
115
- return false;
116
- if (kind === "pair" && (binding || isRoom))
102
+ const admitted = admitChatEvent(state, "slack", identity.installation, { kind, member, channel, isRoom, payload });
103
+ if (admitted === null)
117
104
  return false;
105
+ const { binding } = admitted;
118
106
  return state.enqueue({
119
107
  id,
120
108
  installation: identity.installation,
@@ -139,7 +127,6 @@ const delivery = (options) => ({
139
127
  }),
140
128
  });
141
129
  export const processSlackEvent = (options) => processChatEvent(delivery(options));
142
- export const applySlackAction = (options, event, binding, repos) => applyChatAction(delivery(options), event, binding, repos);
143
130
  export const planSlackNotifications = (options) => planChatNotifications(delivery(options));
144
131
  export const planSlackRooms = (options) => planRoomMessages(delivery(options));
145
132
  const access = (options, binding, ceiling) => channelAccess(delivery(options), binding, ceiling);
@@ -1,8 +1,4 @@
1
1
  import type { Store } from "./store.js";
2
2
  import { type Html } from "./html.js";
3
- export declare function slackSettingsHtml(store: Store, dir: string, options?: {
4
- code?: string;
5
- problem?: string;
6
- now?: Date;
7
- who?: string;
8
- }): Html;
3
+ import { type ChatSettingsOptions } from "./chat-settings-page.js";
4
+ export declare function slackSettingsHtml(store: Store, dir: string, options?: ChatSettingsOptions): Html;
@@ -1,41 +1,13 @@
1
- import { loadPrimary } from "./webhooks.js";
2
- import { html, postForm } from "./html.js";
1
+ import { html } from "./html.js";
3
2
  import { loadSlackCredentials } from "./slack-api.js";
4
- import { ChatState } from "./chat-delivery-state.js";
3
+ import { chatSettingsHtml } from "./chat-settings-page.js";
5
4
  export function slackSettingsHtml(store, dir, options = {}) {
6
- const credentials = loadSlackCredentials(dir), state = new ChatState(store, "slack"), bindings = credentials ? state.bindings(credentials.installation).filter(one => state.live(one)) : [], binding = options.who === undefined ? null : bindings.find(one => one.approver === options.who) ?? null, others = bindings.length - (binding === null ? 0 : 1);
7
- const runtime = credentials ? state.runtime(credentials.installation) : null;
8
- const now = options.now ?? new Date(), live = runtime &&
9
- runtime.leaseUntil !== null &&
10
- runtime.leaseUntil > now.toISOString() &&
11
- runtime.connected;
12
- const password = html `<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>`;
13
- const post = (action, content) => postForm(`/settings/slack/${action}`, content, { attrs: { class: "card" } });
14
- const parts = [];
15
- if (!credentials) {
16
- parts.push(html `<p>Manage projects and review results in a private Slack conversation.</p>`, html `<details open><summary>Create your Slack app</summary><ol><li>Download the <a href="/settings/slack/manifest">app manifest</a>, then <a href="https://api.slack.com/apps" target="_blank" rel="noopener noreferrer">create an app</a> in your workspace using that manifest.</li><li>Under Basic Information, create an app-level token with <code>connections:write</code>.</li><li>Under OAuth &amp; Permissions, install the app and copy its bot token.</li></ol></details>`, post("connect", html `<label>App token<input style="min-height:44px" type="password" name="app-token" autocomplete="off" placeholder="xapp-…" required></label><label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" placeholder="xoxb-…" required></label>${password}<p class="meta">Tokens stay on this installation. Next, pair your own Slack account.</p><button type="submit">Connect Slack</button>`));
17
- }
18
- else {
19
- parts.push(html `<p><strong>${credentials.workspace}</strong> · ${live ? "Connected" : "Waiting for connection"}</p>`);
20
- if (runtime?.problem)
21
- parts.push(html `<p role="status">${String(runtime.problem)}</p>`);
22
- if (!live && !runtime?.problem)
23
- parts.push(html `<p>Keep the Toolroll worker running to receive Slack messages.</p>`);
24
- if (options.code) {
25
- parts.push(html `<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll Slack app. It expires in 10 minutes.</p>`, html `<label>Pairing message<input type="text" readonly value="pair ${options.code}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>`, html `<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/slack">Check connection</a>`);
26
- }
27
- else if (binding && state.live(binding)) {
28
- const { pending, dropped: failed } = state.partCounts(binding.id);
29
- parts.push(html `<p>Your Slack account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>`, html `<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>`, html `<p><a href="/chat">Open saved chat</a></p>`, loadPrimary(process.env, dir) === "slack"
30
- ? html `<p>Task updates are sent here.</p>`
31
- : post("alerts", html `<button type="submit">Send task updates here</button>`));
32
- }
33
- else {
34
- parts.push(post("pair", html `<h2>Pair your account</h2><p>Your paired Slack account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : ""}</p>${password}<button type="submit">Create pairing code</button>`));
35
- }
36
- if (binding && state.live(binding) && !options.code)
37
- parts.push(post("unpair", html `${password}<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>`));
38
- parts.push(html `<details><summary>Connection settings</summary>${post("disconnect", html `${password}<button type="submit">Disconnect Slack</button>`)}</details>`);
39
- }
40
- return html `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Slack</h1>${options.problem ? html `<p class="problem" role="alert">${options.problem}</p>` : ""}${parts}<details><summary>Access and setup</summary><p>Private messages and threads use your Toolroll permissions. Shared channels are not supported. Secure review links use this installation’s configured HTTPS console address.</p><p>Socket Mode connects out to Slack. It does not need a public webhook or a Tailscale account.</p></details></section>`;
5
+ return chatSettingsHtml(store, dir, options, {
6
+ provider: "slack",
7
+ label: "Slack",
8
+ credentials: loadSlackCredentials(dir),
9
+ setup: html `<details open><summary>Create your Slack app</summary><ol><li>Download the <a href="/settings/slack/manifest">app manifest</a>, then <a href="https://api.slack.com/apps" target="_blank" rel="noopener noreferrer">create an app</a> in your workspace using that manifest.</li><li>Under Basic Information, create an app-level token with <code>connections:write</code>.</li><li>Under OAuth &amp; Permissions, install the app and copy its bot token.</li></ol></details>`,
10
+ connect: password => html `<label>App token<input style="min-height:44px" type="password" name="app-token" autocomplete="off" placeholder="xapp-…" required></label><label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" placeholder="xoxb-…" required></label>${password}<p class="meta">Tokens stay on this installation. Next, pair your own Slack account.</p><button type="submit">Connect Slack</button>`,
11
+ access: html `<p>Private messages and threads use your Toolroll permissions. Shared channels are not supported. Secure review links use this installation’s configured HTTPS console address.</p><p>Socket Mode connects out to Slack. It does not need a public webhook or a Tailscale account.</p>`,
12
+ });
41
13
  }
package/dist/slack.js CHANGED
@@ -1,19 +1,12 @@
1
1
  /** One leased Socket Mode connection, hosted by the ordinary worker. */
2
2
  import { randomBytes } from "node:crypto";
3
- import { setTimeout as sleep } from "node:timers/promises";
3
+ import { abortablePause, workChatQueue } from "./chat-follow.js";
4
4
  import { loadSlackCredentials, slackApi } from "./slack-api.js";
5
5
  import { ChatState } from "./chat-delivery-state.js";
6
6
  import { receiveSlack, processSlackEvent, deliverSlackPart, planSlackNotifications, planSlackRooms, } from "./slack-chat.js";
7
7
  export async function followSlack(options) {
8
8
  const owner = randomBytes(16).toString("hex"), state = new ChatState(options.store, "slack");
9
- const pause = async (ms) => {
10
- try {
11
- await sleep(ms, undefined, { signal: options.signal });
12
- }
13
- catch {
14
- /* shutdown */
15
- }
16
- };
9
+ const pause = abortablePause(options.signal);
17
10
  while (!options.signal.aborted) {
18
11
  const credentials = loadSlackCredentials(options.dir);
19
12
  if (!credentials ||
@@ -104,20 +97,16 @@ export async function followSlack(options) {
104
97
  };
105
98
  try {
106
99
  await client.start();
107
- while (same()) {
108
- if (!connected || state.retryAt(credentials.installation) > new Date().toISOString()) {
109
- await pause(1000);
110
- continue;
111
- }
112
- await processSlackEvent(chat);
113
- if (same())
114
- await planSlackRooms(chat);
115
- if (same())
116
- await deliverSlackPart(chat);
117
- if (same() && options.notifications())
118
- await planSlackNotifications(chat);
119
- await pause(1000);
120
- }
100
+ await workChatQueue({
101
+ same,
102
+ ready: () => connected && !(state.retryAt(credentials.installation) > new Date().toISOString()),
103
+ pause,
104
+ notifications: () => options.notifications(),
105
+ process: () => processSlackEvent(chat),
106
+ rooms: () => planSlackRooms(chat),
107
+ deliver: () => deliverSlackPart(chat),
108
+ notify: () => planSlackNotifications(chat),
109
+ });
121
110
  }
122
111
  catch {
123
112
  if (alive)
@@ -0,0 +1,2 @@
1
+ /** Wait `ms` milliseconds on the global timer (so fake timers in tests still drive it). */
2
+ export declare const sleep: (ms: number) => Promise<void>;
package/dist/sleep.js ADDED
@@ -0,0 +1,2 @@
1
+ /** Wait `ms` milliseconds on the global timer (so fake timers in tests still drive it). */
2
+ export const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
package/dist/store.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type WritesMode } from "./writes-mode.js";
2
- import { type ProjectNetwork, type RunNetworkRecord } from "./egress.js";
2
+ import { type CheckAccess } from "./check-access.js";
3
+ import { type NetworkProfile, type ProjectNetwork, type RunNetworkRecord } from "./egress.js";
3
4
  import { type AcpLaunch } from "./acp-attest.js";
4
5
  import { type ChatBinding } from "./chat-core.js";
5
6
  import { ChatMessages, TELEGRAM_HOLD_REASONS, TELEGRAM_SKIPPED_ELSEWHERE, TELEGRAM_SKIPPED_OTHER_CHAT, TELEGRAM_SKIPPED_QUIET } from "./chat-messages.js";
@@ -179,7 +180,7 @@ export type SealedLedgerEntry = LedgerEntry & {
179
180
  hash: string;
180
181
  } | null;
181
182
  };
182
- export declare const SCHEMA_VERSION = 131;
183
+ export declare const SCHEMA_VERSION = 132;
183
184
  /** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
184
185
  export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
185
186
  /**
@@ -318,6 +319,8 @@ export declare const V129_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
318
319
  export declare const CONFINEMENT_SCHEMA = "\nCREATE TABLE IF NOT EXISTS project_network (\n repo TEXT PRIMARY KEY,\n network_json TEXT NOT NULL CHECK (length(network_json) <= 16384),\n updated_at TEXT NOT NULL,\n updated_by TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS confinement_setting (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n unfenced_allowed INTEGER NOT NULL DEFAULT 0 CHECK (unfenced_allowed IN (0, 1)),\n updated_at TEXT,\n updated_by TEXT\n);\nINSERT OR IGNORE INTO confinement_setting (id, unfenced_allowed) VALUES (1, 0);\n";
319
320
  /** v131: where a project's builds may write (an approver's setting, agent-fence.ts WritesMode). */
320
321
  export declare const PROJECT_WRITES_SCHEMA = "\nCREATE TABLE IF NOT EXISTS project_writes (\n repo TEXT PRIMARY KEY,\n mode TEXT NOT NULL CHECK (mode IN ('workspace', 'anywhere')),\n updated_at TEXT NOT NULL,\n updated_by TEXT NOT NULL\n);\n";
322
+ /** v132: how far a project's checks reach beyond the check fence (check-access.ts CheckAccess). */
323
+ export declare const PROJECT_CHECK_ACCESS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS project_check_access (\n repo TEXT PRIMARY KEY,\n level TEXT NOT NULL CHECK (level IN ('sandboxed', 'sign-ins', 'unsandboxed')),\n updated_at TEXT NOT NULL,\n updated_by TEXT NOT NULL\n);\n";
321
324
  /** v129: a finished run's How it ran, derived from its own records on first view (display only: never a verdict). Its
322
325
  * parser version says when to derive again; `steps` says how complete the step detail was, and why not. */
323
326
  export declare const RUN_EFFICIENCY_SCHEMA = "\nCREATE TABLE IF NOT EXISTS run_efficiency (\n run INTEGER PRIMARY KEY REFERENCES run(id) ON DELETE CASCADE,\n parser_version INTEGER NOT NULL,\n steps TEXT NOT NULL CHECK (steps IN ('complete','truncated','expired','absent')),\n steps_reason TEXT,\n payload_json TEXT NOT NULL CHECK (length(payload_json) <= 16384),\n derived_at TEXT NOT NULL\n);\n";
@@ -415,8 +418,6 @@ type CancellationTransition = {
415
418
  changed: false;
416
419
  reason: CancellationRefusal;
417
420
  };
418
- /** States from which no further work is dispatched. */
419
- export declare const TERMINAL_STATES: readonly TaskState[];
420
421
  export type Task = {
421
422
  id: string;
422
423
  title: string;
@@ -486,18 +487,6 @@ export type TaskRef = {
486
487
  /** Recorded, not asserted: what the grant's selector is checked against. */
487
488
  origin: TaskOrigin;
488
489
  };
489
- /** A standing order: a pre-approved template whose instances build unattended. */
490
- export type ExecutionSlot = {
491
- id: number;
492
- runner: string;
493
- state: "reserved" | "running" | "released";
494
- run: number | null;
495
- incarnation: string | null;
496
- processGroup: number | null;
497
- reservedAt: string;
498
- runningAt: string | null;
499
- releasedAt: string | null;
500
- };
501
490
  /** Chat providers are direct API adapters — a DISJOINT type from the build
502
491
  * harness providers, by design (Codex v3 review, change 1). */
503
492
  export type DirectChatProviderId = "anthropic-api" | "openrouter-api";
@@ -999,8 +988,6 @@ export type Capability = {
999
988
  lastResult: string | null;
1000
989
  expiresAt: string | null;
1001
990
  };
1002
- /** `kind:name`, the unambiguous way a requirement names a capability. */
1003
- export declare function capabilityKey(capability: Pick<Capability, "kind" | "name">): string;
1004
991
  /** Parse `kind:name`; a bare name is refused rather than guessed at. */
1005
992
  export declare function parseCapabilityKey(key: string): {
1006
993
  kind: Capability["kind"];
@@ -2297,7 +2284,6 @@ export declare class Store {
2297
2284
  mirrorAllowsCompletion(taskId: string): boolean;
2298
2285
  /** Whether a stored acquire mutation exists — the replay marker's fact. */
2299
2286
  hasMutationRecord(mutation: Mutation): boolean;
2300
- enqueueExternalIntent(mirror: string, kind: "comment" | "transition" | "close", body: string | null, now: Date): void;
2301
2287
  pendingExternalIntents(limit?: number): {
2302
2288
  id: number;
2303
2289
  mirror: string;
@@ -4053,24 +4039,6 @@ export declare class Store {
4053
4039
  liveClaimByLease(leaseId: string, now: Date): {
4054
4040
  leaseId: string;
4055
4041
  } | null;
4056
- reserveExecutionSlots(runner: string, count: number, now: Date): number[];
4057
- /** reserved → running happens in the stamp-before-spawn moment, carrying
4058
- * the process group so recovery can ask the OS before freeing capacity. */
4059
- markSlotRunning(id: number, facts: {
4060
- run: number;
4061
- incarnation?: string | null;
4062
- processGroup?: number | null;
4063
- }, now: Date): boolean;
4064
- /** A correction process replaces the exited provider in the same open
4065
- * root attempt. It may refresh only that attempt's exact occupied slot. */
4066
- refreshSlotProcess(id: number, facts: {
4067
- run: number;
4068
- incarnation?: string | null;
4069
- processGroup: number;
4070
- }): boolean;
4071
- releaseExecutionSlot(id: number, now: Date): boolean;
4072
- liveSlotCount(runner: string): number;
4073
- getExecutionSlot(id: number): ExecutionSlot | null;
4074
4042
  /** Minted from a POST, never a GET. Bounded per approver; expired rows
4075
4043
  * are swept rather than accumulating. */
4076
4044
  mintCeremonyNonce(spec: {
@@ -4868,22 +4836,6 @@ export declare class Store {
4868
4836
  listTasksScoped(repo: string | null, state: TaskState | undefined, limit: number, selectedId: string | null): (Task & {
4869
4837
  repo: string | null;
4870
4838
  })[];
4871
- /**
4872
- * The Work destination's one bounded page (workspace package 1 query):
4873
- * the newest tasks the viewer may see, with the viewer's already-proved
4874
- * admission bound BEFORE the ordering and the limit — never a wider
4875
- * read post-filtered in the caller. `admitted` is the resolved
4876
- * permitted repo set: `null` means unrestricted (every placed row),
4877
- * `[]` means no placed project is permitted (no placed row at all).
4878
- * `includeUnplaced` says whether NULL-repo rows belong to this viewer;
4879
- * an account restricted to projects never sees them, so they must not
4880
- * spend its page. Every predicate is a bound parameter; the limit is
4881
- * clamped here, not trusted from the caller. Read-only; the legacy
4882
- * `listTasksScoped` is untouched.
4883
- */
4884
- listWorkTasksAdmitted(admitted: readonly string[] | null, includeUnplaced: boolean, limit: number): (Task & {
4885
- repo: string | null;
4886
- })[];
4887
4839
  /** Newest first, because the question is almost always "what just happened". */
4888
4840
  runsFor(taskRef: number): Run[];
4889
4841
  /**
@@ -5309,7 +5261,6 @@ export declare class Store {
5309
5261
  run: number;
5310
5262
  kind: Incident["kind"];
5311
5263
  }, now: Date): number;
5312
- incidentForRun(run: number): Incident | null;
5313
5264
  /** Unresolved, task attached, oldest first. No time window: these do not age out. */
5314
5265
  openIncidents(repo?: string | null): (Incident & {
5315
5266
  taskId: string;
@@ -5516,9 +5467,6 @@ export declare class Store {
5516
5467
  claimMergeIntent(id: number, by: string, leaseMs: number, now: Date): {
5517
5468
  generation: number;
5518
5469
  } | null;
5519
- /** Renew the lease AND re-prove the generation in one CAS — the last
5520
- * gate before the external call; a failed re-proof aborts the attempt. */
5521
- renewMergeClaim(id: number, generation: number, leaseMs: number, now: Date): boolean;
5522
5470
  /** STATE-SPECIFIC and TERMINALLY MONOTONIC (E1): the writer names the
5523
5471
  * state it believes it owns, and a terminal row is never overwritten —
5524
5472
  * a stale claim owner cannot un-merge what the observer already settled,
@@ -5583,12 +5531,6 @@ export declare class Store {
5583
5531
  lastError: string | null;
5584
5532
  firingDeadline: string | null;
5585
5533
  } | null;
5586
- latestObservation(githubRepo: string, prNumber: number): {
5587
- headSha: string;
5588
- state: string;
5589
- observedAt: string;
5590
- generation: number;
5591
- } | null;
5592
5534
  /** Revocation is immediate: intents not yet pushed die with the grant. */
5593
5535
  revokePublicationGrant(repo: string, by: string, now: Date): boolean;
5594
5536
  publicationGrantFor(repo: string): PublicationGrant | null;
@@ -6408,7 +6350,6 @@ export declare class Store {
6408
6350
  recentLeadTurns(approver: string, limit?: number): LeadTurn[];
6409
6351
  getLeadTurn(id: number): LeadTurn | null;
6410
6352
  liveLeadTurnFor(approver: string): LeadTurn | null;
6411
- leadStepsOf(leadTurn: number): ChatTurn[];
6412
6353
  /** Remember a project was opened. Upsert keeps added_at; recency always moves.
6413
6354
  * A project added for the first time starts on Quick checks. */
6414
6355
  upsertProject(path: string, name: string, now: Date, admission?: {
@@ -6469,17 +6410,6 @@ export declare class Store {
6469
6410
  repo: string | null;
6470
6411
  blockerRepo: string | null;
6471
6412
  }[];
6472
- /**
6473
- * Active work only, running first — the Work tab's page. Terminal history
6474
- * belongs to Done and the build ledger, never to this list.
6475
- */
6476
- listActiveWorkScoped(repo: string | null, now: Date, limit?: number): (Task & {
6477
- repo: string | null;
6478
- approved: boolean;
6479
- held: boolean;
6480
- buildingBy: string | null;
6481
- strikes: number;
6482
- })[];
6483
6413
  /**
6484
6414
  * Completed work, one row per done task with its final accepted run and
6485
6415
  * any publication attached — never composed from separate task and PR
@@ -7247,6 +7177,30 @@ export declare class Store {
7247
7177
  * removed or expired — any of those means the composer's draft was accepted and must not be sent again). */
7248
7178
  leadRequestReceived(session: number, request: string): boolean;
7249
7179
  forgetQueuedLeadMessage(message: number): void;
7180
+ /** How far a project's checks reach beyond the check fence: its saved level, or sandboxed. */
7181
+ projectCheckAccess(repo: string): CheckAccess;
7182
+ /** Who last changed a project's check access, and when (null: never changed). */
7183
+ projectCheckAccessChange(repo: string): {
7184
+ by: string;
7185
+ at: string;
7186
+ } | null;
7187
+ /** The level a run's setup and checks ran at (its sealed approval's, or unsandboxed where no OS fence exists). Its
7188
+ * follow-up and batch checks never run wider than this. */
7189
+ stampRunCheckAccess(runId: number, level: CheckAccess, network?: NetworkProfile | null): void;
7190
+ /** The network a run's sign-ins checks ran under (null: none sealed; such a run's later checks run sandboxed). */
7191
+ runCheckNetwork(runId: number): NetworkProfile | null;
7192
+ /** The level a run's checks ran at: missing or unreadable reads as sandboxed. */
7193
+ runCheckAccess(runId: number): CheckAccess;
7194
+ /** A project's check access changed: saved, and in the ledger as before → after. `may` is what the caller proved:
7195
+ * "widen" only after an instance operator's password typed now; anything else may only narrow. The level is read again
7196
+ * and compared in the same transaction as the write, so a change made while the caller was proving itself can't turn
7197
+ * a narrowing into a widening. Refused: null, nothing written. */
7198
+ setProjectCheckAccess(repo: string, level: CheckAccess, by: string, now: Date, authority: {
7199
+ may: "narrow" | "widen";
7200
+ }): {
7201
+ before: CheckAccess;
7202
+ after: CheckAccess;
7203
+ } | null;
7250
7204
  }
7251
7205
  /** The receipt a bot's first pairing writes over pre-pairing history: an
7252
7206
  * explicit skip, with no delivered timestamp and no attempt, so suppressed