toolroll 0.9.54 → 0.9.56

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 (350) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +29 -12
  3. package/dist/action-ledger.d.ts +2 -4
  4. package/dist/action-ledger.js +2 -10
  5. package/dist/agent-onboard.js +4 -1
  6. package/dist/approval-policy.d.ts +5 -5
  7. package/dist/approval-policy.js +4 -4
  8. package/dist/approval-rules-ui.d.ts +3 -2
  9. package/dist/approval-rules-ui.js +17 -22
  10. package/dist/assignment-ui.d.ts +3 -2
  11. package/dist/assignment-ui.js +4 -11
  12. package/dist/backup-ui.d.ts +2 -1
  13. package/dist/backup-ui.js +16 -27
  14. package/dist/brand-mark.d.ts +3 -2
  15. package/dist/brand-mark.js +4 -4
  16. package/dist/browser/workspace.css +1 -1
  17. package/dist/browser/workspace.js +25 -25
  18. package/dist/browser-shell.d.ts +2 -1
  19. package/dist/browser-shell.js +9 -9
  20. package/dist/browser-workspace.d.ts +14 -15
  21. package/dist/browser-workspace.js +0 -3
  22. package/dist/budget-alerts.js +3 -3
  23. package/dist/chat-actions.d.ts +23 -18
  24. package/dist/chat-actions.js +110 -88
  25. package/dist/chat-ask.d.ts +2 -2
  26. package/dist/chat-ask.js +5 -5
  27. package/dist/chat-channel.d.ts +15 -15
  28. package/dist/chat-channel.js +20 -20
  29. package/dist/chat-controls.d.ts +0 -4
  30. package/dist/chat-controls.js +0 -1
  31. package/dist/chat-core.js +1 -1
  32. package/dist/chat-delivery-state.js +2 -2
  33. package/dist/chat-delivery.d.ts +2 -2
  34. package/dist/chat-delivery.js +27 -34
  35. package/dist/chat-flow.js +3 -3
  36. package/dist/chat-messages.d.ts +1 -1
  37. package/dist/chat-messages.js +4 -4
  38. package/dist/chat-migration.d.ts +1 -1
  39. package/dist/chat-migration.js +11 -11
  40. package/dist/chat-polish.d.ts +5 -4
  41. package/dist/chat-polish.js +9 -10
  42. package/dist/chat-rooms.js +3 -3
  43. package/dist/chat-warmth.d.ts +2 -2
  44. package/dist/check-levels-ui.d.ts +2 -1
  45. package/dist/check-levels-ui.js +26 -53
  46. package/dist/cli.d.ts +3 -1
  47. package/dist/cli.js +15 -10
  48. package/dist/coding-shipping-ui.d.ts +2 -1
  49. package/dist/coding-shipping-ui.js +6 -6
  50. package/dist/coding-ui.d.ts +3 -1
  51. package/dist/coding-ui.js +30 -29
  52. package/dist/contracts/chat-actions.d.ts +32 -27
  53. package/dist/contracts/chat-actions.js +29 -11
  54. package/dist/contracts/chat-content.d.ts +1 -1
  55. package/dist/contracts/chat-content.js +1 -1
  56. package/dist/contracts/chat-tables.js +4 -4
  57. package/dist/contracts/cli.d.ts +1 -3
  58. package/dist/contracts/console-api.d.ts +7 -7
  59. package/dist/contracts/console-api.js +13 -12
  60. package/dist/contracts/flow-propose.d.ts +3 -3
  61. package/dist/contracts/flow.d.ts +132 -125
  62. package/dist/contracts/flow.js +27 -12
  63. package/dist/contracts/health.d.ts +1 -3
  64. package/dist/contracts/lead-context.d.ts +1 -1
  65. package/dist/contracts/lead-context.js +2 -2
  66. package/dist/contracts/lead-tools.d.ts +20 -18
  67. package/dist/contracts/lead-tools.js +15 -15
  68. package/dist/contracts/stage-output.d.ts +1 -1
  69. package/dist/contracts/stage-output.js +1 -1
  70. package/dist/contracts/store-json.d.ts +23 -22
  71. package/dist/contracts/store-json.js +17 -17
  72. package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
  73. package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
  74. package/dist/control-ui.d.ts +5 -4
  75. package/dist/control-ui.js +9 -34
  76. package/dist/converse.d.ts +23 -23
  77. package/dist/converse.js +28 -28
  78. package/dist/coordinator-proposals.d.ts +2 -2
  79. package/dist/coordinator-proposals.js +1 -1
  80. package/dist/coordinator.js +1 -1
  81. package/dist/credentials-ui.d.ts +5 -4
  82. package/dist/credentials-ui.js +23 -24
  83. package/dist/demo-chat.d.ts +3 -3
  84. package/dist/demo-chat.js +29 -76
  85. package/dist/deprecations.d.ts +12 -0
  86. package/dist/deprecations.js +20 -0
  87. package/dist/desktop-update-gate.js +8 -4
  88. package/dist/desktop-update.js +6 -1
  89. package/dist/discord-chat.js +6 -6
  90. package/dist/discord-settings.d.ts +3 -2
  91. package/dist/discord-settings.js +17 -42
  92. package/dist/dispatch.d.ts +1 -1
  93. package/dist/dispatch.js +1 -1
  94. package/dist/envelope.js +10 -1
  95. package/dist/evidence-pack.d.ts +2 -1
  96. package/dist/evidence-pack.js +45 -49
  97. package/dist/export-ui.d.ts +7 -2
  98. package/dist/export-ui.js +4 -10
  99. package/dist/export.js +7 -7
  100. package/dist/flow-engine.d.ts +2 -2
  101. package/dist/flow-engine.js +21 -21
  102. package/dist/flow-gallery-ui.d.ts +4 -3
  103. package/dist/flow-gallery-ui.js +28 -43
  104. package/dist/flow-insights.d.ts +1 -1
  105. package/dist/flow-share.js +13 -8
  106. package/dist/flow-starters-ui.d.ts +2 -1
  107. package/dist/flow-starters-ui.js +12 -15
  108. package/dist/flow-steps.d.ts +5 -5
  109. package/dist/flow-steps.js +19 -19
  110. package/dist/flow-triggers.d.ts +2 -1
  111. package/dist/flow-triggers.js +17 -15
  112. package/dist/flows-cli.js +1 -1
  113. package/dist/flows-ui.d.ts +4 -3
  114. package/dist/flows-ui.js +38 -49
  115. package/dist/flows.js +21 -20
  116. package/dist/guides.js +112 -6
  117. package/dist/html.d.ts +76 -0
  118. package/dist/html.js +173 -0
  119. package/dist/integrations-ui.d.ts +2 -1
  120. package/dist/integrations-ui.js +19 -23
  121. package/dist/invoke.js +1 -1
  122. package/dist/kits-ui.d.ts +5 -4
  123. package/dist/kits-ui.js +22 -24
  124. package/dist/kits.d.ts +12 -12
  125. package/dist/kits.js +31 -31
  126. package/dist/knowledge-ui.d.ts +6 -5
  127. package/dist/knowledge-ui.js +26 -25
  128. package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
  129. package/dist/{mate-cli.js → lead-cli.js} +38 -38
  130. package/dist/lead-commitments.js +4 -4
  131. package/dist/lead-context.d.ts +12 -2
  132. package/dist/lead-context.js +15 -17
  133. package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
  134. package/dist/{mate-contract.js → lead-contract.js} +7 -6
  135. package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
  136. package/dist/{mate-doors.js → lead-doors.js} +24 -24
  137. package/dist/lead-follow.d.ts +5 -5
  138. package/dist/lead-follow.js +9 -9
  139. package/dist/lead-people.d.ts +5 -5
  140. package/dist/lead-people.js +14 -14
  141. package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
  142. package/dist/{mate-progress.js → lead-progress.js} +3 -3
  143. package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
  144. package/dist/{mate-tools.js → lead-tools.js} +109 -101
  145. package/dist/{mate.d.ts → lead.d.ts} +27 -27
  146. package/dist/{mate.js → lead.js} +102 -102
  147. package/dist/ledger-view.d.ts +2 -1
  148. package/dist/ledger-view.js +27 -29
  149. package/dist/limits-ui.d.ts +3 -2
  150. package/dist/limits-ui.js +8 -8
  151. package/dist/live-bus.d.ts +50 -8
  152. package/dist/live-bus.js +133 -50
  153. package/dist/mcp-connect.d.ts +1 -1
  154. package/dist/mcp-connect.js +3 -3
  155. package/dist/mcp-core.js +1 -1
  156. package/dist/mcp-oauth.js +35 -36
  157. package/dist/memory-pass.d.ts +2 -2
  158. package/dist/memory-pass.js +7 -7
  159. package/dist/models-ui.d.ts +2 -1
  160. package/dist/models-ui.js +25 -35
  161. package/dist/monitoring-ui.d.ts +4 -3
  162. package/dist/monitoring-ui.js +13 -26
  163. package/dist/notification-rows.d.ts +2 -2
  164. package/dist/openrouter-models.d.ts +2 -1
  165. package/dist/openrouter-models.js +5 -9
  166. package/dist/operate.d.ts +3 -3
  167. package/dist/operate.js +46 -41
  168. package/dist/people-audit-ui.d.ts +2 -1
  169. package/dist/people-audit-ui.js +13 -13
  170. package/dist/policy-ui.d.ts +3 -2
  171. package/dist/policy-ui.js +17 -23
  172. package/dist/policy.d.ts +1 -1
  173. package/dist/principal.d.ts +1 -1
  174. package/dist/principal.js +2 -2
  175. package/dist/project-delete-ui.d.ts +4 -3
  176. package/dist/project-delete-ui.js +16 -28
  177. package/dist/project-delete.d.ts +1 -1
  178. package/dist/project-delete.js +26 -26
  179. package/dist/project-memory.js +1 -1
  180. package/dist/project-tools.d.ts +1 -1
  181. package/dist/project-tools.js +1 -1
  182. package/dist/pull-request-ui.d.ts +2 -1
  183. package/dist/pull-request-ui.js +17 -32
  184. package/dist/recipe-ui.d.ts +8 -7
  185. package/dist/recipe-ui.js +39 -32
  186. package/dist/release-adapters.d.ts +60 -0
  187. package/dist/release-adapters.js +435 -0
  188. package/dist/release-cli.d.ts +24 -0
  189. package/dist/release-cli.js +181 -0
  190. package/dist/release-gate.d.ts +80 -0
  191. package/dist/release-gate.js +150 -0
  192. package/dist/release-links.d.ts +29 -0
  193. package/dist/release-links.js +102 -0
  194. package/dist/release.d.ts +331 -0
  195. package/dist/release.js +550 -0
  196. package/dist/reply-shape.d.ts +2 -1
  197. package/dist/reply-shape.js +5 -5
  198. package/dist/repository-context-ui.d.ts +2 -1
  199. package/dist/repository-context-ui.js +2 -7
  200. package/dist/request-budget-ui.d.ts +2 -1
  201. package/dist/request-budget-ui.js +16 -16
  202. package/dist/result-review.d.ts +2 -3
  203. package/dist/result-review.js +3 -9
  204. package/dist/retention-ui.d.ts +7 -1
  205. package/dist/retention-ui.js +7 -11
  206. package/dist/retention.js +4 -4
  207. package/dist/scope.d.ts +2 -2
  208. package/dist/scout-report.d.ts +1 -1
  209. package/dist/scout-report.js +1 -1
  210. package/dist/serve.d.ts +11 -2
  211. package/dist/serve.js +105 -99
  212. package/dist/server/chat.d.ts +7 -10
  213. package/dist/server/chat.js +984 -1021
  214. package/dist/server/chrome.d.ts +291 -0
  215. package/dist/server/chrome.js +747 -0
  216. package/dist/server/console.css +2819 -0
  217. package/dist/server/flows.d.ts +2 -6
  218. package/dist/server/flows.js +854 -773
  219. package/dist/server/guards.d.ts +1 -1
  220. package/dist/server/guards.js +8 -5
  221. package/dist/server/handler-context.d.ts +1 -1
  222. package/dist/server/handler-context.js +1 -1
  223. package/dist/server/handler-registry.d.ts +8 -6
  224. package/dist/server/handler-registry.js +14 -2
  225. package/dist/server/http.d.ts +274 -0
  226. package/dist/server/http.js +239 -0
  227. package/dist/server/live.d.ts +34 -0
  228. package/dist/server/live.js +309 -0
  229. package/dist/server/pages.d.ts +2 -6
  230. package/dist/server/pages.js +808 -789
  231. package/dist/server/people-tokens.d.ts +1 -6
  232. package/dist/server/people-tokens.js +581 -573
  233. package/dist/server/remote.d.ts +4 -7
  234. package/dist/server/remote.js +39 -70
  235. package/dist/server/render-chat.d.ts +402 -0
  236. package/dist/server/render-chat.js +927 -0
  237. package/dist/server/render-pages.d.ts +535 -0
  238. package/dist/server/render-pages.js +1277 -0
  239. package/dist/server/render-people.d.ts +32 -0
  240. package/dist/server/render-people.js +121 -0
  241. package/dist/server/render-results.d.ts +744 -0
  242. package/dist/server/render-results.js +2237 -0
  243. package/dist/server/render-settings.d.ts +154 -0
  244. package/dist/server/render-settings.js +580 -0
  245. package/dist/server/render-tasks.d.ts +632 -0
  246. package/dist/server/render-tasks.js +2345 -0
  247. package/dist/server/request-context.d.ts +46 -0
  248. package/dist/server/request-context.js +17 -0
  249. package/dist/server/route-table.d.ts +25 -3
  250. package/dist/server/route-table.js +167 -77
  251. package/dist/server/runtime.d.ts +28 -20
  252. package/dist/server/runtime.js +7 -1
  253. package/dist/server/session.d.ts +138 -0
  254. package/dist/server/session.js +94 -0
  255. package/dist/server/settings.d.ts +1 -5
  256. package/dist/server/settings.js +2186 -2121
  257. package/dist/server/task-views.d.ts +11 -4
  258. package/dist/server/task-views.js +26 -22
  259. package/dist/server/tasks.d.ts +9 -9
  260. package/dist/server/tasks.js +1489 -1420
  261. package/dist/server-telemetry.d.ts +4 -3
  262. package/dist/server-telemetry.js +4 -8
  263. package/dist/setup-guide.d.ts +2 -2
  264. package/dist/skills-ui.d.ts +4 -3
  265. package/dist/skills-ui.js +14 -17
  266. package/dist/skills.d.ts +1 -1
  267. package/dist/skills.js +2 -1
  268. package/dist/slack-chat.d.ts +2 -2
  269. package/dist/slack-chat.js +6 -6
  270. package/dist/slack-settings.d.ts +3 -2
  271. package/dist/slack-settings.js +17 -42
  272. package/dist/spend-ui.d.ts +5 -4
  273. package/dist/spend-ui.js +17 -40
  274. package/dist/spend.d.ts +14 -14
  275. package/dist/spend.js +31 -31
  276. package/dist/sso-ui.d.ts +3 -2
  277. package/dist/sso-ui.js +26 -27
  278. package/dist/storage-ui.d.ts +2 -1
  279. package/dist/storage-ui.js +14 -33
  280. package/dist/store-baseline.d.ts +19 -0
  281. package/dist/store-baseline.js +2873 -0
  282. package/dist/store.d.ts +197 -249
  283. package/dist/store.js +747 -3084
  284. package/dist/subagent-admin.d.ts +53 -0
  285. package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
  286. package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
  287. package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
  288. package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
  289. package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
  290. package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
  291. package/dist/{teammate-question.js → subagent-question.js} +17 -17
  292. package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
  293. package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
  294. package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
  295. package/dist/{teammate-week.js → subagent-week.js} +23 -23
  296. package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
  297. package/dist/{teammate-work.js → subagent-work.js} +50 -50
  298. package/dist/subagents-ui.d.ts +22 -0
  299. package/dist/subagents-ui.js +137 -0
  300. package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
  301. package/dist/{teammates.js → subagents.js} +21 -21
  302. package/dist/subscription-chat.d.ts +13 -13
  303. package/dist/subscription-chat.js +19 -19
  304. package/dist/surface.d.ts +19 -2
  305. package/dist/surface.js +20 -33
  306. package/dist/task-composer.d.ts +2 -1
  307. package/dist/task-composer.js +7 -7
  308. package/dist/task-status.d.ts +4 -3
  309. package/dist/task-status.js +15 -16
  310. package/dist/team-cli.d.ts +1 -0
  311. package/dist/team-cli.js +14 -2
  312. package/dist/team-http.d.ts +5 -9
  313. package/dist/team-http.js +5 -63
  314. package/dist/team-leads.js +13 -13
  315. package/dist/team-runtime.d.ts +2 -2
  316. package/dist/team-runtime.js +8 -8
  317. package/dist/team-ui.d.ts +2 -1
  318. package/dist/team-ui.js +2 -5
  319. package/dist/teams-chat.js +6 -6
  320. package/dist/teams-settings.d.ts +3 -2
  321. package/dist/teams-settings.js +20 -31
  322. package/dist/telegram-flow.d.ts +3 -3
  323. package/dist/telegram-flow.js +3 -3
  324. package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
  325. package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
  326. package/dist/telegram-settings.d.ts +3 -2
  327. package/dist/telegram-settings.js +14 -25
  328. package/dist/telegram-status.js +1 -1
  329. package/dist/telegram-team.d.ts +1 -1
  330. package/dist/telegram-team.js +5 -5
  331. package/dist/telegram.d.ts +1 -1
  332. package/dist/telegram.js +9 -30
  333. package/dist/text-limits.d.ts +18 -18
  334. package/dist/text-limits.js +18 -18
  335. package/dist/toolroll-update-ui.d.ts +3 -2
  336. package/dist/toolroll-update-ui.js +35 -39
  337. package/dist/toolroll-update.d.ts +8 -0
  338. package/dist/toolroll-update.js +45 -8
  339. package/dist/tools-ui.d.ts +5 -2
  340. package/dist/tools-ui.js +31 -31
  341. package/dist/when-html.d.ts +11 -3
  342. package/dist/when-html.js +5 -6
  343. package/dist/workspace-ui.d.ts +4 -2
  344. package/dist/workspace-ui.js +10 -10
  345. package/package.json +1 -2
  346. package/dist/server/shared.d.ts +0 -3151
  347. package/dist/server/shared.js +0 -12001
  348. package/dist/teammate-admin.d.ts +0 -53
  349. package/dist/teammates-ui.d.ts +0 -20
  350. package/dist/teammates-ui.js +0 -166
