toolroll 0.9.83 → 0.9.85

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 (137) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +94 -1192
  3. package/dist/agent-fence.d.ts +3 -3
  4. package/dist/agent-fence.js +3 -3
  5. package/dist/agent-onboard.d.ts +2 -0
  6. package/dist/agent-onboard.js +3 -0
  7. package/dist/assignment-ui.js +5 -1
  8. package/dist/browser/workspace.css +2 -2
  9. package/dist/browser/workspace.js +31 -31
  10. package/dist/browser-crew.js +5 -1
  11. package/dist/browser-workspace.d.ts +12 -0
  12. package/dist/chat-channel.d.ts +10 -1
  13. package/dist/chat-channel.js +35 -5
  14. package/dist/chat-decide.d.ts +16 -4
  15. package/dist/chat-decide.js +239 -31
  16. package/dist/chat-messages.d.ts +5 -0
  17. package/dist/chat-messages.js +1 -1
  18. package/dist/chat-quiet.js +3 -2
  19. package/dist/chat-review.d.ts +4 -0
  20. package/dist/chat-review.js +13 -4
  21. package/dist/chat-terms.d.ts +9 -0
  22. package/dist/chat-terms.js +46 -0
  23. package/dist/contracts/console-api.d.ts +4 -2
  24. package/dist/contracts/console-api.js +7 -2
  25. package/dist/contracts/lead-tools.d.ts +30 -0
  26. package/dist/contracts/lead-tools.js +3 -0
  27. package/dist/contracts/youiyoux-request.d.ts +248 -0
  28. package/dist/contracts/youiyoux-request.js +152 -0
  29. package/dist/control-setup.d.ts +9 -0
  30. package/dist/control-setup.js +31 -4
  31. package/dist/control-ui.d.ts +7 -1
  32. package/dist/control-ui.js +3 -2
  33. package/dist/desktop-update.d.ts +1 -1
  34. package/dist/dispatch.d.ts +15 -0
  35. package/dist/dispatch.js +26 -4
  36. package/dist/first-run.d.ts +5 -2
  37. package/dist/first-run.js +13 -2
  38. package/dist/flow-cadence.d.ts +8 -2
  39. package/dist/flow-cadence.js +16 -3
  40. package/dist/flow-card-task.d.ts +9 -0
  41. package/dist/flow-card-task.js +15 -6
  42. package/dist/folder-look.d.ts +3 -0
  43. package/dist/folder-look.js +8 -4
  44. package/dist/guides.js +4 -3
  45. package/dist/integrations-ui.js +9 -4
  46. package/dist/integrations.d.ts +9 -1
  47. package/dist/integrations.js +43 -2
  48. package/dist/landed-cleanup.d.ts +4 -0
  49. package/dist/landed-cleanup.js +3 -2
  50. package/dist/lead-capabilities.js +2 -1
  51. package/dist/lead-cli.js +3 -1
  52. package/dist/lead-tools.d.ts +2 -0
  53. package/dist/lead-tools.js +30 -4
  54. package/dist/lead.d.ts +2 -0
  55. package/dist/lead.js +3 -3
  56. package/dist/model-catalog.d.ts +5 -0
  57. package/dist/model-catalog.js +11 -2
  58. package/dist/model-choices.d.ts +188 -0
  59. package/dist/model-choices.js +405 -0
  60. package/dist/model-names.d.ts +6 -0
  61. package/dist/model-names.js +34 -0
  62. package/dist/models-ui.d.ts +69 -5
  63. package/dist/models-ui.js +53 -10
  64. package/dist/needs-you.d.ts +8 -0
  65. package/dist/needs-you.js +8 -0
  66. package/dist/operate.d.ts +22 -2
  67. package/dist/operate.js +107 -41
  68. package/dist/project-activity.d.ts +20 -0
  69. package/dist/project-activity.js +23 -0
  70. package/dist/project-admission.d.ts +4 -1
  71. package/dist/project-admission.js +23 -2
  72. package/dist/project-cli.d.ts +1 -0
  73. package/dist/project-cli.js +36 -19
  74. package/dist/project-identity.d.ts +9 -2
  75. package/dist/project-identity.js +80 -33
  76. package/dist/project-knowledge.d.ts +9 -9
  77. package/dist/project-outcomes.d.ts +119 -0
  78. package/dist/project-outcomes.js +79 -0
  79. package/dist/result-actions.d.ts +4 -0
  80. package/dist/result-actions.js +41 -9
  81. package/dist/result-review.d.ts +9 -0
  82. package/dist/result-review.js +45 -5
  83. package/dist/serve.js +60 -13
  84. package/dist/server/chat.js +5 -4
  85. package/dist/server/chrome.d.ts +3 -0
  86. package/dist/server/chrome.js +5 -2
  87. package/dist/server/console.css +17 -2
  88. package/dist/server/flows.js +4 -0
  89. package/dist/server/http.d.ts +1 -0
  90. package/dist/server/http.js +6 -0
  91. package/dist/server/pages.js +53 -7
  92. package/dist/server/people-tokens.js +40 -20
  93. package/dist/server/render-chat.js +3 -2
  94. package/dist/server/render-pages.d.ts +12 -2
  95. package/dist/server/render-pages.js +36 -16
  96. package/dist/server/render-people.d.ts +14 -2
  97. package/dist/server/render-people.js +35 -12
  98. package/dist/server/render-results.d.ts +2 -0
  99. package/dist/server/render-results.js +20 -7
  100. package/dist/server/render-settings.js +1 -0
  101. package/dist/server/render-tasks.d.ts +1 -0
  102. package/dist/server/render-tasks.js +22 -19
  103. package/dist/server/request-context.d.ts +2 -0
  104. package/dist/server/route-table.js +9 -1
  105. package/dist/server/settings.d.ts +58 -1
  106. package/dist/server/settings.js +216 -70
  107. package/dist/server/signup-gate.d.ts +9 -0
  108. package/dist/server/signup-gate.js +40 -0
  109. package/dist/server/task-views.js +6 -0
  110. package/dist/server/tasks.js +54 -8
  111. package/dist/settings-kit-script.d.ts +21 -0
  112. package/dist/settings-kit-script.js +67 -0
  113. package/dist/settings-kit.d.ts +137 -0
  114. package/dist/settings-kit.js +186 -0
  115. package/dist/settings-proofs.d.ts +64 -0
  116. package/dist/settings-proofs.js +101 -0
  117. package/dist/setup-guide.js +4 -2
  118. package/dist/spend-report.d.ts +87 -0
  119. package/dist/spend-report.js +265 -0
  120. package/dist/spend-ui.d.ts +29 -8
  121. package/dist/spend-ui.js +74 -39
  122. package/dist/store.d.ts +46 -3
  123. package/dist/store.js +165 -6
  124. package/dist/task-sizing.d.ts +5 -3
  125. package/dist/task-sizing.js +4 -4
  126. package/dist/task-source.d.ts +13 -1
  127. package/dist/task-source.js +17 -1
  128. package/dist/telegram-lead.js +7 -5
  129. package/dist/telegram.js +1 -1
  130. package/dist/toolroll-update.js +2 -2
  131. package/dist/webhooks.d.ts +6 -0
  132. package/dist/webhooks.js +25 -0
  133. package/dist/work-index.d.ts +4 -0
  134. package/dist/work-index.js +32 -0
  135. package/dist/youiyoux-intake.d.ts +234 -0
  136. package/dist/youiyoux-intake.js +908 -0
  137. package/package.json +11 -8
