toolroll 0.9.84 → 0.9.86

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 (132) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +94 -1193
  3. package/dist/agent-fence.d.ts +9 -5
  4. package/dist/agent-fence.js +6 -5
  5. package/dist/agent-onboard.d.ts +2 -0
  6. package/dist/agent-onboard.js +3 -0
  7. package/dist/assignment.js +4 -3
  8. package/dist/browser/workspace.css +1 -1
  9. package/dist/browser/workspace.js +33 -31
  10. package/dist/browser-workspace.d.ts +45 -0
  11. package/dist/chat-actions.d.ts +11 -1
  12. package/dist/chat-actions.js +53 -1
  13. package/dist/chat-channel.d.ts +10 -1
  14. package/dist/chat-channel.js +36 -5
  15. package/dist/chat-decide.d.ts +16 -4
  16. package/dist/chat-decide.js +239 -31
  17. package/dist/chat-messages.d.ts +5 -0
  18. package/dist/chat-messages.js +1 -1
  19. package/dist/chat-quiet.js +6 -4
  20. package/dist/chat-review.d.ts +4 -0
  21. package/dist/chat-review.js +13 -4
  22. package/dist/chat-terms.d.ts +9 -0
  23. package/dist/chat-terms.js +46 -0
  24. package/dist/claim.d.ts +3 -0
  25. package/dist/claim.js +4 -1
  26. package/dist/contracts/chat-actions.d.ts +9 -0
  27. package/dist/contracts/chat-actions.js +3 -0
  28. package/dist/contracts/console-api.d.ts +6 -2
  29. package/dist/contracts/console-api.js +6 -2
  30. package/dist/contracts/flow-propose.d.ts +4 -1
  31. package/dist/contracts/flow-propose.js +1 -1
  32. package/dist/contracts/flow.d.ts +41 -0
  33. package/dist/contracts/flow.js +4 -2
  34. package/dist/contracts/lead-tools.d.ts +51 -0
  35. package/dist/contracts/lead-tools.js +5 -0
  36. package/dist/contracts/store-json.d.ts +2 -0
  37. package/dist/contracts/trigger-payloads.d.ts +20 -0
  38. package/dist/contracts/trigger-payloads.js +4 -0
  39. package/dist/contracts/youiyoux-request.d.ts +248 -0
  40. package/dist/contracts/youiyoux-request.js +152 -0
  41. package/dist/coordinator.js +1 -0
  42. package/dist/deploy-gate.js +4 -0
  43. package/dist/dispatch.d.ts +3 -0
  44. package/dist/dispatch.js +4 -1
  45. package/dist/flow-browser.d.ts +103 -0
  46. package/dist/flow-browser.js +204 -0
  47. package/dist/flow-cadence.d.ts +8 -2
  48. package/dist/flow-cadence.js +16 -3
  49. package/dist/flow-card-task.d.ts +9 -0
  50. package/dist/flow-card-task.js +15 -6
  51. package/dist/flow-code.d.ts +5 -1
  52. package/dist/flow-code.js +4 -1
  53. package/dist/flow-engine.js +16 -0
  54. package/dist/flow-live.js +9 -1
  55. package/dist/flow-pull-request.js +15 -7
  56. package/dist/flow-share.js +3 -1
  57. package/dist/flow-steps.d.ts +9 -1
  58. package/dist/flow-steps.js +55 -5
  59. package/dist/flow-triggers.js +55 -0
  60. package/dist/flows-cli.d.ts +19 -0
  61. package/dist/flows-cli.js +47 -2
  62. package/dist/flows-ui.js +11 -2
  63. package/dist/flows.js +5 -2
  64. package/dist/integrations-ui.js +9 -4
  65. package/dist/integrations.d.ts +9 -1
  66. package/dist/integrations.js +43 -2
  67. package/dist/landed-cleanup.d.ts +4 -0
  68. package/dist/landed-cleanup.js +3 -2
  69. package/dist/lead-capabilities.js +2 -1
  70. package/dist/lead-cli.js +3 -1
  71. package/dist/lead-tools.d.ts +2 -0
  72. package/dist/lead-tools.js +65 -10
  73. package/dist/lead-voice.js +16 -5
  74. package/dist/lead.d.ts +2 -0
  75. package/dist/lead.js +3 -3
  76. package/dist/operate.d.ts +2 -3
  77. package/dist/operate.js +226 -21
  78. package/dist/project-activity.d.ts +20 -0
  79. package/dist/project-activity.js +23 -0
  80. package/dist/project-cli.d.ts +1 -0
  81. package/dist/project-cli.js +36 -19
  82. package/dist/project-delete.js +7 -3
  83. package/dist/project-knowledge.d.ts +3 -3
  84. package/dist/project-outcomes.d.ts +119 -0
  85. package/dist/project-outcomes.js +79 -0
  86. package/dist/pull-request-flow.js +35 -4
  87. package/dist/result-actions.d.ts +4 -0
  88. package/dist/result-actions.js +41 -9
  89. package/dist/result-follow-ups.js +4 -0
  90. package/dist/result-review.d.ts +9 -0
  91. package/dist/result-review.js +45 -5
  92. package/dist/scope.d.ts +5 -0
  93. package/dist/scope.js +7 -0
  94. package/dist/server/chat.js +4 -2
  95. package/dist/server/console.css +41 -0
  96. package/dist/server/flows.js +15 -0
  97. package/dist/server/pages.js +11 -1
  98. package/dist/server/render-chat.js +3 -2
  99. package/dist/server/render-pages.d.ts +15 -2
  100. package/dist/server/render-pages.js +52 -4
  101. package/dist/server/render-results.d.ts +2 -0
  102. package/dist/server/render-results.js +20 -7
  103. package/dist/server/render-tasks.d.ts +9 -0
  104. package/dist/server/render-tasks.js +32 -11
  105. package/dist/server/route-table.js +12 -0
  106. package/dist/server/settings.js +18 -1
  107. package/dist/server/task-views.js +11 -0
  108. package/dist/server/tasks.d.ts +10 -0
  109. package/dist/server/tasks.js +95 -7
  110. package/dist/settings-proofs.js +1 -0
  111. package/dist/store.d.ts +116 -5
  112. package/dist/store.js +385 -24
  113. package/dist/surface.js +18 -2
  114. package/dist/task-archive.d.ts +10 -0
  115. package/dist/task-archive.js +16 -0
  116. package/dist/task-control.d.ts +1 -1
  117. package/dist/task-source.d.ts +13 -1
  118. package/dist/task-source.js +17 -1
  119. package/dist/telegram-lead.js +7 -5
  120. package/dist/telegram.js +1 -1
  121. package/dist/tidy.d.ts +130 -0
  122. package/dist/tidy.js +503 -0
  123. package/dist/toolroll-update.js +2 -2
  124. package/dist/webhooks.d.ts +6 -0
  125. package/dist/webhooks.js +25 -0
  126. package/dist/work-index.d.ts +28 -0
  127. package/dist/work-index.js +82 -28
  128. package/dist/workspace-ui.d.ts +1 -1
  129. package/dist/workspace-ui.js +3 -2
  130. package/dist/youiyoux-intake.d.ts +234 -0
  131. package/dist/youiyoux-intake.js +908 -0
  132. package/package.json +11 -8