@@ -0,0 +1,137 @@
1
+ import { labelOf, nameOf, summaryOf, SUBAGENT_MODELS, zonesOf } from "./subagent-admin.js";
2
+ import { SUBAGENT_TEMPLATES } from "./subagents.js";
3
+ import { projectToolsOf } from "./project-tools.js";
4
+ import { callWords, defaultRule, numberFields, receiptWords } from "./subagent-tools.js";
5
+ import { MEMORY_CHARS, searchMemories } from "./subagent-memory.js";
6
+ import { deskOf, localZone, routinesOf } from "./subagent-desk.js";
7
+ import { weekOf, weekWords } from "./subagent-week.js";
8
+ import { describeSchedule, parseSchedule } from "./flow-schedule.js";
9
+ import { html, joinHtml, postForm } from "./html.js";
10
+ /** v94: the Tools section's rule rows; a limit's fields show only while "Up to a limit" is chosen. */
11
+ export const SUBAGENT_CSS = `.subagent-tools .tool-grant{border-top:1px solid var(--so-line);padding-top:12px;margin-top:12px}.subagent-tools .tool-grant:first-of-type{border-top:0;margin-top:0;padding-top:0}.subagent-tools h3{font-size:1rem;margin:0 0 4px}.subagent-tools .tool-rule{display:grid;gap:6px;padding:10px 0;border-bottom:1px solid var(--so-line)}.subagent-tools .tool-rule:last-of-type{border-bottom:0}.subagent-tools .tool-rule .meta{display:block}.subagent-tools select,.subagent-tools input{max-width:100%;box-sizing:border-box}.subagent-tools .tool-limit{display:none;flex-wrap:wrap;align-items:center;gap:6px}.subagent-tools .tool-rule:has(option[value="limit"]:checked) .tool-limit{display:flex}.subagent-tools .tool-rule>select{width:auto;min-width:16em}.subagent-tools .tool-limit select{width:auto}.subagent-tools .tool-limit input{width:7em}.subagent-tools .tool-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.subagent-tools button{min-height:44px}.subagent-activity li{overflow-wrap:anywhere}.subagent-memory .memory-tell,.subagent-memory .memory-search{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 12px}.subagent-memory .memory-tell textarea{flex:1 1 280px}.subagent-memory .memory-search input{flex:1 1 220px}.subagent-memory button{min-height:44px}.subagent-memories{list-style:none;padding:0;margin:0}.subagent-memories li{padding:10px 0;border-top:1px solid var(--so-line);overflow-wrap:anywhere}.subagent-memories li:first-child{border-top:0}.subagent-memories .meta{display:block;font-size:.85rem}.subagent-memories details.memory-edit{border:0;padding:0;margin:2px 0 0;background:transparent;box-shadow:none}.memory-edit summary{cursor:pointer;min-height:32px;display:inline-flex;align-items:center;font-size:.85rem;font-weight:400;color:var(--so-muted)}.tool-undo{border:0;padding:0;margin:8px 0;background:transparent;box-shadow:none}.tool-undo summary{cursor:pointer;min-height:36px;display:inline-flex;align-items:center;font-size:.9rem}.tool-undo-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0}.tool-undo-row select{width:auto}.subagent-overrides,.subagent-undo{padding-left:18px}.subagent-undo li{margin:6px 0}.subagent-undo form{display:inline}.subagent-week h3{font-size:1rem;margin:14px 0 4px}.subagent-week button{min-height:44px}.subagent-routines{list-style:none;padding:0;margin:8px 0}.subagent-routines li{display:grid;gap:4px;padding:10px 0;border-top:1px solid var(--so-line)}.subagent-routines li:first-child{border-top:0}.routine-buttons{display:flex;flex-wrap:wrap;gap:8px}.routine-buttons button,.routine-add button{min-height:44px}.routine-add{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:8px 0}.routine-add label{display:grid;gap:4px;flex:1 1 200px}.routine-add label:first-of-type{flex:0 1 180px}.subagent-actions,.question-options{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.subagent-actions form,.question-options form{margin:0}.memory-edit form{display:grid;gap:8px;margin-top:6px}.memory-buttons{display:flex;flex-wrap:wrap;gap:8px}@media(max-width:600px){.subagent-tools select,.subagent-tools input{font-size:16px}}`;
12
+ const USES = [["free", "Do it"], ["limit", "Do it, up to a limit"], ["ask", "Ask first"], ["never", "Never"]];
13
+ /** v94: which project tools it may use, and its rule for each action. */
14
+ function toolsSection(store, mate, canManage) {
15
+ const name = nameOf(mate);
16
+ const tools = projectToolsOf(store, mate.repo);
17
+ const grants = store.subagentGrants(mate.id);
18
+ const toolsPage = `/settings/tools?repo=${encodeURIComponent(mate.repo)}`;
19
+ const granted = grants.map(grant => {
20
+ const gone = !tools.some(one => one.name === grant.tool);
21
+ const rows = grant.actions.map(action => {
22
+ const rule = grant.rules[action.name] ?? defaultRule(action);
23
+ const use = rule.use === "free" && rule.limit !== undefined ? "limit" : rule.use;
24
+ const numbers = numberFields(action);
25
+ const words = rule.use === "never" ? "Never" : rule.use === "ask" ? "Ask first" : rule.limit === undefined ? "Do it" : `Do it up to ${rule.limit.field} ${rule.limit.over}, ask first above`;
26
+ const about = action.about !== "" && html `<span class="meta">${action.about}</span>`;
27
+ if (!canManage)
28
+ return html `<li class="tool-rule"><span><strong>${action.name}</strong> · ${words}</span>${about}</li>`;
29
+ const field = `${grant.tool}-${action.name}`.replace(/[^A-Za-z0-9_-]/g, "-");
30
+ const limit = numbers.length > 0 && html `<span class="tool-limit">Ask first when <select name="field.${action.name}" aria-label="The number the limit is on">${numbers.map(one => html `<option value="${one}"${rule.limit?.field === one && html ` selected`}>${one}</option>`)}</select> is over <input type="number" name="over.${action.name}" min="0" step="any" value="${rule.limit === undefined ? "" : rule.limit.over}" aria-label="The limit"></span>`;
31
+ return html `<div class="tool-rule" data-tool-action="${action.name}"><label for="use-${field}"><strong>${action.name}</strong>${about}</label><select id="use-${field}" name="use.${action.name}">${USES.filter(([value]) => value !== "limit" || numbers.length > 0).map(([value, label]) => html `<option value="${value}"${value === use && html ` selected`}>${label}</option>`)}</select>${limit}</div>`;
32
+ });
33
+ const head = html `<h3>${grant.tool}</h3>${gone && html `<p class="problem">This project doesn't have ${grant.tool} any more, so ${name} can't use it.</p>`}`;
34
+ // v97: which action undoes which, for the Undo button on its receipts. Optional, so it's folded away.
35
+ const undoing = grant.actions.filter(one => !one.readOnly).map(action => {
36
+ const chosen = grant.rules[action.name]?.undo ?? "";
37
+ return html `<label class="tool-undo-row">${action.name} is undone by<select name="undo.${action.name}"><option value="">nothing</option>${grant.actions.filter(other => other.name !== action.name).map(other => html `<option value="${other.name}"${other.name === chosen && html ` selected`}>${other.name}</option>`)}</select></label>`;
38
+ });
39
+ const undoBox = undoing.length > 0 && html `<details class="tool-undo"${grant.actions.some(one => grant.rules[one.name]?.undo !== undefined) && html ` open`}><summary>Undo</summary><p class="meta">When an action has an opposite, a person can press Undo on its receipts: the opposite is called with the same input.</p>${undoing}</details>`;
40
+ return canManage
41
+ ? html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}${postForm(`/settings/lead/subagents/${mate.id}/tools`, html `${rows}${undoBox}<div class="tool-buttons"><button name="op" value="rules">Save rules</button><button name="op" value="revoke" class="secondary">Stop using ${grant.tool}</button></div>`, { hidden: { tool: grant.tool } })}</div>`
42
+ : html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}<ul>${rows}</ul></div>`;
43
+ });
44
+ const open = tools.filter(one => !grants.some(grant => grant.tool === one.name));
45
+ const add = !canManage ? null : tools.length === 0 ? html `<p class="meta">This project has no tools yet. <a href="${toolsPage}">Add one</a>, then let ${name} use it here.</p>`
46
+ : open.length === 0 ? null : postForm(`/settings/lead/subagents/${mate.id}/tools`, html `<label>Let ${name} use<select name="tool">${open.map(one => html `<option value="${one.name}">${one.name}${one.spec.about === "" ? "" : ` — ${one.spec.about.slice(0, 80)}`}</option>`)}</select></label><button>Add tool</button>`, { attrs: { class: "tool-add" }, hidden: { op: "grant" } });
47
+ if (grants.length === 0 && add === null)
48
+ return "";
49
+ return html `<section class="card subagent-tools" id="tools"><h2>Tools</h2>${grants.length === 0 && html `<p class="meta">${name} doesn't use any tools yet. Reading starts as “Do it”; everything else asks you first.</p>`}${granted}${add}</section>`;
50
+ }
51
+ const when = (at) => new Date(at).toLocaleString("en", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
52
+ const startOfDay = () => { const day = new Date(); day.setHours(0, 0, 0, 0); return day.toISOString(); };
53
+ /** Text whose line breaks show as breaks. */
54
+ const lines = (text) => joinHtml(text.split("\n"), html `<br>`);
55
+ /** A blank soul file: the sections a subagent reads, to fill in. */
56
+ export const BLANK_SOUL = `---\nname: \nrole: \n---\n\n## Who you are\n\n\n## How you write\n\n\n## What you know\n\n\n## Decide on your own\n- \n\n## Ask first\n- \n\n## Never\n- \n`;
57
+ export function subagentsListHtml(store, mates, projects, projectName, canCreate, notice) {
58
+ const rows = mates.map(mate => {
59
+ const today = store.subagentTurnsSince(mate.id, startOfDay());
60
+ const open = store.openSubagentQuestions([mate.id]).length;
61
+ return html `<article class="card" data-subagent="${mate.handle}"><div class="flow-row"><h2><a href="/settings/lead/subagents/${mate.id}">${labelOf(mate)}</a></h2><span class="flow-counts">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused"} · ${today} today${open > 0 ? ` · ${open} question${open === 1 ? "" : "s"} for you` : ""}</span></div></article>`;
62
+ });
63
+ const create = canCreate && projects.length > 0
64
+ ? html `<details class="card" id="add"${(mates.length === 0 || notice.adding === true) && html ` open`}><summary>Add subagent</summary>${postForm("/settings/lead/subagents/new", html `<label>Start from<select name="template">${SUBAGENT_TEMPLATES.map(one => html `<option value="${one.id}">${one.label}: ${one.about}</option>`)}<option value="blank">Blank: write its soul file yourself</option></select></label><label>Name<input name="name" maxlength="40" placeholder="keep the template's name"></label>${projects.length === 1 ? html `<input type="hidden" name="repo" value="${projects[0]}">` : html `<label>Project<select name="repo">${projects.map(repo => html `<option value="${repo}">${projectName(repo)}</option>`)}</select></label>`}<button>Add subagent</button>`, { attrs: { "data-new-subagent": true } })}</details>`
65
+ : "";
66
+ const intro = html `<p class="meta">Your lead's helpers. Each works within rules you write and asks you when they say to. The quickest start is a <a href="/kits">starter kit</a>.</p>`;
67
+ return html `<section class="subagents">${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}${intro}${rows.length > 0 ? rows : html `<p class="meta">No subagents yet.</p>`}${create}</section>`;
68
+ }
69
+ /** v97: its week: what it did and cost, what people overrode, and calls that can be undone. */
70
+ function weekSection(store, mate, canManage) {
71
+ const week = weekOf(store, mate, new Date());
72
+ const overrides = week.overrides.length > 0 && html `<h3>What you overrode</h3><ul class="subagent-overrides">${week.overrides.map(one => html `<li>${one.said}${one.href !== null && html ` · <a href="${one.href}">open</a>`} <span class="meta">${when(one.at)}</span></li>`)}</ul>`;
73
+ const undo = week.undoable.length > 0 && html `<h3>Undo</h3><ul class="subagent-undo">${week.undoable.map(one => html `<li data-undo="${one.call}"><span>${one.words}</span> <span class="meta">${when(one.at)}${one.href !== null && html ` · <a href="${one.href}">open</a>`}</span>${canManage && postForm(`/settings/lead/subagents/${mate.id}/week`, html `<button name="op" value="undo" class="secondary">Undo with ${one.undo}</button>`, { hidden: { id: one.call } })}</li>`)}</ul>`;
74
+ const send = canManage && postForm(`/settings/lead/subagents/${mate.id}/week`, html `<button name="op" value="send" class="secondary">Send the week's report</button>`);
75
+ return html `<details class="card subagent-week" id="week" open><summary>This week</summary><p class="subagent-week-said">${lines(weekWords(mate, week))}</p>${overrides}${undo}${send}</details>`;
76
+ }
77
+ /** v96: its desk — where messages to it by name and its routines land — and its routines. */
78
+ function deskSection(store, mate, canManage) {
79
+ const name = nameOf(mate);
80
+ const desk = deskOf(store, mate);
81
+ const routines = routinesOf(store, mate);
82
+ const intro = html `<p class="meta">Message ${name} by name in your chat app, like “@${mate.handle} where's order 1042?”. It lands on ${desk === null ? "its desk" : html `<a href="/flows/${desk.id}">${name}'s desk</a>`}, and the answer comes back to you.</p>`;
83
+ const list = routines.length > 0 && html `<ul class="subagent-routines">${routines.map(one => html `<li data-routine="${one.id}"><span><strong>${scheduleWords(one.schedule)}</strong> · ${one.text}</span><span class="meta">${one.state === "paused" ? "Paused" : one.nextAt === null ? "" : `Next ${when(one.nextAt)}`}${one.lastAt !== null && ` · last ${when(one.lastAt)}${one.lastOutcome === null ? "" : `: ${one.lastOutcome}`}`}</span>${canManage && postForm(`/settings/lead/subagents/${mate.id}/routines`, html `<button name="op" value="run" class="secondary">Run now</button><button name="op" value="remove" class="secondary">Remove</button>`, { attrs: { class: "routine-buttons" }, hidden: { id: one.id } })}</li>`)}</ul>`;
84
+ const add = canManage && postForm(`/settings/lead/subagents/${mate.id}/routines`, html `<label>When<input type="text" name="schedule" maxlength="80" placeholder="weekdays 09:00" required></label><label>What to do<input type="text" name="text" maxlength="200" placeholder="Look up yesterday's refunds and tell me the total." required></label><button>Add routine</button>`, { attrs: { class: "routine-add" }, hidden: { op: "add" } });
85
+ return html `<section class="card subagent-desk" id="desk"><h2>Desk and routines</h2>${intro}${list}${add}${canManage && html `<p class="meta">A routine puts a card on its desk on that schedule (“weekdays 09:00”, “daily 17:00”, “monday 09:00”, “every 2 hours”; times are this computer's, ${localZone()}, unless you name one); the answer goes to ${mate.manager}.</p>`}</section>`;
86
+ }
87
+ /** A schedule's stored form ("weekdays:09:00@Europe/London") in words. */
88
+ const scheduleWords = (text) => { const parsed = parseSchedule(text); return parsed === null ? text : describeSchedule(parsed); };
89
+ /** v95: what it remembers — what its people told it, and what it kept from cards — to search, edit and forget. */
90
+ function memorySection(store, mate, canManage, query) {
91
+ const name = nameOf(mate);
92
+ const all = store.subagentMemories(mate.id, { limit: 500 });
93
+ const shown = query === null || query.trim() === "" ? all.slice(0, 30) : searchMemories(store, mate, query);
94
+ const tell = canManage && postForm(`/settings/lead/subagents/${mate.id}/note`, html `<label class="sr-only" for="subagent-note">Tell ${name} something</label><textarea id="subagent-note" name="note" rows="2" maxlength="${MEMORY_CHARS}" placeholder="For example: this week, offer free shipping instead of a refund when you can."></textarea><button>Tell ${name}</button>`, { attrs: { class: "memory-tell" } });
95
+ const search = (all.length > 8 || (query ?? "") !== "") && html `<form method="get" action="/settings/lead/subagents/${mate.id}#memory" class="memory-search" role="search"><label class="sr-only" for="memory-q">Search what ${name} remembers</label><input id="memory-q" type="search" name="q" value="${query ?? ""}" placeholder="Search what ${name} remembers"><button class="secondary">Search</button></form>`;
96
+ const items = shown.map(one => {
97
+ const card = one.card === null ? null : store.getFlowCard(one.card);
98
+ const whence = one.source === "person" ? `${one.createdBy} told it` : card === null ? "it kept this" : html `it kept this from <a href="/flows/${card.flow}?card=${card.id}">${card.title.slice(0, 60)}</a>`;
99
+ const edit = canManage && html `<details class="memory-edit"><summary>Edit</summary>${postForm(`/settings/lead/subagents/${mate.id}/memory`, html `<label class="sr-only" for="memory-${one.id}">Memory</label><textarea id="memory-${one.id}" name="text" rows="2" maxlength="${MEMORY_CHARS}">${one.text}</textarea><div class="memory-buttons"><button name="op" value="edit">Save</button><button name="op" value="forget" class="secondary">Forget</button></div>`, { hidden: { id: one.id } })}</details>`;
100
+ return html `<li data-memory="${one.id}" data-source="${one.source}"><span>${one.text}</span> <span class="meta">${whence} · ${when(one.updatedAt)}</span>${edit}</li>`;
101
+ });
102
+ const empty = (query ?? "") !== "" ? html `<p class="meta">Nothing ${name} remembers matches that.</p>` : html `<p class="meta">Nothing yet. What you tell ${name} is kept here, and it keeps short facts from the cards it works.</p>`;
103
+ return html `<section class="card subagent-memory" id="memory"><h2>Memory</h2>${tell}${search}${shown.length === 0 ? empty : html `<ul class="subagent-memories">${items}</ul>`}${query === null && all.length > shown.length && html `<p class="meta">${all.length - shown.length} older ${all.length - shown.length === 1 ? "memory" : "memories"}: search to find them.</p>`}</section>`;
104
+ }
105
+ export function subagentPageHtml(store, mate, viewer, projectName, canManage, approvers, notice) {
106
+ const name = nameOf(mate);
107
+ const questions = store.openSubagentQuestions([mate.id]);
108
+ const asked = questions.map(question => {
109
+ const card = store.getFlowCard(question.card);
110
+ const flow = card === null ? null : store.getFlow(card.flow);
111
+ const mine = question.askedOf === viewer;
112
+ const cardLink = card === null || flow === null ? "a card" : html `<a href="/flows/${flow.id}?card=${card.id}">${card.title}</a>`;
113
+ const answer = mine && html `<div class="question-options">${question.options.map(one => postForm(`/settings/lead/subagents/questions/${question.id}/answer`, html `<button>${one.label}</button>`, { hidden: { choice: one.id } }))}</div>${postForm(`/settings/lead/subagents/questions/${question.id}/answer`, html `<label>Or answer in your words<textarea name="text" rows="2" maxlength="2000"></textarea></label><button>Answer</button>`, { attrs: { class: "question-reply" } })}`;
114
+ return html `<article class="card subagent-question" data-question="${question.id}"${question.suggestion !== null && html ` data-suggestion`}><p class="meta">${question.suggestion === null ? html `About ${cardLink}` : "Suggests a rule change"} · ${when(question.createdAt)}${!mine && ` · for ${question.askedOf}`}</p><p><strong>${question.question}</strong></p>${answer}</article>`;
115
+ });
116
+ const zones = zonesOf(store, mate);
117
+ const where = zones.length === 0
118
+ ? html `<p class="meta">Not on any zone yet. In a flow, choose ${name} under “Who decides” on a “Person decides” zone, or add a “Subagent handles it” zone.</p>`
119
+ : html `<ul>${zones.map(one => html `<li><a href="/flows/${one.flow}">${one.flowName}</a> · ${one.kind === "decides" ? "decides" : "handles"} ${one.title}</li>`)}</ul>`;
120
+ // What it did and why, with every tool call it made or asked to make (v94), newest first.
121
+ const events = [
122
+ ...store.subagentEvents(mate.id, 40).filter(one => one.kind !== "note").map(one => ({ kind: one.kind, said: one.said, card: one.card, at: one.at })),
123
+ ...store.subagentCallsOf(mate.id, 40).map(one => ({ kind: "call", said: `Used ${callWords(one.tool, one.action, one.input, 200)} · ${receiptWords(store, one)}`, card: one.card, at: one.createdAt })),
124
+ ].sort((a, b) => b.at.localeCompare(a.at)).slice(0, 40);
125
+ const activity = events.length === 0 ? html `<p class="meta">Nothing yet.</p>` : html `<ul class="subagent-activity">${events.map(one => {
126
+ const card = one.card === null ? null : store.getFlowCard(one.card);
127
+ return html `<li data-event="${one.kind}">${one.said}${card !== null && html ` · <a href="/flows/${card.flow}?card=${card.id}">open</a>`} <span class="meta">${when(one.at)}</span></li>`;
128
+ })}</ul>`;
129
+ const versions = store.subagentVersions(mate.id);
130
+ const latest = versions[0];
131
+ const manage = canManage && html `<div class="subagent-actions">${postForm(`/settings/lead/subagents/${mate.id}/state`, html `<button${mate.state === "active" && html ` class="secondary"`}>${mate.state === "active" ? `Pause ${name}` : `Resume ${name}`}</button>`, { hidden: { state: mate.state === "active" ? "paused" : "active" } })}${postForm(`/settings/lead/subagents/${mate.id}/summary`, html `<button class="secondary">Send today's summary</button>`)}</div>`;
132
+ const soul = html `<details class="card"${canManage && html ` open`}><summary>Soul file</summary><p class="meta">Who ${name} is and its rules, read every turn. Version ${mate.version}${latest !== undefined && ` · saved by ${latest.savedBy}, ${when(latest.savedAt)}`} · <a href="/settings/lead/subagents/${mate.id}/soul.md" download>Download</a></p>${canManage
133
+ ? postForm(`/settings/lead/subagents/${mate.id}/soul`, html `<label class="sr-only" for="subagent-soul">Soul file</label><textarea id="subagent-soul" name="soul" rows="24" class="mono" maxlength="12000" spellcheck="false">${notice.soulDraft ?? mate.soul}</textarea><button>Save</button>`, { attrs: { "data-soul-form": true } })
134
+ : html `<pre class="mono">${mate.soul}</pre>`}</details>`;
135
+ const settings = canManage && html `<details class="card"><summary>Settings</summary>${postForm(`/settings/lead/subagents/${mate.id}/settings`, html `<label>Model<select name="model">${SUBAGENT_MODELS.map(one => html `<option value="${one}"${(mate.model ?? "default") === one && html ` selected`}>${one === "default" ? "The lead chat's model" : one[0].toUpperCase() + one.slice(1)}</option>`)}</select></label><label>Turns a day, at most<input type="number" name="dailyTurns" min="1" max="2000" value="${mate.dailyTurns}"></label><label>Reports to<select name="manager">${[...new Set([mate.manager, ...approvers])].map(one => html `<option value="${one}"${one === mate.manager && html ` selected`}>${one}</option>`)}</select></label><button>Save settings</button>`)}${postForm(`/settings/lead/subagents/${mate.id}/state`, html `<button class="danger">Remove ${name} from the team</button>`, { attrs: { class: "danger-zone" }, hidden: { state: "removed" } })}</details>`;
136
+ return html `${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}<p class="meta">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused: its decisions go to people"} · reports to ${mate.manager}</p>${manage}${questions.length > 0 && html `<section><h2>Questions</h2>${asked}</section>`}<section class="card"><h2>Today</h2><p class="subagent-summary">${lines(summaryOf(store, mate, startOfDay()).said)}</p></section>${weekSection(store, mate, canManage)}<section class="card"><h2>Works on</h2>${where}</section>${toolsSection(store, mate, canManage)}${deskSection(store, mate, canManage)}${memorySection(store, mate, canManage, notice.query ?? null)}${soul}<details class="card"><summary>What ${name} did</summary>${activity}</details>${settings}`;
137
+ }
@@ -1,6 +1,6 @@
1
1
  import { run, type ExecResult } from "./exec.js";
2
2
  import type { ContractResult } from "./contracts/contract.js";
3
- import { type TurnAnswer } from "./contracts/teammate-turn.js";
3
+ import { type TurnAnswer } from "./contracts/subagent-turn.js";
4
4
  import { type Overrun } from "./text-limits.js";
5
5
  export declare const SOUL_CHARS = 12000;
6
6
  export type Soul = {
@@ -24,17 +24,17 @@ export declare function parseSoul(text: string): {
24
24
  };
25
25
  /** A soul file with one section replaced (by title, any case), or added at the end when it has no such section. */
26
26
  export declare function withSection(soul: string, title: string, text: string): string;
27
- /** Starters to copy and make your own. Every rule is plain words the teammate reads each turn. */
28
- export declare const TEAMMATE_TEMPLATES: readonly {
27
+ /** Starters to copy and make your own. Every rule is plain words the subagent reads each turn. */
28
+ export declare const SUBAGENT_TEMPLATES: readonly {
29
29
  id: string;
30
30
  label: string;
31
31
  about: string;
32
32
  soul: string;
33
33
  }[];
34
- export type { TurnAction, TurnAnswer } from "./contracts/teammate-turn.js";
34
+ export type { TurnAction, TurnAnswer } from "./contracts/subagent-turn.js";
35
35
  /** Each field's limit, in characters: stated in the prompt before it answers (turnPrompt), and an answer over one is
36
- * asked once to shorten (teammateTurn); one still over is kept whole, never cut. "text" is what the next zones read;
37
- * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/teammate-turn.ts): the CLI would
36
+ * asked once to shorten (subagentTurn); one still over is kept whole, never cut. "text" is what the next zones read;
37
+ * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/subagent-turn.ts): the CLI would
38
38
  * refuse an answer a few characters over one whole. */
39
39
  export declare const TURN_LIMITS: {
40
40
  readonly answer: 60;
@@ -53,7 +53,7 @@ export type TurnContext = {
53
53
  role: string;
54
54
  flow: string;
55
55
  zone: string;
56
- /** decide: a "Person decides" zone; handle: a "Teammate handles it" zone. */
56
+ /** decide: a "Person decides" zone; handle: a "Subagent handles it" zone. */
57
57
  kind: "decide" | "handle";
58
58
  instructions: string | null;
59
59
  /** decide: the draft being decided on (null: none), whether it can be sent back, and to whom hard ones go. */
@@ -111,7 +111,7 @@ export type TurnContext = {
111
111
  };
112
112
  export declare function turnPrompt(context: TurnContext): string;
113
113
  /**
114
- * A turn's answer, read through its contract (src/contracts/teammate-turn.ts) and then checked against what this zone
114
+ * A turn's answer, read through its contract (src/contracts/subagent-turn.ts) and then checked against what this zone
115
115
  * allows, each refusal naming its path (`action: "route" isn't one this zone allows`). Text is trimmed and kept whole
116
116
  * (a field over its limit is asked to shorten, turnOverruns); blank options are dropped, and an ask offers at most 4,
117
117
  * each cut to an answer's length, and a tool name is cut to 140, as they always were.
@@ -142,7 +142,7 @@ export declare const TURN_TIMEOUT_MS = 180000;
142
142
  type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
143
143
  /** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
144
144
  export declare function claudeTurnRunner(runner?: CommandRunner): TurnRunner;
145
- /** How a teammate is named to people: "Maya · Support". */
146
- export declare const teammateLabel: (soul: Pick<Soul, "name" | "role">) => string;
145
+ /** How a subagent is named to people: "Maya · Support". */
146
+ export declare const subagentLabel: (soul: Pick<Soul, "name" | "role">) => string;
147
147
  /** How its acts read in a card's history: "Maya (AI)". */
148
- export declare const teammateActor: (soul: Pick<Soul, "name">) => string;
148
+ export declare const subagentActor: (soul: Pick<Soul, "name">) => string;
@@ -1,19 +1,19 @@
1
1
  /**
2
- * AI teammates (v92): agents that work a flow's cards the way an employee
2
+ * subagents (v92): agents that work a flow's cards the way an employee
3
3
  * would, within rules a person wrote for them.
4
4
  *
5
- * A teammate is a soul file: who it is, how it writes, what it knows, what it
5
+ * A subagent is a soul file: who it is, how it writes, what it knows, what it
6
6
  * decides on its own, what it asks about first, and what it never does. It is
7
7
  * staffed on zones: a "Person decides" zone it decides (approve, send back,
8
- * or hand it to a person), or a "Teammate handles it" zone where it picks
8
+ * or hand it to a person), or a "Subagent handles it" zone where it picks
9
9
  * where the card goes and writes what the next zones send. It never acts
10
10
  * itself: each turn answers with one decision as JSON, through Claude with no
11
11
  * tools, no repository and no MCP servers, and Toolroll carries it out
12
12
  * within the zone's choices. The card is data, never instructions. (v94: it
13
13
  * may ask for a project tool call on its turn; Toolroll makes it, or
14
- * asks a person first, by the rules in teammate-tools.ts.)
14
+ * asks a person first, by the rules in subagent-tools.ts.)
15
15
  *
16
- * Teammates do flow work only: they never approve a code task, a merge or
16
+ * Subagents do flow work only: they never approve a code task, a merge or
17
17
  * spending — those stay with people (or a signed hands-off mode).
18
18
  */
19
19
  import { Buffer } from "node:buffer";
@@ -24,13 +24,13 @@ import { strictJsonParse } from "./converse.js";
24
24
  import { scanForSecrets } from "./evidence.js";
25
25
  import { run } from "./exec.js";
26
26
  import { ALL_CREDENTIAL_ENV } from "./provider.js";
27
- import { readTurnAnswer, TURN_MODEL_SCHEMA } from "./contracts/teammate-turn.js";
27
+ import { readTurnAnswer, TURN_MODEL_SCHEMA } from "./contracts/subagent-turn.js";
28
28
  import { LIMITS } from "./decision.js";
29
29
  import { overruns, TEXT_LIMITS } from "./text-limits.js";
30
30
  export const SOUL_CHARS = 12_000;
31
31
  const HANDLE = /^[a-z0-9][a-z0-9-]{0,31}$/;
32
32
  /** A handle from a name: "Maya" → "maya". */
33
- export const handleOf = (name) => name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "teammate";
33
+ export const handleOf = (name) => name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "subagent";
34
34
  export const isHandle = (value) => typeof value === "string" && HANDLE.test(value);
35
35
  /** A soul file read: its name and role (front matter) and its sections. */
36
36
  export function parseSoul(text) {
@@ -47,9 +47,9 @@ export function parseSoul(text) {
47
47
  const field = (key) => new RegExp(`^${key}:\\s*(.+)$`, "im").exec(front[1])?.[1]?.trim() ?? "";
48
48
  const name = field("name"), role = field("role");
49
49
  if (!/^[\p{L}][\p{L}\p{N} .'-]{0,39}$/u.test(name))
50
- return { ok: false, problem: "Give the teammate a name of up to 40 letters, like “name: Maya”." };
50
+ return { ok: false, problem: "Give the subagent a name of up to 40 letters, like “name: Maya”." };
51
51
  if (role === "" || role.length > 40)
52
- return { ok: false, problem: "Say what the teammate does in up to 40 characters, like “role: Support”." };
52
+ return { ok: false, problem: "Say what the subagent does in up to 40 characters, like “role: Support”." };
53
53
  const body = normal.slice(front[0].length);
54
54
  const sections = [];
55
55
  for (const part of body.split(/^## /m).slice(1)) {
@@ -57,7 +57,7 @@ export function parseSoul(text) {
57
57
  sections.push({ title: title.trim(), body: rest.join("\n").trim() });
58
58
  }
59
59
  if (sections.length === 0 || sections.every(one => one.body === ""))
60
- return { ok: false, problem: "Write at least one section, like “## Who you are”, saying who the teammate is." };
60
+ return { ok: false, problem: "Write at least one section, like “## Who you are”, saying who the subagent is." };
61
61
  return { ok: true, soul: { name, role, sections } };
62
62
  }
63
63
  /** A soul file with one section replaced (by title, any case), or added at the end when it has no such section. */
@@ -73,8 +73,8 @@ export function withSection(soul, title, text) {
73
73
  return `${parts.join("").trimEnd()}\n`;
74
74
  }
75
75
  const soul = (name, role, parts) => `---\nname: ${name}\nrole: ${role}\n---\n\n${Object.entries(parts).map(([title, lines]) => `## ${title}\n${lines.join("\n")}`).join("\n\n")}\n`;
76
- /** Starters to copy and make your own. Every rule is plain words the teammate reads each turn. */
77
- export const TEAMMATE_TEMPLATES = [
76
+ /** Starters to copy and make your own. Every rule is plain words the subagent reads each turn. */
77
+ export const SUBAGENT_TEMPLATES = [
78
78
  {
79
79
  id: "support", label: "Support rep", about: "Answers customers, handles refunds and replacements within limits, and hands angry or costly cases to you.",
80
80
  soul: soul("Maya", "Support", {
@@ -121,10 +121,10 @@ export const TEAMMATE_TEMPLATES = [
121
121
  },
122
122
  ];
123
123
  /** Each field's limit, in characters: stated in the prompt before it answers (turnPrompt), and an answer over one is
124
- * asked once to shorten (teammateTurn); one still over is kept whole, never cut. "text" is what the next zones read;
125
- * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/teammate-turn.ts): the CLI would
124
+ * asked once to shorten (subagentTurn); one still over is kept whole, never cut. "text" is what the next zones read;
125
+ * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/subagent-turn.ts): the CLI would
126
126
  * refuse an answer a few characters over one whole. */
127
- export const TURN_LIMITS = { answer: TEXT_LIMITS.teammateAnswer, text: TEXT_LIMITS.stageOutput, note: LIMITS.note, question: TEXT_LIMITS.teammateQuestion, reason: TEXT_LIMITS.teammateReason, remember: TEXT_LIMITS.teammateRemember };
127
+ export const TURN_LIMITS = { answer: TEXT_LIMITS.subagentAnswer, text: TEXT_LIMITS.stageOutput, note: LIMITS.note, question: TEXT_LIMITS.subagentQuestion, reason: TEXT_LIMITS.subagentReason, remember: TEXT_LIMITS.subagentRemember };
128
128
  /** The fields of an answer over their limits. */
129
129
  export function turnOverruns(answer) {
130
130
  return overruns({ answer: answer.answer, text: answer.text, note: answer.note, question: answer.question, reason: answer.reason, remember: answer.remember }, TURN_LIMITS);
@@ -186,7 +186,7 @@ export function turnPrompt(context) {
186
186
  /** How many options an ask offers, and the longest tool name and input a tool call may give. */
187
187
  const ASK_OPTIONS = 4, TOOL_NAME_CHARS = 140, TOOL_INPUT_CHARS = 8000;
188
188
  /**
189
- * A turn's answer, read through its contract (src/contracts/teammate-turn.ts) and then checked against what this zone
189
+ * A turn's answer, read through its contract (src/contracts/subagent-turn.ts) and then checked against what this zone
190
190
  * allows, each refusal naming its path (`action: "route" isn't one this zone allows`). Text is trimmed and kept whole
191
191
  * (a field over its limit is asked to shorten, turnOverruns); blank options are dropped, and an ask offers at most 4,
192
192
  * each cut to an answer's length, and a tool name is cut to 140, as they always were.
@@ -204,7 +204,7 @@ export function readTurn(value, context) {
204
204
  const refuse = (path, what) => ({ ok: false, issues: [{ path, kind: "bad-value", line: `${path}: ${what}` }] });
205
205
  if (!allowed.includes(answer.action))
206
206
  return refuse("action", `"${answer.action}" isn't one this zone allows (${allowed.map(one => `"${one}"`).join(", ")})`);
207
- // A tool call is checked against the teammate's rules where it's carried out; here only that it names one and fits.
207
+ // A tool call is checked against the subagent's rules where it's carried out; here only that it names one and fits.
208
208
  if (answer.action === "use_tool" && answer.tool === "")
209
209
  return refuse("tool", "required for use_tool");
210
210
  if (answer.action === "use_tool" && answer.input.length > TOOL_INPUT_CHARS)
@@ -221,7 +221,7 @@ export const TURN_TIMEOUT_MS = 180_000;
221
221
  /** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
222
222
  export function claudeTurnRunner(runner = run) {
223
223
  return async (request) => {
224
- const dir = mkdtempSync(join(tmpdir(), "standing-orders-teammate-"));
224
+ const dir = mkdtempSync(join(tmpdir(), "standing-orders-subagent-"));
225
225
  const started = Date.now();
226
226
  try {
227
227
  const result = await runner("claude", [
@@ -259,7 +259,7 @@ function costOf(body) {
259
259
  const tokensIn = count("input_tokens") + count("cache_creation_input_tokens") + count("cache_read_input_tokens"), tokensOut = count("output_tokens");
260
260
  return { ...(cost === undefined ? {} : { costUsd: cost }), ...(tokensIn > 0 ? { tokensIn } : {}), ...(tokensOut > 0 ? { tokensOut } : {}) };
261
261
  }
262
- /** How a teammate is named to people: "Maya · Support". */
263
- export const teammateLabel = (soul) => `${soul.name} · ${soul.role}`;
262
+ /** How a subagent is named to people: "Maya · Support". */
263
+ export const subagentLabel = (soul) => `${soul.name} · ${soul.role}`;
264
264
  /** How its acts read in a card's history: "Maya (AI)". */
265
- export const teammateActor = (soul) => `${soul.name} (AI)`;
265
+ export const subagentActor = (soul) => `${soul.name} (AI)`;
@@ -1,16 +1,16 @@
1
- import { type MateHistoryMessage, type MateProviderAnswer, type MateToolSchema } from "./converse.js";
1
+ import { type LeadHistoryMessage, type LeadProviderAnswer, type LeadToolSchema } from "./converse.js";
2
2
  import { run, type ExecResult } from "./exec.js";
3
3
  import type { SubscriptionChatProviderId } from "./store.js";
4
- export type SubscriptionMateRequest = {
4
+ export type SubscriptionLeadRequest = {
5
5
  provider: SubscriptionChatProviderId;
6
6
  /** `default` lets the authenticated harness choose its current default. */
7
7
  model: string;
8
8
  system: string;
9
9
  dataDocument: string;
10
- history: readonly MateHistoryMessage[];
11
- tools: readonly MateToolSchema[];
10
+ history: readonly LeadHistoryMessage[];
11
+ tools: readonly LeadToolSchema[];
12
12
  /** A structured answer of another shape (the memory pass's verdict): this JSON Schema is the harness's
13
- * `--json-schema` (Codex's `--output-schema`) in place of the mate envelope, and its JSON text comes back as the
13
+ * `--json-schema` (Codex's `--output-schema`) in place of the lead envelope, and its JSON text comes back as the
14
14
  * answer's text, unread, for the caller's own schema to parse. */
15
15
  outputSchema?: Record<string, unknown>;
16
16
  timeoutMs: number;
@@ -20,31 +20,31 @@ export type SubscriptionMateRequest = {
20
20
  * (Claude). Display only: the finished answer is still parsed whole. */
21
21
  onText?: (text: string) => void;
22
22
  };
23
- export type SubscriptionMateRunner = (request: SubscriptionMateRequest) => Promise<{
23
+ export type SubscriptionLeadRunner = (request: SubscriptionLeadRequest) => Promise<{
24
24
  ok: true;
25
- answer: MateProviderAnswer;
25
+ answer: LeadProviderAnswer;
26
26
  } | {
27
27
  ok: false;
28
28
  problem: string;
29
29
  }>;
30
30
  type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
31
- /** The model-visible request. Tool results are already redacted by mateView. */
32
- export declare function composeSubscriptionMatePrompt(request: Omit<SubscriptionMateRequest, "provider" | "model" | "timeoutMs" | "signal">): string;
31
+ /** The model-visible request. Tool results are already redacted by leadView. */
32
+ export declare function composeSubscriptionLeadPrompt(request: Omit<SubscriptionLeadRequest, "provider" | "model" | "timeoutMs" | "signal">): string;
33
33
  /** Strictly turn the harness's structured output into the provider-neutral answer. */
34
- export declare function parseSubscriptionMateAnswer(text: string, usage?: {
34
+ export declare function parseSubscriptionLeadAnswer(text: string, usage?: {
35
35
  tokensIn?: unknown;
36
36
  tokensOut?: unknown;
37
37
  }): {
38
38
  ok: true;
39
- answer: MateProviderAnswer;
39
+ answer: LeadProviderAnswer;
40
40
  } | {
41
41
  ok: false;
42
42
  problem: string;
43
43
  };
44
44
  /** Production runner; injectable so tests never consume a subscription turn. */
45
- export declare function performSubscriptionMateRequest(request: SubscriptionMateRequest, runner?: CommandRunner): Promise<{
45
+ export declare function performSubscriptionLeadRequest(request: SubscriptionLeadRequest, runner?: CommandRunner): Promise<{
46
46
  ok: true;
47
- answer: MateProviderAnswer;
47
+ answer: LeadProviderAnswer;
48
48
  } | {
49
49
  ok: false;
50
50
  problem: string;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Subscription-backed transport for the mate.
2
+ * Subscription-backed transport for the lead.
3
3
  *
4
4
  * The direct API transport owns dollar reservations and native function
5
5
  * calls. A cached Codex or Claude login has neither an API key nor a
@@ -7,16 +7,16 @@
7
7
  * temporary directory and asks for the same tool-call envelope as strict
8
8
  * structured JSON. The harness receives no repository path, no project
9
9
  * instructions, no MCP servers, and no executable tool surface. Tool calls
10
- * are still interpreted and executed by mate.ts, where every act remains a
10
+ * are still interpreted and executed by lead.ts, where every act remains a
11
11
  * proposal that needs a human confirmation card.
12
12
  */
13
13
  import { Buffer } from "node:buffer";
14
14
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
15
15
  import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
17
- import { MATE_MAX_CALLS_PER_STEP, MATE_STEP_TEXT_CAP_BYTES, MATE_TOOL_CALL_CAP_BYTES, MATE_TOOL_CALL_ID_CAP_BYTES, strictJsonParse, tokenCount, } from "./converse.js";
17
+ import { LEAD_MAX_CALLS_PER_STEP, LEAD_STEP_TEXT_CAP_BYTES, LEAD_TOOL_CALL_CAP_BYTES, LEAD_TOOL_CALL_ID_CAP_BYTES, strictJsonParse, tokenCount, } from "./converse.js";
18
18
  import { run } from "./exec.js";
19
- import { claudeStreamReader } from "./mate-progress.js";
19
+ import { claudeStreamReader } from "./lead-progress.js";
20
20
  import { ALL_CREDENTIAL_ENV } from "./provider.js";
21
21
  import { claudeLimitsOf, noteLimits } from "./provider-limits.js";
22
22
  import { claudeBillingFrom } from "./spend.js";
@@ -25,18 +25,18 @@ function outputSchema(tools) {
25
25
  return {
26
26
  type: "object",
27
27
  properties: {
28
- text: { type: "string", maxLength: MATE_STEP_TEXT_CAP_BYTES },
28
+ text: { type: "string", maxLength: LEAD_STEP_TEXT_CAP_BYTES },
29
29
  // With no tools offered there is nothing to name: an empty enum is not
30
30
  // a valid schema, so the calls array is simply required to be empty.
31
31
  calls: tools.length === 0 ? { type: "array", maxItems: 0 } : {
32
32
  type: "array",
33
- maxItems: MATE_MAX_CALLS_PER_STEP,
33
+ maxItems: LEAD_MAX_CALLS_PER_STEP,
34
34
  items: {
35
35
  type: "object",
36
36
  properties: {
37
- id: { type: "string", minLength: 1, maxLength: MATE_TOOL_CALL_ID_CAP_BYTES },
37
+ id: { type: "string", minLength: 1, maxLength: LEAD_TOOL_CALL_ID_CAP_BYTES },
38
38
  name: { type: "string", enum: tools.map(tool => tool.name) },
39
- argumentsJson: { type: "string", maxLength: MATE_TOOL_CALL_CAP_BYTES },
39
+ argumentsJson: { type: "string", maxLength: LEAD_TOOL_CALL_CAP_BYTES },
40
40
  },
41
41
  required: ["id", "name", "argumentsJson"],
42
42
  additionalProperties: false,
@@ -47,8 +47,8 @@ function outputSchema(tools) {
47
47
  additionalProperties: false,
48
48
  };
49
49
  }
50
- /** The model-visible request. Tool results are already redacted by mateView. */
51
- export function composeSubscriptionMatePrompt(request) {
50
+ /** The model-visible request. Tool results are already redacted by leadView. */
51
+ export function composeSubscriptionLeadPrompt(request) {
52
52
  return [
53
53
  request.system,
54
54
  "SUBSCRIPTION HARNESS PROTOCOL:",
@@ -68,7 +68,7 @@ function exactKeys(value, keys) {
68
68
  return actual.length === keys.length && [...keys].sort().every((key, index) => actual[index] === key);
69
69
  }
70
70
  /** Strictly turn the harness's structured output into the provider-neutral answer. */
71
- export function parseSubscriptionMateAnswer(text, usage = {}) {
71
+ export function parseSubscriptionLeadAnswer(text, usage = {}) {
72
72
  const parsed = strictJsonParse(Buffer.from(text, "utf8"), RESPONSE_CAP_BYTES, 8);
73
73
  if (!parsed.ok || typeof parsed.value !== "object" || parsed.value === null || Array.isArray(parsed.value)) {
74
74
  return { ok: false, problem: parsed.ok ? "not-an-object" : parsed.problem };
@@ -77,7 +77,7 @@ export function parseSubscriptionMateAnswer(text, usage = {}) {
77
77
  if (!exactKeys(body, ["text", "calls"]) || typeof body["text"] !== "string" || !Array.isArray(body["calls"])) {
78
78
  return { ok: false, problem: "wrong-shape" };
79
79
  }
80
- if (Buffer.byteLength(body["text"], "utf8") > MATE_STEP_TEXT_CAP_BYTES || body["calls"].length > MATE_MAX_CALLS_PER_STEP) {
80
+ if (Buffer.byteLength(body["text"], "utf8") > LEAD_STEP_TEXT_CAP_BYTES || body["calls"].length > LEAD_MAX_CALLS_PER_STEP) {
81
81
  return { ok: false, problem: "over-cap" };
82
82
  }
83
83
  const calls = [];
@@ -87,20 +87,20 @@ export function parseSubscriptionMateAnswer(text, usage = {}) {
87
87
  const call = raw;
88
88
  if (!exactKeys(call, ["id", "name", "argumentsJson"]))
89
89
  return { ok: false, problem: "bad-tool-call" };
90
- if (typeof call["id"] !== "string" || call["id"] === "" || Buffer.byteLength(call["id"], "utf8") > MATE_TOOL_CALL_ID_CAP_BYTES) {
90
+ if (typeof call["id"] !== "string" || call["id"] === "" || Buffer.byteLength(call["id"], "utf8") > LEAD_TOOL_CALL_ID_CAP_BYTES) {
91
91
  return { ok: false, problem: "bad-tool-call" };
92
92
  }
93
93
  if (typeof call["name"] !== "string" || call["name"] === "" || call["name"].length > 64 || typeof call["argumentsJson"] !== "string") {
94
94
  return { ok: false, problem: "bad-tool-call" };
95
95
  }
96
96
  const argumentsBytes = Buffer.from(call["argumentsJson"], "utf8");
97
- if (argumentsBytes.byteLength > MATE_TOOL_CALL_CAP_BYTES)
97
+ if (argumentsBytes.byteLength > LEAD_TOOL_CALL_CAP_BYTES)
98
98
  return { ok: false, problem: "bad-tool-call" };
99
- const args = strictJsonParse(argumentsBytes, MATE_TOOL_CALL_CAP_BYTES, 6);
99
+ const args = strictJsonParse(argumentsBytes, LEAD_TOOL_CALL_CAP_BYTES, 6);
100
100
  if (!args.ok || typeof args.value !== "object" || args.value === null || Array.isArray(args.value))
101
101
  return { ok: false, problem: "bad-tool-call" };
102
102
  const normalized = { id: call["id"], name: call["name"], args: args.value };
103
- if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > MATE_TOOL_CALL_CAP_BYTES)
103
+ if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > LEAD_TOOL_CALL_CAP_BYTES)
104
104
  return { ok: false, problem: "bad-tool-call" };
105
105
  calls.push(normalized);
106
106
  }
@@ -186,10 +186,10 @@ function lastResultLine(stdout) {
186
186
  return "";
187
187
  }
188
188
  /** Production runner; injectable so tests never consume a subscription turn. */
189
- export async function performSubscriptionMateRequest(request, runner = run) {
189
+ export async function performSubscriptionLeadRequest(request, runner = run) {
190
190
  const dir = mkdtempSync(join(tmpdir(), "standing-orders-mate-"));
191
191
  try {
192
- const prompt = composeSubscriptionMatePrompt(request);
192
+ const prompt = composeSubscriptionLeadPrompt(request);
193
193
  const streaming = request.provider !== "codex-subscription" && request.onText !== undefined;
194
194
  const schema = request.outputSchema ?? outputSchema(request.tools);
195
195
  let command;
@@ -274,7 +274,7 @@ export async function performSubscriptionMateRequest(request, runner = run) {
274
274
  if (request.outputSchema !== undefined) {
275
275
  return { ok: true, answer: { text: output.text, calls: [], tokensIn: output.tokensIn, tokensOut: output.tokensOut, reportedCostMicrousd: null } };
276
276
  }
277
- return parseSubscriptionMateAnswer(output.text, output);
277
+ return parseSubscriptionLeadAnswer(output.text, output);
278
278
  }
279
279
  finally {
280
280
  rmSync(dir, { recursive: true, force: true });
package/dist/surface.d.ts CHANGED
@@ -1,10 +1,27 @@
1
+ /**
2
+ * The declared command guide (arc 5): the agent-facing surface as data,
3
+ * dumped by `contract --commands`. This is DOCUMENTATION with a stable
4
+ * shape, not authority — legacy verbs do not execute from it,
5
+ * and the runtime envelope remains the truth. Its honest limits ride the
6
+ * envelope itself as SURFACE_NOTES, because a source comment is invisible
7
+ * to consumers (arc-5 review, finding 1).
8
+ *
9
+ * What the tests DO hold to the code: every root here is a routed verb
10
+ * and every routed verb appears here; every declared flag lives in the
11
+ * parser's global vocabulary with the declared arity; the subcommand
12
+ * inventories for task/publish/config/approver match the
13
+ * exported action lists their dispatchers consult; keyed mutations
14
+ * declare --key; notableReasons come from DOCUMENTED_REASONS. What they
15
+ * CANNOT prove — that a given flag is read by a given handler — is why
16
+ * SURFACE_NOTES.flags says "intended".
17
+ */
1
18
  import { type CommandFlag, type CommandRow, type RemotePolicy } from './contracts/cli.js';
2
19
  /** A guide row and its flags are derived from their schemas in src/contracts/cli.ts. */
3
20
  export type { CommandFlag, CommandRow, RemotePolicy };
4
21
  /** The guide's limits, stated machine-readably in every dump. */
5
22
  export declare const SURFACE_NOTES: {
6
- readonly authority: "documentation is not permission; session descriptors also drive parser and request validation; legacy entries remain advisory";
7
- readonly flags: "session flags are exact per operation; legacy flags are intended per command and may use a global parser vocabulary";
23
+ readonly authority: "documentation is not permission; legacy entries remain advisory";
24
+ readonly flags: "flags are intended per command and may use a global parser vocabulary";
8
25
  readonly reasons: "curated, not exhaustive — the runtime `reason` field is the truth; ignore tokens you do not recognize";
9
26
  readonly database: "local database commands wait up to 15 seconds for another writer; an exhausted wait returns database-busy and names the selected file";
10
27
  };