@chipschat/protocol 0.0.0-stage → 1.0.1

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 (371) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +56 -2
  3. package/dist/generated/types.d.ts +4052 -0
  4. package/dist/generated/types.d.ts.map +1 -0
  5. package/dist/generated/types.js +7 -0
  6. package/dist/generated/types.js.map +1 -0
  7. package/dist/src/address.d.ts +106 -0
  8. package/dist/src/address.d.ts.map +1 -0
  9. package/dist/src/address.js +141 -0
  10. package/dist/src/address.js.map +1 -0
  11. package/dist/src/agent-context-windows.d.ts +235 -0
  12. package/dist/src/agent-context-windows.d.ts.map +1 -0
  13. package/dist/src/agent-context-windows.js +213 -0
  14. package/dist/src/agent-context-windows.js.map +1 -0
  15. package/dist/src/agent-heartbeats.d.ts +106 -0
  16. package/dist/src/agent-heartbeats.d.ts.map +1 -0
  17. package/dist/src/agent-heartbeats.js +111 -0
  18. package/dist/src/agent-heartbeats.js.map +1 -0
  19. package/dist/src/agent-models.d.ts +158 -0
  20. package/dist/src/agent-models.d.ts.map +1 -0
  21. package/dist/src/agent-models.js +171 -0
  22. package/dist/src/agent-models.js.map +1 -0
  23. package/dist/src/agent-place.d.ts +76 -0
  24. package/dist/src/agent-place.d.ts.map +1 -0
  25. package/dist/src/agent-place.js +80 -0
  26. package/dist/src/agent-place.js.map +1 -0
  27. package/dist/src/agents.d.ts +24 -0
  28. package/dist/src/agents.d.ts.map +1 -0
  29. package/dist/src/agents.js +58 -0
  30. package/dist/src/agents.js.map +1 -0
  31. package/dist/src/capabilities.d.ts +121 -0
  32. package/dist/src/capabilities.d.ts.map +1 -0
  33. package/dist/src/capabilities.js +120 -0
  34. package/dist/src/capabilities.js.map +1 -0
  35. package/dist/src/checklist.d.ts +19 -0
  36. package/dist/src/checklist.d.ts.map +1 -0
  37. package/dist/src/checklist.js +22 -0
  38. package/dist/src/checklist.js.map +1 -0
  39. package/dist/src/commands.d.ts +50 -0
  40. package/dist/src/commands.d.ts.map +1 -0
  41. package/dist/src/commands.js +274 -0
  42. package/dist/src/commands.js.map +1 -0
  43. package/dist/src/connectors.d.ts +90 -0
  44. package/dist/src/connectors.d.ts.map +1 -0
  45. package/dist/src/connectors.js +119 -0
  46. package/dist/src/connectors.js.map +1 -0
  47. package/dist/src/context-files.d.ts +304 -0
  48. package/dist/src/context-files.d.ts.map +1 -0
  49. package/dist/src/context-files.js +247 -0
  50. package/dist/src/context-files.js.map +1 -0
  51. package/dist/src/context.d.ts +369 -0
  52. package/dist/src/context.d.ts.map +1 -0
  53. package/dist/src/context.js +526 -0
  54. package/dist/src/context.js.map +1 -0
  55. package/dist/src/direct-asks.d.ts +36 -0
  56. package/dist/src/direct-asks.d.ts.map +1 -0
  57. package/dist/src/direct-asks.js +43 -0
  58. package/dist/src/direct-asks.js.map +1 -0
  59. package/dist/src/envelope.d.ts +70 -0
  60. package/dist/src/envelope.d.ts.map +1 -0
  61. package/dist/src/envelope.js +46 -0
  62. package/dist/src/envelope.js.map +1 -0
  63. package/dist/src/events/catalogue.d.ts +50 -0
  64. package/dist/src/events/catalogue.d.ts.map +1 -0
  65. package/dist/src/events/catalogue.js +70 -0
  66. package/dist/src/events/catalogue.js.map +1 -0
  67. package/dist/src/events/chips-to-harness.d.ts +37 -0
  68. package/dist/src/events/chips-to-harness.d.ts.map +1 -0
  69. package/dist/src/events/chips-to-harness.js +511 -0
  70. package/dist/src/events/chips-to-harness.js.map +1 -0
  71. package/dist/src/events/harness-to-chips.d.ts +38 -0
  72. package/dist/src/events/harness-to-chips.d.ts.map +1 -0
  73. package/dist/src/events/harness-to-chips.js +593 -0
  74. package/dist/src/events/harness-to-chips.js.map +1 -0
  75. package/dist/src/events/index.d.ts +6 -0
  76. package/dist/src/events/index.d.ts.map +1 -0
  77. package/dist/src/events/index.js +5 -0
  78. package/dist/src/events/index.js.map +1 -0
  79. package/dist/src/events/workspace.d.ts +222 -0
  80. package/dist/src/events/workspace.d.ts.map +1 -0
  81. package/dist/src/events/workspace.js +1039 -0
  82. package/dist/src/events/workspace.js.map +1 -0
  83. package/dist/src/exchange.d.ts +262 -0
  84. package/dist/src/exchange.d.ts.map +1 -0
  85. package/dist/src/exchange.js +413 -0
  86. package/dist/src/exchange.js.map +1 -0
  87. package/dist/src/first-names.d.ts +29 -0
  88. package/dist/src/first-names.d.ts.map +1 -0
  89. package/dist/src/first-names.js +80 -0
  90. package/dist/src/first-names.js.map +1 -0
  91. package/dist/src/frames.d.ts +526 -0
  92. package/dist/src/frames.d.ts.map +1 -0
  93. package/dist/src/frames.js +346 -0
  94. package/dist/src/frames.js.map +1 -0
  95. package/dist/src/harness-jobs.d.ts +313 -0
  96. package/dist/src/harness-jobs.d.ts.map +1 -0
  97. package/dist/src/harness-jobs.js +210 -0
  98. package/dist/src/harness-jobs.js.map +1 -0
  99. package/dist/src/ids/index.d.ts +3 -0
  100. package/dist/src/ids/index.d.ts.map +1 -0
  101. package/dist/src/ids/index.js +2 -0
  102. package/dist/src/ids/index.js.map +1 -0
  103. package/dist/src/ids/ulid.d.ts +96 -0
  104. package/dist/src/ids/ulid.d.ts.map +1 -0
  105. package/dist/src/ids/ulid.js +212 -0
  106. package/dist/src/ids/ulid.js.map +1 -0
  107. package/dist/src/index.d.ts +81 -0
  108. package/dist/src/index.d.ts.map +1 -0
  109. package/dist/src/index.js +47 -0
  110. package/dist/src/index.js.map +1 -0
  111. package/dist/src/invite-link.d.ts +64 -0
  112. package/dist/src/invite-link.d.ts.map +1 -0
  113. package/dist/src/invite-link.js +99 -0
  114. package/dist/src/invite-link.js.map +1 -0
  115. package/dist/src/join.d.ts +188 -0
  116. package/dist/src/join.d.ts.map +1 -0
  117. package/dist/src/join.js +190 -0
  118. package/dist/src/join.js.map +1 -0
  119. package/dist/src/limits.d.ts +50 -0
  120. package/dist/src/limits.d.ts.map +1 -0
  121. package/dist/src/limits.js +106 -0
  122. package/dist/src/limits.js.map +1 -0
  123. package/dist/src/matters.d.ts +22 -0
  124. package/dist/src/matters.d.ts.map +1 -0
  125. package/dist/src/matters.js +26 -0
  126. package/dist/src/matters.js.map +1 -0
  127. package/dist/src/mentions.d.ts +63 -0
  128. package/dist/src/mentions.d.ts.map +1 -0
  129. package/dist/src/mentions.js +129 -0
  130. package/dist/src/mentions.js.map +1 -0
  131. package/dist/src/names.d.ts +58 -0
  132. package/dist/src/names.d.ts.map +1 -0
  133. package/dist/src/names.js +192 -0
  134. package/dist/src/names.js.map +1 -0
  135. package/dist/src/no-reply.d.ts +17 -0
  136. package/dist/src/no-reply.d.ts.map +1 -0
  137. package/dist/src/no-reply.js +48 -0
  138. package/dist/src/no-reply.js.map +1 -0
  139. package/dist/src/notifications.d.ts +81 -0
  140. package/dist/src/notifications.d.ts.map +1 -0
  141. package/dist/src/notifications.js +65 -0
  142. package/dist/src/notifications.js.map +1 -0
  143. package/dist/src/pages.d.ts +32 -0
  144. package/dist/src/pages.d.ts.map +1 -0
  145. package/dist/src/pages.js +32 -0
  146. package/dist/src/pages.js.map +1 -0
  147. package/dist/src/pairing.d.ts +99 -0
  148. package/dist/src/pairing.d.ts.map +1 -0
  149. package/dist/src/pairing.js +163 -0
  150. package/dist/src/pairing.js.map +1 -0
  151. package/dist/src/primitives.d.ts +267 -0
  152. package/dist/src/primitives.d.ts.map +1 -0
  153. package/dist/src/primitives.js +279 -0
  154. package/dist/src/primitives.js.map +1 -0
  155. package/dist/src/project.d.ts +335 -0
  156. package/dist/src/project.d.ts.map +1 -0
  157. package/dist/src/project.js +499 -0
  158. package/dist/src/project.js.map +1 -0
  159. package/dist/src/ranges.d.ts +31 -0
  160. package/dist/src/ranges.d.ts.map +1 -0
  161. package/dist/src/ranges.js +51 -0
  162. package/dist/src/ranges.js.map +1 -0
  163. package/dist/src/relay.d.ts +177 -0
  164. package/dist/src/relay.d.ts.map +1 -0
  165. package/dist/src/relay.js +147 -0
  166. package/dist/src/relay.js.map +1 -0
  167. package/dist/src/room-files.d.ts +146 -0
  168. package/dist/src/room-files.d.ts.map +1 -0
  169. package/dist/src/room-files.js +135 -0
  170. package/dist/src/room-files.js.map +1 -0
  171. package/dist/src/room-note.d.ts +213 -0
  172. package/dist/src/room-note.d.ts.map +1 -0
  173. package/dist/src/room-note.js +457 -0
  174. package/dist/src/room-note.js.map +1 -0
  175. package/dist/src/space-icons.d.ts +68 -0
  176. package/dist/src/space-icons.d.ts.map +1 -0
  177. package/dist/src/space-icons.js +142 -0
  178. package/dist/src/space-icons.js.map +1 -0
  179. package/dist/src/spend.d.ts +57 -0
  180. package/dist/src/spend.d.ts.map +1 -0
  181. package/dist/src/spend.js +94 -0
  182. package/dist/src/spend.js.map +1 -0
  183. package/dist/src/visibility.d.ts +170 -0
  184. package/dist/src/visibility.d.ts.map +1 -0
  185. package/dist/src/visibility.js +218 -0
  186. package/dist/src/visibility.js.map +1 -0
  187. package/fixtures/agent.asked.json +15 -0
  188. package/fixtures/agent.attached.json +15 -0
  189. package/fixtures/agent.deleted.json +16 -0
  190. package/fixtures/agent.fork.json +14 -0
  191. package/fixtures/agent.lock_set.json +13 -0
  192. package/fixtures/agent.registered.json +17 -0
  193. package/fixtures/agent.released.json +16 -0
  194. package/fixtures/agent.updated.json +14 -0
  195. package/fixtures/approval.executed.json +16 -0
  196. package/fixtures/approval.request.json +45 -0
  197. package/fixtures/approval.resolved.json +20 -0
  198. package/fixtures/approval.routed.json +20 -0
  199. package/fixtures/artifact.create.json +18 -0
  200. package/fixtures/attachment.fetched.json +15 -0
  201. package/fixtures/attachment.limits_set.json +15 -0
  202. package/fixtures/budget.warning.json +20 -0
  203. package/fixtures/checkpoint.json +16 -0
  204. package/fixtures/context.compacted.json +16 -0
  205. package/fixtures/context.usage.json +17 -0
  206. package/fixtures/exchange.limits_set.json +15 -0
  207. package/fixtures/exchange.set.json +13 -0
  208. package/fixtures/feedback.json +21 -0
  209. package/fixtures/frames/ack.json +6 -0
  210. package/fixtures/frames/context.edit.json +11 -0
  211. package/fixtures/frames/context_window.change.json +9 -0
  212. package/fixtures/frames/context_window.compact.json +8 -0
  213. package/fixtures/frames/error.json +6 -0
  214. package/fixtures/frames/event.json +20 -0
  215. package/fixtures/frames/events.json +39 -0
  216. package/fixtures/frames/folder.open.json +6 -0
  217. package/fixtures/frames/folder.open.page.json +7 -0
  218. package/fixtures/frames/folder.run.json +8 -0
  219. package/fixtures/frames/folder.run_state.json +12 -0
  220. package/fixtures/frames/heartbeat.change.json +10 -0
  221. package/fixtures/frames/hello.json +18 -0
  222. package/fixtures/frames/job.change.json +9 -0
  223. package/fixtures/frames/join.json +19 -0
  224. package/fixtures/frames/model.change.json +10 -0
  225. package/fixtures/frames/ping.json +4 -0
  226. package/fixtures/frames/pong.json +4 -0
  227. package/fixtures/frames/presence.json +12 -0
  228. package/fixtures/frames/receipt.json +5 -0
  229. package/fixtures/frames/restart.json +5 -0
  230. package/fixtures/frames/subscribe.json +6 -0
  231. package/fixtures/handoff.json +20 -0
  232. package/fixtures/harness.removed.json +15 -0
  233. package/fixtures/harness.transferred.json +15 -0
  234. package/fixtures/hello.json +61 -0
  235. package/fixtures/join.allowed.json +20 -0
  236. package/fixtures/join.denied.json +17 -0
  237. package/fixtures/lane.updated.json +17 -0
  238. package/fixtures/membership.added.json +16 -0
  239. package/fixtures/membership.removed.json +14 -0
  240. package/fixtures/membership.replaced.json +15 -0
  241. package/fixtures/message.deleted.json +15 -0
  242. package/fixtures/message.edited.json +15 -0
  243. package/fixtures/message.handed.json +14 -0
  244. package/fixtures/message.heard.json +14 -0
  245. package/fixtures/message.held.json +14 -0
  246. package/fixtures/message.hidden.json +14 -0
  247. package/fixtures/message.new.json +96 -0
  248. package/fixtures/message.overheard.json +22 -0
  249. package/fixtures/message.pinned.json +14 -0
  250. package/fixtures/message.post.json +14 -0
  251. package/fixtures/message.shown.json +14 -0
  252. package/fixtures/message.unpinned.json +14 -0
  253. package/fixtures/person.face_set.json +17 -0
  254. package/fixtures/person.joined.json +16 -0
  255. package/fixtures/person.removed.json +16 -0
  256. package/fixtures/person.renamed.json +14 -0
  257. package/fixtures/policy.updated.json +30 -0
  258. package/fixtures/project.changed.json +21 -0
  259. package/fixtures/question.answered.json +23 -0
  260. package/fixtures/question.ask.json +37 -0
  261. package/fixtures/receipt.json +15 -0
  262. package/fixtures/relay.set.json +12 -0
  263. package/fixtures/role.changed.json +15 -0
  264. package/fixtures/run.cancel.json +15 -0
  265. package/fixtures/run.completed.json +20 -0
  266. package/fixtures/run.failed.json +20 -0
  267. package/fixtures/run.pause.json +15 -0
  268. package/fixtures/run.plan.json +24 -0
  269. package/fixtures/run.requested.json +34 -0
  270. package/fixtures/run.resume.json +15 -0
  271. package/fixtures/run.started.json +33 -0
  272. package/fixtures/run.status.json +17 -0
  273. package/fixtures/run.steer.json +21 -0
  274. package/fixtures/session.ended.json +15 -0
  275. package/fixtures/session.renamed.json +15 -0
  276. package/fixtures/session.started.json +17 -0
  277. package/fixtures/space.claimed.json +15 -0
  278. package/fixtures/space.created.json +14 -0
  279. package/fixtures/space.erased.json +13 -0
  280. package/fixtures/space.lead_set.json +13 -0
  281. package/fixtures/space.lock_set.json +13 -0
  282. package/fixtures/space.spend_ceiling_set.json +13 -0
  283. package/fixtures/space.updated.json +14 -0
  284. package/fixtures/step.log.json +26 -0
  285. package/fixtures/task.json +22 -0
  286. package/fixtures/turn.passed.json +14 -0
  287. package/fixtures/usage.json +21 -0
  288. package/fixtures/workspace.renamed.json +13 -0
  289. package/generated/agent-connect.json +13966 -0
  290. package/generated/schema/agent.asked.json +114 -0
  291. package/generated/schema/agent.attached.json +465 -0
  292. package/generated/schema/agent.deleted.json +127 -0
  293. package/generated/schema/agent.fork.json +107 -0
  294. package/generated/schema/agent.lock_set.json +105 -0
  295. package/generated/schema/agent.registered.json +130 -0
  296. package/generated/schema/agent.released.json +129 -0
  297. package/generated/schema/agent.updated.json +151 -0
  298. package/generated/schema/approval.executed.json +125 -0
  299. package/generated/schema/approval.request.json +279 -0
  300. package/generated/schema/approval.resolved.json +162 -0
  301. package/generated/schema/approval.routed.json +185 -0
  302. package/generated/schema/artifact.create.json +206 -0
  303. package/generated/schema/attachment.fetched.json +114 -0
  304. package/generated/schema/attachment.limits_set.json +136 -0
  305. package/generated/schema/budget.warning.json +152 -0
  306. package/generated/schema/checkpoint.json +111 -0
  307. package/generated/schema/context.compacted.json +124 -0
  308. package/generated/schema/context.usage.json +120 -0
  309. package/generated/schema/exchange.limits_set.json +127 -0
  310. package/generated/schema/exchange.set.json +144 -0
  311. package/generated/schema/feedback.json +173 -0
  312. package/generated/schema/handoff.json +122 -0
  313. package/generated/schema/harness.removed.json +122 -0
  314. package/generated/schema/harness.transferred.json +117 -0
  315. package/generated/schema/hello.json +509 -0
  316. package/generated/schema/join.allowed.json +176 -0
  317. package/generated/schema/join.denied.json +150 -0
  318. package/generated/schema/lane.updated.json +223 -0
  319. package/generated/schema/membership.added.json +153 -0
  320. package/generated/schema/membership.removed.json +126 -0
  321. package/generated/schema/membership.replaced.json +106 -0
  322. package/generated/schema/message.deleted.json +105 -0
  323. package/generated/schema/message.edited.json +107 -0
  324. package/generated/schema/message.handed.json +111 -0
  325. package/generated/schema/message.heard.json +100 -0
  326. package/generated/schema/message.held.json +181 -0
  327. package/generated/schema/message.hidden.json +100 -0
  328. package/generated/schema/message.new.json +1369 -0
  329. package/generated/schema/message.overheard.json +701 -0
  330. package/generated/schema/message.pinned.json +100 -0
  331. package/generated/schema/message.post.json +290 -0
  332. package/generated/schema/message.shown.json +100 -0
  333. package/generated/schema/message.unpinned.json +100 -0
  334. package/generated/schema/person.face_set.json +134 -0
  335. package/generated/schema/person.joined.json +128 -0
  336. package/generated/schema/person.removed.json +133 -0
  337. package/generated/schema/person.renamed.json +114 -0
  338. package/generated/schema/policy.updated.json +277 -0
  339. package/generated/schema/project.changed.json +199 -0
  340. package/generated/schema/question.answered.json +154 -0
  341. package/generated/schema/question.ask.json +194 -0
  342. package/generated/schema/receipt.json +109 -0
  343. package/generated/schema/relay.set.json +99 -0
  344. package/generated/schema/role.changed.json +116 -0
  345. package/generated/schema/run.cancel.json +106 -0
  346. package/generated/schema/run.completed.json +139 -0
  347. package/generated/schema/run.failed.json +135 -0
  348. package/generated/schema/run.pause.json +106 -0
  349. package/generated/schema/run.plan.json +128 -0
  350. package/generated/schema/run.requested.json +309 -0
  351. package/generated/schema/run.resume.json +106 -0
  352. package/generated/schema/run.started.json +142 -0
  353. package/generated/schema/run.status.json +120 -0
  354. package/generated/schema/run.steer.json +163 -0
  355. package/generated/schema/session.ended.json +106 -0
  356. package/generated/schema/session.renamed.json +108 -0
  357. package/generated/schema/session.started.json +116 -0
  358. package/generated/schema/space.claimed.json +107 -0
  359. package/generated/schema/space.created.json +148 -0
  360. package/generated/schema/space.erased.json +129 -0
  361. package/generated/schema/space.lead_set.json +108 -0
  362. package/generated/schema/space.lock_set.json +99 -0
  363. package/generated/schema/space.spend_ceiling_set.json +102 -0
  364. package/generated/schema/space.updated.json +112 -0
  365. package/generated/schema/step.log.json +187 -0
  366. package/generated/schema/task.json +268 -0
  367. package/generated/schema/turn.passed.json +99 -0
  368. package/generated/schema/usage.json +155 -0
  369. package/generated/schema/workspace.renamed.json +107 -0
  370. package/generated/types.ts +4178 -0
  371. package/package.json +38 -3
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Join requests (ADR 0122): something asks to join Chips, and a person allows it. A harness asks
3
+ * for a key and the room it starts in; a device asks to be paired. No token is copied by hand.
4
+ *
5
+ * Two ways in, one request:
6
+ *
7
+ * - **The requester starts.** It sends `POST /v1/join` with no credential and is answered with a
8
+ * secret, which only it holds, and a short code, which it shows. An owner or admin compares the
9
+ * code, says which of what it asks for it may have, and allows it or not. The requester polls
10
+ * `POST /v1/join/:id/redeem` with the secret and is handed its credential once.
11
+ * - **A person starts.** An owner or admin makes a link, choosing first what the harness may do.
12
+ * The link is `<address>/j/<secret>`, good once, for fifteen minutes; whoever holds it redeems
13
+ * it at `POST /v1/join/redeem`, already allowed.
14
+ *
15
+ * The code is for comparing, not a secret: the 128-bit secret is the credential. Every rule a
16
+ * client draws from — how long each lasts, how many may wait — is named here once, so the server,
17
+ * the apps and the SDK cannot disagree.
18
+ */
19
+ import { z } from "zod";
20
+ import { HARNESS_CAPABILITIES } from "./capabilities.js";
21
+ import { pairingCode } from "./pairing.js";
22
+ import { id, isoTimestamp, workspaceRole } from "./primitives.js";
23
+ /** How long a request a requester started waits for a person: ten minutes. */
24
+ export const JOIN_CODE_SECONDS = 600;
25
+ /** How long a link a person made waits for its harness: fifteen minutes. */
26
+ export const JOIN_LINK_SECONDS = 900;
27
+ /** Requests waiting for a person at once, on one server. The sixth is refused. */
28
+ export const JOIN_WAITING_MAX = 5;
29
+ /** Requests one address may start in a minute. */
30
+ export const JOIN_ASKS_PER_MINUTE = 10;
31
+ /**
32
+ * How often a requester asks whether it was allowed, in seconds: twenty a minute, under the thirty
33
+ * a relay lets through a tunnel without a credential.
34
+ */
35
+ export const JOIN_POLL_SECONDS = 3;
36
+ export const joinKind = z
37
+ .enum(["harness", "device", "invite", "pairing"])
38
+ .describe("A harness asking for a key; a device asking to be paired; a person joining by an invite, or a device pairing by a code, that waits for whoever handed it out (ADR 0232).");
39
+ /** The kinds a link makes, which wait for the one person who handed it out (ADR 0232). */
40
+ export const LINK_KINDS = ["invite", "pairing"];
41
+ /** Whether a request is a link's, asked of one person: an invite's, or a pairing code's. */
42
+ export function isLinkKind(kind) {
43
+ return LINK_KINDS.includes(kind);
44
+ }
45
+ /**
46
+ * The code a link's request shows (ADR 0232): six digits made from the joining device's key, the
47
+ * same on the device and on the Allow sheet, for a person to compare.
48
+ */
49
+ export const keyCode = z.string().regex(/^[0-9]{6}$/, "six digits");
50
+ /** Six digits as people read them: two groups of three. */
51
+ export function keyCodeShown(code) {
52
+ return keyCode.safeParse(code).success ? `${code.slice(0, 3)} ${code.slice(3)}` : code;
53
+ }
54
+ /** A code a request shows: a requester's to compare or type, or a link's six digits. */
55
+ const requestCode = z.union([pairingCode, keyCode]);
56
+ export const joinState = z
57
+ .enum(["waiting", "allowed", "denied", "expired", "redeemed"])
58
+ .describe("`waiting` for a person; `allowed`, and not yet collected; `denied` by a person, or withdrawn; `expired` before it was collected; `redeemed`, which spends it.");
59
+ export const joinStartedBy = z
60
+ .enum(["requester", "person"])
61
+ .describe("Who started it: the requester, with a code to compare, or a person, with a link that is allowed already.");
62
+ /** What a person may grant a harness: every capability a harness may take (ADR 0046). */
63
+ export const GRANTABLE = Object.values(HARNESS_CAPABILITIES);
64
+ const capabilities = z
65
+ .array(z.string().min(1).max(120))
66
+ .max(64)
67
+ .describe("Capability names, as `HARNESS_CAPABILITIES` spells them.");
68
+ const words = (most) => z.string().min(1).max(most);
69
+ /** A request as an owner's or admin's client sees it: never the secret. */
70
+ export const joinRequestView = z
71
+ .object({
72
+ request_id: id("join"),
73
+ kind: joinKind,
74
+ state: joinState,
75
+ started_by: joinStartedBy,
76
+ code: requestCode
77
+ .optional()
78
+ .describe("The code the requester shows, to compare. A harness's link has none; an invite's or a pairing code's is six digits from the joining device's key (ADR 0232)."),
79
+ name: words(120)
80
+ .optional()
81
+ .describe("What it calls itself. A link's is known once it is redeemed."),
82
+ software: words(120).optional().describe("Its software and version, as it says."),
83
+ machine: words(120).optional().describe("The machine it runs on, as it says."),
84
+ from_addr: words(120).optional().describe("The address it asked from, as Chips saw it."),
85
+ wants: capabilities.describe("What a harness asked to do. Nothing, for a device."),
86
+ capabilities: capabilities.describe("What a person allowed it, which may be less than it asked for. Empty until allowed."),
87
+ url: z
88
+ .url({ protocol: /^https?$/ })
89
+ .describe("Where Chips tells the requester to find it: the link's address, or the server's."),
90
+ decided_by: id("person").optional().describe("Who allowed it, refused it or made the link."),
91
+ decided_at: isoTimestamp.optional(),
92
+ created_at: isoTimestamp,
93
+ expires_at: isoTimestamp.describe("When it stops being good, unless it was redeemed."),
94
+ harness_id: id("harness").optional().describe("The harness it made, once redeemed."),
95
+ space_id: id("space")
96
+ .optional()
97
+ .describe("The harness's direct room, once redeemed; for an invite, the room it brings the person into."),
98
+ device_id: id("device").optional().describe("The device it paired, once redeemed."),
99
+ pair_as: id("person")
100
+ .optional()
101
+ .describe("Whom a device is paired as, when the owner who allowed it chose somebody already here (ADR 0206), or the person a pairing code was made for. Absent: the person who allowed it."),
102
+ role: workspaceRole.optional().describe("For an invite: the role the person joins with."),
103
+ })
104
+ .describe("A join request, as an owner or admin sees it (ADR 0122).");
105
+ /** `POST /v1/join`: what a requester sends. */
106
+ export const joinAsk = z
107
+ .object({
108
+ kind: joinKind.default("harness"),
109
+ name: words(120).describe("What it calls itself: the harness's or the device's name."),
110
+ software: words(120).optional(),
111
+ machine: words(120).optional(),
112
+ wants: capabilities.optional().describe("For a harness: what it asks to be allowed."),
113
+ public_key: z
114
+ .string()
115
+ .regex(/^[A-Za-z0-9+/]{40,64}={0,2}$/)
116
+ .optional()
117
+ .describe("For a device: the public half of its Ed25519 key, SPKI DER, base64."),
118
+ })
119
+ .describe("A request to join.");
120
+ /** The answer to `POST /v1/join`. */
121
+ export const joinAsked = z.object({
122
+ request_id: id("join"),
123
+ secret: z.string().min(16).max(200).describe("The requester's alone: it polls with it."),
124
+ code: requestCode,
125
+ expires_in: z.int().positive().describe("Seconds until it expires."),
126
+ interval: z.int().positive().describe("Seconds between polls."),
127
+ });
128
+ /**
129
+ * What `POST /v1/invites/redeem` and `POST /v1/pair` answer, `202`, for a link that asks (ADR
130
+ * 0232): a request that waits for the person who handed the link out, polled as a device's is.
131
+ */
132
+ export const linkWaiting = joinAsked
133
+ .extend({ state: z.literal("waiting"), code: keyCode })
134
+ .describe("A link's request, waiting for the person who handed it out (ADR 0232).");
135
+ /** What a harness is handed once, when its request is redeemed. */
136
+ export const joinedHarness = z.object({
137
+ kind: z.literal("harness"),
138
+ url: z.url({ protocol: /^https?$/ }),
139
+ harness_id: id("harness"),
140
+ token: z.string().min(16).max(400).describe("Shown once. Chips keeps its digest."),
141
+ space_id: id("space").optional().describe("The room it speaks in first, with the person."),
142
+ capabilities,
143
+ });
144
+ /** What a device is handed once, when its request is redeemed. */
145
+ export const joinedDevice = z.object({
146
+ kind: z.literal("device"),
147
+ url: z.url({ protocol: /^https?$/ }),
148
+ device_id: id("device"),
149
+ participant_id: id("person"),
150
+ workspace_id: id("workspace"),
151
+ display_name: words(120),
152
+ workspace_name: words(120).optional(),
153
+ server_key: z
154
+ .string()
155
+ .min(1)
156
+ .max(128)
157
+ .optional()
158
+ .describe("The server's tunnel key, base64url (ADR 0191): what the device pins, so from here on it reaches the server through the tunnel door on this network and through the relay, and never a plain address again."),
159
+ role: workspaceRole.optional().describe("For an invite: the role the person joined with."),
160
+ space_id: id("space").optional().describe("For an invite: the room it put the person in."),
161
+ });
162
+ /** A poll's answer while the request still waits for a person: ask again in `interval`. */
163
+ export const joinPending = z.object({
164
+ state: z.literal("waiting"),
165
+ interval: z.int().positive(),
166
+ });
167
+ /** The secret a link carries: URL-safe, at least 128 bits. */
168
+ const SECRET = /^[A-Za-z0-9_-]{22,120}$/;
169
+ /** A link a person hands a harness: the server's address, then `/j/`, then the secret. */
170
+ export function joinLink(base, secret) {
171
+ return `${base.replace(/\/$/, "")}/j/${secret}`;
172
+ }
173
+ /** A link taken apart: where Chips is, and the secret. Nothing when it is not a join link. */
174
+ export function parseJoinLink(link) {
175
+ let parsed;
176
+ try {
177
+ parsed = new URL(link.trim());
178
+ }
179
+ catch {
180
+ return undefined;
181
+ }
182
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
183
+ return undefined;
184
+ const match = /^(.*)\/j\/([^/]+)\/?$/.exec(parsed.pathname);
185
+ const secret = match?.[2];
186
+ if (match === null || secret === undefined || !SECRET.test(secret))
187
+ return undefined;
188
+ return { url: `${parsed.origin}${match[1] ?? ""}`, secret };
189
+ }
190
+ //# sourceMappingURL=join.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join.js","sourceRoot":"","sources":["../../src/join.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * How big an attachment may be (ADR 0047).
3
+ *
4
+ * Two numbers decide it. The server has a ceiling, `CHIPS_MAX_ATTACHMENT_BYTES`, which nothing
5
+ * gets past; under it the workspace may cap photos, videos and files separately, by an event an
6
+ * owner or an admin sends (`attachment.limits_set`). The arithmetic is here, in the package both
7
+ * the server and the clients already share, so the phone refuses a file with the sentence the
8
+ * server would have used, before a byte of it has left the phone.
9
+ */
10
+ /** The three caps a workspace sets. Audio and everything else that is not a picture is a file. */
11
+ export type CapKind = "image" | "video" | "file";
12
+ export declare const CAP_KINDS: readonly CapKind[];
13
+ /** A cap in bytes for each kind, or `null` for the server's own ceiling. */
14
+ export interface AttachmentCaps {
15
+ readonly image: number | null;
16
+ readonly video: number | null;
17
+ readonly file: number | null;
18
+ }
19
+ export declare const NO_CAPS: AttachmentCaps;
20
+ /**
21
+ * The longest voice note (ADR 0054): a phone stops recording at it, and the server writes down
22
+ * what was said in sound no longer than it. Longer sound is kept, with its length and no words.
23
+ */
24
+ export declare const LONGEST_VOICE_NOTE_MS: number;
25
+ /** The kind a cap is named for, from the kind a MIME type sorts into. */
26
+ export declare function capKind(kind: "image" | "video" | "audio" | "file"): CapKind;
27
+ /** The words each kind goes by on a screen and in a sentence. */
28
+ export declare const CAP_WORDS: Readonly<Record<CapKind, {
29
+ readonly one: string;
30
+ readonly many: string;
31
+ }>>;
32
+ /** `50 MB`, `1.5 MB`, `512 KB` — whole megabytes without a decimal, since a cap is usually one. */
33
+ export declare function formatLimit(bytes: number, decimals?: number): string;
34
+ /** The cap that applies to a kind here: the workspace's when it set one under the ceiling, else the ceiling. */
35
+ export declare function attachmentCap(caps: AttachmentCaps, kind: "image" | "video" | "audio" | "file", ceiling: number): number;
36
+ /**
37
+ * Why a file of this size may not be sent, or nothing when it may. One sentence, the same on
38
+ * the server and on every client, naming whose limit it is: the workspace's cap, or the server's
39
+ * ceiling when the workspace set none.
40
+ */
41
+ export declare function sizeRefusal(caps: AttachmentCaps, kind: "image" | "video" | "audio" | "file", bytes: number, ceiling: number): string | undefined;
42
+ /** A cap above the ceiling is refused when it is set, so no cap in the log is ever a dead letter. */
43
+ export declare function capsOverCeiling(caps: AttachmentCaps, ceiling: number): string | undefined;
44
+ /**
45
+ * The kind a file's name suggests, for a courtesy before the bytes are seen: a slot refused by
46
+ * the name's kind saves the upload, and the bytes are judged again by what they turn out to be.
47
+ * An SVG is a file, as the server serves it.
48
+ */
49
+ export declare function kindFromName(filename: string): "image" | "video" | "audio" | "file";
50
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,kGAAkG;AAClG,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjD,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAA+B,CAAC;AAExE,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,OAAO,EAAE,cAAyD,CAAC;AAEhF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAgB,CAAC;AAEnD,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAE3E;AAED,iEAAiE;AACjE,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAK9F,CAAC;AAKJ,mGAAmG;AACnG,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAK/D;AAED,gHAAgH;AAChH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,EAC1C,OAAO,EAAE,MAAM,GACd,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,EAC1C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,SAAS,CAapB;AAED,qGAAqG;AACrG,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQzF;AAkBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAOnF"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * How big an attachment may be (ADR 0047).
3
+ *
4
+ * Two numbers decide it. The server has a ceiling, `CHIPS_MAX_ATTACHMENT_BYTES`, which nothing
5
+ * gets past; under it the workspace may cap photos, videos and files separately, by an event an
6
+ * owner or an admin sends (`attachment.limits_set`). The arithmetic is here, in the package both
7
+ * the server and the clients already share, so the phone refuses a file with the sentence the
8
+ * server would have used, before a byte of it has left the phone.
9
+ */
10
+ export const CAP_KINDS = ["image", "video", "file"];
11
+ export const NO_CAPS = { image: null, video: null, file: null };
12
+ /**
13
+ * The longest voice note (ADR 0054): a phone stops recording at it, and the server writes down
14
+ * what was said in sound no longer than it. Longer sound is kept, with its length and no words.
15
+ */
16
+ export const LONGEST_VOICE_NOTE_MS = 5 * 60 * 1000;
17
+ /** The kind a cap is named for, from the kind a MIME type sorts into. */
18
+ export function capKind(kind) {
19
+ return kind === "image" || kind === "video" ? kind : "file";
20
+ }
21
+ /** The words each kind goes by on a screen and in a sentence. */
22
+ export const CAP_WORDS = {
23
+ image: { one: "photo", many: "photos" },
24
+ video: { one: "video", many: "videos" },
25
+ file: { one: "file", many: "files" },
26
+ };
27
+ const KB = 1024;
28
+ const MB = KB * KB;
29
+ /** `50 MB`, `1.5 MB`, `512 KB` — whole megabytes without a decimal, since a cap is usually one. */
30
+ export function formatLimit(bytes, decimals = 1) {
31
+ if (bytes < MB)
32
+ return `${Math.round(bytes / KB)} KB`;
33
+ const mb = bytes / MB;
34
+ // "1.0 MB" beside a cap of "1 MB" reads as a bug; a decimal that is all zeros goes.
35
+ return `${mb.toFixed(decimals).replace(/\.0+$/, "")} MB`;
36
+ }
37
+ /** The cap that applies to a kind here: the workspace's when it set one under the ceiling, else the ceiling. */
38
+ export function attachmentCap(caps, kind, ceiling) {
39
+ const own = caps[capKind(kind)];
40
+ return own === null ? ceiling : Math.min(own, ceiling);
41
+ }
42
+ /**
43
+ * Why a file of this size may not be sent, or nothing when it may. One sentence, the same on
44
+ * the server and on every client, naming whose limit it is: the workspace's cap, or the server's
45
+ * ceiling when the workspace set none.
46
+ */
47
+ export function sizeRefusal(caps, kind, bytes, ceiling) {
48
+ const cap = attachmentCap(caps, kind, ceiling);
49
+ if (bytes <= cap)
50
+ return undefined;
51
+ const words = CAP_WORDS[capKind(kind)];
52
+ const capWords = formatLimit(cap);
53
+ // A size that rounds to the cap's own words is said more finely: "capped at 1 MB, and this
54
+ // one is 1 MB" reads as a bug.
55
+ const size = formatLimit(bytes) === capWords ? formatLimit(bytes, 2) : formatLimit(bytes);
56
+ const own = caps[capKind(kind)];
57
+ if (own !== null && own <= ceiling) {
58
+ return `${capitalise(words.many)} here are capped at ${capWords}, and this one is ${size}.`;
59
+ }
60
+ return `This server takes ${words.many} up to ${capWords}, and this one is ${size}.`;
61
+ }
62
+ /** A cap above the ceiling is refused when it is set, so no cap in the log is ever a dead letter. */
63
+ export function capsOverCeiling(caps, ceiling) {
64
+ for (const kind of CAP_KINDS) {
65
+ const cap = caps[kind];
66
+ if (cap !== null && cap > ceiling) {
67
+ return `A cap of ${formatLimit(cap)} on ${CAP_WORDS[kind].many} is more than this server takes (${formatLimit(ceiling)}).`;
68
+ }
69
+ }
70
+ return undefined;
71
+ }
72
+ const IMAGE = new Set([
73
+ "jpg",
74
+ "jpeg",
75
+ "png",
76
+ "gif",
77
+ "webp",
78
+ "heic",
79
+ "heif",
80
+ "avif",
81
+ "bmp",
82
+ "tif",
83
+ "tiff",
84
+ ]);
85
+ const VIDEO = new Set(["mp4", "m4v", "mov", "webm", "mkv", "avi", "3gp"]);
86
+ const AUDIO = new Set(["mp3", "m4a", "aac", "wav", "ogg", "oga", "flac", "caf", "opus"]);
87
+ /**
88
+ * The kind a file's name suggests, for a courtesy before the bytes are seen: a slot refused by
89
+ * the name's kind saves the upload, and the bytes are judged again by what they turn out to be.
90
+ * An SVG is a file, as the server serves it.
91
+ */
92
+ export function kindFromName(filename) {
93
+ const dot = filename.lastIndexOf(".");
94
+ const extension = dot === -1 ? "" : filename.slice(dot + 1).toLowerCase();
95
+ if (IMAGE.has(extension))
96
+ return "image";
97
+ if (VIDEO.has(extension))
98
+ return "video";
99
+ if (AUDIO.has(extension))
100
+ return "audio";
101
+ return "file";
102
+ }
103
+ function capitalise(word) {
104
+ return word.charAt(0).toUpperCase() + word.slice(1);
105
+ }
106
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Whether an action matters (ADR 0008, ADR 0036 Gate 5, ADR 0195): it leaves the workspace,
3
+ * spends money, cannot be undone, or carries one of the hot risk classes. What Chips stops for
4
+ * at "Ask when it matters", what a lead is never handed, and since ADR 0195 what always waits
5
+ * for a person: a harness may not settle such an ask itself, a person answers it once (no This
6
+ * session, no Always), and a rule a person's answer made never allows one.
7
+ *
8
+ * Chips goes by what the harness says about the action; it never sees the tool call. Here, so
9
+ * the server, the conformance kit's fake and the apps read the same sentence.
10
+ */
11
+ /** The classes that make an action matter: leaving the workspace, money, credentials, destruction. */
12
+ export declare const HOT_RISK: ReadonlySet<string>;
13
+ /** The flags an ask carries about its action, as the harness declared them. */
14
+ export interface ActionFlags {
15
+ readonly external?: boolean | undefined;
16
+ readonly irreversible?: boolean | undefined;
17
+ readonly money_usd?: number | undefined;
18
+ readonly risk?: readonly string[] | undefined;
19
+ }
20
+ /** Whether the action is one that always waits for a person. */
21
+ export declare function actionMatters(action: ActionFlags): boolean;
22
+ //# sourceMappingURL=matters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matters.d.ts","sourceRoot":"","sources":["../../src/matters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sGAAsG;AACtG,eAAO,MAAM,QAAQ,EAAE,WAAW,CAAC,MAAM,CAMvC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC/C;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAO1D"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Whether an action matters (ADR 0008, ADR 0036 Gate 5, ADR 0195): it leaves the workspace,
3
+ * spends money, cannot be undone, or carries one of the hot risk classes. What Chips stops for
4
+ * at "Ask when it matters", what a lead is never handed, and since ADR 0195 what always waits
5
+ * for a person: a harness may not settle such an ask itself, a person answers it once (no This
6
+ * session, no Always), and a rule a person's answer made never allows one.
7
+ *
8
+ * Chips goes by what the harness says about the action; it never sees the tool call. Here, so
9
+ * the server, the conformance kit's fake and the apps read the same sentence.
10
+ */
11
+ /** The classes that make an action matter: leaving the workspace, money, credentials, destruction. */
12
+ export const HOT_RISK = new Set([
13
+ "external",
14
+ "external_communication",
15
+ "destructive",
16
+ "financial",
17
+ "credential",
18
+ ]);
19
+ /** Whether the action is one that always waits for a person. */
20
+ export function actionMatters(action) {
21
+ return (action.external === true ||
22
+ action.irreversible === true ||
23
+ (action.money_usd ?? 0) > 0 ||
24
+ (action.risk ?? []).some((risk) => HOT_RISK.has(risk)));
25
+ }
26
+ //# sourceMappingURL=matters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matters.js","sourceRoot":"","sources":["../../src/matters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sGAAsG;AACtG,MAAM,CAAC,MAAM,QAAQ,GAAwB,IAAI,GAAG,CAAC;IACnD,UAAU;IACV,wBAAwB;IACxB,aAAa;IACb,WAAW;IACX,YAAY;CACb,CAAC,CAAC;AAUH,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,MAAmB;IAC/C,OAAO,CACL,MAAM,CAAC,QAAQ,KAAK,IAAI;QACxB,MAAM,CAAC,YAAY,KAAK,IAAI;QAC5B,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC,GAAG,CAAC;QAC3B,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CACvD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * What an `@handle` is, on the wire.
3
+ *
4
+ * A message mentions a participant by name — `@Ledger`, `@arjun` — and Chips resolves the
5
+ * handle to a participant id against the members of the space (M3.3), so a harness can tell a
6
+ * message addressed to it from one addressed to the room, and a client can draw the chip. The
7
+ * server that resolves and the clients that render have to agree on what a handle is, so the
8
+ * rule lives here, with the schemas, and nowhere else.
9
+ *
10
+ * A handle is `@` followed by two to sixty of letters, digits, `_`, `-` and `.`, not ending in
11
+ * a dot or an underscore — so `@Ledger.` at the end of a sentence names Ledger, and so does
12
+ * `_@Ledger_` in italics — at the start of the text, after a space, or after what opens Markdown's
13
+ * emphasis, a parenthesis or a quotation: `**@Ledger:**`, `(@Ledger)`, `"@Ledger"`. The apps draw
14
+ * a mention inside emphasis as one, so the server has to hear it there too (AW-20: a lead's
15
+ * `**@OpenClaw3:**` drew a mention and reached nobody). After a letter or a digit it is not one,
16
+ * so an email address names nobody.
17
+ *
18
+ * A participant answers to two handles, both case-insensitive: their display name
19
+ * with its spaces removed, and its first word; `Scout · mini` is `@scout`. The whole name comes
20
+ * first (ADR 0091): a first word names a participant only where nobody else among them has it,
21
+ * as a first word or a whole name, so beside "OpenClaw 2", `@OpenClaw` is OpenClaw's.
22
+ *
23
+ * `@all` names every agent in the space (ADR 0081), so no participant answers to it.
24
+ */
25
+ /** Whether a word, without its `@`, is one a text can mention: `OpenClaw2`, not `J` or `Scout·mini`. */
26
+ export declare function isHandle(word: string): boolean;
27
+ /** A fresh, non-global pattern that captures the handle after the `@` in group 1. */
28
+ export declare function mentionPattern(): RegExp;
29
+ /** The handles a text mentions, lowercased, in order, each once. */
30
+ export declare function handlesIn(text: string): string[];
31
+ /** The handle that names every harness in the space rather than one participant. */
32
+ export declare const EVERYONE_HANDLE = "all";
33
+ /** Whether a text says `@all`. */
34
+ export declare function mentionsEveryone(text: string): boolean;
35
+ /**
36
+ * The handles a participant answers to: the whole name without spaces, and its first word.
37
+ * Never `all`, which names everyone.
38
+ */
39
+ export declare function handlesOf(displayName: string): string[];
40
+ export interface Mentionable {
41
+ readonly id: string;
42
+ readonly displayName: string;
43
+ /**
44
+ * Other names it answers to, after every participant's own: an agent answers to its harness's
45
+ * name where it is the harness's one agent in the room (ADR 0087's addendum), so `@Jacobian`
46
+ * still names Jacobian's agent there, whatever the agent is called.
47
+ */
48
+ readonly also?: readonly string[] | undefined;
49
+ }
50
+ /**
51
+ * Who each handle names among these participants (ADR 0091): every whole name first, the first
52
+ * given winning where two are one (Chips numbers an agent whose name clashes, so only names from
53
+ * before that can be); then each first word that one participant has and nobody has as a whole
54
+ * name; then the names a participant is only `also` known by, where nobody answers to them yet. A
55
+ * first word two participants share names nobody, and no `also` takes it.
56
+ */
57
+ export declare function handleMap(participants: readonly Mentionable[]): Map<string, string>;
58
+ /**
59
+ * The participants a text mentions, as ids, in the order the text names them and each once. A
60
+ * handle nobody answers to is not a mention: it stays plain text on screen.
61
+ */
62
+ export declare function resolveMentions(text: string, participants: readonly Mentionable[]): string[];
63
+ //# sourceMappingURL=mentions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mentions.d.ts","sourceRoot":"","sources":["../../src/mentions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAUH,wGAAwG;AACxG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,qFAAqF;AACrF,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQhD;AAED,oFAAoF;AACpF,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,kCAAkC;AAClC,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAOvD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC/C;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBnF;AA0BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,CAQ5F"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * What an `@handle` is, on the wire.
3
+ *
4
+ * A message mentions a participant by name — `@Ledger`, `@arjun` — and Chips resolves the
5
+ * handle to a participant id against the members of the space (M3.3), so a harness can tell a
6
+ * message addressed to it from one addressed to the room, and a client can draw the chip. The
7
+ * server that resolves and the clients that render have to agree on what a handle is, so the
8
+ * rule lives here, with the schemas, and nowhere else.
9
+ *
10
+ * A handle is `@` followed by two to sixty of letters, digits, `_`, `-` and `.`, not ending in
11
+ * a dot or an underscore — so `@Ledger.` at the end of a sentence names Ledger, and so does
12
+ * `_@Ledger_` in italics — at the start of the text, after a space, or after what opens Markdown's
13
+ * emphasis, a parenthesis or a quotation: `**@Ledger:**`, `(@Ledger)`, `"@Ledger"`. The apps draw
14
+ * a mention inside emphasis as one, so the server has to hear it there too (AW-20: a lead's
15
+ * `**@OpenClaw3:**` drew a mention and reached nobody). After a letter or a digit it is not one,
16
+ * so an email address names nobody.
17
+ *
18
+ * A participant answers to two handles, both case-insensitive: their display name
19
+ * with its spaces removed, and its first word; `Scout · mini` is `@scout`. The whole name comes
20
+ * first (ADR 0091): a first word names a participant only where nobody else among them has it,
21
+ * as a first word or a whole name, so beside "OpenClaw 2", `@OpenClaw` is OpenClaw's.
22
+ *
23
+ * `@all` names every agent in the space (ADR 0081), so no participant answers to it.
24
+ */
25
+ const HANDLE = "[a-z0-9_-][a-z0-9_.-]{0,58}[a-z0-9-]";
26
+ /**
27
+ * Where a handle may begin: the start of the text, after a space or one of `*("'“‘`, or after an
28
+ * underscore that is not a word's, as italics open: `_@Ledger_`, not `first_@example.com`.
29
+ */
30
+ const BEFORE = `(?:^|(?<=[\\s*("'\u201C\u2018])|(?<=(?:^|[^a-z0-9])_))`;
31
+ /** Whether a word, without its `@`, is one a text can mention: `OpenClaw2`, not `J` or `Scout·mini`. */
32
+ export function isHandle(word) {
33
+ return new RegExp(`^${HANDLE}$`, "i").test(word);
34
+ }
35
+ /** A fresh, non-global pattern that captures the handle after the `@` in group 1. */
36
+ export function mentionPattern() {
37
+ return new RegExp(`${BEFORE}@(${HANDLE})`, "i");
38
+ }
39
+ /** The handles a text mentions, lowercased, in order, each once. */
40
+ export function handlesIn(text) {
41
+ const found = [];
42
+ const pattern = new RegExp(`${BEFORE}@(${HANDLE})`, "gi");
43
+ for (const match of text.matchAll(pattern)) {
44
+ const handle = match[1].toLowerCase();
45
+ if (!found.includes(handle))
46
+ found.push(handle);
47
+ }
48
+ return found;
49
+ }
50
+ /** The handle that names every harness in the space rather than one participant. */
51
+ export const EVERYONE_HANDLE = "all";
52
+ /** Whether a text says `@all`. */
53
+ export function mentionsEveryone(text) {
54
+ return handlesIn(text).includes(EVERYONE_HANDLE);
55
+ }
56
+ /**
57
+ * The handles a participant answers to: the whole name without spaces, and its first word.
58
+ * Never `all`, which names everyone.
59
+ */
60
+ export function handlesOf(displayName) {
61
+ const trimmed = displayName.trim().toLowerCase();
62
+ if (trimmed === "")
63
+ return [];
64
+ const whole = trimmed.replace(/\s+/g, "");
65
+ const first = trimmed.split(/\s+/)[0];
66
+ const handles = whole === first ? [whole] : [whole, first];
67
+ return handles.filter((handle) => handle !== EVERYONE_HANDLE);
68
+ }
69
+ /**
70
+ * Who each handle names among these participants (ADR 0091): every whole name first, the first
71
+ * given winning where two are one (Chips numbers an agent whose name clashes, so only names from
72
+ * before that can be); then each first word that one participant has and nobody has as a whole
73
+ * name; then the names a participant is only `also` known by, where nobody answers to them yet. A
74
+ * first word two participants share names nobody, and no `also` takes it.
75
+ */
76
+ export function handleMap(participants) {
77
+ const byHandle = new Map();
78
+ for (const participant of participants) {
79
+ const whole = handlesOf(participant.displayName)[0];
80
+ if (whole !== undefined && !byHandle.has(whole))
81
+ byHandle.set(whole, participant.id);
82
+ }
83
+ const shared = giveFirstWords(byHandle, participants);
84
+ const also = participants.flatMap((participant) => (participant.also ?? []).flatMap((name) => handlesOf(name).map((handle) => [handle, participant.id])));
85
+ for (const [handle, id] of also) {
86
+ if (!byHandle.has(handle) && !shared.has(handle))
87
+ byHandle.set(handle, id);
88
+ }
89
+ return byHandle;
90
+ }
91
+ /**
92
+ * Each first word one participant has and nobody has as a whole name, given to its participant;
93
+ * the first words two share, which name nobody, returned.
94
+ */
95
+ function giveFirstWords(byHandle, participants) {
96
+ const firsts = new Map();
97
+ for (const participant of participants) {
98
+ const handles = handlesOf(participant.displayName);
99
+ const first = handles[1] ?? handles[0];
100
+ if (first === undefined)
101
+ continue;
102
+ firsts.set(first, (firsts.get(first) ?? new Set()).add(participant.id));
103
+ }
104
+ const shared = new Set();
105
+ for (const [first, ids] of firsts) {
106
+ if (byHandle.has(first))
107
+ continue;
108
+ if (ids.size === 1)
109
+ byHandle.set(first, [...ids][0]);
110
+ else
111
+ shared.add(first);
112
+ }
113
+ return shared;
114
+ }
115
+ /**
116
+ * The participants a text mentions, as ids, in the order the text names them and each once. A
117
+ * handle nobody answers to is not a mention: it stays plain text on screen.
118
+ */
119
+ export function resolveMentions(text, participants) {
120
+ const byHandle = handleMap(participants);
121
+ const ids = [];
122
+ for (const handle of handlesIn(text)) {
123
+ const id = byHandle.get(handle);
124
+ if (id !== undefined && !ids.includes(id))
125
+ ids.push(id);
126
+ }
127
+ return ids;
128
+ }
129
+ //# sourceMappingURL=mentions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mentions.js","sourceRoot":"","sources":["../../src/mentions.ts"],"names":[],"mappings":"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"}