@@ -614,6 +614,8 @@ export type ResultDetail = {
614
614
  fileAnchors: ReadonlyMap<string, string>;
615
615
  comments: DiffComment[];
616
616
  pastComments?: DiffComment[];
617
+ /** Live saved notes whose screenshot would be refused now (result-actions.ts feedbackImageProblem). */
618
+ unusableImageNotes?: readonly number[];
617
619
  reviewerFindings: DiffComment[];
618
620
  /** The build's one automatic review, when its project had review on. */
619
621
  automaticReview?: BuildReviewView | null;
@@ -1068,7 +1068,7 @@ export function proofBundleView(store, run, artifacts, root) {
1068
1068
  catch {
1069
1069
  problem = "the file could not be read";
1070
1070
  }
1071
- return [{ path, caption: captionFor(path), artifactId: artifact.id, problem }];
1071
+ return [{ path, caption: captionFor(path), artifactId: artifact.id, sha256: artifact.sha256, problem }];
1072
1072
  });
1073
1073
  const stored = new Set(screenshots.map(one => one.path));
1074
1074
  const uncapturedScreenshots = (proof?.screenshots ?? []).map(one => one.path).filter(path => !stored.has(path));
@@ -1598,6 +1598,8 @@ export function resultPanelParts(detail, o) {
1598
1598
  const physicalReasons = receipt.reasons.filter(reason => !assessmentReasons.has(reason));
1599
1599
  const runId = run.id;
1600
1600
  const shots = proof?.screenshots ?? [];
1601
+ const picture = (shot) => html `<figure class="result-image"><a class="receipt-shot" href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"><span>${shot.caption}</span></a>${detail.canAnnotate && o.csrf !== "" && shot.sha256 !== undefined
1602
+ ? html `<button type="button" class="quiet" data-comment-image="${JSON.stringify({ run: runId, artifact: shot.artifactId, sha256: shot.sha256 })}" data-image-caption="${shot.caption}">Comment</button>` : ""}</figure>`;
1601
1603
  const shown = shots.filter(one => one.problem === null);
1602
1604
  const stat = terminal?.stat ?? null;
1603
1605
  const statOk = stat !== null && !("problem" in stat);
@@ -1694,7 +1696,7 @@ export function resultPanelParts(detail, o) {
1694
1696
  }
1695
1697
  else if (lead === "screenshots") {
1696
1698
  summaryParts.push(html `<div class="receipt-visuals result-visuals" aria-label="validated screenshots">${shown
1697
- .map(shot => html `<a class="receipt-shot" href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"><span>${shot.caption}</span></a>`)}</div>`);
1699
+ .map(picture)}</div>`);
1698
1700
  }
1699
1701
  const unavailableShots = shots.filter(one => one.problem !== null);
1700
1702
  if (lead === "changes") {
@@ -1848,7 +1850,7 @@ export function resultPanelParts(detail, o) {
1848
1850
  ? directAssessment && !screenshotRequired ? html `` : html `<p class="meta" data-cockpit-source="screenshots">No screenshots${screenshotRequired ? " were captured, although one was required" : " were needed"}.</p>`
1849
1851
  : lead === "screenshots"
1850
1852
  ? html `<p class="meta" data-cockpit-source="screenshots">${shown.length} validated screenshot${shown.length === 1 ? "" : "s"} shown in Summary${unavailableShots.length > 0 ? `; ${unavailableShots.length} unavailable` : ""}.</p>`
1851
- : html `<details class="cockpit-proof-group" data-cockpit-source="screenshots"><summary>Screenshots · ${shown.length}${unavailableShots.length > 0 ? ` (${unavailableShots.length} unavailable)` : ""}</summary><div class="receipt-visuals" aria-label="validated screenshots">${shown.map(shot => html `<a class="receipt-shot" href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"><span>${shot.caption}</span></a>`)}</div></details>`);
1853
+ : html `<details class="cockpit-proof-group" data-cockpit-source="screenshots"><summary>Screenshots · ${shown.length}${unavailableShots.length > 0 ? ` (${unavailableShots.length} unavailable)` : ""}</summary><div class="receipt-visuals" aria-label="validated screenshots">${shown.map(picture)}</div></details>`);
1852
1854
  checkParts.push(receipt.caveats.length === 0
1853
1855
  ? directAssessment ? html `` : html `<p class="meta" data-cockpit-source="caveats">No caveats were reported.</p>`
1854
1856
  : html `<details class="cockpit-proof-group" data-cockpit-source="caveats"><summary>Agent caveats · ${receipt.caveats.length}</summary><ul>${receipt.caveats.map(one => html `<li>${one}</li>`)}</ul></details>`);
@@ -1863,19 +1865,29 @@ export function resultPanelParts(detail, o) {
1863
1865
  checkParts.push(o.extraChecks);
1864
1866
  // ---- request changes: both feedback styles, one sealed road ------------
1865
1867
  const requestParts = [html `<h3 class="so-sr-only">What should change?</h3>`];
1868
+ const imageNote = (one) => {
1869
+ if (one.image == null)
1870
+ return html ``;
1871
+ const shot = shots.find(shot => shot.artifactId === one.image.artifact && shot.sha256 === one.image.sha256 && one.image.run === runId && shot.problem === null);
1872
+ // A live saved note whose screenshot no longer verifies can be kept without it, so it never blocks a revision.
1873
+ const drop = o.csrf === "" || !(detail.unusableImageNotes ?? []).includes(one.id) ? html ``
1874
+ : postForm(`/r/${runId}/comment`, html `<input type="hidden" name="intent" value="drop-image"><input type="hidden" name="comment" value="${String(one.id)}"><button type="submit" class="quiet">Keep note without screenshot</button>`, { attrs: { class: "feedback-image-drop" }, returnTo: o.returnTo, hidden: { tab: o.tab } });
1875
+ return html `<figure class="feedback-image">${shot === undefined ? html `<figcaption>Screenshot ${one.image.artifact} is unavailable.</figcaption>`
1876
+ : html `<a href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"></a><figcaption>${shot.caption} · Screenshot ${shot.artifactId}</figcaption>`}${drop}</figure>`;
1877
+ };
1866
1878
  const pathWords = (path, line) => {
1867
1879
  const shownPath = `${path}${line === null ? "" : `:${line}`}`;
1868
1880
  const href = detail.editor === null ? null : editorFileHref(detail.editor.worktree, path, line);
1869
1881
  return href === null ? html `<span class="mono">${shownPath}</span> ` : html `<a class="mono" href="${href}">${shownPath}</a> `;
1870
1882
  };
1871
1883
  if (detail.comments.length > 0) {
1872
- requestParts.push(html `<p class="meta">Saved for later · ${detail.comments.length}</p><div class="diff-comments" data-result-notes="${detail.comments.length}">${detail.comments.map(one => html `<div class="diff-comment"><span class="diff-comment-pin" aria-hidden="true"></span><p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div>`);
1884
+ requestParts.push(html `<p class="meta">Saved for later · ${detail.comments.length}</p><div class="diff-comments" data-result-notes="${detail.comments.length}">${detail.comments.map(one => html `<div class="diff-comment">${imageNote(one)}<span class="diff-comment-pin" aria-hidden="true"></span><p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div>`);
1873
1885
  if (o.csrf !== "" && !detail.canAnnotate) {
1874
1886
  requestParts.push(postForm(`/r/${runId}/revise`, html `${revisionSealFields(detail.comments, detail.sourceDigest)}<div><strong>${detail.comments.length} note${detail.comments.length === 1 ? "" : "s"} ready</strong></div><button type="submit">Request changes</button>`, { attrs: { class: "card revision-from-comments" }, returnTo: o.returnTo }));
1875
1887
  }
1876
1888
  }
1877
1889
  if ((detail.pastComments?.length ?? 0) > 0) {
1878
- requestParts.push(html `<details class="result-feedback-history"><summary>Earlier feedback</summary><div class="diff-comments" data-past-feedback>${detail.pastComments.map(one => html `<div class="diff-comment"><p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div></details>`);
1890
+ requestParts.push(html `<details class="result-feedback-history"><summary>Earlier feedback</summary><div class="diff-comments" data-past-feedback>${detail.pastComments.map(one => html `<div class="diff-comment">${imageNote(one)}<p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div></details>`);
1879
1891
  }
1880
1892
  for (const revision of detail.revisions) {
1881
1893
  // The child's standing is the shared status projection's own words
@@ -1892,6 +1904,7 @@ export function resultPanelParts(detail, o) {
1892
1904
  requestParts.push(html `<details class="result-request-open result-request-form"${detail.comments.length > 0 ? " open" : ""}><summary>What should change?</summary>${postForm(`/r/${runId}/comment`, joinHtml([
1893
1905
  revisionSealFields(detail.comments, detail.sourceDigest),
1894
1906
  html `<input type="hidden" data-recorded-requests value="${[...detail.comments, ...(detail.pastComments ?? [])].filter(one => one.sourceKey?.startsWith(`review:${o.user}:`)).map(one => one.sourceKey.slice(`review:${o.user}:`.length)).join(",")}">`,
1907
+ html `<input type="hidden" name="image" value=""><figure class="feedback-image" data-feedback-image hidden><img alt="Selected screenshot" hidden><figcaption></figcaption><button type="button" class="quiet" data-remove-image>Remove image</button></figure>`,
1895
1908
  html `<textarea name="note" rows="2" maxlength="${LIMITS.note}" placeholder="Describe the change…" aria-label="review comment" aria-describedby="comment-note-limit"${o.noted ? " autofocus" : ""}></textarea>`,
1896
1909
  html `<span class="meta diff-comment-limit" id="comment-note-limit">up to ${LIMITS.note} characters</span>`,
1897
1910
  html `<details class="result-pin"><summary>Attach to a file or line</summary>`,
@@ -1980,7 +1993,7 @@ export function personCheckItems(proof, patchText, shots, runId) {
1980
1993
  return {
1981
1994
  id: row.id, statement: row.statement, words: personCheckWords(row.statement), note: notes.length === 0 ? null : notes.join(" "),
1982
1995
  excerpts: files.filter(one => one.lines.length > 0).slice(0, 3),
1983
- shots: pictures.slice(0, 4).map(shot => ({ src: `/r/${runId}/evidence/${shot.artifactId}`, href: `/r/${runId}/evidence/${shot.artifactId}`, caption: shot.caption })),
1996
+ shots: pictures.slice(0, 4).map(shot => ({ src: `/r/${runId}/evidence/${shot.artifactId}`, href: `/r/${runId}/evidence/${shot.artifactId}`, caption: shot.caption, ...(shot.sha256 === undefined ? {} : { image: { run: runId, artifact: shot.artifactId, sha256: shot.sha256 } }) })),
1984
1997
  };
1985
1998
  });
1986
1999
  }
@@ -2215,7 +2228,7 @@ export function runPage(chrome, run, taskId, artifacts, terminal = null, notes =
2215
2228
  noted,
2216
2229
  requestToken: result.requestToken,
2217
2230
  hrefFor: one => (one === "summary" ? `/r/${run.id}` : `/r/${run.id}?tab=${one}`),
2218
- returnTo: `/r/${run.id}`,
2231
+ returnTo: `/r/${run.id}?record=1`,
2219
2232
  back: null,
2220
2233
  });
2221
2234
  return screen(`build #${run.id}`, joinHtml([
@@ -511,6 +511,7 @@ export const SETTINGS_GROUPS = [
511
511
  ["Agents", [
512
512
  ["/settings/lead", "Lead", html `<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><path d="M8 9h8M8 13h5"/>`],
513
513
  ["/settings/models", "Models", html `<rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M9 2v2M15 2v2M9 20v2M15 20v2M2 9h2M2 15h2M20 9h2M20 15h2"/>`],
514
+ ["/settings/spend", "Spend", html `<circle cx="12" cy="12" r="9"/><path d="M15 9.5c-.6-.9-1.7-1.5-3-1.5-1.7 0-3 .9-3 2s1.3 1.7 3 2 3 .9 3 2-1.3 2-3 2c-1.3 0-2.4-.6-3-1.5M12 6v2M12 16v2"/>`],
514
515
  ["/settings/skills", "Skills", html `<path d="m12 3 1.9 5.8L20 10l-5 3.6L16.8 20 12 16.4 7.2 20 9 13.6 4 10l6.1-1.2z"/>`],
515
516
  ["/settings/tools", "Tools", html `<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>`],
516
517
  ["/settings/knowledge", "Knowledge", html `<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V3H6.5A2.5 2.5 0 0 0 4 5.5z"/><path d="M4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/>`],
@@ -398,6 +398,7 @@ export type RevisionView = {
398
398
  line: number | null;
399
399
  note: string;
400
400
  author: string;
401
+ image?: import("../result-review.js").FeedbackImage | null;
401
402
  }[];
402
403
  /** The lineage and ACTUAL terms (contract handoff task 2), read from
403
404
  * the child's own rows — the same projection chat and the approval
@@ -25,13 +25,14 @@ import { taskRoad } from "../first-run.js";
25
25
  import { starterForWork } from "../flow-starters.js";
26
26
  import {} from "../gaps.js";
27
27
  import { readAuthModeStrict } from "../keys.js";
28
- import { failedAttemptSentence, retryNoteOf } from "../needs-you.js";
28
+ import { failedAttemptSentence, retryNoteOf, START_BUILDER_COMMAND, startBuilderHref } from "../needs-you.js";
29
29
  import { chosenWords, makesNoPlan, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
30
30
  import { parseExecutionPlanDocument } from "../plan.js";
31
31
  import {} from "../planner-source.js";
32
32
  import { projectName } from "../project.js";
33
33
  import { dispatchStatusToken, manualReviewOnly, plainReasonWords, reviewConflict } from "../proof.js";
34
34
  import { providerName } from "../provider-auth.js";
35
+ import { agentModelName } from "../model-names.js";
35
36
  import {} from "../provider.js";
36
37
  import { qualityModeTitle } from "../quality.js";
37
38
  import { evidenceShortenedWords } from "../result-review.js";
@@ -49,7 +50,7 @@ import { reviewHref, taskChatHref, taskHref } from "./http.js";
49
50
  import { coordinatorProposalsSection } from "./render-chat.js";
50
51
  import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, verificationExplanation, verificationRecovered } from "./render-results.js";
51
52
  import {} from "./session.js";
52
- import { linkWords, originNote } from "../task-source.js";
53
+ import { linkWords, originNote, requestName, requestWords } from "../task-source.js";
53
54
  export function contractChangeHtml(change) {
54
55
  const tag = html `<span class="contract-change-kind contract-change-${change.kind}">${change.kind}</span>`;
55
56
  switch (change.field) {
@@ -134,17 +135,7 @@ export function acceptanceCeremonyHtml(criteria) {
134
135
  }
135
136
  /** "claude" + "opus" → "Claude Opus"; "claude-opus-5-5" → "Claude Opus 5.5"; anything else keeps its exact id. */
136
137
  export function agentNameWords(provider, model) {
137
- const name = providerName(provider);
138
- if (model === null || model === "" || model === "default")
139
- return name;
140
- const bare = model.startsWith(`${provider}-`) ? model.slice(provider.length + 1) : model;
141
- const title = (word) => `${word[0].toUpperCase()}${word.slice(1)}`;
142
- if (/^[a-z]+$/.test(bare))
143
- return `${name} ${title(bare)}`;
144
- const versioned = /^([a-z]+)-(\d+(?:-\d+)*)$/.exec(bare);
145
- if (versioned !== null)
146
- return `${name} ${title(versioned[1])} ${versioned[2].replace(/-/g, ".")}`;
147
- return `${name} ${model}`;
138
+ return agentModelName(provider, model);
148
139
  }
149
140
  /** The permission a sealed profile grants, in two or three words. */
150
141
  export function permissionWordsOf(profile) {
@@ -212,7 +203,8 @@ const fragmentString = (fragment) => fragment === "" ? "" : htmlString(fragment)
212
203
  function originWords(origin) {
213
204
  const flow = origin.flow === null ? null : origin.flow === "hidden" ? "from a flow" : origin.flow === "removed" ? "from a flow (removed)" : `from flow “${origin.flow.name}”`;
214
205
  const source = origin.source === null ? null : `${origin.source.label} (${origin.source.proved})`;
215
- return [flow, source].filter(one => one !== null).join(" · ");
206
+ const request = origin.request == null ? null : `from ${requestName(origin.request)}`;
207
+ return [flow, source, request].filter(one => one !== null).join(" · ");
216
208
  }
217
209
  /** v126: a task's origin as a fact: the flow, linked for those who may open it, and the outside event, linked to where
218
210
  * it lives, with that link's host always shown. */
@@ -228,6 +220,12 @@ function originParts(origin) {
228
220
  if (origin.source.url !== null)
229
221
  parts.push(" · ", { label: linkWords(origin.source.url), href: origin.source.url });
230
222
  }
223
+ if (origin.request != null) {
224
+ if (parts.length > 0)
225
+ parts.push(" · ");
226
+ const where = requestWords(origin.request);
227
+ parts.push(`${requestName(origin.request)}${where === "" ? "" : `: ${where}`}`);
228
+ }
231
229
  return parts;
232
230
  }
233
231
  export function approvalSheetHtml(input) {
@@ -303,7 +301,7 @@ export function approvalSheetHtml(input) {
303
301
  const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
304
302
  html `<div class="revision-card" data-revision-feedback>`,
305
303
  html `<p><a href="${input.revisionSourceHref}"${input.surface === "chat" ? html ` data-revision-source` : ""}>${input.surface === "chat" ? html `Original result: build #${input.revision.sourceRun} →` : html `build #${input.revision.sourceRun}`}</a></p>`,
306
- input.revision.comments.length === 0 ? "" : html `<ul>${input.revision.comments.map(one => html `<li>${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> · `}${one.note} <span class="meta">— ${one.author}</span></li>`)}</ul>`,
304
+ input.revision.comments.length === 0 ? "" : html `<ul>${input.revision.comments.map(one => html `<li>${revisionImageLink(one.image)}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> · `}${one.note} <span class="meta">— ${one.author}</span></li>`)}</ul>`,
307
305
  revisionLineageHtml(input.revision.lineage),
308
306
  html `</div>`,
309
307
  ]));
@@ -815,7 +813,9 @@ export function taskRecoveryHref(taskId, diagnosis) {
815
813
  return null;
816
814
  const task = taskHref(taskId);
817
815
  switch (diagnosis.action) {
816
+ // The task page only repeats that no builder is connected: lead to the command that connects one.
818
817
  case "start-worker":
818
+ return startBuilderHref(taskId);
819
819
  case "repair-dependency":
820
820
  return `${task}#run-status`;
821
821
  case "retry-task":
@@ -1179,6 +1179,9 @@ export function executionPlanHtml(document, compact = false) {
1179
1179
  html `</div>`,
1180
1180
  ]);
1181
1181
  }
1182
+ function revisionImageLink(image) {
1183
+ return image == null ? html `` : html `<a href="/r/${image.run}/evidence/${image.artifact}">Screenshot ${image.artifact} · result #${image.run}</a> · `;
1184
+ }
1182
1185
  /**
1183
1186
  * A revision's lineage and ACTUAL terms, in the same words on the task
1184
1187
  * page, the approval card, and chat (contract handoff task 2): which
@@ -1350,11 +1353,11 @@ export function taskBodyParts(data) {
1350
1353
  firstConnection
1351
1354
  ? html `<p>Toolroll is open, but this project has not been connected to a builder yet.</p><p>On the machine where the project lives, open that folder and run:</p>`
1352
1355
  : html `<p>Toolroll is open, but this project's builder stopped checking in. Reopen Toolroll on the machine where the project lives.</p><p>If you normally start it from a terminal, open the project folder and run:</p>`,
1353
- html `<code class="dispatch-recovery-command">toolroll up</code>`,
1356
+ html `<code class="dispatch-recovery-command">${START_BUILDER_COMMAND}</code>`,
1354
1357
  firstConnection
1355
1358
  ? html `<p class="meta">This is the normal start command: it opens the app, connects the project, and starts its builder. Keep Toolroll running; approved tasks begin automatically.</p>`
1356
1359
  : html `<p class="meta">This task resumes automatically when the builder reconnects. You do not need to file or approve it again.</p>`,
1357
- html `<p><a href="/system">See connection status →</a></p></div></details>`,
1360
+ html `<p><a href="${startBuilderHref(task.id)}">See connection status →</a></p></div></details>`,
1358
1361
  ]);
1359
1362
  }
1360
1363
  // Never a link to this page's own acts: Retry sits on the status card and below.
@@ -1601,7 +1604,7 @@ export function taskBodyParts(data) {
1601
1604
  : joinHtml([
1602
1605
  html `<div class="revision-card" data-revision-feedback>`,
1603
1606
  html `<p><strong>${data.revision.kind === "ci-repair" ? "CI repair" : data.revision.kind === "criterion-repair" ? "Criterion repair" : "Revision feedback"}</strong> <span class="meta">from <a href="/r/${data.revision.sourceRun}">build #${data.revision.sourceRun}</a></span></p>`,
1604
- ...data.revision.comments.map(one => html `<p class="row"><span class="meta">${one.author}</span> ${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> `}${one.note}</p>`),
1607
+ ...data.revision.comments.map(one => html `<p class="row"><span class="meta">${one.author}</span> ${revisionImageLink(one.image)}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> `}${one.note}</p>`),
1605
1608
  revisionLineageHtml(data.revision.lineage),
1606
1609
  html `</div>`,
1607
1610
  ], "\n");
@@ -2177,7 +2180,7 @@ export function taskBodyParts(data) {
2177
2180
  const feedback = data.revision;
2178
2181
  thread.push({ key: "revision", at: task.createdAt, kind: "reply", who: "person", author: feedback.comments[0]?.author ?? "You",
2179
2182
  title: feedback.kind === "ci-repair" ? "Asked for a CI repair" : feedback.kind === "criterion-repair" ? "Asked for a repair" : "Asked for changes",
2180
- text: feedback.comments.map(one => `${one.path === null ? "" : `${one.path}${one.line === null ? "" : `:${one.line}`} — `}${one.note.trim()}`).filter(one => one !== "").join("\n") || null,
2183
+ text: feedback.comments.map(one => `${one.image ? `Screenshot ${one.image.artifact} · result #${one.image.run} — ` : ""}${one.path === null ? "" : `${one.path}${one.line === null ? "" : `:${one.line}`} — `}${one.note.trim()}`).filter(one => one !== "").join("\n") || null,
2181
2184
  link: { label: `From build #${feedback.sourceRun}`, href: `/r/${feedback.sourceRun}` }, html: "", more: null });
2182
2185
  }
2183
2186
  for (const decision of data.decisions) {
@@ -43,6 +43,8 @@ export type RequestFacts = {
43
43
  };
44
44
  /** Passwordless is on and this is a signed-in browser: ceremony password fields render as nothing (html.ts reauth). */
45
45
  passwordless?: boolean;
46
+ /** The browser session's open project (display only: the sidebar's picker names it). */
47
+ openProject?: string | null;
46
48
  };
47
49
  /**
48
50
  * The request's own session facts, readable from anywhere below the
@@ -133,12 +133,15 @@ get("pages", "workbench", "/workbench", free);
133
133
  get("tasks", "board", "/board", { limited: "collection" });
134
134
  get("tasks", "review", "/review", { limited: "collection" });
135
135
  get("settings", "spend", "/spend", { ...free, ...anyOperator("An instance operator sees spend.", "/") });
136
+ get("settings", "settings.spend", "/settings/spend", { ...free, ...anyOperator("An instance operator sets spending limits.", "/settings") });
136
137
  get("pages", "ledger", "/ledger", { limited: "unscoped", ...free });
137
138
  get("pages", "activity", "/activity");
138
139
  get("tasks", "done", "/done", { limited: "collection" });
139
140
  get("settings", "mode", "/mode");
140
141
  get("people", "people.page", "/people", { limited: "unscoped", ...free });
141
142
  get("pages", "system", "/system");
143
+ // Where every "Connect a builder" leads: read-only, open to a project-limited person (the handler shows only a visible project).
144
+ get("pages", "start-builder", "/start-builder", { limited: "unscoped", ...free });
142
145
  get("tasks", "tasks", "/tasks", { limited: "collection", ...free }); // a redirect to /work now: never sent to the project opener first
143
146
  get("tasks", "queue", "/queue");
144
147
  get("pages", "peek", "/peek", free);
@@ -333,9 +336,14 @@ for (const one of ["policy", "approval", "request-limits", "sessions", "sign-in"
333
336
  post(one === "sessions" ? "people" : "settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`, { ...(["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "retention", "storage", "storage/clean", "storage/discard", "pull-requests", "checks", "backups", "data", "monitoring", "tools/connect", "tools/change"].includes(one) ? { scope: "step-up" } : {}), ...SETTINGS_ACT_ROLES[one] });
334
337
  }
335
338
  post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection", ...approver("An approver switches starter flows on.", "/settings/flows") });
339
+ post("settings", "settings.integrations-youiyoux", "/settings/integrations/youiyoux", { limited: "collection", ...approver("An approver turns youiyoux requests on or off.", "/settings/integrations") });
336
340
  const MODELS = approver("Sign in as an approver to change models.", "/settings/models");
337
- for (const one of ["check", "watch", "update", "agent"])
341
+ for (const one of ["check", "watch", "update"])
338
342
  post("settings", `settings.models-${one}`, `/settings/models/${one}`, MODELS);
343
+ // Changing which model a role uses: an instance operator's browser step-up (the handler proves the password, or the
344
+ // Passwordless session; model-choices.ts re-proves the session and standing inside its one transaction).
345
+ for (const one of ["agent", "apply"])
346
+ post("settings", `settings.models-${one}`, `/settings/models/${one}`, { scope: "step-up", ...operator("An instance operator changes models.", "/settings/models") });
339
347
  // Any other models action answers the models area's own refusal.
340
348
  post("settings", "settings.models-act", "^\\/settings\\/models\\/.*$", { sample: "/settings/models/unknown", ...MODELS });
341
349
  post("settings", "settings.knowledge-refresh", "/settings/knowledge/refresh", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to refresh project context.", "/settings/knowledge") });
@@ -2,7 +2,7 @@ import type { ServerRuntime } from './runtime.js';
2
2
  import { type RoleView } from "../models-ui.js";
3
3
  export declare function createSettingsHandlers(runtime: ServerRuntime): {
4
4
  registrations: import("./handler-registry.js").Registration[];
5
- modelsView: (canManage: boolean, csrf: string, now: Date, said: string | null, problem: string | null) => {
5
+ modelsView: (canManage: boolean, csrf: string, now: Date, said: string | null, problem: string | null, query?: URLSearchParams) => {
6
6
  runtimes: import("../model-catalog.js").RuntimeState[];
7
7
  watch: import("../model-catalog.js").WatchState;
8
8
  roles: RoleView[];
@@ -14,5 +14,62 @@ export declare function createSettingsHandlers(runtime: ServerRuntime): {
14
14
  words: string;
15
15
  } | null;
16
16
  fresh: import("../model-catalog.js").SeenModel[];
17
+ scope: {
18
+ value: string;
19
+ label: string;
20
+ digest: string;
21
+ options: {
22
+ value: string;
23
+ label: string;
24
+ }[];
25
+ };
26
+ inUse: string | null;
27
+ presets: {
28
+ name: "opus" | "sized";
29
+ label: string;
30
+ words: string;
31
+ inUse: boolean;
32
+ recommended: boolean;
33
+ }[];
34
+ newer: {
35
+ from: string;
36
+ to: string;
37
+ name: string;
38
+ roles: string;
39
+ }[];
40
+ preview: {
41
+ title: string;
42
+ rows: {
43
+ role: string;
44
+ before: string;
45
+ after: string;
46
+ notes: string[];
47
+ }[];
48
+ hidden: {
49
+ scope: string;
50
+ digest: string;
51
+ change: string;
52
+ };
53
+ } | null;
54
+ unresolved: string | null;
55
+ sizing: {
56
+ small: string;
57
+ routine: string;
58
+ large: string;
59
+ risky: string;
60
+ classifier: string;
61
+ };
62
+ routing: string;
63
+ recent: {
64
+ taskId: string;
65
+ runId: number;
66
+ role: string;
67
+ chosen: string;
68
+ ranOn: string;
69
+ }[];
70
+ waiting: {
71
+ taskId: string;
72
+ title: string;
73
+ }[];
17
74
  };
18
75
  };