toolroll 0.9.53 → 0.9.55

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 (254) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +5 -4
  3. package/dist/agent-onboard.js +4 -1
  4. package/dist/approval-rules-ui.d.ts +3 -2
  5. package/dist/approval-rules-ui.js +15 -20
  6. package/dist/assignment-ui.d.ts +3 -2
  7. package/dist/assignment-ui.js +4 -11
  8. package/dist/backup-ui.d.ts +2 -1
  9. package/dist/backup-ui.js +16 -27
  10. package/dist/brand-mark.d.ts +3 -2
  11. package/dist/brand-mark.js +4 -4
  12. package/dist/browser/workspace.css +1 -1
  13. package/dist/browser/workspace.js +25 -25
  14. package/dist/browser-shell.d.ts +2 -1
  15. package/dist/browser-shell.js +9 -9
  16. package/dist/browser-workspace.d.ts +2 -3
  17. package/dist/browser-workspace.js +0 -3
  18. package/dist/chat-ask.js +8 -8
  19. package/dist/chat-core.d.ts +171 -0
  20. package/dist/chat-core.js +288 -0
  21. package/dist/chat-decide.d.ts +10 -8
  22. package/dist/chat-decide.js +26 -21
  23. package/dist/chat-delivery-state.d.ts +51 -75
  24. package/dist/chat-delivery-state.js +140 -170
  25. package/dist/chat-delivery.d.ts +21 -2
  26. package/dist/chat-delivery.js +313 -194
  27. package/dist/chat-flow.d.ts +29 -1
  28. package/dist/chat-flow.js +24 -50
  29. package/dist/chat-messages.d.ts +319 -0
  30. package/dist/chat-messages.js +841 -0
  31. package/dist/chat-migration.d.ts +30 -0
  32. package/dist/chat-migration.js +802 -0
  33. package/dist/chat-polish.d.ts +5 -4
  34. package/dist/chat-polish.js +9 -10
  35. package/dist/chat-quiet.d.ts +3 -0
  36. package/dist/chat-quiet.js +5 -0
  37. package/dist/check-levels-ui.d.ts +2 -1
  38. package/dist/check-levels-ui.js +26 -53
  39. package/dist/cli.d.ts +3 -1
  40. package/dist/cli.js +9 -3
  41. package/dist/coding-shipping-ui.d.ts +2 -1
  42. package/dist/coding-shipping-ui.js +6 -6
  43. package/dist/coding-ui.d.ts +3 -1
  44. package/dist/coding-ui.js +30 -29
  45. package/dist/contracts/chat-content.d.ts +71 -2
  46. package/dist/contracts/chat-content.js +22 -3
  47. package/dist/contracts/chat-tables.d.ts +11 -4
  48. package/dist/contracts/chat-tables.js +165 -87
  49. package/dist/contracts/cli.d.ts +1 -3
  50. package/dist/contracts/console-api.d.ts +1 -1
  51. package/dist/contracts/console-api.js +6 -5
  52. package/dist/contracts/discord-callback.js +2 -1
  53. package/dist/contracts/health.d.ts +1 -3
  54. package/dist/contracts/slack-callback.js +3 -2
  55. package/dist/contracts/store-json.d.ts +1 -5
  56. package/dist/contracts/store-json.js +1 -4
  57. package/dist/contracts/teams-callback.js +2 -1
  58. package/dist/control-ui.d.ts +5 -4
  59. package/dist/control-ui.js +9 -34
  60. package/dist/credentials-ui.d.ts +5 -4
  61. package/dist/credentials-ui.js +23 -24
  62. package/dist/demo-chat.d.ts +3 -3
  63. package/dist/demo-chat.js +29 -76
  64. package/dist/discord-chat.js +19 -38
  65. package/dist/discord-settings.d.ts +3 -2
  66. package/dist/discord-settings.js +21 -55
  67. package/dist/discord.js +4 -14
  68. package/dist/envelope.js +10 -0
  69. package/dist/evidence-pack.d.ts +2 -1
  70. package/dist/evidence-pack.js +45 -49
  71. package/dist/export-ui.d.ts +7 -2
  72. package/dist/export-ui.js +4 -10
  73. package/dist/export.js +4 -4
  74. package/dist/flow-gallery-ui.d.ts +4 -3
  75. package/dist/flow-gallery-ui.js +28 -43
  76. package/dist/flow-starters-ui.d.ts +2 -1
  77. package/dist/flow-starters-ui.js +12 -15
  78. package/dist/flow-triggers.d.ts +1 -0
  79. package/dist/flow-triggers.js +16 -14
  80. package/dist/flows-ui.d.ts +4 -3
  81. package/dist/flows-ui.js +19 -30
  82. package/dist/flows.js +2 -1
  83. package/dist/guides.js +108 -3
  84. package/dist/html.d.ts +76 -0
  85. package/dist/html.js +173 -0
  86. package/dist/integrations-ui.d.ts +2 -1
  87. package/dist/integrations-ui.js +19 -23
  88. package/dist/kits-ui.d.ts +5 -4
  89. package/dist/kits-ui.js +18 -20
  90. package/dist/knowledge-ui.d.ts +6 -5
  91. package/dist/knowledge-ui.js +26 -25
  92. package/dist/lead-context.d.ts +11 -1
  93. package/dist/lead-context.js +6 -8
  94. package/dist/ledger-view.d.ts +2 -1
  95. package/dist/ledger-view.js +27 -29
  96. package/dist/limits-ui.d.ts +2 -1
  97. package/dist/limits-ui.js +7 -7
  98. package/dist/live-bus.d.ts +50 -8
  99. package/dist/live-bus.js +133 -50
  100. package/dist/mate-tools.js +3 -2
  101. package/dist/mcp-oauth.js +35 -36
  102. package/dist/models-ui.d.ts +2 -1
  103. package/dist/models-ui.js +25 -35
  104. package/dist/modes.d.ts +13 -1
  105. package/dist/modes.js +17 -1
  106. package/dist/monitoring-ui.d.ts +4 -3
  107. package/dist/monitoring-ui.js +13 -26
  108. package/dist/notification-rows.d.ts +30 -0
  109. package/dist/notification-rows.js +102 -0
  110. package/dist/openrouter-models.d.ts +2 -1
  111. package/dist/openrouter-models.js +5 -9
  112. package/dist/operate.js +16 -9
  113. package/dist/people-audit-ui.d.ts +2 -1
  114. package/dist/people-audit-ui.js +13 -13
  115. package/dist/plane-review.js +5 -11
  116. package/dist/policy-ui.d.ts +3 -2
  117. package/dist/policy-ui.js +17 -23
  118. package/dist/project-delete-ui.d.ts +4 -3
  119. package/dist/project-delete-ui.js +15 -27
  120. package/dist/project-delete.js +21 -26
  121. package/dist/pull-request-ui.d.ts +2 -1
  122. package/dist/pull-request-ui.js +17 -32
  123. package/dist/recipe-ui.d.ts +8 -7
  124. package/dist/recipe-ui.js +39 -32
  125. package/dist/release-adapters.d.ts +60 -0
  126. package/dist/release-adapters.js +435 -0
  127. package/dist/release-cli.d.ts +24 -0
  128. package/dist/release-cli.js +181 -0
  129. package/dist/release-gate.d.ts +80 -0
  130. package/dist/release-gate.js +150 -0
  131. package/dist/release-links.d.ts +29 -0
  132. package/dist/release-links.js +102 -0
  133. package/dist/release.d.ts +331 -0
  134. package/dist/release.js +550 -0
  135. package/dist/reply-shape.d.ts +2 -1
  136. package/dist/reply-shape.js +5 -5
  137. package/dist/repository-context-ui.d.ts +2 -1
  138. package/dist/repository-context-ui.js +2 -7
  139. package/dist/request-budget-ui.d.ts +2 -1
  140. package/dist/request-budget-ui.js +16 -16
  141. package/dist/result-review.d.ts +2 -3
  142. package/dist/result-review.js +3 -9
  143. package/dist/result-shots.js +2 -2
  144. package/dist/retention-ui.d.ts +7 -1
  145. package/dist/retention-ui.js +7 -11
  146. package/dist/retention.js +1 -1
  147. package/dist/serve.d.ts +11 -2
  148. package/dist/serve.js +69 -58
  149. package/dist/server/chat.d.ts +3 -6
  150. package/dist/server/chat.js +969 -1006
  151. package/dist/server/chrome.d.ts +291 -0
  152. package/dist/server/chrome.js +747 -0
  153. package/dist/server/console.css +2819 -0
  154. package/dist/server/flows.d.ts +2 -6
  155. package/dist/server/flows.js +840 -766
  156. package/dist/server/guards.d.ts +1 -1
  157. package/dist/server/guards.js +5 -2
  158. package/dist/server/handler-context.d.ts +1 -1
  159. package/dist/server/handler-context.js +1 -1
  160. package/dist/server/handler-registry.d.ts +8 -6
  161. package/dist/server/handler-registry.js +14 -2
  162. package/dist/server/http.d.ts +274 -0
  163. package/dist/server/http.js +239 -0
  164. package/dist/server/live.d.ts +34 -0
  165. package/dist/server/live.js +309 -0
  166. package/dist/server/pages.d.ts +2 -6
  167. package/dist/server/pages.js +808 -789
  168. package/dist/server/people-tokens.d.ts +1 -6
  169. package/dist/server/people-tokens.js +580 -572
  170. package/dist/server/remote.d.ts +4 -7
  171. package/dist/server/remote.js +37 -70
  172. package/dist/server/render-chat.d.ts +402 -0
  173. package/dist/server/render-chat.js +927 -0
  174. package/dist/server/render-pages.d.ts +535 -0
  175. package/dist/server/render-pages.js +1277 -0
  176. package/dist/server/render-people.d.ts +32 -0
  177. package/dist/server/render-people.js +121 -0
  178. package/dist/server/render-results.d.ts +744 -0
  179. package/dist/server/render-results.js +2237 -0
  180. package/dist/server/render-settings.d.ts +147 -0
  181. package/dist/server/render-settings.js +577 -0
  182. package/dist/server/render-tasks.d.ts +632 -0
  183. package/dist/server/render-tasks.js +2345 -0
  184. package/dist/server/request-context.d.ts +46 -0
  185. package/dist/server/request-context.js +17 -0
  186. package/dist/server/route-table.d.ts +25 -3
  187. package/dist/server/route-table.js +163 -75
  188. package/dist/server/runtime.d.ts +15 -7
  189. package/dist/server/runtime.js +7 -1
  190. package/dist/server/session.d.ts +138 -0
  191. package/dist/server/session.js +94 -0
  192. package/dist/server/settings.d.ts +1 -5
  193. package/dist/server/settings.js +2182 -2117
  194. package/dist/server/task-views.d.ts +11 -4
  195. package/dist/server/task-views.js +26 -22
  196. package/dist/server/tasks.d.ts +9 -9
  197. package/dist/server/tasks.js +1486 -1418
  198. package/dist/server-telemetry.d.ts +4 -3
  199. package/dist/server-telemetry.js +4 -8
  200. package/dist/skills-ui.d.ts +4 -3
  201. package/dist/skills-ui.js +14 -17
  202. package/dist/skills.d.ts +1 -1
  203. package/dist/skills.js +2 -1
  204. package/dist/slack-api.d.ts +5 -1
  205. package/dist/slack-api.js +2 -2
  206. package/dist/slack-chat.d.ts +9 -4
  207. package/dist/slack-chat.js +29 -52
  208. package/dist/slack-settings.d.ts +3 -2
  209. package/dist/slack-settings.js +23 -57
  210. package/dist/slack.js +6 -16
  211. package/dist/spend-ui.d.ts +2 -1
  212. package/dist/spend-ui.js +9 -32
  213. package/dist/sso-ui.d.ts +3 -2
  214. package/dist/sso-ui.js +26 -27
  215. package/dist/storage-ui.d.ts +2 -1
  216. package/dist/storage-ui.js +14 -33
  217. package/dist/store.d.ts +97 -364
  218. package/dist/store.js +207 -1531
  219. package/dist/surface.js +14 -3
  220. package/dist/task-composer.d.ts +2 -1
  221. package/dist/task-composer.js +7 -7
  222. package/dist/task-status.d.ts +4 -3
  223. package/dist/task-status.js +15 -16
  224. package/dist/team-http.d.ts +5 -9
  225. package/dist/team-http.js +5 -63
  226. package/dist/team-ui.d.ts +2 -1
  227. package/dist/team-ui.js +2 -5
  228. package/dist/teammate-question.js +12 -12
  229. package/dist/teammates-ui.d.ts +5 -4
  230. package/dist/teammates-ui.js +61 -90
  231. package/dist/teams-chat.js +12 -9
  232. package/dist/teams-settings.d.ts +3 -2
  233. package/dist/teams-settings.js +22 -33
  234. package/dist/teams.js +4 -5
  235. package/dist/telegram-flow.js +5 -5
  236. package/dist/telegram-settings.d.ts +3 -2
  237. package/dist/telegram-settings.js +14 -25
  238. package/dist/telegram.js +4 -4
  239. package/dist/toolroll-update-ui.d.ts +3 -2
  240. package/dist/toolroll-update-ui.js +35 -39
  241. package/dist/toolroll-update.d.ts +10 -0
  242. package/dist/toolroll-update.js +50 -3
  243. package/dist/tools-ui.d.ts +5 -2
  244. package/dist/tools-ui.js +31 -31
  245. package/dist/when-html.d.ts +11 -3
  246. package/dist/when-html.js +5 -6
  247. package/dist/workspace-revision.js +3 -1
  248. package/dist/workspace-ui.d.ts +4 -2
  249. package/dist/workspace-ui.js +10 -10
  250. package/package.json +1 -1
  251. package/dist/server/shared.d.ts +0 -3151
  252. package/dist/server/shared.js +0 -12001
  253. package/dist/slack-state.d.ts +0 -13
  254. package/dist/slack-state.js +0 -10
