toolroll 0.9.85 → 0.9.87
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 +83 -0
- package/dist/agent-fence.d.ts +6 -2
- package/dist/agent-fence.js +3 -2
- package/dist/agents-board.d.ts +12 -0
- package/dist/agents-board.js +26 -0
- package/dist/approval-rules-ui.d.ts +4 -1
- package/dist/approval-rules-ui.js +31 -15
- package/dist/assignment-ui.js +1 -1
- package/dist/assignment.js +4 -3
- package/dist/backup-ui.d.ts +5 -4
- package/dist/backup-ui.js +65 -32
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +57 -54
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +35 -68
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +363 -177
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.js +1 -0
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-quiet.js +3 -2
- package/dist/chat-settings-page.d.ts +29 -9
- package/dist/chat-settings-page.js +59 -28
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +67 -58
- package/dist/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +4 -1
- package/dist/contracts/console-api.js +4 -1
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +21 -0
- package/dist/contracts/lead-tools.js +2 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/coordinator.js +1 -0
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/deploy-gate.js +4 -0
- package/dist/design/kit.css +685 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +19 -1
- package/dist/discord-settings.js +8 -4
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flow-unattended-words.d.ts +20 -0
- package/dist/flow-unattended-words.js +24 -0
- package/dist/flow-unattended.d.ts +9 -5
- package/dist/flow-unattended.js +45 -23
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +54 -5
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +152 -32
- package/dist/flows.js +5 -2
- package/dist/integrations-ui.d.ts +6 -4
- package/dist/integrations-ui.js +34 -40
- package/dist/integrations.d.ts +2 -2
- package/dist/integrations.js +4 -4
- package/dist/kits-ui.d.ts +8 -10
- package/dist/kits-ui.js +29 -33
- package/dist/kits.js +1 -1
- package/dist/knowledge-ui.js +76 -19
- package/dist/lead-tools.js +35 -6
- package/dist/lead-voice.js +16 -5
- package/dist/ledger-view.d.ts +2 -0
- package/dist/ledger-view.js +41 -38
- package/dist/models-ui.d.ts +8 -3
- package/dist/models-ui.js +82 -59
- package/dist/monitoring-ui.d.ts +4 -2
- package/dist/monitoring-ui.js +44 -13
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +8 -0
- package/dist/operate-remote-arguments.js +1 -0
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +249 -6
- package/dist/passwordless.d.ts +1 -1
- package/dist/passwordless.js +1 -1
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +25 -23
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +27 -21
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +14 -1
- package/dist/project-delete-ui.d.ts +4 -2
- package/dist/project-delete-ui.js +135 -126
- package/dist/project-delete.js +7 -3
- package/dist/project-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- package/dist/pull-request-flow.js +35 -4
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +39 -28
- package/dist/recipe-ui.d.ts +4 -2
- package/dist/recipe-ui.js +14 -4
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +22 -2
- package/dist/release-cli.js +81 -9
- package/dist/release.d.ts +21 -0
- package/dist/release.js +40 -4
- package/dist/releases.js +1 -1
- package/dist/request-budget-ui.d.ts +4 -2
- package/dist/request-budget-ui.js +30 -19
- package/dist/result-follow-ups.js +4 -0
- package/dist/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +8 -0
- package/dist/scope.js +13 -0
- package/dist/serve.d.ts +0 -1
- package/dist/serve.js +51 -7
- package/dist/server/agents-computers.d.ts +9 -0
- package/dist/server/agents-computers.js +67 -0
- package/dist/server/chat.js +20 -18
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +31 -2
- package/dist/server/flows.js +79 -36
- package/dist/server/home.d.ts +35 -0
- package/dist/server/home.js +104 -0
- package/dist/server/http.d.ts +3 -0
- package/dist/server/http.js +5 -0
- package/dist/server/kit-standalone.d.ts +22 -0
- package/dist/server/kit-standalone.js +69 -0
- package/dist/server/kit.d.ts +405 -0
- package/dist/server/kit.js +361 -0
- package/dist/server/pages.d.ts +2 -1
- package/dist/server/pages.js +213 -174
- package/dist/server/people-tokens.js +26 -85
- package/dist/server/render-history.d.ts +51 -0
- package/dist/server/render-history.js +228 -0
- package/dist/server/render-new-task.d.ts +29 -0
- package/dist/server/render-new-task.js +96 -0
- package/dist/server/render-next.d.ts +63 -0
- package/dist/server/render-next.js +109 -0
- package/dist/server/render-pages.d.ts +35 -190
- package/dist/server/render-pages.js +154 -563
- package/dist/server/render-people.d.ts +24 -3
- package/dist/server/render-people.js +93 -72
- package/dist/server/render-projects.d.ts +103 -0
- package/dist/server/render-projects.js +247 -0
- package/dist/server/render-results.d.ts +5 -24
- package/dist/server/render-results.js +48 -185
- package/dist/server/render-settings.d.ts +5 -74
- package/dist/server/render-settings.js +102 -493
- package/dist/server/render-tasks.d.ts +43 -5
- package/dist/server/render-tasks.js +131 -91
- package/dist/server/route-table.js +31 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +90 -2
- package/dist/server/tasks.d.ts +18 -0
- package/dist/server/tasks.js +148 -41
- package/dist/server/terminals.js +1 -1
- package/dist/server/welcome.d.ts +22 -0
- package/dist/server/welcome.js +69 -0
- package/dist/settings-groups-ui.d.ts +279 -0
- package/dist/settings-groups-ui.js +615 -0
- package/dist/settings-kit.d.ts +42 -6
- package/dist/settings-kit.js +69 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +59 -7
- package/dist/slack-settings.js +12 -5
- package/dist/spend-ui.d.ts +45 -26
- package/dist/spend-ui.js +124 -56
- package/dist/sso-ui.d.ts +5 -2
- package/dist/sso-ui.js +63 -31
- package/dist/storage-ui.d.ts +3 -2
- package/dist/storage-ui.js +56 -50
- package/dist/store.d.ts +182 -5
- package/dist/store.js +512 -38
- package/dist/surface.js +23 -3
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- package/dist/teams-settings.d.ts +2 -1
- package/dist/teams-settings.js +24 -41
- package/dist/telegram-settings.d.ts +0 -4
- package/dist/telegram-settings.js +52 -25
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/toolroll-update-ui.d.ts +8 -4
- package/dist/toolroll-update-ui.js +55 -45
- package/dist/tools-ui.d.ts +5 -3
- package/dist/tools-ui.js +107 -42
- package/dist/work-index.d.ts +24 -0
- package/dist/work-index.js +56 -30
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +8 -4
- package/package.json +1 -1
package/dist/ledger-view.js
CHANGED
|
@@ -1,57 +1,60 @@
|
|
|
1
1
|
import { projectName } from "./project.js";
|
|
2
2
|
import { exactWhenHtml } from "./when-html.js";
|
|
3
|
-
import { html, postForm
|
|
3
|
+
import { html, postForm } from "./html.js";
|
|
4
|
+
import { acts, btn, disclose, empty, infoLine, note, notice, pageHeader, select } from "./server/kit.js";
|
|
4
5
|
/** The ledger's kinds of event, as the filter names them. */
|
|
5
6
|
const EVENT_KINDS = [["work", "Work"], ["request", "Requests"], ["access", "Access"], ["sign-in", "Sign-ins"], ["policy", "Policy changes"]];
|
|
6
7
|
function chainHtml(chain) {
|
|
7
8
|
const status = chain.ok
|
|
8
|
-
? html `<
|
|
9
|
-
: html `<
|
|
10
|
-
const latest = chain.latest === null ? "" : html
|
|
11
|
-
const make = chain.csrf === null ? "" : postForm("/ledger/checkpoint",
|
|
9
|
+
? infoLine(html `<span data-ledger-chain="ok">Chain verified · ${chain.entries} entries through #${chain.through ?? 0} · head <span class="k-mono">${chain.head.slice(0, 16)}…</span>${chain.checkedAt === null ? "" : ` · checked in full ${chain.checkedAt.slice(11, 16)} UTC`}</span>`)
|
|
10
|
+
: notice(html `<span data-ledger-chain="broken">Chain broken: ${chain.problem?.what ?? "the chain doesn't verify"}</span>`);
|
|
11
|
+
const latest = chain.latest === null ? "" : note(html `Latest checkpoint (${chain.latest.at.slice(0, 16).replace("T", " ")} UTC). Copy it somewhere outside this machine: <span class="k-mono h-select">${chain.latest.through}:${chain.latest.hash}</span>`);
|
|
12
|
+
const make = chain.csrf === null ? "" : postForm("/ledger/checkpoint", btn("Make a checkpoint", { sm: true, type: "submit", attrs: { "data-k-busy": "Making…" } }), { attrs: { class: "h-inline" } });
|
|
12
13
|
const start = new Date(Date.parse(`${chain.today}T00:00:00Z`) - 29 * 86_400_000).toISOString().slice(0, 10);
|
|
13
|
-
const exportForm =
|
|
14
|
-
|
|
14
|
+
const exportForm = disclose("Audit export", html `<form method="get" action="/ledger/export" class="h-filters h-export" aria-label="Audit export">\
|
|
15
|
+
<div class="k-field"><label class="k-label" for="ledger-from">From</label><input class="k-input" id="ledger-from" type="date" name="from" value="${start}" required></div>\
|
|
16
|
+
<div class="k-field"><label class="k-label" for="ledger-to">To</label><input class="k-input" id="ledger-to" type="date" name="to" value="${chain.today}" required></div>\
|
|
17
|
+
<div class="k-acts">${btn("Download", { sm: true, type: "submit" })}</div></form>${note("Every entry in the range with its seal, and a full record for each task it names. JSON.")}`);
|
|
18
|
+
return html `<section class="h-chain" aria-label="Audit log integrity">${status}${latest}${make}${exportForm}</section>`;
|
|
15
19
|
}
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
.ledger-filter-panel{margin-bottom:12px}.ledger-filter-panel summary{font-size:13px;padding:6px 0;cursor:pointer}.ledger-filters{display:grid;grid-template-columns:repeat(5,minmax(0,1fr)) auto auto;gap:8px;align-items:end;margin:0 0 12px}.ledger-filters label{display:grid;gap:3px;min-width:0;font-size:12px}.ledger-filters input,.ledger-filters select{width:100%;min-width:0;box-sizing:border-box;margin:0;padding:6px 8px;font-size:13px}.ledger-filters button{padding:7px 12px}.ledger-filters>a{padding:8px 0;font-size:13px}
|
|
19
|
-
.ledger-detail{display:block;color:var(--muted-foreground);font-size:12px}.ledger-list{border:1px solid var(--border);border-radius:8px;overflow:hidden}.ledger-event{display:grid;grid-template-columns:140px minmax(90px,1fr) minmax(160px,2fr) minmax(100px,1fr) minmax(100px,1fr);gap:8px;align-items:baseline;padding:7px 10px;border-bottom:1px solid var(--border);font-size:13px;line-height:1.4;overflow-wrap:anywhere}.ledger-event:last-child{border-bottom:0}.ledger-event .actor{color:inherit;font-weight:600;text-decoration:none}.ledger-time,.ledger-place,.ledger-result small{font-size:11px;color:var(--muted-foreground)}.ledger-time time{font-variant-numeric:tabular-nums}.ledger-place a{display:block}.ledger-result small{display:block}.ledger-labels{font-size:11px;color:var(--muted-foreground);font-weight:600}.ledger-empty{padding:12px}
|
|
20
|
-
@media(max-width:1000px){.ledger-filters{grid-template-columns:repeat(3,minmax(0,1fr)) auto}.ledger-event{grid-template-columns:122px minmax(80px,1fr) minmax(130px,2fr) minmax(85px,1fr)}.ledger-place{grid-column:2/4}.ledger-result{grid-column:4;grid-row:1/3}.ledger-labels{display:none}}
|
|
21
|
-
@media(max-width:600px){.ledger-filters{grid-template-columns:repeat(2,minmax(0,1fr))}.ledger-event{grid-template-columns:minmax(0,1fr) auto;gap:2px 8px;padding:8px 10px}.ledger-actor{grid-column:1;grid-row:1}.ledger-action{grid-column:1;grid-row:2}.ledger-result{grid-column:2;grid-row:1/3;text-align:right}.ledger-time{grid-column:1;grid-row:3}.ledger-place{grid-column:2;grid-row:3;text-align:right}.ledger-place a{display:inline}.ledger-heading>a{font-size:13px}}
|
|
22
|
-
.ledger-chain-panel{margin:0 0 12px}.ledger-chain{margin:0 0 4px;font-size:13px}.ledger-chain.ok strong{color:var(--success)}.ledger-checkpoint code{overflow-wrap:anywhere;user-select:all}.ledger-checkpoint-form{margin:6px 0 0}.ledger-export{grid-template-columns:repeat(2,minmax(0,180px)) auto;margin-bottom:4px}
|
|
23
|
-
`;
|
|
20
|
+
/** The audit log (`/ledger`): what people and tokens did, newest first, as quiet timed rows, with the filters, the CSV
|
|
21
|
+
* and the sealed export. A kit list page: the top bar, then rows between hairlines. */
|
|
24
22
|
export function ledgerBody(rows, projects, params, chain) {
|
|
25
|
-
const selected = (key, value) => params.get(key) === value ? html ` selected` : "";
|
|
26
23
|
const more = rows.length > 50;
|
|
27
24
|
const shown = rows.slice(0, 50);
|
|
28
|
-
const
|
|
25
|
+
const kept = new URLSearchParams();
|
|
29
26
|
for (const key of ["project", "actor", "source", "outcome", "task"]) {
|
|
30
27
|
const value = params.get(key);
|
|
31
28
|
if (value)
|
|
32
|
-
|
|
29
|
+
kept.set(key, value);
|
|
33
30
|
}
|
|
34
|
-
const next = new URLSearchParams(
|
|
31
|
+
const next = new URLSearchParams(kept);
|
|
35
32
|
if (shown.length)
|
|
36
33
|
next.set("before", String(shown.at(-1).id));
|
|
37
|
-
const csv = new URLSearchParams(
|
|
34
|
+
const csv = new URLSearchParams(kept);
|
|
38
35
|
csv.set("format", "csv");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
const text = (id, label, name, placeholder) => html `<div class="k-field"><label class="k-label" for="${id}">${label}</label><input class="k-input" id="${id}" type="text" name="${name}" value="${params.get(name) ?? ""}" placeholder="${placeholder}"></div>`;
|
|
37
|
+
const filters = disclose(`Filters · ${kept.size === 0 ? "all actions" : `${kept.size} active`}`, html `<form method="get" action="/ledger" class="h-filters" aria-label="Filter the audit log">\
|
|
38
|
+
<div class="k-field"><label class="k-label" for="ledger-project">Project</label>${select({ id: "ledger-project", name: "project", wide: true, current: params.get("project") ?? "", items: [["", "All your projects"], ...projects.map(repo => [repo, projectName(repo)])] })}</div>\
|
|
39
|
+
${text("ledger-actor", "Person or worker", "actor", "Anyone")}\
|
|
40
|
+
<div class="k-field"><label class="k-label" for="ledger-source">Events</label>${select({ id: "ledger-source", name: "source", wide: true, current: params.get("source") ?? "", items: [["", "All events"], ...EVENT_KINDS] })}</div>\
|
|
41
|
+
${text("ledger-outcome", "Outcome", "outcome", "Any outcome")}${text("ledger-task", "Task", "task", "Any task")}\
|
|
42
|
+
<div class="k-acts">${btn("Filter", { sm: true, type: "submit" })}${btn("Clear", { sm: true, variant: "ghost", href: "/ledger" })}</div></form>`, kept.size > 0);
|
|
43
|
+
const list = shown.length === 0
|
|
44
|
+
? empty({ title: kept.size === 0 ? "Nothing yet." : "No actions match these filters.", text: kept.size === 0 ? "Approvals, invites, tokens and sign-ins appear here." : "Clear the filters to see every action." })
|
|
45
|
+
: html `<div class="h-audit-list" aria-label="Action history">${shown.map(row => {
|
|
46
|
+
const subject = row.runId !== null ? html `<a class="k-link" href="/r/${row.runId}">Run #${row.runId}</a>` : row.taskId !== null ? html `<a class="k-link k-mono" href="/t/${encodeURIComponent(row.taskId)}">${row.taskId}</a>` : "";
|
|
47
|
+
const byActor = new URLSearchParams(kept);
|
|
48
|
+
byActor.set("actor", row.actor);
|
|
49
|
+
return html `<article class="h-audit" data-ledger-id="${row.id}"><span class="h-audit-time k-num">${exactWhenHtml(row.at)}</span>\
|
|
50
|
+
<span class="h-audit-what"><a class="h-actor" href="/ledger?${byActor.toString()}" title="Only this actor's actions">${row.actor}</a> ${row.action}${row.detail === null ? "" : html `<span class="h-audit-detail">${row.detail}</span>`}</span>\
|
|
51
|
+
<span class="h-audit-place">${row.repo === null ? "Instance" : projectName(row.repo)} ${subject}</span><span class="h-audit-out">${row.outcome} · ${row.source}</span></article>`;
|
|
52
|
+
})}</div>`;
|
|
53
|
+
const paging = kept.has("before") || params.has("before") || more
|
|
54
|
+
? acts(params.has("before") ? btn("Newest actions", { sm: true, variant: "ghost", href: `/ledger?${kept.toString()}` }) : "", more ? btn("Older actions", { sm: true, variant: "ghost", href: `/ledger?${next.toString()}`, attrs: { rel: "next" } }) : "")
|
|
55
|
+
: "";
|
|
56
|
+
return html `<div class="h-page h-ledger" data-kit-page>${pageHeader("Audit log", {
|
|
57
|
+
ctx: "What people and tokens did, newest first. Times in UTC.",
|
|
58
|
+
right: btn("Export CSV", { sm: true, variant: "ghost", href: `/ledger?${csv.toString()}`, attrs: { download: true } }),
|
|
59
|
+
})}<div class="h-tools-row">${chain === undefined ? "" : chainHtml(chain)}${filters}</div>${list}<div class="h-foot-row">${paging}${note("Accepted requests record acceptance; worker events record results. History starts from the upgrade that added it. Review evidence is on each task or run. The CSV holds every matching action.")}</div></div>`;
|
|
57
60
|
}
|
package/dist/models-ui.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
/** Settings → Models
|
|
2
|
-
*
|
|
1
|
+
/** Settings → Models, in the kit's Settings frame (a sub-page of "Agents and models"): what is in use and the one
|
|
2
|
+
* choice (a preset, previewed before it applies), then the model for each role behind a disclosure, the CLIs on
|
|
3
|
+
* this computer and the models that just arrived. Saving is an instance operator's step-up. */
|
|
3
4
|
import { type Html } from "./html.js";
|
|
5
|
+
import { type ApprovalFacts } from "./design/kit.js";
|
|
4
6
|
import type { ModelOption, RuntimeState, SeenModel, WatchState } from "./model-catalog.js";
|
|
5
|
-
|
|
7
|
+
/** The page's own layout on the kit: one-column preset cards, the role pickers in the form rows' control column. */
|
|
8
|
+
export declare const MODELS_CSS: string;
|
|
6
9
|
/** One row of the editor: a role at the scope being edited. */
|
|
7
10
|
export type RoleView = {
|
|
8
11
|
/** The slot's form field (model-choices.ts slotKey). */
|
|
@@ -93,6 +96,8 @@ export type ModelsView = {
|
|
|
93
96
|
taskId: string;
|
|
94
97
|
title: string;
|
|
95
98
|
}[];
|
|
99
|
+
/** What this request's approval shows (approvalFactsNow): a step-up asks for the password unless Passwordless is on. */
|
|
100
|
+
facts?: ApprovalFacts;
|
|
96
101
|
};
|
|
97
102
|
export declare function modelsHtml(view: ModelsView): Html;
|
|
98
103
|
/** Filters one long picker as you type; the saved choice always stays listed. */
|
package/dist/models-ui.js
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
|
-
/** Settings → Models
|
|
2
|
-
*
|
|
3
|
-
|
|
1
|
+
/** Settings → Models, in the kit's Settings frame (a sub-page of "Agents and models"): what is in use and the one
|
|
2
|
+
* choice (a preset, previewed before it applies), then the model for each role behind a disclosure, the CLIs on
|
|
3
|
+
* this computer and the models that just arrived. Saving is an instance operator's step-up. */
|
|
4
|
+
import { html, postForm } from "./html.js";
|
|
5
|
+
import { approvalAsk, termsOf } from "./design/kit.js";
|
|
6
|
+
import { acts, btn, chip, disclose, dock, facts as factList, field, formRow, group, input, note, notice, radios, settingsShell } from "./server/kit.js";
|
|
4
7
|
import { priceWords } from "./model-catalog.js";
|
|
5
8
|
import { whenUtc } from "./when-html.js";
|
|
6
9
|
const when = (iso) => iso === null ? "never" : whenUtc(iso);
|
|
7
|
-
|
|
10
|
+
/** The page's own layout on the kit: one-column preset cards, the role pickers in the form rows' control column. */
|
|
11
|
+
export const MODELS_CSS = `.models{display:grid;gap:24px;min-width:0;overflow-wrap:anywhere}.models .k-radios{grid-template-columns:minmax(0,1fr);padding-top:12px}` +
|
|
12
|
+
`.models .k-secthead small{font-weight:400;color:var(--so-muted);font-size:12.5px}.models-presets{display:grid;gap:12px}.models-scope{display:flex;gap:8px;width:100%;max-width:300px;margin:0}.models-scope .k-selw{flex:1;min-width:0}.k-frow-c .models-scope .k-selw{max-width:none}.models-presets .k-acts{padding-top:4px}` +
|
|
13
|
+
`.models .role{display:grid;gap:6px;width:100%;max-width:300px;min-width:0}.models .role .k-input{max-width:none}.models-editor{display:grid;gap:12px;padding-top:4px}` +
|
|
14
|
+
`.models-editor > .k-field{max-width:300px}.models .k-grp{padding:16px 0 0}.models .k-disclose[open]>summary{margin-bottom:8px}.models-diff dd .k-mono{overflow-wrap:anywhere}` +
|
|
15
|
+
`.models-sizing{display:grid;gap:8px;padding-top:4px}.models-sizing p{margin:0;max-width:66ch}` +
|
|
16
|
+
`@media(max-width:760px){.models .role,.models-scope{max-width:none}.models-editor > .k-field{max-width:none}.models .k-btn.k-btn-primary.k-docked{display:none}}`;
|
|
17
|
+
const sec = (id, title, state, body) => html `<section class="k-sec" aria-labelledby="models-${id}"><div class="k-secthead"><h3 id="models-${id}">${title}${state ? html ` <small>· ${state}</small>` : ""}</h3></div>${body}</section>`;
|
|
18
|
+
const back = (view) => `/settings/models${view.scope.value === "installation" ? "" : `?scope=${encodeURIComponent(view.scope.value)}`}`;
|
|
19
|
+
const scopeQuery = (view) => view.scope.value === "installation" ? "" : `&scope=${encodeURIComponent(view.scope.value)}`;
|
|
20
|
+
const factsOf = (view) => view.facts ?? { passwordless: false, remembered: false };
|
|
21
|
+
/** The step-up's password, only when the rules ask for it (never under Passwordless). Named `password`, as the
|
|
22
|
+
* model posts read it; the server proves it again in the write. */
|
|
23
|
+
function password(view, id) {
|
|
24
|
+
return approvalAsk("step-up", factsOf(view)) === "password"
|
|
25
|
+
? field({ id, label: "Your password", control: input({ id, name: "password", type: "password", attrs: { autocomplete: "current-password", required: true } }) })
|
|
26
|
+
: "";
|
|
27
|
+
}
|
|
8
28
|
function toolRow(one, manage) {
|
|
9
|
-
const state = one.installed === null ? "Version unknown"
|
|
10
|
-
: one.behind ? html `${one.installed} · <strong>${one.latest} available</strong>`
|
|
11
|
-
: `${one.installed} · Up to date`;
|
|
29
|
+
const state = one.installed === null ? "Version unknown" : one.behind ? `${one.installed} · ${one.latest} available` : `${one.installed} · up to date`;
|
|
12
30
|
const action = manage && one.behind && one.updateCommand !== null
|
|
13
|
-
? postForm("/settings/models/update",
|
|
14
|
-
: one.behind ? html `<span class="
|
|
15
|
-
return
|
|
31
|
+
? postForm("/settings/models/update", btn(`Update ${one.name}`, { sm: true, type: "submit" }), { hidden: { tool: one.tool } })
|
|
32
|
+
: one.behind ? html `<span class="k-note">Update it the way you installed it</span>` : html ``;
|
|
33
|
+
return formRow(one.name, one.problem === null ? state : `${state} · ${one.problem}`, action, "inline");
|
|
16
34
|
}
|
|
17
|
-
function
|
|
35
|
+
function roleRow(role, manage) {
|
|
18
36
|
if (!manage)
|
|
19
|
-
return html `<
|
|
37
|
+
return formRow(role.label, null, html `<span>${role.words}</span>`);
|
|
20
38
|
const known = role.groups.some(group => group.options.some(option => `${group.provider}|${option.value}` === role.current));
|
|
21
39
|
const saved = !known && role.current !== "" ? html `<option value="${role.current}" selected>${role.words} · current</option>` : "";
|
|
22
40
|
const groups = role.groups.map(group => html `<optgroup label="${group.label}">${group.options.map(option => {
|
|
@@ -26,24 +44,23 @@ function roleForm(role, manage) {
|
|
|
26
44
|
const unset = html `<option value="clear"${role.current === "" ? html ` selected` : ""}>${role.unset}</option>`;
|
|
27
45
|
const count = role.groups.reduce((sum, group) => sum + group.options.length, 0);
|
|
28
46
|
const id = `role-${role.key}`;
|
|
29
|
-
|
|
47
|
+
const control = html `<span class="role">${count > 40 ? html `<input class="k-input" type="search" data-model-filter hidden aria-label="Search ${role.label.toLowerCase()} models" placeholder="Search ${count} models" autocomplete="off">` : ""}\
|
|
48
|
+
<span class="k-selw wide"><select class="k-sel" id="${id}" name="${role.key}" aria-label="${role.label}">${unset}${saved}${groups}</select></span></span>`;
|
|
49
|
+
return formRow(role.label, role.current === "" ? role.words : null, control);
|
|
30
50
|
}
|
|
31
51
|
export function modelsHtml(view) {
|
|
32
52
|
const manage = view.canManage && view.csrf !== "";
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
? html `<details><summary>Automatic checks · ${view.watch.enabled ? "On" : "Off"}</summary>${postForm("/settings/models/watch", html `<p>Check every 6 hours and send a message when a new model or CLI update appears. It uses public lists and sends nothing about you or your projects.</p><button type="submit" class="secondary">${view.watch.enabled ? "Turn off" : "Turn on"}</button>`, { hidden: { enabled: view.watch.enabled ? "0" : "1" } })}</details>`
|
|
45
|
-
: "";
|
|
46
|
-
return html `<section class="models">${status}${problem}${previewCard(view, manage)}${editorSection(view, manage)}${presetsSection(view, manage)}${sizingSection(view)}${recentSection(view)}<h2>AI tools</h2>${tools}<p class="meta">Checked ${when(view.watch.checkedAt)} ${check}</p>${fresh}${view.chat === null ? "" : html `<h2>Chat</h2><p>${view.chat.words} · <a href="/settings/lead">Change</a></p>`}${watch}</section>`;
|
|
53
|
+
const outcome = view.problem !== null ? notice(view.problem) : view.said !== null ? html `<p class="k-note" role="status">${view.said}</p>` : "";
|
|
54
|
+
const preview = previewSection(view, manage);
|
|
55
|
+
const previewing = manage && view.preview !== null && view.preview.rows.length > 0;
|
|
56
|
+
const tools = view.runtimes.length === 0 ? note("No Claude, Codex or Gemini CLI found on this computer yet.") : view.runtimes.map(one => toolRow(one, manage));
|
|
57
|
+
const checks = manage ? html `${formRow("Check for new versions", `Checked ${when(view.watch.checkedAt)}.`, postForm("/settings/models/check", btn("Check now", { sm: true, type: "submit" })), "inline")}\
|
|
58
|
+
${formRow("Automatic checks", `${view.watch.enabled ? "On" : "Off"}. Every 6 hours, with a message when a new model or CLI update appears. Uses public lists and sends nothing about you or your projects.`, postForm("/settings/models/watch", btn(view.watch.enabled ? "Turn off" : "Turn on", { sm: true, type: "submit" }), { hidden: { enabled: view.watch.enabled ? "0" : "1" } }), "inline")}`
|
|
59
|
+
: note(`Checked ${when(view.watch.checkedAt)}.`);
|
|
60
|
+
const fresh = view.fresh.length === 0 ? "" : sec("new", "New models", "", view.fresh.map(model => formRow(model.name, `${priceWords(model)}${model.releasedAt === null ? "" : ` · released ${model.releasedAt.slice(0, 10)}`}`, chip("New", "", { dot: false }), "inline")));
|
|
61
|
+
const chat = view.chat === null ? "" : formRow("Chat", view.chat.words, btn("Change", { sm: true, variant: "ghost", href: "/settings/lead" }), "inline");
|
|
62
|
+
const body = html `<div class="models">${outcome}${preview}${choiceSection(view, manage, previewing)}${recentSection(view)}${sec("tools", "AI tools on this computer", "", html `${tools}${checks}`)}${fresh}${chat}</div>`;
|
|
63
|
+
return settingsShell({ group: "agents", sub: true, title: "Models", lead: "Which model plans, builds and reviews each task. Changes apply to tasks filed from now on.", body });
|
|
47
64
|
}
|
|
48
65
|
/** Filters one long picker as you type; the saved choice always stays listed. */
|
|
49
66
|
export function modelsScript() {
|
|
@@ -56,47 +73,53 @@ export function modelsScript() {
|
|
|
56
73
|
select.value=chosen;});
|
|
57
74
|
})})();`;
|
|
58
75
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const scopes = view.scope.options.length < 2 ? "" :
|
|
76
|
+
/** What is in use and the one choice (a preset, previewed before it applies); the model for each role behind a
|
|
77
|
+
* disclosure, saved with one step-up; how tasks are sized, on demand. */
|
|
78
|
+
function choiceSection(view, manage, previewing) {
|
|
79
|
+
const inUse = view.inUse === null ? "a custom set" : view.inUse;
|
|
80
|
+
const scopes = view.scope.options.length < 2 ? "" : formRow("Settings for", "A project can follow these or choose its own.", html `<form method="get" action="/settings/models" class="models-scope">\
|
|
81
|
+
<span class="k-selw wide"><select class="k-sel" id="models-scope" name="scope" aria-label="Settings for">${view.scope.options.map(one => html `<option value="${one.value}"${one.value === view.scope.value ? html ` selected` : ""}>${one.label}</option>`)}</select></span>${btn("Show", { sm: true, type: "submit" })}</form>`);
|
|
82
|
+
const unresolved = view.unresolved === null ? "" : notice(`New tasks can't be filed here yet: ${view.unresolved}`);
|
|
83
|
+
const current = view.preview?.hidden["preset"] ?? view.presets.find(one => one.inUse)?.name ?? "";
|
|
84
|
+
const presetItems = view.presets.map(one => ({ value: one.name, label: one.label, text: `${one.words}${one.recommended ? " Recommended." : ""}${one.inUse ? " In use." : ""}` }));
|
|
85
|
+
const presets = manage
|
|
86
|
+
? html `<form method="get" action="/settings/models" class="models-presets" id="models-preset">${view.scope.value === "installation" ? "" : html `<input type="hidden" name="scope" value="${view.scope.value}">`}\
|
|
87
|
+
${radios({ name: "preset", legend: "Preset", current, items: presetItems })}${acts(btn("Review changes", { variant: previewing ? "outline" : "primary", type: "submit", docked: !previewing }))}</form>`
|
|
88
|
+
: factList(view.presets.map(one => [one.label, `${one.words}${one.inUse ? " In use." : ""}`]));
|
|
89
|
+
const newer = view.newer.length === 0 ? "" : html `${group("Newer models")}${view.newer.map(one => formRow(one.name, `Newer than ${one.from}, used by ${one.roles}.`, manage ? btn("Switch to it", { sm: true, href: `/settings/models?switch=${encodeURIComponent(one.from)}&to=${encodeURIComponent(one.to)}${scopeQuery(view)}` }) : html ``, "inline"))}`;
|
|
64
90
|
const main = view.roles.filter(role => !role.tier), tiers = view.roles.filter(role => role.tier);
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
const rows = html `${main.map(role => roleRow(role, manage))}${tiers.length === 0 ? "" : html `${group("By task size")}${tiers.map(role => roleRow(role, manage))}`}`;
|
|
92
|
+
const editor = manage
|
|
93
|
+
? postForm("/settings/models/agent", html `${rows}${note(`Approved and waiting tasks keep the models they were filed with. ${termsOf("step-up", factsOf(view))}`)}${password(view, "models-agent-pw")}${acts(btn("Save models", { type: "submit" }))}`, { attrs: { class: "models-editor" }, hidden: { scope: view.scope.value, digest: view.scope.digest } })
|
|
94
|
+
: html `<div class="models-editor">${rows}</div>`;
|
|
95
|
+
const docked = !manage || previewing ? "" : dock(btn("Review changes", { variant: "primary", type: "submit", attrs: { form: "models-preset" } }));
|
|
96
|
+
return html `${sec("choice", "Models for new tasks", `in use: ${inUse}${view.scope.value === "installation" ? "" : ` · ${view.scope.label}`}`, html `${scopes}${unresolved}${presets}${newer}`)}\
|
|
97
|
+
${disclose("Model for each role", editor)}${sizingSection(view)}${docked}`;
|
|
72
98
|
}
|
|
73
|
-
/** A preset or a switch shows exactly what it changes, and applies only with a second,
|
|
74
|
-
function
|
|
99
|
+
/** A preset or a switch shows exactly what it changes, and applies only with a second, step-up submit. */
|
|
100
|
+
function previewSection(view, manage) {
|
|
75
101
|
const preview = view.preview;
|
|
76
102
|
if (preview === null || !manage)
|
|
77
|
-
return
|
|
78
|
-
const back = `/settings/models${view.scope.value === "installation" ? "" : `?scope=${encodeURIComponent(view.scope.value)}`}`;
|
|
103
|
+
return "";
|
|
79
104
|
if (preview.rows.length === 0)
|
|
80
|
-
return html `<
|
|
81
|
-
const rows = preview.rows.map(row => html `<
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const presets = view.presets.map(one => html `<div class="card preset"><div><strong>${one.label}</strong>${one.recommended ? html ` <span class="meta">Default</span>` : ""}<p>${one.words}</p></div>${one.inUse ? html `<span class="meta">In use</span>` : manage ? html `<a class="button-link" href="/settings/models?preset=${one.name}${scopeQuery(view)}">Review changes</a>` : ""}</div>`);
|
|
86
|
-
const newer = view.newer.length === 0 ? "" : html `<h2>Newer models</h2>${view.newer.map(one => html `<div class="card preset"><div><strong>${one.name}</strong><p>Newer than <span class="mono">${one.from}</span>, used by ${one.roles}.</p></div>${manage ? html `<a class="button-link" href="/settings/models?switch=${encodeURIComponent(one.from)}&to=${encodeURIComponent(one.to)}${scopeQuery(view)}">Switch to it</a>` : ""}</div>`)}`;
|
|
87
|
-
return html `<h2>Presets</h2>${presets}${newer}`;
|
|
105
|
+
return sec("preview", preview.title, "", html `<p class="k-note" role="status">Already in use. Nothing would change.</p>${acts(btn("Back", { sm: true, href: back(view) }))}`);
|
|
106
|
+
const rows = factList(preview.rows.map(row => [row.role, html `<span class="k-mono">${row.before}</span> → <span class="k-mono">${row.after}</span>${row.notes.length === 0 ? "" : html `<br><span class="k-note">${row.notes.join(" ")}</span>`}`]));
|
|
107
|
+
const label = `Apply ${preview.rows.length} change${preview.rows.length === 1 ? "" : "s"}`;
|
|
108
|
+
const form = postForm("/settings/models/apply", html `${note(`Tasks filed from now on use these. ${termsOf("step-up", factsOf(view))}`)}${password(view, "models-apply-pw")}${acts(btn(label, { variant: "primary", type: "submit", docked: true }), btn("Cancel", { variant: "ghost", href: back(view) }))}`, { hidden: preview.hidden, attrs: { id: "models-apply", class: "models-editor" } });
|
|
109
|
+
return html `<div class="models-diff">${sec("preview", preview.title, "", html `${rows}${form}`)}</div>${dock(btn(label, { variant: "primary", type: "submit", attrs: { form: "models-apply" } }))}`;
|
|
88
110
|
}
|
|
89
|
-
/** How a task's size is decided, in plain words, and what routing does at filing. */
|
|
111
|
+
/** How a task's size is decided, in plain words, and what routing does at filing; on demand. */
|
|
90
112
|
function sizingSection(view) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
const waiting = view.waiting.length === 0 ? "" : html `<p>${view.waiting.length} plan${view.waiting.length === 1 ? "" : "s"} waiting for approval keep the models they were filed with: ${view.waiting.map((one, index) => html `${index === 0 ? "" : ", "}<a class="k-link" href="/t/${encodeURIComponent(one.taskId)}">${one.title}</a>`)}.</p>`;
|
|
114
|
+
return disclose("How tasks are sized", html `<div class="models-sizing"><p>Each task is sized once, when it is filed. ${view.sizing.classifier} If it doesn't answer within 5 seconds, the size comes from the description.</p>${factList([
|
|
115
|
+
["Small", `${view.sizing.small}. Built by the builder for small tasks, with no plan.`],
|
|
116
|
+
["Routine", `${view.sizing.routine}. Planned and built by the planner and builder for routine tasks.`],
|
|
117
|
+
["Large or risky", `Large: ${view.sizing.large}. Risky: ${view.sizing.risky} Planned and built by the large or risky models.`],
|
|
118
|
+
])}<p>${view.routing}</p><p>Changing a model never re-sizes or re-routes a task that was already filed. One exception: an older build review saved without its own route uses the reviewer set here now.</p>${waiting}</div>`);
|
|
96
119
|
}
|
|
97
120
|
/** The model each recent run was set to, next to the model its provider reported. */
|
|
98
121
|
function recentSection(view) {
|
|
99
122
|
if (view.recent.length === 0)
|
|
100
|
-
return
|
|
101
|
-
return
|
|
123
|
+
return "";
|
|
124
|
+
return sec("recent", "Recent work", "", view.recent.map(one => formRow(one.role, `Chosen ${one.chosen} → ran on ${one.ranOn}`, html `<a class="k-link k-mono" href="/t/${encodeURIComponent(one.taskId)}">${one.taskId}</a>`, "inline")));
|
|
102
125
|
}
|
package/dist/monitoring-ui.d.ts
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
* Settings → Monitoring (v104): an instance operator points Toolroll at
|
|
3
3
|
* the company's tools. One form, the status of each destination beside it,
|
|
4
4
|
* and how Prometheus reads `/metrics`. The signing secret is shown once.
|
|
5
|
+
* Built from the revamp kit; the Settings frame adds the title.
|
|
5
6
|
*/
|
|
6
7
|
import { type Html } from "./html.js";
|
|
7
8
|
import type { MonitoringStatus } from "./store.js";
|
|
8
9
|
import type { MonitoringSettings } from "./monitoring-settings.js";
|
|
9
|
-
export
|
|
10
|
+
/** The page is kit pieces now; nothing of the old look is left. Kept as an export for the shared stylesheet. */
|
|
11
|
+
export declare const MONITORING_CSS = "";
|
|
10
12
|
export type MonitoringView = {
|
|
11
13
|
settings: MonitoringSettings;
|
|
12
14
|
status: MonitoringStatus[];
|
|
13
15
|
head: number;
|
|
14
16
|
origin: string | null;
|
|
15
17
|
};
|
|
16
|
-
export declare function monitoringHtml(view: MonitoringView,
|
|
18
|
+
export declare function monitoringHtml(view: MonitoringView, said: {
|
|
17
19
|
said?: string | null;
|
|
18
20
|
problem?: string | null;
|
|
19
21
|
}): Html;
|
package/dist/monitoring-ui.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* Settings → Monitoring (v104): an instance operator points Toolroll at
|
|
3
3
|
* the company's tools. One form, the status of each destination beside it,
|
|
4
4
|
* and how Prometheus reads `/metrics`. The signing secret is shown once.
|
|
5
|
+
* Built from the revamp kit; the Settings frame adds the title.
|
|
5
6
|
*/
|
|
6
|
-
import { html,
|
|
7
|
+
import { html, joinHtml } from "./html.js";
|
|
7
8
|
import { targetOf } from "./monitoring.js";
|
|
8
9
|
import { whenUtc } from "./when-html.js";
|
|
10
|
+
import { codeLine, disclose, doc, docHeader, formRow, icon, infoLine, input, note, notice, settingsSection, switchInput } from "./server/kit.js";
|
|
11
|
+
import { kitSectionForm, SECRET_OPT_OUTS } from "./settings-kit.js";
|
|
9
12
|
const when = (at) => whenUtc(at);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`.monitoring input[type=text],.monitoring input[type=url],.monitoring input[type=password]{width:100%;box-sizing:border-box}.monitoring .choice{display:flex;gap:8px;align-items:center}` +
|
|
13
|
-
`.monitoring .status{margin:4px 0 0;font-size:.8125rem}.monitoring .status.problem{color:var(--danger)}.monitoring .step-up{margin-top:18px}.monitoring button[type=submit]{margin-top:14px}` +
|
|
14
|
-
`.monitoring pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:.75rem}.monitoring details{margin-top:6px}`;
|
|
13
|
+
/** The page is kit pieces now; nothing of the old look is left. Kept as an export for the shared stylesheet. */
|
|
14
|
+
export const MONITORING_CSS = "";
|
|
15
15
|
/** How a destination is doing, in one line. */
|
|
16
16
|
function statusLine(view, sink, address) {
|
|
17
17
|
// An empty field already says it's off.
|
|
@@ -20,20 +20,51 @@ function statusLine(view, sink, address) {
|
|
|
20
20
|
// A status for another address (it just changed) says nothing about this one yet.
|
|
21
21
|
const one = view.status.find(row => row.sink === sink && row.target === targetOf(address));
|
|
22
22
|
if (one === undefined || (one.lastOkAt === null && one.lastError === null))
|
|
23
|
-
return html `<p class="
|
|
23
|
+
return html `<p class="k-note" data-monitoring="${sink}" data-state="starting">Starting: the first delivery goes out within a few seconds.</p>`;
|
|
24
24
|
if (one.failures > 0 && one.lastError !== null) {
|
|
25
|
-
return html `<p class="
|
|
25
|
+
return html `<p class="k-notice" role="alert" data-monitoring="${sink}" data-state="failing">${icon("alert", true)}<span>Failing: ${one.lastError}${one.nextTryAt === null ? "" : html ` · trying again ${when(one.nextTryAt)}`}</span></p>`;
|
|
26
26
|
}
|
|
27
27
|
const behind = Math.max(0, view.head - one.through);
|
|
28
|
-
return html `<p class="
|
|
28
|
+
return html `<p class="k-note" data-monitoring="${sink}" data-state="ok">${sink === "traces" ? "Sending" : `Sent through entry #${one.through}`}${one.lastOkAt === null ? "" : html ` · last ${when(one.lastOkAt)}`}${behind > 0 && sink !== "traces" ? ` · ${behind} to go` : ""}</p>`;
|
|
29
29
|
}
|
|
30
|
-
export function monitoringHtml(view,
|
|
30
|
+
export function monitoringHtml(view, said) {
|
|
31
31
|
const s = view.settings;
|
|
32
|
-
const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
|
|
33
32
|
const metrics = view.origin === null ? "/metrics" : `${view.origin}/metrics`;
|
|
34
|
-
|
|
33
|
+
const audit = settingsSection({ id: "audit-stream", title: "Audit stream", lead: "Every ledger entry with its seal, as it happens, to your log system.",
|
|
34
|
+
body: joinHtml([
|
|
35
|
+
formRow("Webhook", "Blank: off.", input({ id: "monitoring-webhook", name: "webhook", type: "url", value: s.webhook?.url ?? "",
|
|
36
|
+
attrs: { placeholder: "https://logs.example.com/standing-orders", spellcheck: "false", "aria-label": "Webhook" } })),
|
|
37
|
+
statusLine(view, "webhook", s.webhook?.url ?? null),
|
|
38
|
+
s.webhook === null ? "" : formRow("Signing secret", "Make a new one. It is shown once, after Save.", switchInput({ id: "monitoring-rotate", name: "rotate", label: "Make a new signing secret", on: false, attrs: { value: "1" } }), "inline"),
|
|
39
|
+
formRow("Folder", "JSON Lines, a file per day. Blank: off.", input({ id: "monitoring-folder", name: "folder", value: s.folder?.path ?? "",
|
|
40
|
+
attrs: { placeholder: "/var/log/standing-orders", spellcheck: "false", "aria-label": "Folder" } })),
|
|
41
|
+
statusLine(view, "folder", s.folder?.path ?? null),
|
|
42
|
+
]) });
|
|
43
|
+
const traces = settingsSection({ id: "traces", title: "Traces", lead: "Each run as an OpenTelemetry span: timings, model, tokens and cost. Never a prompt or code.",
|
|
44
|
+
body: joinHtml([
|
|
45
|
+
formRow("Collector", "OTLP over HTTP. Blank: off.", input({ id: "monitoring-traces", name: "traces", type: "url", value: s.traces?.endpoint ?? "",
|
|
46
|
+
attrs: { placeholder: "https://otel.example.com:4318", spellcheck: "false", "aria-label": "Collector" } })),
|
|
47
|
+
statusLine(view, "traces", s.traces?.endpoint ?? null),
|
|
48
|
+
formRow("Header name", "Optional.", input({ id: "monitoring-header-name", name: "header-name", value: s.traces?.header?.name ?? "",
|
|
49
|
+
attrs: { placeholder: "x-honeycomb-team", spellcheck: "false", "aria-label": "Header name" } })),
|
|
50
|
+
formRow("Header value", s.traces?.header ? "Saved. Leave blank to keep it." : null, input({ id: "monitoring-header-value", name: "header-value", type: "password",
|
|
51
|
+
attrs: { autocomplete: "off", spellcheck: "false", autocapitalize: "none", "data-so-secret": true, ...SECRET_OPT_OUTS, placeholder: s.traces?.header ? "Saved; leave blank to keep" : "API key", "aria-label": "Header value" } })),
|
|
52
|
+
]) });
|
|
53
|
+
const metricsSection = settingsSection({ id: "metrics", title: "Metrics",
|
|
54
|
+
lead: html `Prometheus reads <code class="k-mono">${metrics}</code> with an instance operator's API token (<a class="k-link" href="/settings/sessions">Sessions and tokens</a>).`,
|
|
55
|
+
body: disclose("Example", codeLine(`curl -H "Authorization: Bearer so_…" ${metrics}`)) });
|
|
56
|
+
return joinHtml([
|
|
57
|
+
said.problem ? notice(said.problem) : said.said ? infoLine(said.said) : "",
|
|
58
|
+
// A new signing secret answers with a page of its own (shown once): post as a navigation.
|
|
59
|
+
kitSectionForm({ proof: "monitoring", action: "/settings/monitoring", id: "monitoring-form", gate: true, native: true, body: joinHtml([audit, traces]) }),
|
|
60
|
+
metricsSection,
|
|
61
|
+
]);
|
|
35
62
|
}
|
|
36
63
|
/** The webhook's signing secret, once: a focused page with no script. */
|
|
37
64
|
export function signingSecretHtml(url, secret) {
|
|
38
|
-
return
|
|
65
|
+
return doc(docHeader({ title: "Signing secret", back: ["/settings/monitoring", "Monitoring"] }), settingsSection({ id: "signing-secret", title: "Shown once", lead: html `Give this to the receiver at <span class="k-mono">${url}</span>. It isn't shown again.`,
|
|
66
|
+
body: joinHtml([
|
|
67
|
+
html `<p class="k-secret-val">${secret}</p>`,
|
|
68
|
+
note(html `Each request carries <code class="k-mono">x-standing-orders-signature: t=<time>,v1=<HMAC-SHA256 of "time.body"></code>. Check it, and that the time is recent.`),
|
|
69
|
+
]) }));
|
|
39
70
|
}
|
package/dist/needs-you.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export declare const ASKS: readonly Ask[];
|
|
|
45
45
|
export declare const ASK_LABEL: Readonly<Record<Ask, string>>;
|
|
46
46
|
/** A waiting row's chip: the specific thing asked, never its group's own word (no "Decide" chip under Decide).
|
|
47
47
|
* Null: the group heading and the row's sentence already say it, so the row wears no chip. */
|
|
48
|
-
export type AskChip = "Plan" | "Result" | "Mismatch" | "Plan changed" | "Failed" | "Builder offline";
|
|
48
|
+
export type AskChip = "Plan" | "Result" | "Mismatch" | "Plan changed" | "Failed" | "Builder offline" | "Question" | "On hold" | "Stopped" | "Sign in";
|
|
49
49
|
/** `mismatch`: the saved report doesn't match its changes; `planChanged`: the plan changed after it was built. */
|
|
50
50
|
export declare function askChipOf(read: {
|
|
51
51
|
headline: string;
|
package/dist/needs-you.js
CHANGED
|
@@ -66,6 +66,14 @@ export function askChipOf(read) {
|
|
|
66
66
|
return "Plan";
|
|
67
67
|
if (read.need === "start-builder" || read.need === "connect-builder")
|
|
68
68
|
return "Builder offline";
|
|
69
|
+
if (read.need === "answer" || read.need === "questions")
|
|
70
|
+
return "Question";
|
|
71
|
+
if (read.need === "hold")
|
|
72
|
+
return "On hold";
|
|
73
|
+
if (read.need === "vanished" || read.need === "confirm-stopped" || read.need === "check-stopped")
|
|
74
|
+
return "Stopped";
|
|
75
|
+
if (read.need === "sign-in")
|
|
76
|
+
return "Sign in";
|
|
69
77
|
return null;
|
|
70
78
|
}
|
|
71
79
|
export function resultHoldUpSentence(facts) {
|
|
@@ -19,6 +19,7 @@ export const REMOTE_ARGUMENTS = new Map([
|
|
|
19
19
|
['task add', args('title*', 'key report checks', { id: 'refused', replaces: 'task' })],
|
|
20
20
|
['task ask', args('id:task', 'person why')],
|
|
21
21
|
['task checks', args('task:task', 'level')],
|
|
22
|
+
['task ship', args('task:task')],
|
|
22
23
|
['task add-tests', args('task:task')],
|
|
23
24
|
['task list', args('', 'state view limit cursor')],
|
|
24
25
|
['task show', args('id:task')],
|
package/dist/operate-remote.js
CHANGED
|
@@ -37,7 +37,7 @@ const references = { kind: "references" }, listing = { kind: "listing" };
|
|
|
37
37
|
export const REMOTE_SCOPES = new Map([
|
|
38
38
|
["status", listing], ["integrations", installation], ["ready", listing], ["grants", installation], ["sync", installation],
|
|
39
39
|
["gaps", repo], ["task add", repo],
|
|
40
|
-
...["ask", "checks", "add-tests", "show", "wait", "complete", "revise", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign", "scope", "plan", "hold",
|
|
40
|
+
...["ask", "checks", "ship", "add-tests", "show", "wait", "complete", "revise", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign", "scope", "plan", "hold",
|
|
41
41
|
"unhold", "require", "requeue", "review", "repair", "route", "reopen", "stop", "resume"].map(action => [`task ${action}`, references]),
|
|
42
42
|
["check-progress", references],
|
|
43
43
|
["task list", listing],
|
package/dist/operate.d.ts
CHANGED
|
@@ -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", "ship"];
|
|
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"];
|