@@ -1,11 +1,14 @@
1
+ import { sameFeedbackImage } from "./result-review.js";
1
2
  import { sweepLine, sweepTable } from "./lead-sweep.js";
2
3
  import { connectionsOf, oneClickOf } from "./mcp-connect.js";
3
4
  import { checkLevelFromWords, isCheckLevel } from "./check-levels.js";
4
5
  import { policyParts } from "./policy.js";
5
6
  import { randomUUID } from 'node:crypto';
6
7
  import { dirname, join } from 'node:path';
8
+ import { projectOutcomes, projectOutcomesPage } from './project-outcomes.js';
7
9
  import { repositoryContextRead } from './repository-context.js';
8
10
  import { assignmentCatchUp } from './assignment-brief.js';
11
+ import { browserViewOf } from './flow-browser.js';
9
12
  import { CHAT_ACTIONS, CHAT_ACTION_FIELDS, isChatAction, prepareSharedAction, sharedActionNeedsReview, sharedActionPayload } from './chat-actions.js';
10
13
  import { conversationSkills, skillsView } from "./project-skills.js";
11
14
  import { readAcceptanceEvidence } from "./chat-acceptance.js";
@@ -18,6 +21,7 @@ import { cancelCommitment, CHECK_RESULTS, conditionWords, latestResultRun, recor
18
21
  import { searchMemory } from "./project-memory.js";
19
22
  import { integrationsNow, STATE_WORDS } from "./integrations.js";
20
23
  import { CAPABILITIES_UNREAD, capabilitiesOf, needsCapabilities } from "./lead-capabilities.js";
24
+ import { feedbackImageProblem } from "./result-actions.js";
21
25
  import { readChatResult, reviewInputProblem } from "./chat-review.js";
22
26
  import { RESULT_IMAGES_PER_TURN_CAP, selectResultImages } from "./chat-evidence.js";
23
27
  import { CHAT_CONTROLS, isChatControl } from "./chat-controls.js";
@@ -36,7 +40,8 @@ import { CHAT_TASK_ACTIONS, chatTaskRun, chatTaskStamp, isChatTaskAction } from
36
40
  * projects after scrubbing; full paths and arbitrary text remain redacted.
37
41
  */
38
42
  import { Buffer } from "node:buffer";
39
- import { LEAD_ASK_OTHER } from "./store.js";
43
+ import { LEAD_ASK_OTHER, refArchived } from "./store.js";
44
+ import { releaseFactsOf, tidyDigest, tidyGroups, tidyPreview, TIDY_DEFAULT_DAYS } from "./tidy.js";
40
45
  import { hasDisguisedText, hasForbiddenControls } from "./decision.js";
41
46
  import { readVerifiedArtifact, scanForSecrets } from "./evidence.js";
42
47
  import { parseAcceptanceCriteria } from "./scope.js";
@@ -377,7 +382,7 @@ export function agentsOver(store, taskId, now) {
377
382
  * find_tools and advertised from the next request of the same reply.
378
383
  */
379
384
  export const LEAD_CORE_TOOLS = new Set([
380
- "find_tools", "get_brief", "recap", "list_repos", "get_project_context", "search_project_memory", "get_capabilities",
385
+ "find_tools", "get_brief", "recap", "list_repos", "get_project_context", "get_project_activity", "search_project_memory", "get_capabilities",
381
386
  "list_tasks", "get_task", "get_result", "get_diff", "get_check_log", "list_decisions", "get_decision",
382
387
  "get_actions", "get_action_status", "propose_action", "propose_task", "propose_scope", "propose_steer",
383
388
  "propose_answer", "propose_review", "remember", "ask_owner", "commit_to",
@@ -475,6 +480,15 @@ const LEAD_TOOL_HANDLERS = {
475
480
  return { ok: true, body: assignmentCatchUp(ctx.store, ctx.now, { principal: 'operator', repos: ctx.who.repos }, repo === null ? {} : { repo }, ctx.evidenceRoot) };
476
481
  },
477
482
  },
483
+ get_project_activity: {
484
+ description: 'Read project Activity: current Ready backlog, finished-to-decision elapsed time, explicit revisions and recorded cost for completed families in the last 60 days. Includes coverage and a page of underlying tasks (offset: nextOffset for more); not review labor or a quality score.',
485
+ handle: (ctx, args) => {
486
+ const repo = repoPathOf(ctx.who, args['repo']);
487
+ if (!repo || !ctx.store.accountCanAccess(ctx.who.name, repo))
488
+ return { ok: false, message: 'Choose an available project from list_repos.' };
489
+ return { ok: true, body: projectOutcomesPage(projectOutcomes(ctx.store, repo, ctx.now, { principal: 'operator', repos: ctx.who.repos }), Number(args['offset'] ?? 0)) };
490
+ },
491
+ },
478
492
  get_project_context: {
479
493
  description: 'Find source excerpts or advisory import impact. Read-only; unavailable indexing falls back to source search.',
480
494
  handle: (ctx, args) => {
@@ -656,6 +670,7 @@ const LEAD_TOOL_HANDLERS = {
656
670
  changes: snippet.slice(0, 1000), changesShortened: snippet.length > 1000,
657
671
  verification: ctx.store.proofVerdictFor(snapshot.run)?.verdict ?? "not verified",
658
672
  accepted: ctx.store.proofAcceptance(snapshot.run) !== null,
673
+ images: (snapshot.images ?? []).map(one => ({ id: one.artifact, run: one.run })),
659
674
  evidenceTool: "get_acceptance_evidence",
660
675
  canRevise: snapshot.execution === snapshot.task } };
661
676
  },
@@ -789,7 +804,7 @@ const LEAD_TOOL_HANDLERS = {
789
804
  },
790
805
  },
791
806
  propose_review: {
792
- description: "Read get_result first. revise uses selected saved_notes plus optional note on the SAME task; note saves only. Omitted saved_notes stays untouched.",
807
+ description: "Read get_result first. revise uses selected saved_notes plus optional note on the SAME task; note saves only. Omitted saved_notes stays untouched. To comment on a screenshot, use its image id from get_result_images. If this channel cannot select one, link to the result Comment control and say feedback still needs submitting there.",
793
808
  handle: (ctx, args) => {
794
809
  const read = ctx.readResults?.get(Number(args["run"]));
795
810
  if (read === undefined || read.step >= ctx.step)
@@ -812,8 +827,20 @@ const LEAD_TOOL_HANDLERS = {
812
827
  return { ok: false, message: "Write feedback or select saved notes for a revision." };
813
828
  if (operation === "revise" && read.snapshot.task !== read.snapshot.execution)
814
829
  return { ok: false, message: "A newer revision is current. Read that version before requesting changes." };
830
+ const wanted = args["image"] === null ? undefined : args["image"] ?? (note !== null && ctx.replyImage?.run === read.snapshot.run ? ctx.replyImage.artifact : undefined);
831
+ let image = null;
832
+ if (wanted !== undefined) {
833
+ image = read.snapshot.images?.find(one => one.artifact === wanted) ?? null;
834
+ if (image === null || ctx.evidenceRoot === undefined || note === null)
835
+ return { ok: false, message: "Choose a saved screenshot from the result you read and write feedback." };
836
+ if (ctx.replyImage?.artifact === wanted && !sameFeedbackImage(ctx.replyImage, image))
837
+ return { ok: false, message: "The screenshot you replied to changed. Open the result and select it again." };
838
+ const problem = feedbackImageProblem(ctx.store, ctx.evidenceRoot, read.snapshot.run, image);
839
+ if (problem !== null)
840
+ return { ok: false, message: problem };
841
+ }
815
842
  const id = ctx.draft("review", { task: read.snapshot.task, taskTitle: read.snapshot.title, run: read.snapshot.run,
816
- snapshot: read.snapshot, operation, note, path, line, notes });
843
+ snapshot: read.snapshot, operation, note, path, line, notes, image });
817
844
  return id === null ? tooMany() : { ok: true, body: { proposal: id, kind: "review", awaiting: "confirmation", operation } };
818
845
  },
819
846
  },
@@ -888,13 +915,19 @@ const LEAD_TOOL_HANDLERS = {
888
915
  const active = cards.filter(one => one.state === "active"), finished = cards.filter(one => one.state !== "active").slice(-10);
889
916
  return { ok: true, body: {
890
917
  flow: flow.id, name: flow.name, project: repoIdOf(ctx.who, flow.repo),
918
+ // Browser use (flow-browser.ts), only for a flow whose script zones start one. Names never reach the model.
919
+ ...(() => {
920
+ const view = browserViewOf(ctx.store, flow);
921
+ return view === null ? {} : { browser: view.status === "allowed" ? "allowed: its browser zones may start Chromium (turn_off_browser stops it)"
922
+ : `${view.status === "ended" ? "approval ended: the flow or a script changed" : "not allowed"}; its browser zones wait. Propose allow_browser: a person allows it with their own password in the secure screen.` };
923
+ })(),
891
924
  steps: definition.stages.map(stage => ({
892
925
  id: stage.id, title: stage.title, does: FLOW_KIND_WORDS[stage.kind].label, kind: stage.kind,
893
926
  ...(stage.instructions === null ? {} : { instructions: stage.instructions.slice(0, 600) }),
894
927
  ...(stage.kind === "task" ? { planning: stage.planning } : {}),
895
928
  ...(stage.kind === "approval" ? { decider: stage.toOwner === true ? `the flow's owner (${flow.owner === ctx.who.name ? "you" : "someone else"})` : stage.approver === null ? "anyone who approves" : stage.approver === ctx.who.name ? "you" : "someone else" } : {}),
896
929
  ...(stage.message === null ? {} : { message: stage.message }),
897
- ...(stage.kind === "check" ? { script: stage.script, scriptExists: stage.script !== null && ctx.store.flowScript(flow.repo, stage.script) !== null } : {}),
930
+ ...(stage.kind === "check" ? { script: stage.script, scriptExists: stage.script !== null && ctx.store.flowScript(flow.repo, stage.script) !== null, ...(stage.browser === true ? { startsBrowser: true } : {}) } : {}),
898
931
  ...(stage.kind === "update" ? { closesIssue: stage.close === true } : {}),
899
932
  ...(stage.request === undefined ? {} : { method: stage.request.method, url: stage.request.url, headers: Object.keys(stage.request.headers), ...(stage.request.body === null ? {} : { body: stage.request.body.slice(0, 600) }) }),
900
933
  ...(stage.email === undefined ? {} : { to: stage.email.to, subject: stage.email.subject, body: stage.email.body.slice(0, 600) }),
@@ -960,7 +993,7 @@ const LEAD_TOOL_HANDLERS = {
960
993
  },
961
994
  },
962
995
  propose_flow: {
963
- description: "Draft a flow change as a card the operator confirms. To make a flow, create with a template or the steps in plain names; leave instructions out unless the operator gave them, and use decider 'me' when they decide. To change one, edit with the whole step list, keeping existing steps by id. For cards use add_card, move_card, approve, send_back with the operator's note, or cancel_card; find the card by what it is about; comment (with @name to ping someone), assign and follow here too. For triage, spam, lead, effort or exception routing, start from the matching template, with the sort as the first step. Use remindAfter and thenMoveTo for follow-ups and decisions that stall. The Issues to PRs template runs GitHub issues labelled toolroll through build, approval, pull request and a comment on the issue. When the operator asks for something to happen every time (fix CI when it fails, turn labelled issues into tasks, queue work overnight, review what went wrong every morning), offer the matching starter; each is one yes, and none merges without a person. When someone new wants a support desk, bug triage, sales follow-up or requests handled, a kit sets up the subagent and its flow in one card. Subagents: propose_subagent adds one (from a template: support, sales, ops, triage), changes one rule section (edit_section), pauses, resumes or removes it, passes it a note, or answers its question for the operator; put it to work with a flow here. When the operator says something like 'Maya can approve refunds up to $100 now', change that section of her soul file; when it's for this week or a one-off, pass it as a note. Subagents use the project's tools under a rule per action (do it, ask first: the operator approves each exact call, never, or up to a limit on a number); propose_subagent use_tool, stop_tool and tool_rule change them, and when a rule has a limit also change the soul file so its words agree. forget or edit_memory when the operator says a subagent has something wrong; a rule change it suggests is a question for the operator, answered only as they say. propose_subagent add_routine puts a card on its desk on a schedule; propose_subagent undo undoes a tool call only when its action has an undo set (tool_rule undoWith). Actions: starter: switch on a starter flow in a project (repo, starter: ci-fix files a fix task when CI fails on the main branch, issue-task makes a task of each GitHub issue labelled toolroll, overnight holds cards added in the day until 22:00 and leaves results for the morning, plane-review reviews the plane's last 24 hours every morning and turns each problem worth fixing into a researched fix and a pull request) — its zones and trigger in one card; offer the matching one when the operator says to do something every time. kit: set up a starter kit in a project (repo, kit: support-desk, bug-triage, sales-follow-up or ops-requests) — a subagent, the flow it works and its buttons, in one card; prefer it when the operator wants a support desk, bug triage, sales follow-up or requests handled. create: a template, or the steps in order (each leads to the next; Done is added; instructions may be left out). 'request' calls a web address (method, url with its host written out, headers — {{secret.NAME}} uses a secret the operator saved on the step, never in chat — and body); 'email' sends mail (to, subject, body; {{card.email}} is the card's email address); 'tool' calls one of the project's tools (server: the tool's name from get_project_tools, tool: its function, args: an object). Put a decision before any of these when they send what a model wrote or what an outsider sent. A 'draft' step has Claude write something from the card (instructions: what to write); follow it with an approval step (decider 'owner' asks the flow's owner in their chat app, where they can approve, edit or send it back), then an 'update' or 'notify' step whose message is '{{stage.<draft id>}}'. A 'sort' step has Jev pick one of its answers; make it the first step (never a holding step before it, or new cards wait unsorted): question, answers (answer, means: a few words Jev reads, goesTo: a step), sureAt (percent, default 80), ifNotSure (a step; otherwise the card waits for a person), and up to 3 alsoNote (score with levels lowest first, or yes-no); a sort has no next, so give each branch's last step its own next. A 'pull-request' step opens a pull request for the card's built result and waits for CI: next when it passes, ifFails when it fails (only this step defaults it: to the build before it, as a revision carrying the failing check); merge ('squash', 'merge' or 'rebase') merges once checks pass, and needs an approval step before it on every path. waitFor 'hours' with from and until (like '22:00' and '06:00') holds cards until the clock is inside those hours. A 'wait' step, after an 'email' step, waits for a reply to that email from someone it went to (waitFor 'reply', the default): next is where a reply goes (the reply is {{stage.<wait id>}}), ifNoReply where the card goes when none comes within wait (like '3 days', up to 30 days); waitFor 'time' just waits, then next. Any step but wait and done can have remindAfter (like '2 days': whoever it waits on is reminded once; 'none' removes it) and, on holding and approval steps, thenMoveTo (a step the card moves to then). subagents (get_subagents): an approval step with subagent (its short name) is decided by that subagent within its rules, and it hands hard ones to the step's decider; a 'subagent' step (subagent, instructions, routes of answer and goesTo) has it read the card, pick where it goes and write what the next steps send (the email body is then {{stage.<id>}}), asking the flow's owner when its rules say to. edit: the full step list, keeping existing steps by id — what a kept step leaves out carries over. add_card (starts in the first zone unless zone is named), move_card, approve, send_back (needs a note), cancel_card, comment (note; @name pings that person), assign (owner: a name, 'me', or 'nobody'), follow, unfollow, save_script (repo, and script: name, about, language python|node|shell, and either body (short) or file (a path in the project, like scripts/enrich.py), and timeoutMinutes; scripts belong to the project, so no flow is needed; a 'check' step in any of its flows names it). A 'check' step runs its script with the card as JSON on stdin (and in $FLOW_INPUT); what it prints is its result for later steps ({{stage.<id>}}); runIn 'folder' (an empty folder: for scripts that work on data) or 'copy' (a copy of the card's work, after setup: for tests on code; the default); routes (answer, goesTo) that a last printed line 'goto: <answer>' picks; ifFails is where a failing script sends the card, such as back to the build (it has no default: without it the card waits there); secrets (names of saved secrets it gets as variables). add_trigger with settings (kind button: label, questions; schedule: schedule like 'daily 09:00 Europe/London', and title, or script (a saved script whose printed items — one per line, a title or JSON with title, description, key — each become a card, once) with secrets; github: repo owner/name, watch issues|pulls|checks, label, branch, from team|anyone; linear: team, state, label; flow: follow (another flow's id), when (its zone); email: folder (default INBOX), sender (addresses or domains), subject (words it must contain); plane-review: at (HH:MM, default 07:30), timeZone — every day it reads the plane's last 24 hours and makes one card per problem worth fixing, a returning problem joining its card); pause_trigger, resume_trigger, remove_trigger with trigger. A 'send' step ('Send to me') sends the card's owner what the step before produced (summary, links, screenshots) in their chat apps, then moves on; a 'choose' step sends the same with 2 to 4 options (label, goesTo: a step, or 'end' to ignore the card) as buttons, ifReplied (where a reply goes as {{note}}; the build before by default), and remindAfter with ifNoReply. choose (card, and choice by number or a note to reply) answers one for the operator. A 'task' step may build in another project: repo (from list_repos). Read get_flows first except to create.",
996
+ description: "Draft a flow change as a card the operator confirms. To make a flow, create with a template or the steps in plain names; leave instructions out unless the operator gave them, and use decider 'me' when they decide. To change one, edit with the whole step list, keeping existing steps by id. For cards use add_card, move_card, approve, send_back with the operator's note, or cancel_card; find the card by what it is about; comment (with @name to ping someone), assign and follow here too. For triage, spam, lead, effort or exception routing, start from the matching template, with the sort as the first step. Use remindAfter and thenMoveTo for follow-ups and decisions that stall. The Issues to PRs template runs GitHub issues labelled toolroll through build, approval, pull request and a comment on the issue. When the operator asks for something to happen every time (fix CI when it fails, turn labelled issues into tasks, queue work overnight, review what went wrong every morning), offer the matching starter; each is one yes, and none merges without a person. When someone new wants a support desk, bug triage, sales follow-up or requests handled, a kit sets up the subagent and its flow in one card. Subagents: propose_subagent adds one (from a template: support, sales, ops, triage), changes one rule section (edit_section), pauses, resumes or removes it, passes it a note, or answers its question for the operator; put it to work with a flow here. When the operator says something like 'Maya can approve refunds up to $100 now', change that section of her soul file; when it's for this week or a one-off, pass it as a note. Subagents use the project's tools under a rule per action (do it, ask first: the operator approves each exact call, never, or up to a limit on a number); propose_subagent use_tool, stop_tool and tool_rule change them, and when a rule has a limit also change the soul file so its words agree. forget or edit_memory when the operator says a subagent has something wrong; a rule change it suggests is a question for the operator, answered only as they say. propose_subagent add_routine puts a card on its desk on a schedule; propose_subagent undo undoes a tool call only when its action has an undo set (tool_rule undoWith). Actions: starter: switch on a starter flow in a project (repo, starter: ci-fix files a fix task when CI fails on the main branch, issue-task makes a task of each GitHub issue labelled toolroll, overnight holds cards added in the day until 22:00 and leaves results for the morning, plane-review reviews the plane's last 24 hours every morning and turns each problem worth fixing into a researched fix and a pull request) — its zones and trigger in one card; offer the matching one when the operator says to do something every time. kit: set up a starter kit in a project (repo, kit: support-desk, bug-triage, sales-follow-up or ops-requests) — a subagent, the flow it works and its buttons, in one card; prefer it when the operator wants a support desk, bug triage, sales follow-up or requests handled. create: a template, or the steps in order (each leads to the next; Done is added; instructions may be left out). 'request' calls a web address (method, url with its host written out, headers — {{secret.NAME}} uses a secret the operator saved on the step, never in chat — and body); 'email' sends mail (to, subject, body; {{card.email}} is the card's email address); 'tool' calls one of the project's tools (server: the tool's name from get_project_tools, tool: its function, args: an object). Put a decision before any of these when they send what a model wrote or what an outsider sent. A 'draft' step has Claude write something from the card (instructions: what to write); follow it with an approval step (decider 'owner' asks the flow's owner in their chat app, where they can approve, edit or send it back), then an 'update' or 'notify' step whose message is '{{stage.<draft id>}}'. A 'sort' step has Jev pick one of its answers; make it the first step (never a holding step before it, or new cards wait unsorted): question, answers (answer, means: a few words Jev reads, goesTo: a step), sureAt (percent, default 80), ifNotSure (a step; otherwise the card waits for a person), and up to 3 alsoNote (score with levels lowest first, or yes-no); a sort has no next, so give each branch's last step its own next. A 'pull-request' step opens a pull request for the card's built result and waits for CI: next when it passes, ifFails when it fails (only this step defaults it: to the build before it, as a revision carrying the failing check); merge ('squash', 'merge' or 'rebase') merges once checks pass, and needs an approval step before it on every path. waitFor 'hours' with from and until (like '22:00' and '06:00') holds cards until the clock is inside those hours. A 'wait' step, after an 'email' step, waits for a reply to that email from someone it went to (waitFor 'reply', the default): next is where a reply goes (the reply is {{stage.<wait id>}}), ifNoReply where the card goes when none comes within wait (like '3 days', up to 30 days); waitFor 'time' just waits, then next. Any step but wait and done can have remindAfter (like '2 days': whoever it waits on is reminded once; 'none' removes it) and, on holding and approval steps, thenMoveTo (a step the card moves to then). subagents (get_subagents): an approval step with subagent (its short name) is decided by that subagent within its rules, and it hands hard ones to the step's decider; a 'subagent' step (subagent, instructions, routes of answer and goesTo) has it read the card, pick where it goes and write what the next steps send (the email body is then {{stage.<id>}}), asking the flow's owner when its rules say to. edit: the full step list, keeping existing steps by id — what a kept step leaves out carries over. add_card (starts in the first zone unless zone is named), move_card, approve, send_back (needs a note), cancel_card, comment (note; @name pings that person), assign (owner: a name, 'me', or 'nobody'), follow, unfollow, save_script (repo, and script: name, about, language python|node|shell, and either body (short) or file (a path in the project, like scripts/enrich.py), and timeoutMinutes; scripts belong to the project, so no flow is needed; a 'check' step in any of its flows names it). A 'check' step runs its script with the card as JSON on stdin (and in $FLOW_INPUT); what it prints is its result for later steps ({{stage.<id>}}); runIn 'folder' (an empty folder: for scripts that work on data) or 'copy' (a copy of the card's work, after setup: for tests on code; the default); routes (answer, goesTo) that a last printed line 'goto: <answer>' picks; ifFails is where a failing script sends the card, such as back to the build (it has no default: without it the card waits there); secrets (names of saved secrets it gets as variables). add_trigger with settings (kind button: label, questions; schedule: schedule like 'daily 09:00 Europe/London', and title, or script (a saved script whose printed items — one per line, a title or JSON with title, description, key — each become a card, once) with secrets; github: repo owner/name, watch issues|pulls|checks, label, branch, from team|anyone; linear: team, state, label; flow: follow (another flow's id), when (its zone); email: folder (default INBOX), sender (addresses or domains), subject (words it must contain); plane-review: at (HH:MM, default 07:30), timeZone — every day it reads the plane's last 24 hours and makes one card per problem worth fixing, a returning problem joining its card); pause_trigger, resume_trigger, remove_trigger with trigger. A 'send' step ('Send to me') sends the card's owner what the step before produced (summary, links, screenshots) in their chat apps, then moves on; a 'choose' step sends the same with 2 to 4 options (label, goesTo: a step, or 'end' to ignore the card) as buttons, ifReplied (where a reply goes as {{note}}; the build before by default), and remindAfter with ifNoReply. choose (card, and choice by number or a note to reply) answers one for the operator. A 'task' step may build in another project: repo (from list_repos). Read get_flows first except to create. allow_browser (flow): when get_flows says a flow's browser zones wait, propose allowing them to start Chromium; the operator allows it with their own password in the secure screen, never on the card. turn_off_browser (flow) stops it.",
964
997
  // An edit's kept steps may leave their kind and name to the zone they keep: they are filled in before the call is read.
965
998
  prepare: (ctx, given) => {
966
999
  const editing = given["operation"] === "edit" && Number.isSafeInteger(given["flow"]) ? ctx.store.getFlow(Number(given["flow"])) : null;
@@ -1100,7 +1133,16 @@ const LEAD_TOOL_HANDLERS = {
1100
1133
  operation = "flow_trigger_remove";
1101
1134
  input = pick(["trigger"]);
1102
1135
  break;
1103
- default: return { ok: false, message: "Choose create, edit, add_card, move_card, approve, send_back, cancel_card, comment, assign, follow, unfollow, add_trigger, pause_trigger, resume_trigger or remove_trigger." };
1136
+ // Browser use in the flow's script zones: a proposal only; the person allows it with their own password.
1137
+ case "allow_browser":
1138
+ operation = "flow_browser";
1139
+ input = pick(["flow"]);
1140
+ break;
1141
+ case "turn_off_browser":
1142
+ operation = "flow_browser_off";
1143
+ input = pick(["flow"]);
1144
+ break;
1145
+ default: return { ok: false, message: "Choose create, edit, add_card, move_card, approve, send_back, cancel_card, comment, assign, follow, unfollow, add_trigger, pause_trigger, resume_trigger, remove_trigger, allow_browser or turn_off_browser." };
1104
1146
  }
1105
1147
  const action = prepareSharedAction(ctx.store, ctx.who, operation, input, ctx.evidenceRoot, ctx.now);
1106
1148
  const id = ctx.draft("action", { ...action });
@@ -1497,7 +1539,8 @@ const LEAD_TOOL_HANDLERS = {
1497
1539
  const integrations = list.map(one => ({
1498
1540
  name: one.name, kind: one.group, state: STATE_WORDS[one.state], account: one.account, detail: one.detail, usedBy: one.usedBy,
1499
1541
  lastSuccessAt: one.lastSuccessAt, lastError: one.lastError,
1500
- next: one.action.kind === "setup" ? "Not set up: open Settings → Integrations to set it up" : one.action.kind === "fix" ? `Fix: ${one.action.words}` : null,
1542
+ next: one.action.kind === "setup" ? "Not set up: open Settings → Integrations to set it up" : one.action.kind === "fix" ? `Fix: ${one.action.words}`
1543
+ : one.action.kind === "switch" ? (one.action.words !== null ? `Fix: ${one.action.words}` : one.action.on ? "Turned off: turn it on in Settings → Integrations" : null) : null,
1501
1544
  }));
1502
1545
  return { ok: true, body: { integrations, settings: "show_control integrations opens Settings → Integrations; never invent a link" } };
1503
1546
  },
@@ -1582,11 +1625,12 @@ const LEAD_TOOL_HANDLERS = {
1582
1625
  return { ok: false, message: "search needs a word that names the work, such as a page, feature or file" };
1583
1626
  // A search reads further back than the newest page, then ranks by how many of the words each task's title and goal hold.
1584
1627
  const listed = ctx.store.taskFamiliesAdmitted(repo === null ? ctx.who.repos : [repo], false, { limit: words.length === 0 ? limit + 1 : SEARCH_FAMILIES, order: words.length === 0 ? "created" : "updated", ...(args["state"] === undefined ? {} : { states: [args["state"]] }) });
1585
- const families = words.length === 0 ? listed : listed
1628
+ // A put-away task (v145) is out of every list, the lead's included.
1629
+ const families = (words.length === 0 ? listed : listed
1586
1630
  .map((one, index) => ({ one, index, hits: searchHits(words, [one.root.id, one.root.title, one.current.title, ctx.store.getScope(one.current.id)?.goal ?? "", ctx.store.getScope(one.root.id)?.goal ?? ""]) }))
1587
1631
  .filter(match => match.hits > 0)
1588
1632
  .sort((a, b) => b.hits - a.hits || a.index - b.index)
1589
- .map(match => match.one);
1633
+ .map(match => match.one)).filter(one => !refArchived(ctx.store, one.current.refId));
1590
1634
  const tasks = families.slice(0, limit).map(one => ({
1591
1635
  repo: `r${ctx.who.repos.indexOf(one.current.repo) + 1}`, task: one.root.id, execution: one.current.id,
1592
1636
  title: one.root.title, state: one.current.state, historyProblem: one.problem, otherActive: one.otherActive.map(version => version.id),
@@ -1596,6 +1640,17 @@ const LEAD_TOOL_HANDLERS = {
1596
1640
  return { ok: true, body: { tasks, truncated: families.length > limit } };
1597
1641
  },
1598
1642
  },
1643
+ tidy_preview: {
1644
+ description: "Read-only: what Put away old tasks would put away now and why, grouped (old releases, old routine runs, resolved CI notices, finished more than `days` days ago, default 14). Only a person puts tasks away; you can't.",
1645
+ handle: (ctx, args) => {
1646
+ const days = typeof args["days"] === "number" ? args["days"] : TIDY_DEFAULT_DAYS;
1647
+ const items = tidyPreview(ctx.store, ctx.now, { releases: releaseFactsOf(ctx.store), days, access: { principal: "operator", repos: ctx.who.repos, includeUnplaced: false } });
1648
+ const digest = tidyDigest(items, days);
1649
+ return { ok: true, body: { count: items.length, days, digest,
1650
+ groups: tidyGroups(items, days).map(group => ({ label: group.label, tasks: group.items.map(one => ({ task: one.taskId, title: one.title, why: one.reason })) })),
1651
+ apply: items.length === 0 ? "Nothing to put away." : `A person puts these away: Tasks → Put away old tasks, or \`toolroll task tidy --apply --digest ${digest}${days === TIDY_DEFAULT_DAYS ? "" : ` --days ${days}`}\`.` } };
1652
+ },
1653
+ },
1599
1654
  get_task: {
1600
1655
  description: "Read currentExecution, state, dispatch, history, scope, dependencies, holds, queue, attempts, decisions and whether the agent has read your notes before acting.",
1601
1656
  handle: (ctx, args) => {
@@ -8,7 +8,7 @@
8
8
  import { actorLabel, withActor } from "./actor.js";
9
9
  import { chatControlHref } from "./chat-controls.js";
10
10
  import { BATCH_MS, chatText, chatTitle } from "./chat-voice.js";
11
- import { BUILT_IN } from "./store.js";
11
+ import { BUILT_IN, refArchived } from "./store.js";
12
12
  import { leadNameOf } from "./lead-identity.js";
13
13
  import { leadLapsed } from "./task-status.js";
14
14
  import { phoneText as plain } from "./telegram-status.js";
@@ -85,19 +85,24 @@ export function leadSay(store, actor, text, taskId, now) {
85
85
  // The task's own id never reaches a chat: it reads as the task's short title (chat-voice.ts).
86
86
  const said = taskId === null ? text.trim() : chatText(text.trim(), [{ id: taskId, title: chatTitle(store, taskId) }]);
87
87
  return store.transact(() => {
88
+ // A put-away task (v145) gets no messages: the lead's words about it would nudge a person about nothing.
89
+ if (task !== null && refArchived(store, task.refId))
90
+ return { ok: false, reason: "refused", message: "This task is put away. A person can restore it first." };
88
91
  const stamp = now.toISOString();
89
92
  const ledger = store.recordAction({ at: stamp, actor: actorLabel(actor), repo: task?.repo ?? null, taskId, runId: null,
90
93
  action: LEAD_SAID, outcome: "recorded", source: "work", detail: text.trim() });
91
- const open = store.handle.prepare(`SELECT id, dedupe_key, body FROM notification WHERE kind = ? AND recipient = ? ORDER BY id DESC LIMIT 1`)
94
+ const open = store.handle.prepare(`SELECT id, dedupe_key, body, task_ref FROM notification WHERE kind = ? AND recipient = ? ORDER BY id DESC LIMIT 1`)
92
95
  .get(LEAD_SAY_KIND, actor.account);
93
96
  const opened = open === undefined ? null : /^lead-say:(.+):\d+$/.exec(String(open["dedupe_key"]))?.[1] ?? null;
94
- const joined = open !== undefined && opened !== null && Date.parse(opened) > now.getTime() - BATCH_MS && Date.parse(opened) <= now.getTime();
97
+ // A batch is about one task (or none): words about another start a row of their own, so each row keeps its task.
98
+ const sameTask = open !== undefined && (open["task_ref"] ?? null) === (task === null ? null : task.refId);
99
+ const joined = sameTask && opened !== null && Date.parse(opened) > now.getTime() - BATCH_MS && Date.parse(opened) <= now.getTime();
95
100
  const lines = [...(joined ? String(open["body"]).split("\n") : []), said].slice(-BATCH_LINES);
96
101
  if (joined)
97
102
  store.handle.prepare("UPDATE notification SET resolved_at = ? WHERE id = ? AND resolved_at IS NULL").run(stamp, Number(open["id"]));
98
103
  const key = `lead-say:${joined ? opened : stamp}:${ledger}`;
99
104
  store.enqueueNotification({ dedupeKey: key, kind: LEAD_SAY_KIND, subject: leadNameOf(store, actor.account), body: lines.join("\n"),
100
- recipient: actor.account, source: { installation: true }, ...(taskId === null ? {} : { link: chatControlHref("task", taskId) }) }, now);
105
+ recipient: actor.account, source: task === null ? { installation: true } : { taskRef: task.refId }, ...(taskId === null ? {} : { link: chatControlHref("task", taskId) }) }, now);
101
106
  const row = store.handle.prepare("SELECT id FROM notification WHERE dedupe_key = ?").get(key);
102
107
  return { ok: true, joined, notification: row === undefined ? null : Number(row["id"]) };
103
108
  });
@@ -136,14 +141,20 @@ export function leadClaim(store, actor, taskId, now) {
136
141
  if ("ok" in task)
137
142
  return task;
138
143
  const { root } = familyOf(store, taskId);
139
- store.transact(() => {
144
+ const taken = store.transact(() => {
145
+ // A put-away task (v145) waits on nobody, so the lead takes nothing on there: checked with the claim's own write.
146
+ if (refArchived(store, task.refId))
147
+ return false;
140
148
  const ledger = store.recordAction({ at: now.toISOString(), actor: actorLabel(actor), repo: task.repo, taskId: root, runId: null,
141
149
  action: LEAD_CLAIMED, outcome: "recorded", source: "work" });
142
150
  // The owner's card for this task repaints to "<name> is on it": quiet chat edits its card, and every-update
143
151
  // chat edits the attempt's own card (the Failed alert), or follows it up when that message is gone.
144
152
  store.enqueueNotification({ dedupeKey: `lead-on-it:${ledger}`, kind: LEAD_ON_IT_KIND, subject: `${leadNameOf(store, actor.account)} is on it`, body: "",
145
153
  link: chatControlHref("task", taskId), source: cardSource(store, root) ?? { taskRef: task.refId } }, now);
154
+ return true;
146
155
  });
156
+ if (!taken)
157
+ return { ok: false, reason: "refused", message: "This task is put away. A person can restore it first." };
147
158
  return { ok: true, root, claim: leadClaimOf(store, taskId, now, actor.account) };
148
159
  }
149
160
  /** Where a task's chat card lives: the family's newest attempt (the card every-update chat edits), else its newest
package/dist/lead.d.ts CHANGED
@@ -38,6 +38,8 @@ export type LeadTurnInput = {
38
38
  * visible thread so a task-scoped composer still reads like a normal
39
39
  * conversation. */
40
40
  context?: string;
41
+ /** Exact image from the transport record of the message being answered. */
42
+ replyImage?: import("./result-review.js").FeedbackImage;
41
43
  fetcher?: typeof fetch;
42
44
  /** Injected by tests; production invokes the isolated local harness. */
43
45
  subscriptionRunner?: SubscriptionLeadRunner;
package/dist/lead.js CHANGED
@@ -110,7 +110,7 @@ function settleBy(work, ms) {
110
110
  const late = new Promise(resolve => { timer = setTimeout(() => resolve(LATE), Math.max(0, ms)); });
111
111
  return Promise.race([work, late]).finally(() => clearTimeout(timer));
112
112
  }
113
- const READ_TOOLS = new Set(["get_brief", "get_project_context", "get_actions", "get_action_status", "get_skills", "get_acceptance_evidence", "recap", "list_repos", "list_tasks", "get_task", "get_result", "get_result_images", "get_controls", "get_agents", "get_project_knowledge", "get_task_conversation", "get_diff", "get_check_log", "get_project_tools", "get_flows", "get_flow_insights", "list_decisions", "get_decision", "queue"]);
113
+ const READ_TOOLS = new Set(["get_brief", "get_project_context", "get_project_activity", "get_actions", "get_action_status", "get_skills", "get_acceptance_evidence", "recap", "list_repos", "list_tasks", "get_task", "get_result", "get_result_images", "get_controls", "get_agents", "get_project_knowledge", "get_task_conversation", "get_diff", "get_check_log", "get_project_tools", "get_flows", "get_flow_insights", "list_decisions", "get_decision", "queue"]);
114
114
  /** The last messages of the thread as provider-neutral history, newest kept first until the byte cap. */
115
115
  export function historyFor(store, thread, queuedMessageId, named = queuedMessageId !== undefined) {
116
116
  // Admitted messages only (v128): a queued message — this one, a teammate's, or one removed while waiting — never
@@ -205,7 +205,7 @@ export async function runLeadTurn(input) {
205
205
  return refuse("thread-closed");
206
206
  if (liveSession.ceilingDigest !== who.ceilingDigest || liveThread.ceilingDigest !== who.ceilingDigest)
207
207
  return refuse("ceiling-changed");
208
- const digest = createHash("sha256").update(JSON.stringify([thread.id, message, input.context ?? null])).digest("hex");
208
+ const digest = createHash("sha256").update(JSON.stringify([thread.id, message, input.context ?? null, ...(input.replyImage === undefined ? [] : [input.replyImage])])).digest("hex");
209
209
  const receipt = request === undefined ? null : store.leadRequestReceipt(session.id, request);
210
210
  if (receipt !== null)
211
211
  return receipt.digest === digest ? { ok: true, replayed: true, turn: receipt.turn } : refuse("request-changed");
@@ -568,7 +568,7 @@ export async function runLeadTurn(input) {
568
568
  // caller of executeLeadTool): the model learns the road (find_tools, then the next step) and the reply goes on.
569
569
  const outcome = !advertisedNames.has(call.name)
570
570
  ? { ok: false, message: `${call.name} isn't available in this step. Call find_tools with what you want to do; tools it finds can be called from your next step.` }
571
- : executeLeadTool({ store, who, now: clock(), draft, selectEvidence, step: steps, readDecisions, readResults, searchedMemory, checkedCapabilities, ask, findTools, ...(input.integrations === undefined ? {} : { integrations: input.integrations }), thread: thread.id, turn: turnId, ...(promiseChannelOfTurn(input.channel) === undefined ? {} : { channel: promiseChannelOfTurn(input.channel) }), ...(input.evidenceRoot === undefined ? {} : { evidenceRoot: input.evidenceRoot }), ...(input.mediaDelivery === undefined ? {} : { mediaDelivery: input.mediaDelivery }) }, call.name, call.args, view);
571
+ : executeLeadTool({ store, who, now: clock(), draft, selectEvidence, ...(input.replyImage === undefined ? {} : { replyImage: input.replyImage }), step: steps, readDecisions, readResults, searchedMemory, checkedCapabilities, ask, findTools, ...(input.integrations === undefined ? {} : { integrations: input.integrations }), thread: thread.id, turn: turnId, ...(promiseChannelOfTurn(input.channel) === undefined ? {} : { channel: promiseChannelOfTurn(input.channel) }), ...(input.evidenceRoot === undefined ? {} : { evidenceRoot: input.evidenceRoot }), ...(input.mediaDelivery === undefined ? {} : { mediaDelivery: input.mediaDelivery }) }, call.name, call.args, view);
572
572
  let displayOutcome = { state: "succeeded" };
573
573
  if (!outcome.ok) {
574
574
  const reason = leadToolFailureReason(call.name, outcome.message);
package/dist/operate.d.ts CHANGED
@@ -99,7 +99,7 @@ export type OperateOptions = {
99
99
  /** Injected by tests: how the console looks at project folders off its thread (folder-look.ts). */
100
100
  folderLook?: FolderLookOptions;
101
101
  };
102
- export type OnboardSeams = Partial<Pick<OnboardIo, "home" | "env" | "cwd" | "interactive" | "confirm" | "findRepo" | "checkConnection" | "pullRequests">>;
102
+ export type OnboardSeams = Partial<Pick<OnboardIo, "home" | "env" | "cwd" | "interactive" | "confirm" | "findRepo" | "checkConnection" | "pullRequests" | "youiyoux">>;
103
103
  export declare const OPERATE_HELP: string;
104
104
  /** Parsed flags, with the positionals left over. */
105
105
  type Args = {
@@ -115,7 +115,7 @@ type Args = {
115
115
  * exports are behavior, not commentary — and the declared command guide
116
116
  * (surface.ts) is tested for exact equality against them.
117
117
  */
118
- export declare const TASK_ACTIONS: readonly ["add", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign", "reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan", "review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask", "checks", "add-tests"];
118
+ export declare const TASK_ACTIONS: readonly ["add", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign", "reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan", "review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask", "checks", "add-tests", "archive", "restore", "tidy"];
119
119
  export declare const PUBLISH_ACTIONS: readonly ["setup", "grant", "revoke", "status", "unblock", "rearm", "merge", "refire"];
120
120
  export declare const CONFIG_ACTIONS: readonly ["show", "set", "clear"];
121
121
  export declare const APPROVER_ACTIONS: readonly ["list", "add"];
@@ -237,7 +237,6 @@ type Context = {
237
237
  export declare function writeLoginFileDurably(path: string, name: string, password: string): void;
238
238
  /** The platform browser opener — detached, silent, never load-bearing. */
239
239
  export declare function openBrowser(url: string): void;
240
- export declare function readUpConsoleUrl(dir: string): string | null;
241
240
  /**
242
241
  * The people allowed to agree to a scope.
243
242
  *