@@ -9,8 +9,9 @@
9
9
  * their own chat; otherwise, and for a plan that widens permissions, exceeds the limit or touches protected paths, the
10
10
  * card keeps its link.
11
11
  *
12
- * Channel-agnostic: Telegram renders these today, and Slack, Discord and Teams reach the same acts through the same
13
- * tokens (the `channel` column). Every act runs through the door the console uses. */
12
+ * The same in every chat app: Telegram's bridge and the shared engine Slack, Discord and Teams run on (chat-delivery.ts)
13
+ * offer, mint and answer these through the same tokens (the `channel` column). Every act runs through the door the
14
+ * console uses. */
14
15
  import { DECIDE_ACTS } from "./contracts/chat-callback-rows.js";
15
16
  import { type ChatApprovalLimits } from "./chat-approval.js";
16
17
  import { type VerifiedApprover } from "./principal.js";
@@ -78,10 +79,11 @@ export type DecideOffer = {
78
79
  text: string | null;
79
80
  rows: Array<Array<ActSpec | DecideLink>>;
80
81
  };
81
- /** What lets this person's chat approve on this repository now: their lasting chat-approval setting (chat-approval.ts),
82
- * or else a live signed mode carrying the chatApprove term, signed by them. `digest` names that authority for the
82
+ /** What lets this person's chat on `channel` approve on this repository now: their lasting chat-approval setting
83
+ * (chat-approval.ts), or else a live signed mode carrying the chatApprove term, signed by them, that names this chat
84
+ * app (a mode signed before the term named its apps covers Telegram alone). `digest` names that authority for the
83
85
  * ledger; the yes itself is the person's tap and is sealed as theirs, so it doesn't lapse when the authority does. */
