toolroll 0.9.54 → 0.9.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +5 -4
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +15 -20
- package/dist/assignment-ui.d.ts +3 -2
- package/dist/assignment-ui.js +4 -11
- package/dist/backup-ui.d.ts +2 -1
- package/dist/backup-ui.js +16 -27
- package/dist/brand-mark.d.ts +3 -2
- package/dist/brand-mark.js +4 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-shell.d.ts +2 -1
- package/dist/browser-shell.js +9 -9
- package/dist/browser-workspace.d.ts +2 -3
- package/dist/browser-workspace.js +0 -3
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +26 -53
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +9 -3
- package/dist/coding-shipping-ui.d.ts +2 -1
- package/dist/coding-shipping-ui.js +6 -6
- package/dist/coding-ui.d.ts +3 -1
- package/dist/coding-ui.js +30 -29
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +6 -5
- package/dist/contracts/health.d.ts +1 -3
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/credentials-ui.d.ts +5 -4
- package/dist/credentials-ui.js +23 -24
- package/dist/demo-chat.d.ts +3 -3
- package/dist/demo-chat.js +29 -76
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +17 -42
- package/dist/envelope.js +10 -0
- package/dist/evidence-pack.d.ts +2 -1
- package/dist/evidence-pack.js +45 -49
- package/dist/export-ui.d.ts +7 -2
- package/dist/export-ui.js +4 -10
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-triggers.d.ts +1 -0
- package/dist/flow-triggers.js +16 -14
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +19 -30
- package/dist/flows.js +2 -1
- package/dist/guides.js +108 -3
- package/dist/html.d.ts +76 -0
- package/dist/html.js +173 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +19 -23
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +18 -20
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/lead-context.d.ts +11 -1
- package/dist/lead-context.js +6 -8
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +2 -1
- package/dist/limits-ui.js +7 -7
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mcp-oauth.js +35 -36
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.js +11 -7
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +15 -27
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +17 -32
- package/dist/recipe-ui.d.ts +8 -7
- package/dist/recipe-ui.js +39 -32
- package/dist/release-adapters.d.ts +60 -0
- package/dist/release-adapters.js +435 -0
- package/dist/release-cli.d.ts +24 -0
- package/dist/release-cli.js +181 -0
- package/dist/release-gate.d.ts +80 -0
- package/dist/release-gate.js +150 -0
- package/dist/release-links.d.ts +29 -0
- package/dist/release-links.js +102 -0
- package/dist/release.d.ts +331 -0
- package/dist/release.js +550 -0
- package/dist/reply-shape.d.ts +2 -1
- package/dist/reply-shape.js +5 -5
- package/dist/repository-context-ui.d.ts +2 -1
- package/dist/repository-context-ui.js +2 -7
- package/dist/request-budget-ui.d.ts +2 -1
- package/dist/request-budget-ui.js +16 -16
- package/dist/result-review.d.ts +2 -3
- package/dist/result-review.js +3 -9
- package/dist/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +69 -58
- package/dist/server/chat.d.ts +3 -6
- package/dist/server/chat.js +969 -1006
- package/dist/server/chrome.d.ts +291 -0
- package/dist/server/chrome.js +747 -0
- package/dist/server/console.css +2819 -0
- package/dist/server/flows.d.ts +2 -6
- package/dist/server/flows.js +840 -766
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +5 -2
- package/dist/server/handler-context.d.ts +1 -1
- package/dist/server/handler-context.js +1 -1
- package/dist/server/handler-registry.d.ts +8 -6
- package/dist/server/handler-registry.js +14 -2
- package/dist/server/http.d.ts +274 -0
- package/dist/server/http.js +239 -0
- package/dist/server/live.d.ts +34 -0
- package/dist/server/live.js +309 -0
- package/dist/server/pages.d.ts +2 -6
- package/dist/server/pages.js +808 -789
- package/dist/server/people-tokens.d.ts +1 -6
- package/dist/server/people-tokens.js +580 -572
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +37 -70
- package/dist/server/render-chat.d.ts +402 -0
- package/dist/server/render-chat.js +927 -0
- package/dist/server/render-pages.d.ts +535 -0
- package/dist/server/render-pages.js +1277 -0
- package/dist/server/render-people.d.ts +32 -0
- package/dist/server/render-people.js +121 -0
- package/dist/server/render-results.d.ts +744 -0
- package/dist/server/render-results.js +2237 -0
- package/dist/server/render-settings.d.ts +147 -0
- package/dist/server/render-settings.js +577 -0
- package/dist/server/render-tasks.d.ts +632 -0
- package/dist/server/render-tasks.js +2345 -0
- package/dist/server/request-context.d.ts +46 -0
- package/dist/server/request-context.js +17 -0
- package/dist/server/route-table.d.ts +25 -3
- package/dist/server/route-table.js +163 -75
- package/dist/server/runtime.d.ts +15 -7
- package/dist/server/runtime.js +7 -1
- package/dist/server/session.d.ts +138 -0
- package/dist/server/session.js +94 -0
- package/dist/server/settings.d.ts +1 -5
- package/dist/server/settings.js +2181 -2117
- package/dist/server/task-views.d.ts +11 -4
- package/dist/server/task-views.js +26 -22
- package/dist/server/tasks.d.ts +9 -9
- package/dist/server/tasks.js +1486 -1418
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/skills-ui.d.ts +4 -3
- package/dist/skills-ui.js +14 -17
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -1
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +17 -42
- package/dist/spend-ui.d.ts +2 -1
- package/dist/spend-ui.js +9 -32
- package/dist/sso-ui.d.ts +3 -2
- package/dist/sso-ui.js +26 -27
- package/dist/storage-ui.d.ts +2 -1
- package/dist/storage-ui.js +14 -33
- package/dist/surface.js +14 -3
- package/dist/task-composer.d.ts +2 -1
- package/dist/task-composer.js +7 -7
- package/dist/task-status.d.ts +4 -3
- package/dist/task-status.js +15 -16
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teammates-ui.d.ts +5 -4
- package/dist/teammates-ui.js +61 -90
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +20 -31
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/tools-ui.d.ts +5 -2
- package/dist/tools-ui.js +31 -31
- package/dist/when-html.d.ts +11 -3
- package/dist/when-html.js +5 -6
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -1
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
package/dist/demo-chat.js
CHANGED
|
@@ -4,48 +4,26 @@
|
|
|
4
4
|
* running build's progress live; without it the page still works by reload.
|
|
5
5
|
*/
|
|
6
6
|
import { START_COMMAND } from "./first-run.js";
|
|
7
|
+
import { html, postForm } from "./html.js";
|
|
7
8
|
import { headlineOf } from "./task-status.js";
|
|
8
9
|
const SUGGESTIONS = ["Fix the flaky refund test", "Rewrite the empty Payouts page", "Put the new invoice view behind a flag"];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function hint(csrf) {
|
|
12
|
-
return `<section class="demo-hint" aria-label="Get started">` +
|
|
13
|
-
`<p class="demo-hint-title">Ask for something, like “fix the flaky refund test”</p>` +
|
|
14
|
-
`<form method="post" action="/chat/demo/ask" class="demo-suggestions">${hidden(csrf)}` +
|
|
15
|
-
SUGGESTIONS.map(one => `<button type="submit" name="message" value="${esc(one)}">${esc(one)}</button>`).join("") +
|
|
16
|
-
`</form></section>`;
|
|
10
|
+
function hint() {
|
|
11
|
+
return html `<section class="demo-hint" aria-label="Get started"><p class="demo-hint-title">Ask for something, like “fix the flaky refund test”</p>${postForm("/chat/demo/ask", SUGGESTIONS.map(one => html `<button type="submit" name="message" value="${one}">${one}</button>`), { attrs: { class: "demo-suggestions" } })}</section>`;
|
|
17
12
|
}
|
|
18
|
-
function planCard(exchange
|
|
13
|
+
function planCard(exchange) {
|
|
19
14
|
const plan = exchange.plan;
|
|
20
15
|
const goal = exchange.note === null ? plan.goal : `${plan.goal} Also: ${exchange.note}`;
|
|
21
16
|
const actions = exchange.state === "proposed"
|
|
22
|
-
? `<div class="demo-actions">`
|
|
23
|
-
`<form method="post" action="/chat/demo/${exchange.id}/approve" class="approve-form">${hidden(csrf)}<button type="submit">Approve</button></form>` +
|
|
24
|
-
`<details class="demo-more"><summary>Change it</summary>` +
|
|
25
|
-
`<form method="post" action="/chat/demo/${exchange.id}/change">${hidden(csrf)}` +
|
|
26
|
-
`<label for="demo-change-${exchange.id}">What should change?</label>` +
|
|
27
|
-
`<textarea id="demo-change-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea>` +
|
|
28
|
-
`<button type="submit">Update plan</button></form></details></div>`
|
|
17
|
+
? html `<div class="demo-actions">${postForm(`/chat/demo/${exchange.id}/approve`, html `<button type="submit">Approve</button>`, { attrs: { class: "approve-form" } })}<details class="demo-more"><summary>Change it</summary>${postForm(`/chat/demo/${exchange.id}/change`, html `<label for="demo-change-${exchange.id}">What should change?</label><textarea id="demo-change-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea><button type="submit">Update plan</button>`)}</details></div>`
|
|
29
18
|
: exchange.state === "replaced"
|
|
30
|
-
? `<p class="meta">Replaced by the updated plan below.</p>`
|
|
31
|
-
: `<p class="meta">Approved.</p>`;
|
|
32
|
-
return `<section class="card demo-plan" aria-label="Plan">`
|
|
33
|
-
`<p class="demo-kicker">Plan · ${esc(plan.project)}</p>` +
|
|
34
|
-
`<h2>${esc(plan.title)}</h2>` +
|
|
35
|
-
`<p>${esc(goal)}</p>` +
|
|
36
|
-
`<h3>Boundaries</h3><ul>${plan.boundaries.map(one => `<li>${esc(one)}</li>`).join("")}</ul>` +
|
|
37
|
-
`<h3>Checks</h3><ul>${plan.checks.map(one => `<li>${esc(one)}</li>`).join("")}</ul>` +
|
|
38
|
-
actions + `</section>`;
|
|
19
|
+
? html `<p class="meta">Replaced by the updated plan below.</p>`
|
|
20
|
+
: html `<p class="meta">Approved.</p>`;
|
|
21
|
+
return html `<section class="card demo-plan" aria-label="Plan"><p class="demo-kicker">Plan · ${plan.project}</p><h2>${plan.title}</h2><p>${goal}</p><h3>Boundaries</h3><ul>${plan.boundaries.map(one => html `<li>${one}</li>`)}</ul><h3>Checks</h3><ul>${plan.checks.map(one => html `<li>${one}</li>`)}</ul>${actions}</section>`;
|
|
39
22
|
}
|
|
40
23
|
function buildCard(exchange) {
|
|
41
24
|
const stages = [["planning", "Planning"], ["building", "Building"], ["checking", "Running checks"]];
|
|
42
25
|
const at = stages.findIndex(([key]) => key === exchange.stage);
|
|
43
|
-
return `<section class="card demo-build" id="demo-${exchange.id}-work" aria-label="Build" aria-busy="true">`
|
|
44
|
-
`<p class="demo-kicker">${esc(exchange.plan.project)}</p>` +
|
|
45
|
-
`<h2>${esc(stages[Math.max(0, at)][1])}…</h2>` +
|
|
46
|
-
`<ol class="demo-steps">${stages.map(([, label], index) => `<li class="${index < at ? "done" : index === at ? "now" : ""}"${index === at ? ` aria-current="step"` : ""}>${esc(label)}</li>`).join("")}</ol>` +
|
|
47
|
-
(exchange.progress === null ? "" : `<p class="meta demo-progress">${esc(exchange.progress)}</p>`) +
|
|
48
|
-
`</section>`;
|
|
26
|
+
return html `<section class="card demo-build" id="demo-${exchange.id}-work" aria-label="Build" aria-busy="true"><p class="demo-kicker">${exchange.plan.project}</p><h2>${stages[Math.max(0, at)][1]}…</h2><ol class="demo-steps">${stages.map(([, label], index) => html `<li class="${index < at ? "done" : index === at ? "now" : ""}"${index === at ? html ` aria-current="step"` : ""}>${label}</li>`)}</ol>${exchange.progress === null ? "" : html `<p class="meta demo-progress">${exchange.progress}</p>`}</section>`;
|
|
49
27
|
}
|
|
50
28
|
function diffHtml(diff) {
|
|
51
29
|
return diff.replace(/\n$/, "").split("\n").map(line => {
|
|
@@ -53,71 +31,46 @@ function diffHtml(diff) {
|
|
|
53
31
|
: line.startsWith("+++") || line.startsWith("---") ? "meta"
|
|
54
32
|
: line.startsWith("@@") ? "hunk"
|
|
55
33
|
: line.startsWith("+") ? "add" : line.startsWith("-") ? "del" : "ctx";
|
|
56
|
-
return `<span class="demo-diff-${kind}">${
|
|
57
|
-
})
|
|
34
|
+
return html `<span class="demo-diff-${kind}">${line || " "}</span>`;
|
|
35
|
+
});
|
|
58
36
|
}
|
|
59
|
-
function resultCard(exchange,
|
|
37
|
+
function resultCard(exchange, result) {
|
|
60
38
|
if (result === null)
|
|
61
|
-
return `<section class="card demo-result"><p class="meta">This result's saved evidence is unavailable.</p></section>`;
|
|
39
|
+
return html `<section class="card demo-result"><p class="meta">This result's saved evidence is unavailable.</p></section>`;
|
|
62
40
|
const passed = result.checks.status === "passed";
|
|
63
41
|
// The shared headline (task-status.ts): the same words as every real task.
|
|
64
42
|
const headline = headlineOf({ stage: exchange.state === "complete" ? "complete" : exchange.state === "sent-back" ? "building" : "finished",
|
|
65
43
|
checks: { status: passed ? "passed" : "failed", exitCode: null, head: null } });
|
|
66
|
-
const status = `<span class="badge${headline === "Complete" ? " demo-complete" : headline === "Ready for review" ? " demo-ready" : ""}" data-headline="${headline}">${headline}</span>`;
|
|
67
|
-
const summary = `${result.files} file${result.files === 1 ? "" : "s"} changed · +${result.additions} −${result.deletions} ·
|
|
68
|
-
`<span class="${passed ? "demo-pass" : "demo-fail"}">${esc(result.checks.detail)}</span>`;
|
|
44
|
+
const status = html `<span class="badge${headline === "Complete" ? " demo-complete" : headline === "Ready for review" ? " demo-ready" : ""}" data-headline="${headline}">${headline}</span>`;
|
|
45
|
+
const summary = html `${result.files} file${result.files === 1 ? "" : "s"} changed · +${result.additions} −${result.deletions} · <span class="${passed ? "demo-pass" : "demo-fail"}">${result.checks.detail}</span>`;
|
|
69
46
|
const actions = exchange.state === "ready"
|
|
70
|
-
? `<div class="demo-actions">`
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`<form method="post" action="/chat/demo/${exchange.id}/revise">${hidden(csrf)}` +
|
|
74
|
-
`<label for="demo-revise-${exchange.id}">What should change?</label>` +
|
|
75
|
-
`<textarea id="demo-revise-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea>` +
|
|
76
|
-
`<button type="submit">Send back</button></form></details></div>`
|
|
77
|
-
: `<p class="meta"><a href="${esc(result.taskHref)}">Open in Tasks</a></p>`;
|
|
78
|
-
return `<section class="card demo-result" id="demo-${exchange.id}-work" aria-label="Result">` +
|
|
79
|
-
`<p class="demo-state">${status}<span class="meta">${esc(exchange.plan.project)}</span></p>` +
|
|
80
|
-
`<h2>${esc(exchange.plan.title)}</h2>` +
|
|
81
|
-
`<p>${esc(exchange.plan.conclusion)}</p>` +
|
|
82
|
-
`<p class="meta">${summary}</p>` +
|
|
83
|
-
actions +
|
|
84
|
-
(result.diff === null ? "" : `<details class="demo-evidence"${exchange.state === "ready" ? " open" : ""}><summary>Changes</summary><pre class="demo-diff">${diffHtml(result.diff)}</pre></details>`) +
|
|
85
|
-
(result.checkLog === null ? "" : `<details class="demo-evidence"><summary>Check log</summary><pre class="demo-log">${esc(result.checkLog)}</pre></details>`) +
|
|
86
|
-
(result.screenshot === null ? "" : `<details class="demo-evidence"><summary>Screenshot</summary><figure><img src="${esc(result.screenshot.href)}" alt="${esc(result.screenshot.caption)}" width="960" height="600"><figcaption class="meta">${esc(result.screenshot.caption)}</figcaption></figure></details>`) +
|
|
87
|
-
`</section>`;
|
|
47
|
+
? html `<div class="demo-actions">${postForm(`/chat/demo/${exchange.id}/complete`, html `<button type="submit" class="demo-primary">Complete</button>`)}<details class="demo-more"><summary>Request changes</summary>${postForm(`/chat/demo/${exchange.id}/revise`, html `<label for="demo-revise-${exchange.id}">What should change?</label><textarea id="demo-revise-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea><button type="submit">Send back</button>`)}</details></div>`
|
|
48
|
+
: html `<p class="meta"><a href="${result.taskHref}">Open in Tasks</a></p>`;
|
|
49
|
+
return html `<section class="card demo-result" id="demo-${exchange.id}-work" aria-label="Result"><p class="demo-state">${status}<span class="meta">${exchange.plan.project}</span></p><h2>${exchange.plan.title}</h2><p>${exchange.plan.conclusion}</p><p class="meta">${summary}</p>${actions}${result.diff === null ? "" : html `<details class="demo-evidence"${exchange.state === "ready" ? html ` open` : ""}><summary>Changes</summary><pre class="demo-diff">${diffHtml(result.diff)}</pre></details>`}${result.checkLog === null ? "" : html `<details class="demo-evidence"><summary>Check log</summary><pre class="demo-log">${result.checkLog}</pre></details>`}${result.screenshot === null ? "" : html `<details class="demo-evidence"><summary>Screenshot</summary><figure><img src="${result.screenshot.href}" alt="${result.screenshot.caption}" width="960" height="600"><figcaption class="meta">${result.screenshot.caption}</figcaption></figure></details>`}</section>`;
|
|
88
50
|
}
|
|
89
51
|
/** The conversation itself; the live script swaps this region in place. */
|
|
90
|
-
export function demoThreadHtml(exchanges,
|
|
52
|
+
export function demoThreadHtml(exchanges, resultOf) {
|
|
91
53
|
if (exchanges.length === 0)
|
|
92
|
-
return hint(
|
|
93
|
-
return exchanges.map(exchange => {
|
|
54
|
+
return hint();
|
|
55
|
+
return html `${exchanges.map(exchange => {
|
|
94
56
|
const parts = [
|
|
95
|
-
`<div class="demo-said demo-you"><p>${
|
|
96
|
-
`<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>${
|
|
97
|
-
planCard(exchange
|
|
57
|
+
html `<div class="demo-said demo-you"><p>${exchange.asked}</p></div>`,
|
|
58
|
+
html `<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>${exchange.reply}</p></div>`,
|
|
59
|
+
planCard(exchange),
|
|
98
60
|
];
|
|
99
61
|
if (exchange.state === "working")
|
|
100
62
|
parts.push(buildCard(exchange));
|
|
101
63
|
if (exchange.state === "ready" || exchange.state === "complete" || exchange.state === "sent-back")
|
|
102
|
-
parts.push(resultCard(exchange,
|
|
64
|
+
parts.push(resultCard(exchange, resultOf(exchange)));
|
|
103
65
|
if (exchange.state === "complete") {
|
|
104
|
-
parts.push(`<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>Done. That's the whole loop: ask, approve, Ready, Complete. Ask for something else whenever you like.</p></div>`, `<section class="card demo-handoff" data-demo-handoff aria-label="Your own project"><h2>Now try it on your own project</h2>`
|
|
105
|
-
`<p>In your repository's folder, run:</p><pre class="demo-command"><code>${esc(START_COMMAND)}</code></pre>` +
|
|
106
|
-
`<p class="meta">It opens in your browser, already signed in.</p></section>`);
|
|
66
|
+
parts.push(html `<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>Done. That's the whole loop: ask, approve, Ready, Complete. Ask for something else whenever you like.</p></div>`, html `<section class="card demo-handoff" data-demo-handoff aria-label="Your own project"><h2>Now try it on your own project</h2><p>In your repository's folder, run:</p><pre class="demo-command"><code>${START_COMMAND}</code></pre><p class="meta">It opens in your browser, already signed in.</p></section>`);
|
|
107
67
|
}
|
|
108
|
-
return `<article class="demo-turn" id="demo-${exchange.id}">${parts
|
|
109
|
-
})
|
|
68
|
+
return html `<article class="demo-turn" id="demo-${exchange.id}">${parts}</article>`;
|
|
69
|
+
})}`;
|
|
110
70
|
}
|
|
111
71
|
export function demoChatHtml(input) {
|
|
112
72
|
const working = input.exchanges.some(one => one.state === "working");
|
|
113
|
-
return `<div class="demo-chat">`
|
|
114
|
-
(input.problem === null ? "" : `<p class="problem" role="alert">${esc(input.problem)}</p>`) +
|
|
115
|
-
`<div id="demo-thread" class="demo-thread" aria-live="polite" data-version="${input.version}" data-working="${working ? "1" : "0"}">` +
|
|
116
|
-
demoThreadHtml(input.exchanges, input.csrf, input.resultOf) + `</div>` +
|
|
117
|
-
`<form method="post" action="/chat/demo/ask" class="demo-composer">${hidden(input.csrf)}` +
|
|
118
|
-
`<label for="demo-message" class="so-sr-only">Message the lead</label>` +
|
|
119
|
-
`<textarea id="demo-message" name="message" rows="2" maxlength="500" placeholder="Ask the lead for a change" required></textarea>` +
|
|
120
|
-
`<button type="submit" class="demo-primary">Send</button></form></div>`;
|
|
73
|
+
return html `<div class="demo-chat">${input.problem === null ? "" : html `<p class="problem" role="alert">${input.problem}</p>`}<div id="demo-thread" class="demo-thread" aria-live="polite" data-version="${input.version}" data-working="${working ? "1" : "0"}">${demoThreadHtml(input.exchanges, input.resultOf)}</div>${postForm("/chat/demo/ask", html `<label for="demo-message" class="so-sr-only">Message the lead</label><textarea id="demo-message" name="message" rows="2" maxlength="500" placeholder="Ask the lead for a change" required></textarea><button type="submit" class="demo-primary">Send</button>`, { attrs: { class: "demo-composer" } })}</div>`;
|
|
121
74
|
}
|
|
122
75
|
/**
|
|
123
76
|
* Keeps a running build live: polls while one is working and swaps the
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Store } from "./store.js";
|
|
2
|
-
|
|
2
|
+
import { type Html } from "./html.js";
|
|
3
|
+
export declare function discordSettingsHtml(store: Store, dir: string, options?: {
|
|
3
4
|
code?: string;
|
|
4
5
|
problem?: string;
|
|
5
6
|
now?: Date;
|
|
6
7
|
who?: string;
|
|
7
|
-
}):
|
|
8
|
+
}): Html;
|
package/dist/discord-settings.js
CHANGED
|
@@ -1,66 +1,41 @@
|
|
|
1
1
|
import { loadPrimary } from "./webhooks.js";
|
|
2
|
+
import { html, postForm } from "./html.js";
|
|
2
3
|
import { loadDiscordCredentials } from "./discord-api.js";
|
|
3
4
|
import { ChatState } from "./chat-delivery-state.js";
|
|
4
|
-
|
|
5
|
-
.replaceAll("&", "&")
|
|
6
|
-
.replaceAll("<", "<")
|
|
7
|
-
.replaceAll(">", ">")
|
|
8
|
-
.replaceAll('"', """)
|
|
9
|
-
.replaceAll("'", "'");
|
|
10
|
-
export function discordSettingsHtml(store, dir, csrf, options = {}) {
|
|
5
|
+
export function discordSettingsHtml(store, dir, options = {}) {
|
|
11
6
|
const credentials = loadDiscordCredentials(dir), state = new ChatState(store, "discord"), bindings = credentials ? state.bindings(credentials.installation).filter(one => state.live(one)) : [], binding = options.who === undefined ? null : bindings.find(one => one.approver === options.who) ?? null, others = bindings.length - (binding === null ? 0 : 1);
|
|
12
7
|
const runtime = credentials ? state.runtime(credentials.installation) : null;
|
|
13
8
|
const now = options.now ?? new Date(), live = runtime &&
|
|
14
9
|
runtime.leaseUntil !== null &&
|
|
15
10
|
runtime.leaseUntil > now.toISOString() &&
|
|
16
11
|
runtime.connected;
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
let content = "";
|
|
12
|
+
const password = html `<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>`;
|
|
13
|
+
const post = (action, content) => postForm(`/settings/discord/${action}`, content, { attrs: { class: "card" } });
|
|
14
|
+
const parts = [];
|
|
21
15
|
if (!credentials) {
|
|
22
|
-
|
|
23
|
-
"<p>Manage projects and review results in a private Discord conversation.</p>" +
|
|
24
|
-
'<details open><summary>Create your Discord app</summary><ol><li><a href="https://discord.com/developers/applications" target="_blank" rel="noopener noreferrer">Create an application</a>, then copy its token from the Bot page.</li><li>Keep the Interactions Endpoint URL empty and privileged intents off.</li><li>Under Installation, enable Guild Install with the bot scope and no server permissions. Use its install link to add it to your server, then open a direct message to the bot.</li></ol></details>' +
|
|
25
|
-
post("connect", '<label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" required></label>' +
|
|
26
|
-
password +
|
|
27
|
-
'<p class="meta">The token stays on this installation. Next, pair your own Discord account.</p><button type="submit">Connect Discord</button>');
|
|
16
|
+
parts.push(html `<p>Manage projects and review results in a private Discord conversation.</p>`, html `<details open><summary>Create your Discord app</summary><ol><li><a href="https://discord.com/developers/applications" target="_blank" rel="noopener noreferrer">Create an application</a>, then copy its token from the Bot page.</li><li>Keep the Interactions Endpoint URL empty and privileged intents off.</li><li>Under Installation, enable Guild Install with the bot scope and no server permissions. Use its install link to add it to your server, then open a direct message to the bot.</li></ol></details>`, post("connect", html `<label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" required></label>${password}<p class="meta">The token stays on this installation. Next, pair your own Discord account.</p><button type="submit">Connect Discord</button>`));
|
|
28
17
|
}
|
|
29
18
|
else {
|
|
30
|
-
|
|
19
|
+
parts.push(html `<p><strong>${credentials.workspace}</strong> · ${live ? "Connected" : "Waiting for connection"}</p>`);
|
|
31
20
|
if (runtime?.problem)
|
|
32
|
-
|
|
21
|
+
parts.push(html `<p role="status">${String(runtime.problem)}</p>`);
|
|
33
22
|
if (!live && !runtime?.problem)
|
|
34
|
-
|
|
35
|
-
"<p>Keep the Toolroll worker running to receive Discord messages.</p>";
|
|
23
|
+
parts.push(html `<p>Keep the Toolroll worker running to receive Discord messages.</p>`);
|
|
36
24
|
if (options.code) {
|
|
37
|
-
|
|
38
|
-
"<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll Discord app. It expires in 10 minutes.</p>" +
|
|
39
|
-
`<label>Pairing message<input type="text" readonly value="pair ${escape(options.code)}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>` +
|
|
40
|
-
'<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/discord">Check connection</a>';
|
|
25
|
+
parts.push(html `<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll Discord app. It expires in 10 minutes.</p>`, html `<label>Pairing message<input type="text" readonly value="pair ${options.code}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>`, html `<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/discord">Check connection</a>`);
|
|
41
26
|
}
|
|
42
27
|
else if (binding && state.live(binding)) {
|
|
43
28
|
const { pending, dropped: failed } = state.partCounts(binding.id);
|
|
44
|
-
|
|
45
|
-
`<p>
|
|
46
|
-
|
|
47
|
-
'<p><a href="/chat">Open saved chat</a></p>' +
|
|
48
|
-
(loadPrimary(process.env, dir) === "discord"
|
|
49
|
-
? "<p>Task updates are sent here.</p>"
|
|
50
|
-
: post("alerts", '<button type="submit">Send task updates here</button>'));
|
|
29
|
+
parts.push(html `<p>Your Discord account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>`, html `<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>`, html `<p><a href="/chat">Open saved chat</a></p>`, loadPrimary(process.env, dir) === "discord"
|
|
30
|
+
? html `<p>Task updates are sent here.</p>`
|
|
31
|
+
: post("alerts", html `<button type="submit">Send task updates here</button>`));
|
|
51
32
|
}
|
|
52
33
|
else {
|
|
53
|
-
|
|
54
|
-
(others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : "") + "</p>" +
|
|
55
|
-
password +
|
|
56
|
-
'<button type="submit">Create pairing code</button>');
|
|
34
|
+
parts.push(post("pair", html `<h2>Pair your account</h2><p>Your paired Discord account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : ""}</p>${password}<button type="submit">Create pairing code</button>`));
|
|
57
35
|
}
|
|
58
36
|
if (binding && state.live(binding) && !options.code)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"<details><summary>Connection settings</summary>" +
|
|
62
|
-
post("disconnect", password + '<button type="submit">Disconnect Discord</button>') +
|
|
63
|
-
"</details>";
|
|
37
|
+
parts.push(post("unpair", html `${password}<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>`));
|
|
38
|
+
parts.push(html `<details><summary>Connection settings</summary>${post("disconnect", html `${password}<button type="submit">Disconnect Discord</button>`)}</details>`);
|
|
64
39
|
}
|
|
65
|
-
return `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Discord</h1>${options.problem ? `<p class="problem" role="alert">${
|
|
40
|
+
return html `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Discord</h1>${options.problem ? html `<p class="problem" role="alert">${options.problem}</p>` : ""}${parts}<details><summary>Access and setup</summary><p>Private messages and replies use your Toolroll permissions. Shared channels are not supported. Secure review links use this installation’s configured HTTPS console address.</p><p>The worker connects out to Discord. It does not need a public webhook or a Tailscale account.</p></details></section>`;
|
|
66
41
|
}
|
package/dist/envelope.js
CHANGED
|
@@ -71,6 +71,16 @@ export const DOCUMENTED_REASONS = [
|
|
|
71
71
|
"repair-attempts-spent", // the chain's signed attempt cap is spent
|
|
72
72
|
"repair-no-progress", // two consecutive repair attempts failed to shrink the unresolved set
|
|
73
73
|
"repair-refused-integrity", // a refutation named an altered term, not a gap — no automatic repair
|
|
74
|
+
"ambiguous", // a value could mean two things (a lease id or a branch) — refused, never guessed
|
|
75
|
+
"drift", // the branch, pull request or result moved from the gated commit
|
|
76
|
+
"check-failed", // the release check failed; it stays visible and nothing reruns it
|
|
77
|
+
"ci-failed", // a required pull-request check failed or is missing — nothing merged
|
|
78
|
+
"base-moved", // main gained a commit the release branch lacks — nothing merged
|
|
79
|
+
"tree-mismatch", // main's merge commit has a different tree than the gated commit — nothing tagged
|
|
80
|
+
"conflict", // a task or tag exists for a different commit
|
|
81
|
+
"mixed-runtime", // the console, worker and CLI names don't run one build
|
|
82
|
+
"gate-held", // a deployment stopped before its swap and left new work paused — `release --release-gate <id>`
|
|
83
|
+
"after-swap", // the deployment got past stopping the service, so its pause is its own recovery's to lift
|
|
74
84
|
];
|
|
75
85
|
let captured = null;
|
|
76
86
|
let captureHook = null;
|
package/dist/evidence-pack.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type Filer } from "./approval-policy.js";
|
|
|
2
2
|
import { type AssignmentAccess, type AssignmentSnapshot } from "./assignment.js";
|
|
3
3
|
import type { SealedLedgerEntry, Store } from "./store.js";
|
|
4
4
|
import type { LedgerChainReport } from "./ledger-chain.js";
|
|
5
|
+
import { type Html } from "./html.js";
|
|
5
6
|
export declare const EVIDENCE_PACK_FORMAT = "standing-orders/evidence-pack/v1";
|
|
6
7
|
/** How to recheck one entry's seal, for whoever reads the JSON. */
|
|
7
8
|
export declare const SEAL_RECIPE = "hash = sha256(prev + \"\\n\" + JSON.stringify([id, at, actor, repo, taskId, runId, action, outcome, source, detail]))";
|
|
@@ -179,7 +180,7 @@ export declare function ledgerExportChunks(store: Store, range: {
|
|
|
179
180
|
}, access: AssignmentAccess, who: string, now: Date, evidenceRoot: string): Generator<string, void, undefined>;
|
|
180
181
|
export declare const EVIDENCE_PACK_CSS: string;
|
|
181
182
|
/** The pack as a page: the same facts, in the order an auditor reads them. No script. */
|
|
182
|
-
export declare function evidencePackHtml(pack: EvidencePack):
|
|
183
|
+
export declare function evidencePackHtml(pack: EvidencePack): Html;
|
|
183
184
|
/** The page as a file on its own (the CLI's --html): the same facts with just enough style to print. */
|
|
184
185
|
export declare function standaloneEvidenceHtml(pack: EvidencePack): string;
|
|
185
186
|
export {};
|
package/dist/evidence-pack.js
CHANGED
|
@@ -15,6 +15,7 @@ import { rulesWords } from "./approval-policy.js";
|
|
|
15
15
|
import { assignmentOf } from "./assignment.js";
|
|
16
16
|
import { projectName } from "./project.js";
|
|
17
17
|
import { changedFilesOf } from "./result-completion.js";
|
|
18
|
+
import { html, htmlString, joinHtml, styleElement } from "./html.js";
|
|
18
19
|
export const EVIDENCE_PACK_FORMAT = "standing-orders/evidence-pack/v1";
|
|
19
20
|
/** How to recheck one entry's seal, for whoever reads the JSON. */
|
|
20
21
|
export const SEAL_RECIPE = 'hash = sha256(prev + "\\n" + JSON.stringify([id, at, actor, repo, taskId, runId, action, outcome, source, detail]))';
|
|
@@ -173,13 +174,12 @@ export function* ledgerExportChunks(store, range, scope, access, who, now, evide
|
|
|
173
174
|
yield `],"truncated":${JSON.stringify({ entries: moreEntries, packs: morePacks })}}`;
|
|
174
175
|
}
|
|
175
176
|
// ---- The printable page -----------------------------------------------------
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const short = (hash) => hash === null ? "—" : `<code title="${e(hash)}">${e(hash.slice(0, 12))}</code>`;
|
|
177
|
+
const when = (at) => at === null ? html `—` : html `${at.slice(0, 16).replace("T", " ")} UTC`;
|
|
178
|
+
const short = (hash) => hash === null ? html `—` : html `<code title="${hash}">${hash.slice(0, 12)}</code>`;
|
|
179
179
|
const usd = (value) => value === null ? "—" : `$${value.toFixed(value < 1 ? 4 : 2)}`;
|
|
180
180
|
/** Only a web address links; anything else (a stored value that isn't one) is shown as text. */
|
|
181
|
-
const link = (url) => /^https:\/\/[^\s"'<>]+$/.test(url) ? `<a href="${
|
|
182
|
-
const filer = (who) => who === null || who.name === null ?
|
|
181
|
+
const link = (url) => /^https:\/\/[^\s"'<>]+$/.test(url) ? html `<a href="${url}">${url}</a>` : html `${url}`;
|
|
182
|
+
const filer = (who) => who === null || who.name === null ? html `automation` : who.kind === "person" ? html `${who.name}` : html `${who.name} (${who.kind})`;
|
|
183
183
|
export const EVIDENCE_PACK_CSS = `.evidence-pack{max-width:960px;min-width:0}.evidence-pack h1{margin:0}.evidence-pack h2{font-size:.9375rem;margin:24px 0 8px}.evidence-pack h3{font-size:.875rem;margin:16px 0 6px}` +
|
|
184
184
|
`.evidence-head{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:baseline}.evidence-pack dl{display:grid;grid-template-columns:minmax(120px,180px) minmax(0,1fr);gap:4px 16px;margin:0}` +
|
|
185
185
|
`.evidence-pack dt{color:var(--muted-foreground);font-size:.8125rem}.evidence-pack dd{margin:0;min-width:0;overflow-wrap:anywhere}.evidence-pack table{width:100%;border-collapse:collapse;font-size:.8125rem}` +
|
|
@@ -193,66 +193,62 @@ export const EVIDENCE_PACK_CSS = `.evidence-pack{max-width:960px;min-width:0}.ev
|
|
|
193
193
|
export function evidencePackHtml(pack) {
|
|
194
194
|
const task = pack.task;
|
|
195
195
|
const chain = pack.ledger.chain.ok
|
|
196
|
-
? `<p class="evidence-chain ok" data-evidence-chain="ok"><strong>Ledger chain verified</strong> · ${pack.ledger.chain.entries} entries · head ${short(pack.ledger.chain.head)}${pack.ledger.chain.checkedAt === null ? "" : ` · checked in full ${when(pack.ledger.chain.checkedAt)}`}${pack.ledger.checkpoint === null ? "" : ` · last checkpoint at entry #${pack.ledger.checkpoint.through}`}</p>`
|
|
197
|
-
: `<p class="evidence-chain problem" role="alert" data-evidence-chain="broken"><strong>Ledger chain broken</strong> · ${
|
|
196
|
+
? html `<p class="evidence-chain ok" data-evidence-chain="ok"><strong>Ledger chain verified</strong> · ${pack.ledger.chain.entries} entries · head ${short(pack.ledger.chain.head)}${pack.ledger.chain.checkedAt === null ? "" : html ` · checked in full ${when(pack.ledger.chain.checkedAt)}`}${pack.ledger.checkpoint === null ? "" : html ` · last checkpoint at entry #${pack.ledger.checkpoint.through}`}</p>`
|
|
197
|
+
: html `<p class="evidence-chain problem" role="alert" data-evidence-chain="broken"><strong>Ledger chain broken</strong> · ${pack.ledger.chain.problem}</p>`;
|
|
198
198
|
const versions = pack.versions.map((version, index) => {
|
|
199
199
|
const scope = version.scope;
|
|
200
200
|
const approved = scope?.approved ?? null;
|
|
201
201
|
const approvals = scope === null ? [] : scope.approvals.filter(one => one.digest === (approved?.digest ?? scope.digest));
|
|
202
202
|
const rows = [
|
|
203
|
-
["Filed", `${when(version.filedAt)} by ${filer(version.filedBy)}`],
|
|
204
|
-
...(scope === null ? [["Scope",
|
|
205
|
-
["Goal",
|
|
206
|
-
...(scope.touches.length ? [["Touches", scope.touches
|
|
207
|
-
...(scope.acceptance.length ? [["Acceptance", `<ol class="criteria">${scope.acceptance.map(one => `<li>${
|
|
208
|
-
...(scope.writtenBy.length ? [["Scope written by", [...new Set(scope.writtenBy.map(one => one.author))]
|
|
209
|
-
["Approved", approved === null ?
|
|
210
|
-
...(approvals.length > 1 ? [["Approvals", approvals.map(one => `${
|
|
203
|
+
["Filed", html `${when(version.filedAt)} by ${filer(version.filedBy)}`],
|
|
204
|
+
...(scope === null ? [["Scope", html `None written`]] : [
|
|
205
|
+
["Goal", html `${scope.goal}`],
|
|
206
|
+
...(scope.touches.length ? [["Touches", joinHtml(scope.touches, ", ")]] : []),
|
|
207
|
+
...(scope.acceptance.length ? [["Acceptance", html `<ol class="criteria">${scope.acceptance.map(one => html `<li>${one.statement}</li>`)}</ol>`]] : []),
|
|
208
|
+
...(scope.writtenBy.length ? [["Scope written by", joinHtml([...new Set(scope.writtenBy.map(one => one.author))], ", ")]] : []),
|
|
209
|
+
["Approved", approved === null ? html `Not approved` : html `${approved.by}${approved.basis === "mode" ? " (operating mode)" : ""} · ${when(approved.at)}${approved.current ? "" : " · the scope changed after this"}`],
|
|
210
|
+
...(approvals.length > 1 ? [["Approvals", joinHtml(approvals.map(one => html `${one.approver} · ${when(one.at)}`), html `<br>`)]] : []),
|
|
211
211
|
["Scope digest", short(scope.digest)],
|
|
212
212
|
]),
|
|
213
213
|
];
|
|
214
|
-
const runs = version.runs.length === 0 ? `<p class="meta">No agent ran.</p>` :
|
|
215
|
-
`<div class="evidence-table"><table><thead><tr><th>Run</th><th>Role</th><th>Agent</th><th>Outcome</th><th>Cost</th><th>Finished</th><th>Commit</th></tr></thead><tbody>`
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
(run.changedFiles === null ? `<p class="meta">Its changed files couldn't be read.</p>` : run.changedFiles.length === 0 ? `<p class="meta">No files changed.</p>` : `<ul class="files">${run.changedFiles.map(file => `<li>${e(file)}</li>`).join("")}</ul>`)).join("");
|
|
220
|
-
return `<section data-evidence-version="${e(version.id)}"><h2>${pack.versions.length > 1 ? `${index === 0 ? "Request" : `Revision ${index}`} · ` : "Request and approval · "}${e(version.id)}</h2><dl>${rows.map(([label, value]) => `<dt>${label}</dt><dd>${value}</dd>`).join("")}</dl>` +
|
|
221
|
-
`<h3>Agents</h3>${runs}${changes === "" ? "" : `<h3>Changes</h3>${changes}`}</section>`;
|
|
222
|
-
}).join("");
|
|
214
|
+
const runs = version.runs.length === 0 ? html `<p class="meta">No agent ran.</p>` :
|
|
215
|
+
html `<div class="evidence-table"><table><thead><tr><th>Run</th><th>Role</th><th>Agent</th><th>Outcome</th><th>Cost</th><th>Finished</th><th>Commit</th></tr></thead><tbody>${version.runs.map(run => html `<tr><td><a href="/r/${run.id}">#${run.id}</a></td><td>${run.role}</td><td>${run.agent}${run.model ? html ` · ${run.model}` : ""}</td><td>${run.outcome ?? "running"}</td><td>${usd(run.costUsd)}</td><td>${when(run.finishedAt)}</td><td>${short(run.head)}</td></tr>`)}</tbody></table></div>`;
|
|
216
|
+
const changes = version.runs.filter(run => run.role === "builder" || run.role === "repair").map(run => html `<p class="meta">Run #${run.id}${run.diff === null ? "" : html ` · diff ${short(run.diff.sha256)}${run.diff.complete ? "" : " (partial)"}`}</p>${run.changedFiles === null ? html `<p class="meta">Its changed files couldn't be read.</p>` : run.changedFiles.length === 0 ? html `<p class="meta">No files changed.</p>` : html `<ul class="files">${run.changedFiles.map(file => html `<li>${file}</li>`)}</ul>`}`);
|
|
217
|
+
return html `<section data-evidence-version="${version.id}"><h2>${pack.versions.length > 1 ? `${index === 0 ? "Request" : `Revision ${index}`} · ` : "Request and approval · "}${version.id}</h2><dl>${rows.map(([label, value]) => html `<dt>${label}</dt><dd>${value}</dd>`)}</dl><h3>Agents</h3>${runs}${changes.length === 0 ? "" : html `<h3>Changes</h3>${changes}`}</section>`;
|
|
218
|
+
});
|
|
223
219
|
const result = pack.result;
|
|
224
|
-
const outcome = result === null ? `<section><h2>Result</h2><p class="meta">No finished result yet.</p></section>` :
|
|
225
|
-
`<section data-evidence-result><h2>Result</h2><dl
|
|
226
|
-
`<dt>Checks</dt><dd>${
|
|
227
|
-
`<dt>Marked complete</dt><dd>${result.completedBy === null ? "Not yet" : `${
|
|
228
|
-
`<dt>Published</dt><dd>${result.publication === null ? "Not published" : `${
|
|
229
|
-
`<dt>Commit</dt><dd>${short(result.base)} → ${short(result.head)}</dd
|
|
230
|
-
|
|
231
|
-
`</dl></section
|
|
220
|
+
const outcome = result === null ? html `<section><h2>Result</h2><p class="meta">No finished result yet.</p></section>` :
|
|
221
|
+
joinHtml([html `<section data-evidence-result><h2>Result</h2><dl>`,
|
|
222
|
+
html `<dt>Checks</dt><dd>${result.checks.detail}${result.checks.command ? html ` <code>${result.checks.command}</code>` : ""}</dd>`,
|
|
223
|
+
html `<dt>Marked complete</dt><dd>${result.completedBy === null ? "Not yet" : html `${result.completedBy} · ${when(result.completedAt)}`}</dd>`,
|
|
224
|
+
html `<dt>Published</dt><dd>${result.publication === null ? "Not published" : html `${result.publication.state}${result.publication.prUrl ? html ` · ${link(result.publication.prUrl)}` : ""}${result.publication.remoteState ? html ` · ${result.publication.remoteState.toLowerCase()}` : ""}`}</dd>`,
|
|
225
|
+
html `<dt>Commit</dt><dd>${short(result.base)} → ${short(result.head)}</dd>`,
|
|
226
|
+
result.caveats.length ? html `<dt>Caveats</dt><dd>${joinHtml(result.caveats, html `<br>`)}</dd>` : "",
|
|
227
|
+
html `</dl></section>`]);
|
|
232
228
|
const decisions = pack.ledger.entries.filter(one => one.source !== "request");
|
|
233
229
|
const requests = pack.ledger.entries.length - decisions.length;
|
|
234
230
|
const askedWords = `${requests} ${requests === 1 ? "request" : "requests"} made through the console (accepted or refused)`;
|
|
235
|
-
const ledger = `<section><h2>Ledger</h2>${decisions.length === 0 ? `<p class="meta">No entries.</p>` :
|
|
236
|
-
`<div class="evidence-table"><table><thead><tr><th>#</th><th>Time</th><th>Who</th><th>What</th><th>Outcome</th><th>Seal</th></tr></thead><tbody>`
|
|
237
|
-
decisions.map(one => `<tr data-ledger-id="${one.id}"><td>${one.id}</td><td>${when(one.at)}</td><td>${e(one.actor)}</td><td>${e(one.action)}${one.detail ? `<br><span class="meta">${e(one.detail)}</span>` : ""}</td><td>${e(one.outcome)}</td><td>${short(one.seal?.hash ?? null)}</td></tr>`).join("") +
|
|
238
|
-
`</tbody></table></div>`}${requests > 0 ? `<p class="meta">Plus ${askedWords}, listed in the JSON.</p>` : ""}${pack.ledger.truncated ? `<p class="meta">This pack holds ${decisions.length} of the task's entries and the newest ${requests} requests; the ledger export has the rest.</p>` : ""}</section>`;
|
|
231
|
+
const ledger = html `<section><h2>Ledger</h2>${decisions.length === 0 ? html `<p class="meta">No entries.</p>` :
|
|
232
|
+
html `<div class="evidence-table"><table><thead><tr><th>#</th><th>Time</th><th>Who</th><th>What</th><th>Outcome</th><th>Seal</th></tr></thead><tbody>${decisions.map(one => html `<tr data-ledger-id="${one.id}"><td>${one.id}</td><td>${when(one.at)}</td><td>${one.actor}</td><td>${one.action}${one.detail ? html `<br><span class="meta">${one.detail}</span>` : ""}</td><td>${one.outcome}</td><td>${short(one.seal?.hash ?? null)}</td></tr>`)}</tbody></table></div>`}${requests > 0 ? html `<p class="meta">Plus ${askedWords}, listed in the JSON.</p>` : ""}${pack.ledger.truncated ? html `<p class="meta">This pack holds ${decisions.length} of the task's entries and the newest ${requests} requests; the ledger export has the rest.</p>` : ""}</section>`;
|
|
239
233
|
const cost = pack.totals.runs === 0 ? "None ran" : `${pack.totals.runs} ${pack.totals.runs === 1 ? "run" : "runs"} · ${usd(pack.totals.costUsd)}`;
|
|
240
234
|
// The rules as they stand, and each change to them (the approval above was under whichever applied then).
|
|
241
|
-
const rules = pack.rules === null ?
|
|
242
|
-
`<ul class="criteria">${pack.rules.changes.slice(-5).map(one => `<li class="meta">${when(one.at)} · ${
|
|
243
|
-
return `<article class="evidence-pack" data-evidence-pack="${
|
|
244
|
-
`<div class="evidence-head"><h1>Evidence pack</h1><p class="evidence-actions"><a href="/t/${encodeURIComponent(task.id)}/evidence?format=json" download>Download JSON</a> · <a href="/t/${encodeURIComponent(task.id)}">Back to task</a></p></div
|
|
245
|
-
`<p><strong>${
|
|
246
|
-
`<dl><dt>Task</dt><dd>${
|
|
247
|
-
`<dt>Approval rules</dt><dd>${rules}</dd><dt>Agents</dt><dd>${cost}</dd
|
|
248
|
-
`<dt>Prepared</dt><dd>${when(pack.generatedAt)} for ${
|
|
249
|
-
versions
|
|
250
|
-
`<p class="evidence-foot">Pack digest <code>${
|
|
235
|
+
const rules = pack.rules === null ? html `None` : html `${pack.rules.now}${pack.rules.changes.length === 0 ? "" :
|
|
236
|
+
html `<ul class="criteria">${pack.rules.changes.slice(-5).map(one => html `<li class="meta">${when(one.at)} · ${one.by}: ${one.change}</li>`)}</ul>`}`;
|
|
237
|
+
return joinHtml([html `<article class="evidence-pack" data-evidence-pack="${task.id}">`,
|
|
238
|
+
html `<div class="evidence-head"><h1>Evidence pack</h1><p class="evidence-actions"><a href="/t/${encodeURIComponent(task.id)}/evidence?format=json" download>Download JSON</a> · <a href="/t/${encodeURIComponent(task.id)}">Back to task</a></p></div>`,
|
|
239
|
+
html `<p><strong>${task.title}</strong></p>`,
|
|
240
|
+
html `<dl><dt>Task</dt><dd>${task.id}${task.project === null ? "" : ` · ${task.project}`} · ${task.state}</dd>`,
|
|
241
|
+
html `<dt>Approval rules</dt><dd>${rules}</dd><dt>Agents</dt><dd>${cost}</dd>`,
|
|
242
|
+
html `<dt>Prepared</dt><dd>${when(pack.generatedAt)} for ${pack.generatedBy}</dd></dl>${chain}`,
|
|
243
|
+
versions, outcome, ledger,
|
|
244
|
+
html `<p class="evidence-foot">Pack digest <code>${pack.digest}</code>. Each ledger entry's seal can be rechecked from the JSON: ${pack.ledger.recipe}.</p></article>`]);
|
|
251
245
|
}
|
|
252
246
|
/** The page as a file on its own (the CLI's --html): the same facts with just enough style to print. */
|
|
253
247
|
export function standaloneEvidenceHtml(pack) {
|
|
254
|
-
|
|
255
|
-
`<style>:root{--muted-foreground:#5c5c5c;--border:#dedede;--success:#1a7f37;--font-mono:ui-monospace,SFMono-Regular,Menlo,monospace}` +
|
|
248
|
+
const css = `:root{--muted-foreground:#5c5c5c;--border:#dedede;--success:#1a7f37;--font-mono:ui-monospace,SFMono-Regular,Menlo,monospace}` +
|
|
256
249
|
`body{font:14px/1.5 system-ui,-apple-system,sans-serif;margin:32px auto;padding:0 16px;max-width:960px;color:#171717;background:#fff}.meta{color:var(--muted-foreground);font-size:.8125rem}.evidence-actions{display:none}` +
|
|
257
|
-
EVIDENCE_PACK_CSS
|
|
250
|
+
EVIDENCE_PACK_CSS;
|
|
251
|
+
// The file is the sink: its bytes.
|
|
252
|
+
return htmlString(html `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Evidence pack · ${pack.task.id}</title>${styleElement(css)}</head><body>${evidencePackHtml(pack)}</body></html>
|
|
253
|
+
`);
|
|
258
254
|
}
|
package/dist/export-ui.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings → Data (v105): an instance operator downloads everything Standing
|
|
3
|
+
* Orders knows as one .zip. One form: the password, typed again, and a button.
|
|
4
|
+
*/
|
|
5
|
+
import { type Html } from "./html.js";
|
|
1
6
|
export declare const EXPORT_CSS: string;
|
|
2
|
-
export declare function dataExportHtml(
|
|
7
|
+
export declare function dataExportHtml(notice: {
|
|
3
8
|
problem?: string | null;
|
|
4
|
-
}):
|
|
9
|
+
}): Html;
|
package/dist/export-ui.js
CHANGED
|
@@ -2,16 +2,10 @@
|
|
|
2
2
|
* Settings → Data (v105): an instance operator downloads everything Standing
|
|
3
3
|
* Orders knows as one .zip. One form: the password, typed again, and a button.
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
import { html, postForm } from "./html.js";
|
|
6
6
|
export const EXPORT_CSS = `.data-export{max-width:720px;min-width:0}.data-export label{display:grid;gap:4px;margin:14px 0 0;font-size:.875rem}` +
|
|
7
7
|
`.data-export input[type=password]{width:100%;max-width:360px;box-sizing:border-box}.data-export button[type=submit]{margin-top:14px;min-height:44px;white-space:nowrap}`;
|
|
8
|
-
export function dataExportHtml(
|
|
9
|
-
const note = notice.problem ? `<p class="problem" role="alert">${
|
|
10
|
-
return `<section class="data-export">${note}
|
|
11
|
-
`<p>Everything Toolroll knows, as one .zip: projects, tasks, runs and cost, the action ledger, evidence packs, chats, flows, teammates and settings. ` +
|
|
12
|
-
`Passwords, keys and tokens are never included.</p>` +
|
|
13
|
-
`<form method="post" action="/settings/data"><input type="hidden" name="csrf" value="${e(csrf)}">` +
|
|
14
|
-
`<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>` +
|
|
15
|
-
`<button type="submit">Download export</button></form>` +
|
|
16
|
-
`<p class="meta">The same export from the command line: <code>toolroll export --out <path> [--zip]</code></p></section>`;
|
|
8
|
+
export function dataExportHtml(notice) {
|
|
9
|
+
const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : "";
|
|
10
|
+
return html `<section class="data-export">${note}<p>Everything Toolroll knows, as one .zip: projects, tasks, runs and cost, the action ledger, evidence packs, chats, flows, teammates and settings. Passwords, keys and tokens are never included.</p>${postForm("/settings/data", html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit">Download export</button>`)}<p class="meta">The same export from the command line: <code>toolroll export --out <path> [--zip]</code></p></section>`;
|
|
17
11
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type GalleryAnswers, type GalleryPreview, type GalleryTemplate, type GalleryTool } from "./flow-gallery.js";
|
|
2
2
|
import { type FlowDefinition, type FlowStage } from "./flows.js";
|
|
3
|
+
import { type Html } from "./html.js";
|
|
3
4
|
export declare const GALLERY_CSS: string;
|
|
4
5
|
/** A flow's main path, as a person reads it: from the start, each step's first way on (a sort's first answer, a choose's
|
|
5
6
|
* first option that goes somewhere, else next; a check's failure when passing just ends), never back to a step shown. */
|
|
6
7
|
export declare function mainPath(definition: FlowDefinition): FlowStage[];
|
|
7
8
|
/** A flow's steps in order, joined by arrows; a person's decisions carry the accent. */
|
|
8
|
-
export declare function stepStrip(definition: FlowDefinition):
|
|
9
|
+
export declare function stepStrip(definition: FlowDefinition): Html;
|
|
9
10
|
/** The gallery: every template, grouped, each with one "Use this". `repo` carries a chosen project to the template's page. */
|
|
10
11
|
export declare function galleryHtml(input: {
|
|
11
12
|
repo: string | null;
|
|
@@ -14,7 +15,7 @@ export declare function galleryHtml(input: {
|
|
|
14
15
|
id: string;
|
|
15
16
|
state: "connected" | "open" | "taken";
|
|
16
17
|
}[] | null;
|
|
17
|
-
}):
|
|
18
|
+
}): Html;
|
|
18
19
|
/** A template's page: its questions, then what it will do and never do, then Create. */
|
|
19
20
|
export declare function galleryUseHtml(input: {
|
|
20
21
|
template: GalleryTemplate;
|
|
@@ -32,4 +33,4 @@ export declare function galleryUseHtml(input: {
|
|
|
32
33
|
/** The template's tools in this project; `said` is how the last Connect went. */
|
|
33
34
|
tools?: readonly GalleryTool[];
|
|
34
35
|
said?: string | null;
|
|
35
|
-
}):
|
|
36
|
+
}): Html;
|