toolroll 0.9.86 → 0.9.88
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 +86 -0
- package/dist/acp-check.d.ts +9 -0
- package/dist/acp-check.js +40 -28
- 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/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 -70
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +329 -183
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- 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/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +1 -1
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/design/kit.css +722 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +31 -1
- package/dist/discord-settings.js +8 -4
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- 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.js +7 -3
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +141 -30
- 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/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 +38 -4
- 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-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- 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 +23 -0
- package/dist/release.js +46 -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/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +3 -0
- package/dist/scope.js +6 -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 +16 -16
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +24 -12
- package/dist/server/flows.js +64 -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 +70 -0
- package/dist/server/kit.d.ts +419 -0
- package/dist/server/kit.js +369 -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 +22 -190
- package/dist/server/render-pages.js +123 -575
- 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 +35 -5
- package/dist/server/render-tasks.js +114 -85
- package/dist/server/route-table.js +20 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +85 -2
- package/dist/server/tasks.d.ts +8 -0
- package/dist/server/tasks.js +95 -40
- 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/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 +112 -3
- package/dist/store.js +292 -20
- package/dist/surface.js +5 -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/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.js +11 -7
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +7 -4
- package/package.json +1 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings → Updates: the installed version, one of three ways to update
|
|
3
3
|
* (behind the operator's password), the update's steps live while it runs,
|
|
4
|
-
* and a one-time "What's new"
|
|
4
|
+
* and a one-time "What's new" section afterwards. The work itself is the
|
|
5
5
|
* `toolroll update` command (toolroll-update.ts), started as its own job.
|
|
6
|
+
* Built from the revamp kit; the Settings frame adds the title.
|
|
6
7
|
*/
|
|
7
8
|
import { type Html } from "./html.js";
|
|
8
9
|
import type { InstallMethod } from "./install-method.js";
|
|
9
10
|
import { type RuntimeUpdateJournal } from "./toolroll-update.js";
|
|
10
|
-
export
|
|
11
|
+
/** The page is kit pieces now; nothing of the old look is left. Kept as an export for the shared stylesheet. */
|
|
12
|
+
export declare const UPDATES_CSS = "";
|
|
11
13
|
export type UpdatesView = {
|
|
12
14
|
current: string;
|
|
13
15
|
/** The newest release, why it is unknown, or not asked because update checks are off. */
|
|
@@ -32,8 +34,10 @@ export type UpdatesView = {
|
|
|
32
34
|
csrf: string;
|
|
33
35
|
};
|
|
34
36
|
/** The steps, each done, current, failed or waiting. Also the live region's fragment. */
|
|
35
|
-
export declare function updateStepsHtml(j: RuntimeUpdateJournal, running: boolean
|
|
36
|
-
|
|
37
|
+
export declare function updateStepsHtml(j: RuntimeUpdateJournal, running: boolean, options?: {
|
|
38
|
+
title?: boolean;
|
|
39
|
+
}): Html;
|
|
40
|
+
export declare function updatesHtml(view: UpdatesView, said: {
|
|
37
41
|
said?: string | null;
|
|
38
42
|
problem?: string | null;
|
|
39
43
|
}): Html;
|
|
@@ -1,79 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings → Updates: the installed version, one of three ways to update
|
|
3
3
|
* (behind the operator's password), the update's steps live while it runs,
|
|
4
|
-
* and a one-time "What's new"
|
|
4
|
+
* and a one-time "What's new" section afterwards. The work itself is the
|
|
5
5
|
* `toolroll update` command (toolroll-update.ts), started as its own job.
|
|
6
|
+
* Built from the revamp kit; the Settings frame adds the title.
|
|
6
7
|
*/
|
|
7
|
-
import { html,
|
|
8
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
8
9
|
import { STEP_WORDS, UPDATE_STEPS, runtimeUpdateTerminal } from "./toolroll-update.js";
|
|
10
|
+
import { btn, chip, codeLine, disclose, formRow, infoLine, note, notice, radios, settingsRecord, settingsSection } from "./server/kit.js";
|
|
11
|
+
import { kitSectionForm } from "./settings-kit.js";
|
|
9
12
|
const clock = (iso) => { const d = new Date(iso); return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; };
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`.updates ol.update-steps li[data-state=done]{color:var(--so-ink)}.updates ol.update-steps li[data-state=done]::before{background:var(--so-success);border-color:var(--so-success)}` +
|
|
17
|
-
`.updates ol.update-steps li[data-state=now]{color:var(--so-ink);font-weight:600}.updates ol.update-steps li[data-state=now]::before{background:var(--so-info);border-color:var(--so-info);box-shadow:0 0 0 4px color-mix(in srgb,var(--so-info) 16%,transparent)}` +
|
|
18
|
-
`.updates ol.update-steps li[data-state=failed]{color:var(--so-danger);font-weight:600}.updates ol.update-steps li[data-state=failed]::before{background:var(--so-danger);border-color:var(--so-danger)}` +
|
|
19
|
-
`.updates ol.update-steps .step-detail{grid-column:2;font-weight:400;color:var(--so-muted);font-size:.8125rem;margin-top:2px}` +
|
|
20
|
-
`.updates .whats-new ul{margin:10px 0 0;padding-left:18px}.updates .whats-new li{margin:4px 0}.updates .whats-new form{margin:14px 0 0}` +
|
|
21
|
-
`.updates .update-problem{color:var(--so-danger)}.updates .stamp{margin:10px 0 0}.updates code{white-space:nowrap}.updates [data-update-outcome=stopped] code,.updates .step-detail code{white-space:normal;overflow-wrap:anywhere}` +
|
|
22
|
-
`@media (prefers-reduced-motion:no-preference){.updates ol.update-steps li[data-state=now]::before{animation:update-pulse 1.6s ease-in-out infinite}}@keyframes update-pulse{50%{box-shadow:0 0 0 7px color-mix(in srgb,var(--so-info) 6%,transparent)}}` +
|
|
23
|
-
`@media (max-width:600px){.updates .update-actions{display:grid}.updates .update-actions button{width:100%;min-height:44px}.updates .step-up{max-width:none}.updates .step-up input{min-height:44px}}`;
|
|
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 UPDATES_CSS = "";
|
|
15
|
+
/** A step's state as one of the kit's status words. */
|
|
16
|
+
const STEP_CHIP = {
|
|
17
|
+
done: chip("Finished", "done"), now: chip("Running", "live"), failed: chip("Failed", "failed"), waiting: chip("Queued", ""),
|
|
18
|
+
};
|
|
24
19
|
/** The steps, each done, current, failed or waiting. Also the live region's fragment. */
|
|
25
|
-
export function updateStepsHtml(j, running) {
|
|
20
|
+
export function updateStepsHtml(j, running, options = {}) {
|
|
26
21
|
const finished = runtimeUpdateTerminal(j.phase);
|
|
27
22
|
const failed = ["restored", "refused", "needs-attention", "rolling-back"].includes(j.phase);
|
|
28
23
|
const last = UPDATE_STEPS.indexOf([...j.steps].reverse().find(s => UPDATE_STEPS.includes(s.phase))?.phase);
|
|
29
24
|
const items = UPDATE_STEPS.map((step, i) => {
|
|
30
25
|
const state = j.phase === "complete" || i < last ? "done" : i > last ? "waiting" : failed ? "failed" : finished ? "waiting" : "now";
|
|
31
26
|
// Only where it adds something: which work it waits for, or what went wrong.
|
|
32
|
-
const detail = state === "now" && step === "draining" && j.waiting ? html
|
|
33
|
-
: (state === "now" && step === "draining") || (state === "failed" && j.phase === "rolling-back") ? html
|
|
34
|
-
return
|
|
27
|
+
const detail = state === "now" && step === "draining" && j.waiting ? html `${j.waiting.on}.${j.waiting.action ? html ` If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>.` : ""}`
|
|
28
|
+
: (state === "now" && step === "draining") || (state === "failed" && j.phase === "rolling-back") ? html `${j.detail}` : "";
|
|
29
|
+
return settingsRecord({ title: STEP_WORDS[step], sub: detail, when: STEP_CHIP[state], attrs: { "data-step": step, "data-state": state } });
|
|
35
30
|
});
|
|
36
31
|
const title = j.phase === "scheduled" ? `Update to ${j.to.version} scheduled for ${clock(j.at ?? j.startedAt)}`
|
|
37
32
|
: j.phase === "rolling-back" ? `Restoring ${j.from.version}`
|
|
38
33
|
: j.kind === "rollback" ? `Rolling back to ${j.to.version}` : `Updating to ${j.to.version}`;
|
|
39
|
-
const stalled = !running && !finished ? html
|
|
40
|
-
return html `<div id="update-live" data-phase="${j.phase}" data-done="${finished ? 1 : 0}"><
|
|
34
|
+
const stalled = !running && !finished ? notice(html `The updater stopped. Run <code class="k-mono">toolroll update --resume</code> to continue it.`) : "";
|
|
35
|
+
return html `<div id="update-live" data-phase="${j.phase}" data-done="${finished ? 1 : 0}">${options.title === false ? "" : html `<p><b>${title}</b></p>`}${j.phase === "scheduled" ? note("Running work finishes first. You can close this page.") : html `<div aria-label="Update steps">${items}</div>`}${stalled}</div>`;
|
|
41
36
|
}
|
|
42
37
|
function outcomeHtml(j) {
|
|
43
|
-
if (j.phase === "complete")
|
|
44
|
-
return j.kind === "rollback" ?
|
|
38
|
+
if (j.phase === "complete") {
|
|
39
|
+
return j.kind === "rollback" ? settingsSection({ id: "update-outcome", title: `Back on ${j.to.version}`, body: html `<div data-update-outcome="rolled-back">${note(j.detail)}</div>` }) : "";
|
|
40
|
+
}
|
|
45
41
|
if (j.phase === "cancelled")
|
|
46
|
-
return html `<div
|
|
42
|
+
return settingsSection({ id: "update-outcome", title: "Update cancelled", body: html `<div data-update-outcome="cancelled">${note("Nothing changed.")}</div>` });
|
|
47
43
|
// Stopped on a finished run it can't show has ended: what is in the way and the one command, nothing more.
|
|
48
|
-
if (j.phase === "refused" && j.waiting)
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
if (j.phase === "refused" && j.waiting) {
|
|
45
|
+
return settingsSection({ id: "update-outcome", title: `Update to ${j.to.version} stopped: run #${j.waiting.run} is in the way`,
|
|
46
|
+
body: html `<div data-update-outcome="stopped">${infoLine(`${j.waiting.on}. New work resumed.`)}${j.waiting.action ? note(html `If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>, then update again.`) : note("Update again once it has stopped.")}</div>` });
|
|
47
|
+
}
|
|
48
|
+
const title = j.phase === "refused" ? `Didn't update to ${j.to.version}` : j.phase === "restored" ? `Update to ${j.to.version} didn't finish` : "The update needs attention";
|
|
49
|
+
return settingsSection({ id: "update-outcome", title, state: html ` ${chip("Failed", "failed")}`,
|
|
50
|
+
body: html `<div data-update-outcome="${j.phase}">${j.phase === "needs-attention" ? notice(j.detail) : html `<p class="k-note" role="alert">${j.detail}</p>`}${disclose("Steps", updateStepsHtml(j, false, { title: false }))}</div>` });
|
|
52
51
|
}
|
|
53
|
-
export function updatesHtml(view,
|
|
54
|
-
const
|
|
52
|
+
export function updatesHtml(view, said) {
|
|
53
|
+
const answer = said.problem ? notice(said.problem) : said.said ? infoLine(said.said) : "";
|
|
55
54
|
const j = view.journal;
|
|
56
55
|
const active = j !== null && !runtimeUpdateTerminal(j.phase);
|
|
57
|
-
const whatsNew = view.whatsNew === null ? "" :
|
|
56
|
+
const whatsNew = view.whatsNew === null ? "" : settingsSection({ id: "whats-new", title: `What’s new in ${view.whatsNew.version}`,
|
|
57
|
+
right: postForm("/settings/updates/seen", btn("Got it", { sm: true, type: "submit" })),
|
|
58
|
+
body: html `<div data-whats-new="${view.whatsNew.version}">${view.whatsNew.notes.length > 0 ? view.whatsNew.notes.map(line => note(line)) : ""}${note(html `<a class="k-link" href="https://github.com/ap9000/toolroll/releases/tag/v${view.whatsNew.version}" rel="noreferrer">Full release notes</a>`)}</div>` });
|
|
58
59
|
if (active) {
|
|
59
|
-
const cancel = ["scheduled", "draining"].includes(j.phase) ? postForm("/settings/updates/cancel",
|
|
60
|
-
return
|
|
60
|
+
const cancel = ["scheduled", "draining"].includes(j.phase) ? postForm("/settings/updates/cancel", btn("Cancel update", { sm: true, type: "submit" })) : "";
|
|
61
|
+
return settingsSection({ id: "update", title: "Update", state: html ` ${chip("Running", "live")}`, ...(cancel === "" ? {} : { right: cancel }),
|
|
62
|
+
body: joinHtml([answer, html `<div id="update-region">${updateStepsHtml(j, view.running)}</div>`, html `<p class="k-note" id="update-region-stamp" aria-live="polite"></p>`]) });
|
|
61
63
|
}
|
|
62
64
|
const latest = "version" in view.latest ? view.latest.version : null;
|
|
63
65
|
const newer = latest !== null && newerThan(latest, view.current);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
const check = postForm("/settings/updates/check", btn("Check now", { sm: true, type: "submit" }));
|
|
67
|
+
const state = view.method.kind === "npx" ? [`Toolroll ${view.current}`, "npx runs the latest release each time, so this is current.", ""]
|
|
68
|
+
: view.method.kind === "source" ? [`Toolroll ${view.current}`, "This runs from a source checkout. Update it with git.", ""]
|
|
69
|
+
: newer && view.method.kind === "desktop" ? [`Toolroll ${latest} is available`, `You have ${view.current}. Update it from the Toolroll app.`, ""]
|
|
70
|
+
: newer ? [`Toolroll ${latest} is available`, `You have ${view.current}. Running work finishes first, and your current version is kept so you can go back.`, ""]
|
|
71
|
+
: "off" in view.latest ? [`Toolroll ${view.current}`, "Update checks are off.", check]
|
|
72
|
+
: "pending" in view.latest ? [`Toolroll ${view.current}`, "Not checked for a newer release yet.", check]
|
|
73
|
+
: latest === null ? [`Toolroll ${view.current}`, `Couldn’t check for a newer release: ${"problem" in view.latest ? view.latest.problem : ""}`, ""]
|
|
74
|
+
: [`Toolroll ${view.current} is up to date`, "", ""];
|
|
72
75
|
const form = newer && !["npx", "source", "desktop"].includes(view.method.kind)
|
|
73
|
-
?
|
|
76
|
+
? kitSectionForm({ proof: "updates", action: "/settings/updates", id: "update-form", hidden: { version: latest }, save: { label: `Update to ${latest}` }, confirm: `Update Toolroll to ${latest}`,
|
|
77
|
+
body: formRow("When", null, radios({ name: "when", legend: "When", current: "now", items: [
|
|
78
|
+
{ value: "now", label: "Now", text: "Only when no work is running." },
|
|
79
|
+
{ value: "when-idle", label: "When idle", text: "Once nothing is running." },
|
|
80
|
+
{ value: "tonight", label: "Tonight (03:00)", text: "At 03:00 on the computer running Toolroll." }
|
|
81
|
+
] }), "wide") })
|
|
74
82
|
: "";
|
|
75
|
-
const rollback = view.rollbackTo !== null ?
|
|
76
|
-
|
|
83
|
+
const rollback = view.rollbackTo !== null ? joinHtml([note(`To go back to ${view.rollbackTo}:`), codeLine("toolroll update --rollback")]) : "";
|
|
84
|
+
const version = settingsSection({ id: "update", title: state[0], ...(state[2] === "" ? {} : { right: state[2] }),
|
|
85
|
+
body: html `<div data-update-state="${newer ? "available" : "current"}">${joinHtml([answer, state[1] === "" ? "" : note(state[1]), form, rollback])}</div>` });
|
|
86
|
+
return joinHtml([whatsNew, j ? outcomeHtml(j) : "", version]);
|
|
77
87
|
}
|
|
78
88
|
export function newerThan(a, b) {
|
|
79
89
|
const [x, y] = [a, b].map(v => v.split(".").map(Number));
|
package/dist/tools-ui.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** Settings → Tools: one project's MCP servers, in plain words. Secrets are
|
|
2
|
-
* entered here and nowhere else, and never shown again.
|
|
2
|
+
* entered here and nowhere else, and never shown again. Built from the revamp kit (src/design/KIT.md): each tool and
|
|
3
|
+
* each service is a record with one action; the password is asked only once a tool or service is picked. */
|
|
3
4
|
import { type Html } from "./html.js";
|
|
4
5
|
import { type FoundTool, type ProjectTool, type ToolSpec } from "./project-tools.js";
|
|
5
|
-
|
|
6
|
+
/** Kept for the console's style bundle (server/chrome.ts): the page is built from the kit and needs none of its own. */
|
|
7
|
+
export declare const TOOLS_CSS = "";
|
|
6
8
|
export type ToolsView = {
|
|
7
9
|
repo: string;
|
|
8
10
|
project: string;
|
|
@@ -16,7 +18,7 @@ export type ToolsView = {
|
|
|
16
18
|
research?: string;
|
|
17
19
|
}[];
|
|
18
20
|
kit: string | null;
|
|
19
|
-
/** The service a link asked for (a kit's checklist, the lead):
|
|
21
|
+
/** The service a link asked for (a kit's checklist, the lead): listed first, its Connect the page's one primary. */
|
|
20
22
|
wanted: string | null;
|
|
21
23
|
tools: {
|
|
22
24
|
tool: ProjectTool;
|
package/dist/tools-ui.js
CHANGED
|
@@ -1,48 +1,97 @@
|
|
|
1
1
|
/** Settings → Tools: one project's MCP servers, in plain words. Secrets are
|
|
2
|
-
* entered here and nowhere else, and never shown again.
|
|
3
|
-
|
|
4
|
-
import {
|
|
2
|
+
* entered here and nowhere else, and never shown again. Built from the revamp kit (src/design/KIT.md): each tool and
|
|
3
|
+
* each service is a record with one action; the password is asked only once a tool or service is picked. */
|
|
4
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
5
|
+
import { brandMarkHtml } from "./brand-mark.js";
|
|
5
6
|
import { signedIn } from "./oauth-run-token.js";
|
|
6
7
|
import { credentialModeOf, toolCommandLine, toolStanding } from "./project-tools.js";
|
|
8
|
+
import { acts, btn, chip, codeLine, disclose, facts, formRow, icon, infoLine, input, note, notice, select, settingsRecord, settingsSection } from "./server/kit.js";
|
|
9
|
+
import { kitSectionForm, SECRET_OPT_OUTS } from "./settings-kit.js";
|
|
7
10
|
const hidden = (data) => Object.entries(data).map(([k, v]) => html `<input type="hidden" name="${k}" value="${v}">`);
|
|
8
|
-
|
|
11
|
+
/** Kept for the console's style bundle (server/chrome.ts): the page is built from the kit and needs none of its own. */
|
|
12
|
+
export const TOOLS_CSS = "";
|
|
9
13
|
/** The project chooser: it opens the chosen project at once; without scripts, its button does. */
|
|
10
14
|
export function toolsProjectPicker(projects, chosen, keep = {}) {
|
|
11
15
|
if (projects.length < 2)
|
|
12
16
|
return "";
|
|
13
|
-
const
|
|
14
|
-
return html `<form
|
|
17
|
+
const items = projects.map(p => [p, p.split(/[\\/]/).filter(Boolean).at(-1) ?? p]);
|
|
18
|
+
return html `<form method="get" action="/settings/tools" class="k-acts k-picker" data-autosave>${hidden(keep)}\
|
|
19
|
+
${select({ name: "repo", label: "Project", current: chosen, items, attrs: { "data-tools-project": true } })}${btn("Show project", { sm: true, type: "submit" })}</form>`;
|
|
15
20
|
}
|
|
16
21
|
/** Choosing another project marks every form on the page as stale before it reloads, so a tap racing the reload is refused. */
|
|
17
22
|
export const TOOLS_PROJECT_SCRIPT = `(function(){var s=document.querySelector('select[data-tools-project]');if(!s)return;s.addEventListener('change',function(){document.querySelectorAll('input[name=shown]').forEach(function(i){i.value=s.value;});});})();`;
|
|
18
|
-
const
|
|
19
|
-
|
|
23
|
+
const slug = (text) => text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
24
|
+
/** A tool secret's own field: masked, never given a value, kept out of password managers. */
|
|
25
|
+
function secretField(id, label) {
|
|
26
|
+
return html `<input class="k-input" id="${id}" type="password" name="value" autocomplete="off" spellcheck="false" autocapitalize="none" data-so-secret \
|
|
27
|
+
data-1p-ignore data-lpignore="${SECRET_OPT_OUTS["data-lpignore"]}" data-bwignore data-form-type="other" required aria-label="${label}">`;
|
|
28
|
+
}
|
|
29
|
+
function toolRecord(one, base, manage, project, research = null) {
|
|
20
30
|
const { tool, secretsSet } = one;
|
|
21
31
|
const standing = toolStanding(tool, secretsSet);
|
|
22
32
|
// How its runs get credentials, in one line; what that means is in Details.
|
|
23
33
|
const mode = credentialModeOf(tool.spec, secretsSet);
|
|
24
|
-
const
|
|
25
|
-
// One
|
|
26
|
-
// in gets its tokens only by signing in again (its Connect
|
|
34
|
+
const sub = html `${tool.spec.about}${mode === null ? "" : html ` · <span data-credential="${mode.mode}">${mode.words}</span>`}`;
|
|
35
|
+
// One form per secret: the disclosure is both where it stands and where it is set. A service connected by signing
|
|
36
|
+
// in gets its tokens only by signing in again (its Connect): no token of it is ever pasted here.
|
|
27
37
|
const secrets = manage && !signedIn(tool.spec) ? tool.spec.secrets.map(secret => {
|
|
28
38
|
const set = secretsSet.includes(secret.name);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
const id = `secret-${slug(tool.name)}-${slug(secret.name)}`;
|
|
40
|
+
return disclose(set ? `Replace ${secret.name}` : `Set ${secret.name}${secret.optional ? " (optional)" : ""}`, kitSectionForm({
|
|
41
|
+
proof: "tools-change", action: "/settings/tools/change", id, gate: true, hidden: { ...base, action: "secret", name: tool.name, secret: secret.name },
|
|
42
|
+
save: { label: `Save ${secret.name}` }, confirm: `Set ${secret.name} for ${tool.name} in ${project}`,
|
|
43
|
+
body: formRow(secret.name, "Stored privately on this computer and never shown again.", secretField(`${id}-value`, secret.name)),
|
|
44
|
+
}));
|
|
45
|
+
}) : [];
|
|
46
|
+
const tested = tool.lastTest?.ok ? `${tool.lastTest.at.slice(0, 16).replace("T", " ")} UTC: ${tool.lastTest.tools.join(", ") || "it lists no tools"}` : null;
|
|
47
|
+
const details = disclose("Details", facts([
|
|
48
|
+
...(mode?.detail == null ? [] : [["Keys", mode.detail]]),
|
|
49
|
+
["Starts", codeLine(toolCommandLine(tool.spec))],
|
|
50
|
+
["Added", `From ${tool.source} · by ${tool.createdBy}`],
|
|
51
|
+
...(tested === null ? [] : [["Last test", tested]]),
|
|
52
|
+
]));
|
|
53
|
+
const failed = tool.lastTest !== null && !tool.lastTest.ok ? notice(tool.lastTest.problem ?? "The last test failed.") : "";
|
|
34
54
|
// Connected, but research can't use it (its grant writes): said once, with the one way to fix it.
|
|
35
|
-
const reconnect = research === null || !manage ? "" :
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
const reconnect = research === null || !manage ? "" : kitSectionForm({
|
|
56
|
+
proof: "tools-connect", action: "/settings/tools/connect", id: `reconnect-${slug(tool.name)}`, native: true, hidden: { ...base, service: tool.name, access: "read" },
|
|
57
|
+
save: { label: "Reconnect read-only", variant: "outline" }, confirm: `Reconnect ${tool.name} read-only for ${project}`, body: html ``,
|
|
58
|
+
});
|
|
59
|
+
const limited = research === null ? "" : html `${infoLine(research)}${reconnect}`;
|
|
60
|
+
const test = manage ? postForm("/settings/tools/change", btn("Test", { sm: true, type: "submit", attrs: { "aria-label": `Test ${tool.name} in ${project}` } }), { hidden: { ...base, action: "test", name: tool.name } }) : "";
|
|
61
|
+
const remove = manage ? postForm("/settings/tools/change", acts(btn(`Remove from ${project}`, { sm: true, variant: "danger", type: "submit" })), { hidden: { ...base, action: "remove", name: tool.name } }) : "";
|
|
62
|
+
return settingsRecord({
|
|
63
|
+
title: html `${brandMarkHtml(tool.source === "custom" ? null : tool.name, tool.name, standing.ready)}<span>${tool.name}</span>${chip(standing.words, standing.ready ? "done" : "")}`,
|
|
64
|
+
sub, action: test, attrs: { id: `tool-${tool.name}`, "data-ready": String(standing.ready) },
|
|
65
|
+
more: joinHtml([failed, limited, ...secrets, details, remove]),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** One service to connect by signing in on its own page: one record, one Connect. The password is asked after it is picked. */
|
|
69
|
+
function connectRecord(one, view, base, problem) {
|
|
70
|
+
const mark = brandMarkHtml(one.id.replace(/-desktop$/, ""), one.label, one.state === "connected");
|
|
71
|
+
const attrs = { id: `connect-${one.id}`, "data-state": one.state };
|
|
72
|
+
if (one.state === "taken")
|
|
73
|
+
return settingsRecord({ title: html `${brandMarkHtml(one.id, one.label, false)}<span>${one.label}</span>`, sub: "Added another way", attrs });
|
|
74
|
+
const to = ` to ${view.project}`;
|
|
75
|
+
const connected = one.state === "connected";
|
|
76
|
+
const label = connected ? (one.local ? "Connect again" : "Sign in again") : "Connect";
|
|
77
|
+
const confirm = connected && !one.local ? `Sign in to ${one.label} again for ${view.project}` : `Connect ${one.label}${to}`;
|
|
78
|
+
const action = kitSectionForm({
|
|
79
|
+
proof: "tools-connect", action: "/settings/tools/connect", id: `connect-${one.id}-form`, native: true,
|
|
80
|
+
hidden: { ...base, ...(view.kit === null ? {} : { kit: view.kit }), service: one.id },
|
|
81
|
+
save: { label, variant: one.id === view.wanted ? "primary" : "outline" }, confirm, body: html ``,
|
|
82
|
+
});
|
|
83
|
+
return settingsRecord({ title: html `${mark}<span>${one.label}</span>${connected ? chip("Connected", "done") : ""}`, sub: connected ? "" : one.about, action, attrs, more: problem });
|
|
39
84
|
}
|
|
40
|
-
/** A service connected here, offered to the person's other projects: each
|
|
85
|
+
/** A service connected here, offered to the person's other projects: each act signs in again for that project (tokens are never copied). */
|
|
41
86
|
function elsewhere(view, base) {
|
|
42
|
-
const offers = view.connections.filter(one => one.state === "connected").flatMap(one => (view.others ?? []).filter(other => other.open.includes(one.id)).map(other =>
|
|
87
|
+
const offers = view.connections.filter(one => one.state === "connected").flatMap(one => (view.others ?? []).filter(other => other.open.includes(one.id)).map(other => kitSectionForm({
|
|
88
|
+
proof: "tools-connect", action: "/settings/tools/connect", id: `also-${slug(one.id)}-${slug(other.project)}`, native: true,
|
|
89
|
+
hidden: { ...base, ...(view.kit === null ? {} : { kit: view.kit }), also: `${one.id}:${other.repo}` },
|
|
90
|
+
save: { label: `Also connect ${one.label} to ${other.project}`, variant: "outline" }, confirm: `Connect ${one.label} to ${other.project}`, body: html ``,
|
|
91
|
+
})));
|
|
43
92
|
if (offers.length === 0)
|
|
44
93
|
return "";
|
|
45
|
-
return
|
|
94
|
+
return settingsSection({ id: "connect-elsewhere", title: "Also connect to another project", lead: "Signs in again for that project. Tokens are never copied between projects.", body: joinHtml(offers) });
|
|
46
95
|
}
|
|
47
96
|
export function toolsHtml(view, csrf, canManage, message = {}) {
|
|
48
97
|
const manage = canManage && csrf !== "";
|
|
@@ -51,25 +100,41 @@ export function toolsHtml(view, csrf, canManage, message = {}) {
|
|
|
51
100
|
const to = ` to ${view.project}`;
|
|
52
101
|
const keptIn = view.storage === undefined || view.storage.filesOnly ? "Secrets stay on this computer" : view.storage.words.replace(/\.$/, "");
|
|
53
102
|
// No keychain here: said once, plainly, where it can't be missed.
|
|
54
|
-
const storage = view.storage?.problem !== undefined ?
|
|
55
|
-
: view.storage?.filesOnly === true ?
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
// One click: sign in on the service's own page. The password is the same as for adding any tool.
|
|
61
|
-
const tiles = view.connections.map(one => one.state === "taken"
|
|
62
|
-
? html `<button type="button" class="connect-tile" id="connect-${one.id}" data-state="taken" disabled>${brandMarkHtml(one.id, one.label, false)}<strong>${one.label}</strong><span>Added another way</span></button>`
|
|
63
|
-
: html `<button name="service" value="${one.id}" class="connect-tile${one.local ? " connect-local" : ""}" id="connect-${one.id}" data-state="${one.state}" aria-label="${one.state === "connected" ? `${one.label}, connected${to}. ${one.local ? "Connect again" : "Sign in again"}` : `Connect ${one.label}${to}`}">${brandMarkHtml(one.id.replace(/-desktop$/, ""), one.label, one.state === "connected")}<strong>${one.label}</strong><span>${one.state === "connected" ? "Connected" : one.about}</span></button>`);
|
|
64
|
-
const wanted = view.connections.find(one => one.id === view.wanted && one.state !== "taken");
|
|
65
|
-
const first = wanted === undefined ? "" : html `<button name="service" value="${wanted.id}" class="connect-wanted">${wanted.state === "connected" && !wanted.local ? `Sign in to ${wanted.label} again for ${view.project}` : `Connect ${wanted.label}${to}`}</button>`;
|
|
103
|
+
const storage = view.storage?.problem !== undefined ? notice(view.storage.problem)
|
|
104
|
+
: view.storage?.filesOnly === true ? infoLine(view.storage.words) : "";
|
|
105
|
+
const how = disclose("How tools work", html `<ul class="k-note"><li>A tool you add reaches work you approve from now on. Approve a task again to give it a new tool.</li><li>Removing a tool takes it away from every build right away.</li><li>${keptIn}, are never shown again, and go only to the tool that needs them.</li><li>Reviewers and the lead chat never get tools. Gemini builds don't use tools yet.</li></ul>`);
|
|
106
|
+
const records = view.tools.length === 0 ? note("No tools yet. Builds in this project get none.")
|
|
107
|
+
: joinHtml(view.tools.map(one => toolRecord(one, base, manage, view.project, view.connections.find(c => c.id === one.tool.name && c.state === "connected")?.research ?? null)));
|
|
108
|
+
const tools = settingsSection({ id: "tools", title: `Tools in ${view.project}`, lead: `Builds in ${view.project} use exactly these tools. MCP servers set up elsewhere on this computer aren't used.`, body: joinHtml([storage, records, how]) });
|
|
66
109
|
const connecting = manage && view.connections.length > 0;
|
|
67
|
-
// A Connect that failed says why at its
|
|
110
|
+
// A Connect that failed says why at its service, focused so it is heard and seen there (not only at the top).
|
|
68
111
|
const atButton = connecting && view.wanted !== null && view.connections.some(one => one.id === view.wanted) && Boolean(message.problem);
|
|
69
|
-
const failed = atButton ? html `<p class="
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
const failed = atButton ? html `<p class="k-notice" id="connect-problem" role="alert" tabindex="-1" autofocus>${icon("alert", true)}<span>${message.problem}</span></p>` : "";
|
|
113
|
+
// The service a link asked for comes first; its Connect is the page's one primary.
|
|
114
|
+
const ordered = [...view.connections.filter(one => one.id === view.wanted), ...view.connections.filter(one => one.id !== view.wanted)];
|
|
115
|
+
const connect = connecting ? settingsSection({ id: "connect", title: `Connect${to}`, lead: "Sign in on the service's own page. Toolroll asks for your password once you pick one.",
|
|
116
|
+
body: joinHtml(ordered.map(one => connectRecord(one, view, base, one.id === view.wanted ? failed : ""))) }) : "";
|
|
117
|
+
const fromList = view.catalog.length === 0 ? "" : kitSectionForm({
|
|
118
|
+
proof: "tools-change", action: "/settings/tools/change", id: "tools-add-catalog", hidden: { ...base, action: "add-catalog" }, save: { label: `Add${to}`, variant: "outline" }, confirm: `Add a tool${to}`,
|
|
119
|
+
body: formRow("Common tools", null, select({ id: "tools-catalog", name: "catalog", label: "Common tools", wide: true, items: view.catalog.map(one => [one.name, `${one.label}: ${one.about}`]) }), "wide"),
|
|
120
|
+
});
|
|
121
|
+
const custom = disclose("Add your own", kitSectionForm({
|
|
122
|
+
proof: "tools-change", action: "/settings/tools/change", id: "tools-add-custom", hidden: { ...base, action: "add-custom" }, save: { label: `Add${to}`, variant: "outline" }, confirm: `Add your own tool${to}`,
|
|
123
|
+
body: joinHtml([
|
|
124
|
+
formRow("Name", "Lowercase letters, numbers, - and _.", input({ id: "tools-custom-name", name: "name", attrs: { pattern: "[a-z0-9][a-z0-9_\\-]{0,39}", maxlength: 40, required: true, placeholder: "for example: postgres", "aria-label": "Name" } })),
|
|
125
|
+
formRow("It runs as", null, select({ id: "tools-custom-transport", name: "transport", label: "It runs as", items: [["stdio", "A program on this computer"], ["http", "A web address"]] })),
|
|
126
|
+
formRow("Command or address", null, input({ id: "tools-custom-target", name: "target", attrs: { required: true, placeholder: "npx -y some-mcp-server@1.2.3, or https://…", "aria-label": "Command or address" } }), "wide"),
|
|
127
|
+
formRow("Secrets it needs", "Names, comma separated. For a web address, the first one is sent as its sign-in token. Set each value after adding.", input({ id: "tools-custom-secrets", name: "secrets", attrs: { placeholder: "for example: DATABASE_URL", "aria-label": "Secrets it needs" } })),
|
|
128
|
+
]),
|
|
129
|
+
}));
|
|
130
|
+
const add = manage ? settingsSection({ id: "add", title: "Add a tool", body: joinHtml([fromList, custom]) }) : "";
|
|
131
|
+
const found = manage && view.found.length > 0 ? settingsSection({ id: "found", title: "Found on this computer", state: ` · ${view.found.length}`,
|
|
132
|
+
lead: "MCP servers your other apps use. Adding one copies its settings, and any secrets it has, into this project.",
|
|
133
|
+
body: kitSectionForm({
|
|
134
|
+
proof: "tools-change", action: "/settings/tools/change", id: "tools-import", hidden: { ...base, action: "import" }, save: { label: `Add selected${to}`, variant: "outline" }, confirm: `Add tools found on this computer${to}`,
|
|
135
|
+
body: html `<fieldset class="k-sopts"><legend class="k-sr">Found on this computer</legend>${view.found.map(one => html `<label class="k-sopt"><input type="checkbox" name="import" value="${one.spec.name}">\
|
|
136
|
+
<span><b>${one.spec.name}</b><span>${one.source} · <span class="k-mono">${toolCommandLine(one.spec)}</span>${one.spec.secrets.length === 0 ? "" : ` · brings ${one.spec.secrets.map(s => s.name).join(", ")}`}</span></span></label>`)}</fieldset>`,
|
|
137
|
+
}) }) : "";
|
|
138
|
+
const answer = atButton ? "" : message.problem ? notice(message.problem) : message.said ? infoLine(message.said) : "";
|
|
139
|
+
return joinHtml([answer, tools, connect, connecting ? elsewhere(view, base) : "", add, found]);
|
|
75
140
|
}
|
package/dist/work-index.js
CHANGED
|
@@ -22,10 +22,10 @@ import { ASKS, NEED_ASK, NEEDS, askChipOf, failedAttemptSentence, processNeedOf,
|
|
|
22
22
|
import { LEAD_CLAIM_SQL, LEAD_IDLE_MS } from './lead-voice.js';
|
|
23
23
|
export const WORK_INDEX_PAGE_LIMIT = 40;
|
|
24
24
|
export const WORK_INDEX_MAX_LIMIT = 100;
|
|
25
|
-
const EMPTY = { all: 0, 'needs-you': 0, running: 0, completed: 0, archived: 0 };
|
|
25
|
+
const EMPTY = { all: 0, 'needs-you': 0, running: 0, queued: 0, completed: 0, archived: 0 };
|
|
26
26
|
const NO_GROUPS = { decide: 0, review: 0, unblock: 0, building: 0, rest: 0 };
|
|
27
27
|
const FAMILY_PROBLEM = 'Task history is unavailable or incomplete. This task is shown separately.';
|
|
28
|
-
const VIEWS = ['all', 'needs-you', 'running', 'completed', 'archived'];
|
|
28
|
+
const VIEWS = ['all', 'needs-you', 'running', 'queued', 'completed', 'archived'];
|
|
29
29
|
/** The family's current version is put away (v145): it waits on nobody and only the Archived view lists it. */
|
|
30
30
|
const ARCHIVED = `EXISTS(SELECT 1 FROM task_archive ta WHERE ta.task_ref=c.ref_id)`;
|
|
31
31
|
const n = (row, key) => Number(row[key] ?? 0);
|
|
@@ -333,11 +333,13 @@ function parameters(now, access, options, custody) {
|
|
|
333
333
|
$leadOf: access.principal === 'operator' && access.viewer != null && access.viewer !== '' ? `lead for ${access.viewer}` : null };
|
|
334
334
|
}
|
|
335
335
|
function counts(row) {
|
|
336
|
-
return { all: n(row, 'all_count'), 'needs-you': n(row, 'needs_count'), running: n(row, 'running_count'), completed: n(row, 'completed_count'), archived: n(row, 'archived_count') };
|
|
336
|
+
return { all: n(row, 'all_count'), 'needs-you': n(row, 'needs_count'), running: n(row, 'running_count'), queued: n(row, 'queued_count'), completed: n(row, 'completed_count'), archived: n(row, 'archived_count') };
|
|
337
337
|
}
|
|
338
338
|
// Put-away families (v145) count only as archived.
|
|
339
|
+
// Queued: waits on someone or something else (rank 2), not a person and not a build of its own family.
|
|
340
|
+
const QUEUED = `rank=2 AND family_running=0 AND archived=0`;
|
|
339
341
|
const TOTALS = `COALESCE(SUM(archived=0),0) all_count,COALESCE(SUM(needs),0) needs_count,COALESCE(SUM(family_running AND archived=0),0) running_count,`
|
|
340
|
-
+ `COALESCE(SUM(code='complete' AND archived=0),0) completed_count,COALESCE(SUM(archived),0) archived_count`;
|
|
342
|
+
+ `COALESCE(SUM(${QUEUED}),0) queued_count,COALESCE(SUM(code='complete' AND archived=0),0) completed_count,COALESCE(SUM(archived),0) archived_count`;
|
|
341
343
|
const GROUP_TOTALS = `COALESCE(SUM(rank=0 AND ask_rank=0 AND archived=0),0) decide_count,COALESCE(SUM(rank=0 AND ask_rank=1 AND archived=0),0) review_count,COALESCE(SUM(rank=0 AND ask_rank=2 AND archived=0),0) unblock_count,`
|
|
342
344
|
+ `COALESCE(SUM(rank=1 AND archived=0),0) building_count,COALESCE(SUM(rank>1 AND archived=0),0) rest_count`;
|
|
343
345
|
function groupCounts(row) {
|
|
@@ -389,7 +391,7 @@ function readWorkIndexPage(store, now, access, options, taskId = null) {
|
|
|
389
391
|
const view = VIEWS.includes(options.view ?? 'all') ? options.view ?? 'all' : 'all';
|
|
390
392
|
const limit = Number.isFinite(options.limit) ? Math.max(1, Math.min(WORK_INDEX_MAX_LIMIT, Math.floor(options.limit))) : WORK_INDEX_PAGE_LIMIT;
|
|
391
393
|
const scope = cursorScope(access, options, view), cursor = readCursor(options.cursor, scope);
|
|
392
|
-
const filter = view === 'archived' ? 'archived=1' : view === 'needs-you' ? 'needs=1' : view === 'running' ? 'family_running=1 AND archived=0' : view === 'completed' ? "code='complete' AND archived=0" : 'archived=0';
|
|
394
|
+
const filter = view === 'archived' ? 'archived=1' : view === 'needs-you' ? 'needs=1' : view === 'running' ? 'family_running=1 AND archived=0' : view === 'queued' ? QUEUED : view === 'completed' ? "code='complete' AND archived=0" : 'archived=0';
|
|
393
395
|
const rows = prepared(store, `${projection}, selected_page AS MATERIALIZED (
|
|
394
396
|
SELECT * FROM ranked WHERE ($taskId IS NOT NULL OR ${filter}) AND ($taskId IS NULL OR root_ref IN (SELECT root_ref FROM members WHERE id=$taskId)) AND ($roots IS NULL OR root_id IN (SELECT value FROM json_each($roots))) AND ($cursorRoot=0 OR
|
|
395
397
|
${ORDER}>$cursorRank OR (${ORDER}=$cursorRank AND (sort_at<$cursorAt OR (sort_at=$cursorAt AND root_ref<$cursorRoot))))
|
|
@@ -399,8 +401,8 @@ function readWorkIndexPage(store, now, access, options, taskId = null) {
|
|
|
399
401
|
SUM(state='done' AND family_running=0 AND archived=0 AND updated_at>=$recent) done_recently FROM ranked GROUP BY repo
|
|
400
402
|
)
|
|
401
403
|
SELECT 0 record_kind,totals.*,(SELECT json_group_array(json_object('repo',repo,'all_count',all_count,'needs_count',needs_count,
|
|
402
|
-
'running_count',running_count,'completed_count',completed_count,'archived_count',archived_count,'queued',queued,'done_recently',done_recently)) FROM project_totals) row_json FROM totals
|
|
403
|
-
UNION ALL SELECT 1,0,0,0,0,0,0,0,0,0,0,json_object('archived',archived,'archived_by',(SELECT by FROM task_archive WHERE task_ref=page.ref_id),
|
|
404
|
+
'running_count',running_count,'queued_count',queued_count,'completed_count',completed_count,'archived_count',archived_count,'queued',queued,'done_recently',done_recently)) FROM project_totals) row_json FROM totals
|
|
405
|
+
UNION ALL SELECT 1,0,0,0,0,0,0,0,0,0,0,0,json_object('archived',archived,'archived_by',(SELECT by FROM task_archive WHERE task_ref=page.ref_id),
|
|
404
406
|
'archived_kind',(SELECT by_kind FROM task_archive WHERE task_ref=page.ref_id),'archived_reason',(SELECT reason FROM task_archive WHERE task_ref=page.ref_id),
|
|
405
407
|
'archived_at',(SELECT at FROM task_archive WHERE task_ref=page.ref_id),'root_ref',root_ref,'root_id',root_id,'id',id,'root_title',root_title,'repo',repo,
|
|
406
408
|
'state',state,'created_at',created_at,'family_updated',family_updated,'version_count',version_count,'earlier_active',earlier_active,
|
|
@@ -716,6 +718,8 @@ function itemOf(row, principal, probe, leadName, readiness, readable = false) {
|
|
|
716
718
|
views.push('needs-you');
|
|
717
719
|
if (archived === null && running)
|
|
718
720
|
views.push('running');
|
|
721
|
+
if (archived === null && !need && !running && n(row, 'rank') === 2)
|
|
722
|
+
views.push('queued');
|
|
719
723
|
if (archived === null && code === 'complete')
|
|
720
724
|
views.push('completed');
|
|
721
725
|
return { rootId: String(row['root_id']), activeTaskId: id, title: String(row['root_title']), repo: s(row, 'repo'),
|
package/dist/workspace-ui.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { type ChecksFact } from "./task-status.js";
|
|
|
26
26
|
import { type Html } from "./html.js";
|
|
27
27
|
/** The Work destination's views — shortcuts over the same rows, never a
|
|
28
28
|
* persisted state. All is the default. */
|
|
29
|
-
export type WorkView = "all" | "needs-you" | "running" | "completed" | "archived";
|
|
29
|
+
export type WorkView = "all" | "needs-you" | "running" | "queued" | "completed" | "archived";
|
|
30
30
|
export declare const WORK_VIEWS: readonly {
|
|
31
31
|
key: WorkView;
|
|
32
32
|
label: string;
|
package/dist/workspace-ui.js
CHANGED
|
@@ -25,12 +25,13 @@ import { html, postForm } from "./html.js";
|
|
|
25
25
|
export const WORK_VIEWS = [
|
|
26
26
|
{ key: "all", label: "All", hint: "Every task in view, most urgent first.", empty: "Nothing is in progress. Describe work in chat or add a task, and it appears here." },
|
|
27
27
|
{ key: "needs-you", label: "Needs you", hint: "Tasks waiting on an answer, approval, or your inspection.", empty: "Nothing needs you right now. Queued and running work continues on its own." },
|
|
28
|
-
{ key: "running", label: "
|
|
29
|
-
{ key: "
|
|
28
|
+
{ key: "running", label: "Running", hint: "Attempts a builder owns right now.", empty: "Nothing is running right now. Approved tasks start when a builder with capacity is connected." },
|
|
29
|
+
{ key: "queued", label: "Queued", hint: "Tasks that start on their own: waiting for a builder, a dependency or a retry.", empty: "Nothing is queued." },
|
|
30
|
+
{ key: "completed", label: "Finished", hint: "Tasks the lead or user has marked complete. Recorded check results remain available.", empty: "Nothing has finished in this view yet." },
|
|
30
31
|
{ key: "archived", label: "Archived", hint: "Tasks put away by you or by tidy. Their history and results are kept.", empty: "Nothing is put away." },
|
|
31
32
|
];
|
|
32
33
|
export function parseWorkView(raw) {
|
|
33
|
-
return raw === "needs-you" || raw === "running" || raw === "completed" || raw === "archived" ? raw : "all";
|
|
34
|
+
return raw === "needs-you" || raw === "running" || raw === "queued" || raw === "completed" || raw === "archived" ? raw : "all";
|
|
34
35
|
}
|
|
35
36
|
/** The review facts from the store's bounded retry projection — the same
|
|
36
37
|
* reading `diagnoseTaskDispatch` makes, so every surface starts from one
|
|
@@ -469,6 +470,8 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
469
470
|
views.push("needs-you");
|
|
470
471
|
if (running)
|
|
471
472
|
views.push("running");
|
|
473
|
+
if (!needs && !running && facts.state === "queued")
|
|
474
|
+
views.push("queued");
|
|
472
475
|
if (facts.state === "done")
|
|
473
476
|
views.push("completed");
|
|
474
477
|
const dispatchWords = (tone, rank, action) => ({
|
|
@@ -526,7 +529,7 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
526
529
|
}
|
|
527
530
|
/** The counts each view tab wears — from the same rows the page lists. */
|
|
528
531
|
export function workCounts(rows) {
|
|
529
|
-
const counts = { all: 0, "needs-you": 0, running: 0, completed: 0, archived: 0 };
|
|
532
|
+
const counts = { all: 0, "needs-you": 0, running: 0, queued: 0, completed: 0, archived: 0 };
|
|
530
533
|
for (const row of rows)
|
|
531
534
|
for (const view of row.views)
|
|
532
535
|
counts[view] += 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.88",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|