84
- export declare function chatApproveMode(store: Store, repo: string | null, approver: string, now: Date): {
86
+ export declare function chatApproveMode(store: Store, repo: string | null, approver: string, now: Date, channel: DecideChannel): {
85
87
  ok: true;
86
88
  digest: string;
87
89
  limits: ChatApprovalLimits;
@@ -94,7 +96,7 @@ export declare function chatApproveMode(store: Store, repo: string | null, appro
94
96
  export declare function permissionPlainWords(profile: Scope["profile"] | null | undefined): string | null;
95
97
  /** A plan this person's chat may approve now: the term, the plan's exact bytes, and none of the reasons it must open
96
98
  * Toolroll instead. `digest` binds the scope and the plan document the card shows. */
97
- export declare function planInChat(store: Store, taskId: string, approver: string, now: Date, root?: string): {
99
+ export declare function planInChat(store: Store, taskId: string, approver: string, now: Date, channel: DecideChannel, root?: string): {
98
100
  ok: true;
99
101
  modeDigest: string;
100
102
  source: "setting" | "mode";
@@ -105,7 +107,7 @@ export declare function planInChat(store: Store, taskId: string, approver: strin
105
107
  why: string;
106
108
  };
107
109
  /** A ready pull request this person's chat may merge now, bound to the exact commit. */
108
- export declare function mergeInChat(store: Store, taskId: string, run: number, approver: string, now: Date): {
110
+ export declare function mergeInChat(store: Store, taskId: string, run: number, approver: string, now: Date, channel: DecideChannel): {
109
111
  ok: true;
110
112
  modeDigest: string;
111
113
  source: "setting" | "mode";
@@ -119,7 +121,7 @@ export declare function mergeInChat(store: Store, taskId: string, run: number, a
119
121
  export declare function planCardText(store: Store, taskId: string, scope: Scope, root?: string, channel?: DecideChannel): string;
120
122
  /** The acts a card about this target can take in chat right now, or null when none can (the card keeps its own
121
123
  * link). Reads only; nothing is minted. */
122
- export declare function decideOffer(store: Store, target: DecideTarget, who: VerifiedApprover, now: Date, root?: string, channel?: DecideChannel): DecideOffer | null;
124
+ export declare function decideOffer(store: Store, target: DecideTarget, who: VerifiedApprover, now: Date, channel: DecideChannel, root?: string): DecideOffer | null;
123
125
  /** One fingerprint of what the card offers, without its tokens: a repaint with the same offer keeps its buttons. */
124
126
  export declare function offerFingerprint(offer: DecideOffer): string;
125
127
  /** The card's buttons, minted now (message unknown until it is sent: `placeDecideTokens` puts them on it). */
@@ -9,8 +9,9 @@
9
9
  * their own chat; otherwise, and for a plan that widens permissions, exceeds the limit or touches protected paths, the
10
10
  * card keeps its link.
11
11
  *
12
- * Channel-agnostic: Telegram renders these today, and Slack, Discord and Teams reach the same acts through the same
13
- * tokens (the `channel` column). Every act runs through the door the console uses. */
12
+ * The same in every chat app: Telegram's bridge and the shared engine Slack, Discord and Teams run on (chat-delivery.ts)
13
+ * offer, mint and answer these through the same tokens (the `channel` column). Every act runs through the door the
14
+ * console uses. */
14
15
  import { DECIDE_ACTS, readDecideActionRow } from "./contracts/chat-callback-rows.js";
15
16
  import { createHash, randomBytes } from "node:crypto";
16
17
  import { acceptAndCompleteAsOperator, assignmentOf, owedAcceptance } from "./assignment.js";
@@ -19,7 +20,7 @@ import { applyChatTaskAction, chatTaskStamp } from "./chat-task-actions.js";
19
20
  import { chatTitle } from "./chat-voice.js";
20
21
  import { readVerifiedArtifact } from "./evidence.js";
21
22
  import { effectiveChatApproval } from "./chat-approval.js";
22
- import { modeTermsFromJson } from "./modes.js";
23
+ import { chatApproveChatsOf, chatNames, modeTermsFromJson } from "./modes.js";
23
24
  import { parseExecutionPlanDocument } from "./plan.js";
24
25
  import { verifyApproverStanding } from "./principal.js";
25
26
  import { pullRequestFollowOf } from "./pull-request-flow.js";
@@ -47,10 +48,11 @@ const sha = (text) => createHash("sha256").update(text).digest("hex");
47
48
  const isAct = (one) => "act" in one;
48
49
  const title = (store, taskId) => phoneText(chatTitle(store, taskId), 60);
49
50
  // ---- the chatApprove term ----------------------------------------------------------
50
- /** What lets this person's chat approve on this repository now: their lasting chat-approval setting (chat-approval.ts),
51
- * or else a live signed mode carrying the chatApprove term, signed by them. `digest` names that authority for the
51
+ /** What lets this person's chat on `channel` approve on this repository now: their lasting chat-approval setting
52
+ * (chat-approval.ts), or else a live signed mode carrying the chatApprove term, signed by them, that names this chat
53
+ * app (a mode signed before the term named its apps covers Telegram alone). `digest` names that authority for the
52
54
  * ledger; the yes itself is the person's tap and is sealed as theirs, so it doesn't lapse when the authority does. */
53
- export function chatApproveMode(store, repo, approver, now) {
55
+ export function chatApproveMode(store, repo, approver, now, channel) {
54
56
  if (repo === null)
55
57
  return { ok: false, why: "Approving from chat isn't turned on for this project." };
56
58
  const setting = effectiveChatApproval(store, repo, approver);
@@ -62,6 +64,9 @@ export function chatApproveMode(store, repo, approver, now) {
62
64
  return setting;
63
65
  if (mode.signedBy !== approver || !store.accountCanAccess(approver, repo))
64
66
  return { ok: false, why: "Only the person who signed this project's mode can approve from chat." };
67
+ const chats = chatApproveChatsOf(terms);
68
+ if (!chats.includes(channel))
69
+ return { ok: false, why: `Your mode approves from ${chatNames(chats)} only. Sign it again to approve from ${chatNames([channel])}.` };
65
70
  return { ok: true, digest: mode.digest, limits: { fullAccess: terms.permissionDefault === "escalated", capMicrousd: terms.perAttemptBudgetMicrousd }, source: "mode" };
66
71
  }
67
72
  /** What a plan's yes allows an agent to do, in a few plain words (the approval sheet's "You're allowing"). */
@@ -87,7 +92,7 @@ function fullAccess(profile) {
87
92
  const money = (micro) => `$${(micro / 1_000_000).toFixed(2)}`;
88
93
  /** A plan this person's chat may approve now: the term, the plan's exact bytes, and none of the reasons it must open
89
94
  * Toolroll instead. `digest` binds the scope and the plan document the card shows. */
90
- export function planInChat(store, taskId, approver, now, root) {
95
+ export function planInChat(store, taskId, approver, now, channel, root) {
91
96
  const ref = store.lookupRef(taskId);
92
97
  const scope = store.getScope(taskId);
93
98
  if (ref === null || ref.repo === null || scope === null)
@@ -96,7 +101,7 @@ export function planInChat(store, taskId, approver, now, root) {
96
101
  return { ok: false, why: "This plan is already approved." };
97
102
  if (ref.plan === "requested")
98
103
  return { ok: false, why: "The plan is still being written." };
99
- const mode = chatApproveMode(store, ref.repo, approver, now);
104
+ const mode = chatApproveMode(store, ref.repo, approver, now, channel);
100
105
  if (!mode.ok)
101
106
  return mode;
102
107
  if (store.getTask(taskId)?.state === "cancelled" || store.getTask(taskId)?.state === "done")
@@ -148,7 +153,7 @@ export function planInChat(store, taskId, approver, now, root) {
148
153
  return { ok: true, modeDigest: mode.digest, source: mode.source, scope, digest: sha(`${scope.digest}:${plan?.sha256 ?? ""}`) };
149
154
  }
150
155
  /** A ready pull request this person's chat may merge now, bound to the exact commit. */
151
- export function mergeInChat(store, taskId, run, approver, now) {
156
+ export function mergeInChat(store, taskId, run, approver, now, channel) {
152
157
  const ref = store.lookupRef(taskId);
153
158
  const publication = store.publicationForRun(run);
154
159
  const follow = publication === null ? null : pullRequestFollowOf(store, publication.id);
@@ -158,7 +163,7 @@ export function mergeInChat(store, taskId, run, approver, now) {
158
163
  return { ok: false, why: "This pull request is already merged." };
159
164
  if (follow.readyHead !== publication.headSha)
160
165
  return { ok: false, why: "This pull request changed since this card was sent." };
161
- const mode = chatApproveMode(store, ref.repo, approver, now);
166
+ const mode = chatApproveMode(store, ref.repo, approver, now, channel);
162
167
  if (!mode.ok)
163
168
  return mode;
164
169
  return { ok: true, modeDigest: mode.digest, source: mode.source, head: publication.headSha };
@@ -216,7 +221,7 @@ export function planCardText(store, taskId, scope, root, channel) {
216
221
  // ---- what a card offers ---------------------------------------------------------------
217
222
  /** The acts a card about this target can take in chat right now, or null when none can (the card keeps its own
218
223
  * link). Reads only; nothing is minted. */
219
- export function decideOffer(store, target, who, now, root, channel) {
224
+ export function decideOffer(store, target, who, now, channel, root) {
220
225
  const ref = store.lookupRef(target.taskId);
221
226
  if (ref === null || ref.repo === null || !who.repos.includes(ref.repo))
222
227
  return null;
@@ -241,14 +246,14 @@ export function decideOffer(store, target, who, now, root, channel) {
241
246
  return { text: null, rows: [[{ act: "retry", run: null, digest: stamp }], [{ label: DECIDE_LABELS.look, path: chatControlHref("task", target.taskId) }]] };
242
247
  }
243
248
  case "plan": {
244
- const plan = planInChat(store, target.taskId, who.name, now, root);
249
+ const plan = planInChat(store, target.taskId, who.name, now, channel, root);
245
250
  if (!plan.ok)
246
251
  return null;
247
252
  return { text: planCardText(store, target.taskId, plan.scope, root, channel),
248
253
  rows: [[{ act: "approve", run: null, digest: plan.digest }], [{ label: DECIDE_LABELS.edit, path: chatControlHref("approval", target.taskId) }, { act: "not-now", run: null, digest: plan.digest }]] };
249
254
  }
250
255
  case "merge": {
251
- const merge = mergeInChat(store, target.taskId, target.run, who.name, now);
256
+ const merge = mergeInChat(store, target.taskId, target.run, who.name, now, channel);
252
257
  if (!merge.ok)
253
258
  return null;
254
259
  return { text: null, rows: [[{ act: "merge", run: target.run, digest: merge.head }], [{ label: DECIDE_LABELS.look, path: chatResultHref(target.taskId, target.run, "changes") }]] };
@@ -348,7 +353,7 @@ function targetOf(row) {
348
353
  }
349
354
  }
350
355
  /** Why the card's act no longer applies, or null while it still does — checked at the offer and again at the yes. */
351
- function staleWhy(store, row, who, now, root) {
356
+ function staleWhy(store, row, who, now, channel, root) {
352
357
  switch (row.act) {
353
358
  case "accept":
354
359
  case "changes": {
@@ -366,13 +371,13 @@ function staleWhy(store, row, who, now, root) {
366
371
  }
367
372
  case "approve":
368
373
  case "not-now": {
369
- const plan = planInChat(store, row.taskId, who.name, now, root);
374
+ const plan = planInChat(store, row.taskId, who.name, now, channel, root);
370
375
  if (!plan.ok)
371
376
  return plan.why;
372
377
  return plan.digest === row.digest ? null : "The plan changed since this card was sent.";
373
378
  }
374
379
  case "merge": {
375
- const merge = mergeInChat(store, row.taskId, row.run ?? 0, who.name, now);
380
+ const merge = mergeInChat(store, row.taskId, row.run ?? 0, who.name, now, channel);
376
381
  if (!merge.ok)
377
382
  return merge.why;
378
383
  return merge.head === row.digest ? null : "This pull request changed since this card was sent.";
@@ -459,13 +464,13 @@ export function applyDecideTap(store, seat, input) {
459
464
  }
460
465
  if (row.phase === "cancel") {
461
466
  spend();
462
- const offer = decideOffer(store, target, who, now, input.root, seat.channel);
467
+ const offer = decideOffer(store, target, who, now, seat.channel, input.root);
463
468
  if (offer === null)
464
469
  return { ack: "Cancelled.", edit: { text: body, rows: linksFor(target), tokens: [] } };
465
470
  const minted = mintDecideButtons(store, seat, target, offer, now, input.message);
466
471
  return { ack: "Cancelled.", edit: { text: offer.text ?? body, ...minted } };
467
472
  }
468
- const why = staleWhy(store, row, who, now, input.root);
473
+ const why = staleWhy(store, row, who, now, seat.channel, input.root);
469
474
  if (why !== null)
470
475
  return stale(why);
471
476
  if (row.phase === "offer") {
@@ -503,7 +508,7 @@ export function applyDecideTap(store, seat, input) {
503
508
  return retried.ok ? done("✓ Queued again. Existing approvals and holds still apply.") : notDone(retried.message);
504
509
  }
505
510
  case "approve": {
506
- const plan = planInChat(store, row.taskId, who.name, now, input.root);
511
+ const plan = planInChat(store, row.taskId, who.name, now, seat.channel, input.root);
507
512
  if (!plan.ok)
508
513
  return notDone(plan.why);
509
514
  // The owner's own yes, sealed as theirs ("chat"), never as a mode's: it stands like the console's after the mode or setting ends.
@@ -514,7 +519,7 @@ export function applyDecideTap(store, seat, input) {
514
519
  return done("✓ Approved. Work starts when a worker is free.");
515
520
  }
516
521
  case "merge": {
517
- const merge = mergeInChat(store, row.taskId, row.run ?? 0, who.name, now);
522
+ const merge = mergeInChat(store, row.taskId, row.run ?? 0, who.name, now, seat.channel);
518
523
  if (!merge.ok)
519
524
  return notDone(merge.why);
520
525
  const via = `via ${seat.channel} · chat binding #${seat.binding} · ${merge.source} ${merge.modeDigest} · ${merge.head.slice(0, 12)}`;
@@ -567,7 +572,7 @@ export function applyDecideFeedback(store, seat, prompt, text, repos, root, now)
567
572
  const verified = verifyApproverStanding(store, seat.approver, seat.generation, repos);
568
573
  if (!verified.ok)
569
574
  return { said: "That result isn't available here now. Nothing was sent.", link: null };
570
- const why = staleWhy(store, { act: "changes", taskId: prompt.taskId, run: prompt.run, digest: prompt.digest }, verified.who, now, root);
575
+ const why = staleWhy(store, { act: "changes", taskId: prompt.taskId, run: prompt.run, digest: prompt.digest }, verified.who, now, seat.channel, root);
571
576
  if (why !== null)
572
577
  return { said: `${why} Your feedback wasn't sent.`, link: { label: "Open task", path: chatControlHref("task", prompt.taskId) } };
573
578
  if (root === undefined)
@@ -1,8 +1,7 @@
1
- import { type Store } from "./store.js";
2
- import { chatSchema, chatTables } from "./contracts/chat-tables.js";
1
+ import { ChatCore, chatHash, type ChatBinding, type ChatProvider, type ChatRoom } from "./chat-core.js";
2
+ import { ChatMessages } from "./chat-messages.js";
3
3
  import { savedChatPart, type ChatContent, type ChatEventBody } from "./contracts/chat-content.js";
4
- export { savedChatPart, type ChatContent, type ChatEventBody };
5
- export { chatSchema, chatTables };
4
+ export { savedChatPart, chatHash, type ChatContent, type ChatEventBody, type ChatBinding, type ChatProvider, type ChatRoom };
6
5
  export type ChatIdentity = {
7
6
  installation: string;
8
7
  team?: string;
@@ -10,18 +9,6 @@ export type ChatIdentity = {
10
9
  bot: string;
11
10
  workspace: string;
12
11
  };
13
- export type ChatBinding = {
14
- id: number;
15
- installation: string;
16
- team: string;
17
- app: string;
18
- member: string;
19
- channel: string;
20
- approver: string;
21
- generation: number;
22
- created: string;
23
- revoked: string | null;
24
- };
25
12
  export type ChatEvent = {
26
13
  id: string;
27
14
  installation: string;
@@ -51,64 +38,20 @@ export type ChatPart = {
51
38
  next_at: string | null;
52
39
  created: string;
53
40
  };
54
- export type ChatRoom = {
55
- id: number;
56
- installation: string;
57
- chat: string;
58
- kind: "group" | "private";
59
- conversation: string;
60
- boundBy: string;
61
- binding: number;
62
- cursor: number;
63
- };
64
- export declare const chatHash: (text: string) => string;
65
- export declare class ChatState {
66
- readonly store: Store;
67
- readonly channel: "slack" | "discord" | "teams";
68
- constructor(store: Store, channel: "slack" | "discord" | "teams");
69
- prepare(sql: string): import("./store.js").Statement;
70
- /** Small transport facts a channel must remember per chat (a Teams service URL). Never credentials. */
71
- meta(installation: string, key: string): string | null;
72
- setMeta(installation: string, key: string, value: string, now: Date): void;
73
- private readRoom;
74
- room(installation: string, chat: string): ChatRoom | null;
75
- rooms(installation: string): ChatRoom[];
76
- /** Point a chat at a conversation; an earlier choice for the same chat is revoked and delivery starts from now. */
77
- bindRoom(args: {
78
- installation: string;
79
- chat: string;
80
- kind: "group" | "private";
81
- conversation: string;
82
- by: string;
83
- binding: number;
84
- }, now: Date): {
85
- ok: true;
86
- } | {
87
- ok: false;
88
- reason: "group-taken";
89
- };
90
- unbindRoom(installation: string, chat: string, by: string, now: Date): boolean;
91
- /** The shared rooms core's view of this installation's room records. */
92
- roomBackend(installation: string, now: Date): import("./chat-rooms.js").RoomBackend;
93
- advanceRoomCursor(id: number, messageId: number): boolean;
94
- get db(): import("./store.js").Database;
95
- /** Every unrevoked binding for an installation, oldest first: one per
96
- * paired person (v73). Liveness is checked per binding with `live`. */
97
- bindings(installation: string): ChatBinding[];
98
- /** The unrevoked binding one channel member holds, if any. */
99
- bindingFor(installation: string, member: string): ChatBinding | null;
100
- bindingById(id: number): ChatBinding | null;
101
- /** The oldest unrevoked binding — the single-person reading kept for
102
- * status lines and fixtures; inbound, delivery and notices read
103
- * `bindingFor` / `bindingById` / `bindings`. */
104
- binding(installation: string): ChatBinding | null;
105
- live(binding: ChatBinding): boolean;
106
- /** End one person's pairing and everything their chat could still do; teammates' bindings stay. */
107
- revokeBinding(binding: ChatBinding, now?: Date): void;
108
- revoke(installation: string, now?: Date): void;
41
+ /** One app's chats on the shared core (the same object Telegram's run on): its events, planned parts and their buttons. */
42
+ export declare class ChatState extends ChatMessages {
43
+ constructor(store: ChatCore["store"], provider: ChatProvider);
44
+ /** Pair-time half kept for the settings pages: mint a code for this installation and return it (only its hash is kept). */
109
45
  pairing(installation: string, approver: string, generation: number, now?: Date): string;
110
- pair(identity: ChatIdentity, hash: string, member: string, channel: string, now: Date): ChatBinding | null;
111
- /** Save a received event with its body (contracts/chat-content.ts), versioned. */
46
+ /**
47
+ * Consume a code sent in a chat (the shared rules, chat-core.ts). The installation's notice cursor starts from now
48
+ * on its FIRST pairing; a later person joins the running cursor (their own history is fenced by their binding's
49
+ * creation time).
50
+ */
51
+ pair(identity: ChatIdentity, hash: string, member: string, channel: string, now: Date, event?: string | null): ChatBinding | null;
52
+ /** The 60-second lease the Slack, Discord and Teams workers renew. */
53
+ lease(installation: string, owner: string, now: Date): boolean;
54
+ /** Save a received event with its body (contracts/chat-content.ts), versioned. False: it was already here. */
112
55
  enqueue(event: Omit<ChatEvent, "session" | "state" | "next_at" | "payload"> & {
113
56
  payload: ChatEventBody;
114
57
  }): boolean;
@@ -116,11 +59,44 @@ export declare class ChatState {
116
59
  next(installation: string, now: Date): ChatEvent | null;
117
60
  defer(event: string, problem: string, until: Date): void;
118
61
  finish(event: string, dropped?: boolean): void;
62
+ /** Point an event at the binding its pairing made. */
63
+ setEventBinding(event: string, binding: number): void;
119
64
  part(id: number): ChatPart | null;
65
+ /** The reply to an event, part by part, with each part's buttons minted with it; the event is then handled. Planned once. */
120
66
  plan(event: string, parts: ChatContent[], now: Date): void;
67
+ /** A proposal card's buttons on its part: earlier ones on that part are spent. Yes and Cancel last ten minutes. */
121
68
  tokens(part: number, proposal: number, phases: Array<"confirm" | "dismiss" | "yes" | "cancel">, now: Date): void;
122
- lease(installation: string, owner: string, now: Date): boolean;
123
- owns(installation: string, owner: string, now?: Date): boolean;
69
+ /** A proposal card's live buttons on one part, in the order they were minted. */
70
+ partTokens(part: number, now: Date): {
71
+ token: string;
72
+ phase: string;
73
+ }[];
74
+ /** The next part to send for an installation: notices only while notifications may go out. */
75
+ nextPart(installation: string, notices: boolean, now: Date, order?: "id" | "created"): ChatPart | null;
76
+ /** A part that will not go: why, in words the saved chat shows. */
77
+ dropPart(id: number, problem: string): void;
78
+ /** Try again no earlier than then (a screenshot waiting for its result's message). */
79
+ holdPart(id: number, until: string): void;
80
+ /** The app's upload identity for a part's file, and whether its bytes are up. */
81
+ setPartFile(id: number, file: string | null, uploaded?: boolean, message?: string | null): void;
82
+ /**
83
+ * Sent: on the app's own message id when it gave one (an uploaded file may not). A card's decide buttons
84
+ * (chat-decide.ts) now ride that message, and any older ones on it stop working; a "What should change?" names it.
85
+ */
86
+ partSent(id: number, message: string | null, now: Date, attempt?: boolean): void;
87
+ /** Not sent this time: counted, named, and (when the answer was lost rather than refused) counted as uncertain. */
88
+ partFailed(id: number, failure: {
89
+ problem: string;
90
+ until: string;
91
+ uncertain: boolean;
92
+ }, giveUpAfter?: number): void;
93
+ /** One person's replies still waiting to send, and those that could not be delivered (settings show both). */
94
+ partCounts(binding: number): {
95
+ pending: number;
96
+ dropped: number;
97
+ };
98
+ /** Replace a part's content and send it again (a repaint after a tap). */
99
+ repaint(id: number, content: ChatContent): void;
124
100
  }
125
101
  /** A saved part's content. One that can't be read is a delivery problem that names the field, never a guess. */
126
102
  export declare function partContent(payload: string): ChatContent;