toolroll 0.9.73 → 0.9.75
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 +63 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-turns.js +5 -0
- package/dist/agents-live.d.ts +31 -0
- package/dist/agents-live.js +43 -0
- package/dist/assignment.d.ts +3 -0
- package/dist/assignment.js +27 -8
- package/dist/browser/THIRD_PARTY_NOTICES.txt +40 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -30
- package/dist/browser-workspace.d.ts +7 -1
- package/dist/builder.js +1 -1
- package/dist/chat-acceptance.d.ts +4 -1
- package/dist/chat-acceptance.js +13 -3
- package/dist/chat-continuity.js +10 -0
- package/dist/chat-quiet.js +3 -3
- package/dist/cli.js +1 -1
- package/dist/coding-handoff.js +4 -3
- package/dist/coding-ui.js +2 -2
- package/dist/coding-workspace.js +16 -4
- package/dist/contracts/console-api.d.ts +5 -1
- package/dist/contracts/console-api.js +10 -0
- package/dist/control-ui.d.ts +3 -2
- package/dist/control-ui.js +7 -2
- package/dist/demo.js +5 -3
- package/dist/deploy-gate.js +5 -2
- package/dist/desktop-update.d.ts +4 -1
- package/dist/desktop-update.js +43 -9
- package/dist/dispose.js +35 -2
- package/dist/exec.js +13 -16
- package/dist/first-run.d.ts +20 -1
- package/dist/first-run.js +71 -0
- package/dist/flow-card-task.js +3 -2
- package/dist/flow-unattended.d.ts +2 -0
- package/dist/flow-unattended.js +12 -1
- package/dist/integrations.d.ts +4 -3
- package/dist/integrations.js +5 -6
- package/dist/invoke.d.ts +13 -0
- package/dist/invoke.js +21 -12
- package/dist/knowledge-ui.d.ts +4 -1
- package/dist/knowledge-ui.js +10 -5
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-follow.js +3 -1
- package/dist/lead-progress.d.ts +4 -1
- package/dist/lead-progress.js +3 -1
- package/dist/lead.js +4 -0
- package/dist/live-bus.d.ts +2 -0
- package/dist/live-bus.js +1 -0
- package/dist/live-children-read.d.ts +52 -0
- package/dist/live-children-read.js +333 -0
- package/dist/live-children.d.ts +73 -0
- package/dist/live-children.js +318 -0
- package/dist/live-detail.d.ts +4 -3
- package/dist/live-detail.js +9 -5
- package/dist/live-input.js +7 -8
- package/dist/live-main-read.d.ts +19 -0
- package/dist/live-main-read.js +91 -0
- package/dist/live-open.d.ts +28 -0
- package/dist/live-open.js +125 -0
- package/dist/live-records.d.ts +10 -1
- package/dist/live-records.js +16 -5
- package/dist/live.d.ts +54 -22
- package/dist/live.js +218 -69
- package/dist/openrouter-models.d.ts +5 -2
- package/dist/openrouter-models.js +7 -4
- package/dist/operate.js +5 -2
- package/dist/peek-cli.d.ts +10 -3
- package/dist/peek-cli.js +17 -8
- package/dist/planner.js +1 -1
- package/dist/project-learning.js +44 -6
- package/dist/project-tools.js +19 -1
- package/dist/provider-limits.d.ts +3 -1
- package/dist/provider-limits.js +5 -2
- package/dist/provider.js +6 -12
- package/dist/pull-request-ui.d.ts +1 -1
- package/dist/pull-request-ui.js +5 -1
- package/dist/push.d.ts +2 -0
- package/dist/push.js +24 -19
- package/dist/read-only-request.d.ts +4 -0
- package/dist/read-only-request.js +15 -0
- package/dist/release-adapters.js +4 -0
- package/dist/release.d.ts +3 -0
- package/dist/release.js +3 -0
- package/dist/repository-context-ui.js +2 -1
- package/dist/repository-context.js +6 -1
- package/dist/result-follow-ups.js +20 -1
- package/dist/result-review.js +5 -1
- package/dist/run-efficiency.d.ts +3 -3
- package/dist/run-efficiency.js +20 -8
- package/dist/scout.d.ts +3 -0
- package/dist/scout.js +5 -3
- package/dist/serve.js +196 -29
- package/dist/server/agents-live-filed.d.ts +17 -0
- package/dist/server/agents-live-filed.js +97 -0
- package/dist/server/agents-live-lead.d.ts +47 -0
- package/dist/server/agents-live-lead.js +116 -0
- package/dist/server/agents-live-room.d.ts +23 -0
- package/dist/server/agents-live-room.js +91 -0
- package/dist/server/chat.js +115 -53
- package/dist/server/chrome.js +2 -2
- package/dist/server/console.css +68 -40
- package/dist/server/http.d.ts +5 -0
- package/dist/server/last-checks.d.ts +17 -0
- package/dist/server/last-checks.js +22 -0
- package/dist/server/live.d.ts +4 -0
- package/dist/server/live.js +42 -2
- package/dist/server/pages.js +98 -4
- package/dist/server/people-tokens.js +7 -4
- package/dist/server/render-chat.d.ts +15 -1
- package/dist/server/render-chat.js +14 -7
- package/dist/server/render-pages.d.ts +10 -5
- package/dist/server/render-pages.js +83 -74
- package/dist/server/render-results.d.ts +11 -2
- package/dist/server/render-results.js +54 -12
- package/dist/server/render-settings.js +3 -3
- package/dist/server/render-tasks.js +13 -3
- package/dist/server/route-table.js +19 -2
- package/dist/server/runtime.d.ts +17 -2
- package/dist/server/settings.js +85 -32
- package/dist/server/task-views.js +1 -2
- package/dist/server/tasks.js +70 -43
- package/dist/storage-ui.d.ts +2 -0
- package/dist/storage-ui.js +1 -1
- package/dist/storage.js +2 -1
- package/dist/store.d.ts +34 -1
- package/dist/store.js +108 -11
- package/dist/stream-scope.d.ts +18 -0
- package/dist/stream-scope.js +28 -0
- package/dist/subagent-work.js +3 -0
- package/dist/subscription-chat.js +5 -3
- package/dist/task-status.d.ts +28 -1
- package/dist/task-status.js +93 -11
- package/dist/teardown-git.d.ts +27 -1
- package/dist/teardown-git.js +185 -39
- package/dist/telegram-progress.js +4 -4
- package/dist/telegram-status.js +8 -3
- package/dist/tool-secret-store.js +8 -0
- package/dist/toolroll-update-ui.d.ts +2 -0
- package/dist/toolroll-update-ui.js +4 -3
- package/dist/toolroll-update.js +10 -9
- package/dist/work-index.js +29 -12
- package/dist/workspace-ui.d.ts +5 -0
- package/dist/workspace-ui.js +38 -10
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ import { CHAT_TASK_ACTIONS } from "../chat-task-actions.js";
|
|
|
20
20
|
import {} from "../contracts/console-api.js";
|
|
21
21
|
import {} from "../dispatch.js";
|
|
22
22
|
import {} from "../evidence.js";
|
|
23
|
-
import {
|
|
23
|
+
import { taskRoad } from "../first-run.js";
|
|
24
24
|
import { starterForWork } from "../flow-starters.js";
|
|
25
25
|
import {} from "../gaps.js";
|
|
26
26
|
import { readAuthModeStrict } from "../keys.js";
|
|
@@ -1437,6 +1437,11 @@ export function taskBodyParts(data) {
|
|
|
1437
1437
|
if (resultStatus.token === "evidence-damaged") {
|
|
1438
1438
|
return withReview(box("problem", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${resultStatus.detail}${publicationWords}`, "evidence-damaged"), html `<div class="proof-review-actions"><a class="button-link" href="/r/${proof.runId}">Review the evidence problems</a></div>`, proofDetails);
|
|
1439
1439
|
}
|
|
1440
|
+
// A recorded pass the check read now doesn't stand behind (unreadable, failed since, or not read here) never
|
|
1441
|
+
// reads "passed": the box says what holds now (task-status.ts approvedCheckOf).
|
|
1442
|
+
if (proof.proofVerdict === "verified" && !accepted && !(proof.receipt?.checks !== undefined && ["ready-to-review", "pr-opened", "merge-observed", "pr-closed"].includes(resultStatus.token))) {
|
|
1443
|
+
return withReview(box(resultStatus.tone === "problem" ? "problem" : "ok", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${resultStatus.detail}${publicationWords}`, resultStatus.token), proofDetails);
|
|
1444
|
+
}
|
|
1440
1445
|
if (proof.proofVerdict === "verified" && !accepted) {
|
|
1441
1446
|
const recovered = verificationRecovered(proof.proofReasons);
|
|
1442
1447
|
return withReview(box("ok", resultStatus.label, recovered
|
|
@@ -2359,10 +2364,15 @@ export function taskBodyParts(data) {
|
|
|
2359
2364
|
{ id: "task-diagnostics", title: "Task options", html: htmlString(optionsHtml), open: optionsOpen, count: null },
|
|
2360
2365
|
],
|
|
2361
2366
|
cancel: cancelForm === "" ? null : { html: htmlString(cancelForm), open: data.cancelDraft !== undefined },
|
|
2362
|
-
|
|
2367
|
+
// Every task's road (the first task's guide used to be the only one): facts only, so the checks step says when
|
|
2368
|
+
// checks didn't run, and a research report has none.
|
|
2369
|
+
journey: task.state === "cancelled" || data.status === undefined ? null : (() => {
|
|
2363
2370
|
const planning = data.plan === "requested" && approveForm === "";
|
|
2364
2371
|
const read = data.assignment != null ? assignmentStageOf(data.assignment, { token: data.status.token }, planning) : stageOfCode(data.status.token, { planning });
|
|
2365
|
-
|
|
2372
|
+
// The one answer the status card's headline and Project checks row read (task-status.ts approvedCheckOf).
|
|
2373
|
+
const checks = statusCard?.status.check ?? null;
|
|
2374
|
+
const report = data.deliverable === "report" || data.report != null;
|
|
2375
|
+
return taskRoad(read, scope !== null && approvalOf(scope).approved, task.state === "done", { checks, report });
|
|
2366
2376
|
})(),
|
|
2367
2377
|
// "Do this every time…": the starter flow that does this kind of work on its own, one yes away.
|
|
2368
2378
|
thread,
|
|
@@ -139,10 +139,14 @@ get("tasks", "done", "/done", { limited: "collection" });
|
|
|
139
139
|
get("settings", "mode", "/mode");
|
|
140
140
|
get("people", "people.page", "/people", { limited: "unscoped", ...free });
|
|
141
141
|
get("pages", "system", "/system");
|
|
142
|
-
get("tasks", "tasks", "/tasks", { limited: "collection" });
|
|
142
|
+
get("tasks", "tasks", "/tasks", { limited: "collection", ...free }); // a redirect to /work now: never sent to the project opener first
|
|
143
143
|
get("tasks", "queue", "/queue");
|
|
144
144
|
get("pages", "peek", "/peek", free);
|
|
145
145
|
get("pages", "agents", "/agents", { limited: "unscoped", ...free });
|
|
146
|
+
// The Agents view's Live grid (docs/plans/agents-view.md §3): read-only, every request filtered to what the viewer may see.
|
|
147
|
+
// Browser sessions only, like the transcript and the peek: no token (a lead's, a runner's, a remote one) reads live output.
|
|
148
|
+
get("pages", "agents.live", "/agents/live", { limited: "unscoped", callers: ["cookie"], ...free });
|
|
149
|
+
get("pages", "agents.live.read", `^\\/agents\\/live\\/${ID}$`, { limited: "unscoped", callers: ["cookie"], ...free, sample: "/agents/live/1" });
|
|
146
150
|
get("pages", "fleet", "/fleet", free);
|
|
147
151
|
get("tasks", "tasks.new", "/tasks/new", { limited: "collection", ...free });
|
|
148
152
|
get("tasks", "task.page", `^\\/t\\/${TASK}$`, { project: "task", limited: "resource", ...free, sample: "/t/one" });
|
|
@@ -233,6 +237,10 @@ for (const one of ["close", "remove"])
|
|
|
233
237
|
post("tasks", `term.${one}`, `^\\/term\\/${TERMINAL}\\/${one}$`, { ...TERM, sample: `/term/${"a".repeat(32)}/${one}` });
|
|
234
238
|
for (const one of ["send", "stop", "resume", "recover", "continue", "ship", "answer"])
|
|
235
239
|
post("tasks", `code.${one}-send`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING_ACT, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
240
|
+
// Reading a coding checkout runs git in a folder an agent writes, so it is a POST with the form token, never a page
|
|
241
|
+
// visit (docs/plans/get-side-effects.md): the session's changes, and the review-for-shipping preview.
|
|
242
|
+
post("tasks", "code.changes-read", `^\\/code\\/${SESSION}\\/changes$`, { project: "coding", limited: "self", callers: ["cookie"], sample: `/code/${"a".repeat(32)}/changes` });
|
|
243
|
+
post("tasks", "code.ship-preview", `^\\/code\\/${SESSION}\\/ship\\/preview$`, { project: "coding", limited: "self", callers: ["cookie"], sample: `/code/${"a".repeat(32)}/ship/preview` });
|
|
236
244
|
// Any other coding action answers "not found" after the same access check.
|
|
237
245
|
post("tasks", "code.act-other", "^\\/code(\\/.*)?$", { ...CODING_ACT, sample: "/code/unknown" });
|
|
238
246
|
post("settings", "settings.skills-revise", "/settings/skills/revise", { project: "form", limited: "unscoped", ...approver("Sign in to revise a test.", "/settings/skills") });
|
|
@@ -304,10 +312,12 @@ const SETTINGS_ACT_ROLES = {
|
|
|
304
312
|
updates: operator("An instance operator updates Toolroll.", "/settings"),
|
|
305
313
|
"updates/seen": operator("An instance operator updates Toolroll.", "/settings"),
|
|
306
314
|
"updates/cancel": operator("An instance operator updates Toolroll.", "/settings"),
|
|
315
|
+
"updates/check": operator("An instance operator updates Toolroll.", "/settings"),
|
|
307
316
|
retention: operator("An instance operator sets retention.", "/settings"),
|
|
308
317
|
storage: operator("An instance operator looks after storage.", "/settings"),
|
|
309
318
|
"storage/clean": operator("An instance operator looks after storage.", "/settings"),
|
|
310
319
|
"storage/discard": operator("An instance operator looks after storage.", "/settings"),
|
|
320
|
+
"storage/check": operator("An instance operator looks after storage.", "/settings"),
|
|
311
321
|
"backups/now": operator("An instance operator looks after backups.", "/settings"),
|
|
312
322
|
backups: operator("An instance operator looks after backups.", "/settings"),
|
|
313
323
|
data: operator("An instance operator exports data.", "/settings"),
|
|
@@ -316,7 +326,7 @@ const SETTINGS_ACT_ROLES = {
|
|
|
316
326
|
"tools/connect": approver("Sign in as an approver to connect tools.", "/settings/tools"),
|
|
317
327
|
"tools/change": approver("Sign in as an approver to manage tools.", "/settings/tools"),
|
|
318
328
|
};
|
|
319
|
-
for (const one of ["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "retention", "storage", "storage/clean", "storage/discard", "pull-requests", "checks", "backups/now", "backups", "data", "integrations/test", "monitoring", "tools/connect", "tools/change", "appearance"]) {
|
|
329
|
+
for (const one of ["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "updates/check", "retention", "storage", "storage/clean", "storage/discard", "storage/check", "pull-requests", "pull-requests/check", "checks", "backups/now", "backups", "data", "integrations/test", "monitoring", "tools/connect", "tools/change", "appearance"]) {
|
|
320
330
|
post(one === "sessions" ? "people" : "settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`, { ...(["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "retention", "storage", "storage/clean", "storage/discard", "pull-requests", "checks", "backups", "data", "monitoring", "tools/connect", "tools/change"].includes(one) ? { scope: "step-up" } : {}), ...SETTINGS_ACT_ROLES[one] });
|
|
321
331
|
}
|
|
322
332
|
post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection", ...approver("An approver switches starter flows on.", "/settings/flows") });
|
|
@@ -330,6 +340,9 @@ post("settings", "settings.knowledge-proposal", "/settings/knowledge/proposal",
|
|
|
330
340
|
post("settings", "settings.knowledge-decision", "/settings/knowledge/decision", { project: "form", ...approver("Sign in as an approver to change project decisions.", "/settings/knowledge") });
|
|
331
341
|
post("settings", "settings.knowledge-change", "/settings/knowledge/change", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to change project knowledge.", "/settings/knowledge") });
|
|
332
342
|
post("settings", "settings.learning-change", "/settings/learning/change", { project: "form", limited: "unscoped" });
|
|
343
|
+
// A page shows the last check; these ask again (docs/plans/get-side-effects.md).
|
|
344
|
+
post("settings", "control.models-refresh", "/control/models-refresh", anyApprover("Project setup requires an approver."));
|
|
345
|
+
post("settings", "settings.connection-check", "/settings/connection/check", anyApprover("An approver checks a sign-in.", "/settings"));
|
|
333
346
|
for (const one of ["setup-preview", "setup-approve", "instructions-preview", "instructions-approve"])
|
|
334
347
|
post("settings", `control.${one}`, `/control/${one}`, { ...(one.endsWith("approve") ? { scope: "step-up" } : {}) });
|
|
335
348
|
for (const [service, name] of [["slack", "Slack"], ["teams", "Teams"], ["discord", "Discord"]]) {
|
|
@@ -344,6 +357,7 @@ post("settings", "settings.telegram-unpair", "/settings/telegram/unpair", { limi
|
|
|
344
357
|
for (const one of ["messaging", "permission-default", "quality-default", "notifications", "notifications/mute", "telegram-digest", "provider-key", "provider-key-clear", "telegram-token", "email", "email-test", "email-read-test", "google", "google/disconnect"]) {
|
|
345
358
|
post("settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`);
|
|
346
359
|
}
|
|
360
|
+
post("pages", "projects.github-list", "/projects/github", { callers: ["cookie"], ...anyApprover("your login can watch — adding projects is an approver's act") });
|
|
347
361
|
post("pages", "projects.select", "/projects/select", { project: "form-path", limited: "unscoped", viewer: true });
|
|
348
362
|
post("pages", "projects.remove", "/projects/remove", { project: "form", ...operator("An instance operator removes projects.", "/settings/project") });
|
|
349
363
|
post("pages", "projects.open", "/projects/open", { project: "form-path" });
|
|
@@ -393,6 +407,9 @@ post("pages", "projects.onboard-preview", "/projects/onboard-preview", { project
|
|
|
393
407
|
post("pages", "projects.onboard-confirm", "/projects/onboard-confirm", { scope: "step-up", project: "none" });
|
|
394
408
|
post("chat", "push.subscribe", "/push/subscribe", { scope: "step-up" });
|
|
395
409
|
post("chat", "push.remove", "/push/remove");
|
|
410
|
+
// Ask this computer's agent CLIs again (docs/plans/get-side-effects.md): Chat's first run and Settings → Lead's Check
|
|
411
|
+
// now. GET /lead/status only reads the last answer.
|
|
412
|
+
post("chat", "lead.check", "/lead/check", { callers: ["cookie"] });
|
|
396
413
|
post("chat", "onboarding.phone-dismiss", "/onboarding/phone/dismiss", approver("An approver puts this away.", "/chat"));
|
|
397
414
|
const LEAD_WORDS = { identity: "An approver names their lead.", about: "An approver edits what their lead knows about them.", "promise/cancel": "An approver manages the lead's promises.", on: "An approver turns the lead on." };
|
|
398
415
|
for (const one of ["identity", "about", "promise/cancel", "on"])
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -146,7 +146,7 @@ export interface ServerRuntime {
|
|
|
146
146
|
via: "cookie";
|
|
147
147
|
}, principal: VerifiedApprover, enabled: Extract<ChatEnablement, {
|
|
148
148
|
ok: true;
|
|
149
|
-
}>, ceilingUsd: number, follow: boolean, now: Date) => number;
|
|
149
|
+
}>, ceilingUsd: number, follow: boolean, now: Date, scope?: LeadThreadScope, request?: string) => number;
|
|
150
150
|
projectFamilyPeek: (repo: string, now: Date) => ProjectPeek;
|
|
151
151
|
needsYouBadge: (project: string | null) => {
|
|
152
152
|
count: number;
|
|
@@ -158,6 +158,8 @@ export interface ServerRuntime {
|
|
|
158
158
|
source: "environment" | "stored";
|
|
159
159
|
} | null;
|
|
160
160
|
chatCatalog: () => Promise<import("../converse.js").CatalogModel[] | null>;
|
|
161
|
+
/** The catalog as last fetched: what a page shows. A page never fetches. */
|
|
162
|
+
chatCatalogKnown: () => import("../converse.js").CatalogModel[] | null;
|
|
161
163
|
leadRecheckedAt: number;
|
|
162
164
|
checkLocalAgents: (fresh?: boolean) => Promise<void>;
|
|
163
165
|
localSignIn: {
|
|
@@ -167,7 +169,18 @@ export interface ServerRuntime {
|
|
|
167
169
|
} | null;
|
|
168
170
|
agentSignInCommand: () => string;
|
|
169
171
|
connectionCheck: (provider: ProviderId, fresh?: boolean) => Promise<ProviderConnection>;
|
|
170
|
-
|
|
172
|
+
/** What the last sign-in check said: what a page shows. A page never checks (docs/plans/get-side-effects.md). */
|
|
173
|
+
connectionKnown: (provider: ProviderId) => ProviderConnection | null;
|
|
174
|
+
/** The last answer of each other check a page shows; a page only reads it. */
|
|
175
|
+
lastChecks: import("./last-checks.js").LastChecks;
|
|
176
|
+
checkRelease: () => Promise<{
|
|
177
|
+
version: string;
|
|
178
|
+
} | {
|
|
179
|
+
problem: string;
|
|
180
|
+
}>;
|
|
181
|
+
modelCatalog: ((key: string | null, refresh?: boolean) => Promise<OpenRouterModels>) & {
|
|
182
|
+
known: (key: string | null) => OpenRouterModels | null;
|
|
183
|
+
};
|
|
171
184
|
modelSeams: CatalogSeams;
|
|
172
185
|
toolsViewOf: (repo: string, codex: unknown[] | null, kit?: string | null, wanted?: string | null) => ToolsView;
|
|
173
186
|
codexServers: (repo: string) => Promise<unknown[] | null>;
|
|
@@ -209,6 +222,8 @@ export interface ServerRuntime {
|
|
|
209
222
|
provider: OidcProvider;
|
|
210
223
|
at: number;
|
|
211
224
|
} | null;
|
|
225
|
+
/** The identity provider as last discovered; a page never discovers it. */
|
|
226
|
+
providerKnown: (issuer: string) => import("../oidc.js").OidcProvider | null;
|
|
212
227
|
providerFor: (issuer: string) => Promise<{
|
|
213
228
|
ok: true;
|
|
214
229
|
provider: OidcProvider;
|
package/dist/server/settings.js
CHANGED
|
@@ -63,7 +63,7 @@ import { starterOf, startersFor, switchOnStarter } from "../flow-starters.js";
|
|
|
63
63
|
import { disconnectGoogle, googleConnected, googleConsent, readGoogleMail, saveGoogleClient } from "../google-mail.js";
|
|
64
64
|
import { integrationsHtml } from "../integrations-ui.js";
|
|
65
65
|
import { clearProviderKey, keyStatus, plausibleKey, PROVIDER_KEY_ENV, readAuthMode, readProviderKey, saveProviderKey, setAuthMode, SUBSCRIPTION_CAPABLE, verdictWords, verifyProviderKey } from "../keys.js";
|
|
66
|
-
import { decisionsHtml, knowledgeHtml, MEMORY_INTRO, memorySearchHtml, proposalsHtml } from "../knowledge-ui.js";
|
|
66
|
+
import { decisionsHtml, framedSection, knowledgeHtml, MEMORY_INTRO, memorySearchHtml, proposalsHtml } from "../knowledge-ui.js";
|
|
67
67
|
import { aboutYouOf, checkAboutYou, saveAboutYou } from "../lead-about.js";
|
|
68
68
|
import { cancelCommitment, conditionWords, openCommitments } from '../lead-commitments.js';
|
|
69
69
|
import { checkLeadIdentity, leadIdentityOf } from "../lead-identity.js";
|
|
@@ -122,14 +122,14 @@ import { teamsSettingsHtml } from "../teams-settings.js";
|
|
|
122
122
|
import { telegramSettingsHtml } from "../telegram-settings.js";
|
|
123
123
|
import { hashPairingCode, loadBotToken, mintPairingCode, PAIRING_TTL_MS, redactToken, saveBotToken } from "../telegram.js";
|
|
124
124
|
import { newerThan, updatesHtml, updatesScript, updateStepsHtml } from "../toolroll-update-ui.js";
|
|
125
|
-
import { abandonRuntimeUpdate, currentRuntime, launchRuntimeUpdate, markWhatsNewSeen, prepareRuntimeUpdate,
|
|
125
|
+
import { abandonRuntimeUpdate, currentRuntime, launchRuntimeUpdate, markWhatsNewSeen, prepareRuntimeUpdate, requestRuntimeUpdateCancel, runningWorkWords, runtimeUpdateStatus, runtimeUpdateTerminal } from "../toolroll-update.js";
|
|
126
126
|
import { TOOLS_PROJECT_SCRIPT, toolsHtml, toolsProjectPicker } from "../tools-ui.js";
|
|
127
127
|
import { effectivePrimary, isMessagingChannel, savePrimary } from "../webhooks.js";
|
|
128
128
|
import { personChip, ROLE_TITLES } from "./chrome.js";
|
|
129
129
|
import { chatReturnWithSaid, goOutside } from "./http.js";
|
|
130
|
-
import {
|
|
130
|
+
import { leadSettingsHtml, notificationProjects, SETTINGS_AUTOSAVE_SCRIPT, settingsPage, settingsWorkers, telegramDeliveryWords, telegramTrouble } from "./render-settings.js";
|
|
131
131
|
export function createSettingsHandlers(runtime) {
|
|
132
|
-
const { store, providerHome, managedRepos, consoleProjects, sendScreen, chromeFor, connectionCheck, clock, modelCatalog, modelSeams, toolsViewOf, codexServers, projectViewOf, sessions, restricted, options, integrations, consoleOrigin, evidenceRoot, storagePool, visible, ssoSettings, checkLocalAgents, chatKeyFor, chatCatalog, leadWords, agentSignInCommand, settingsUpdates, phoneSetup, authenticateApprover, authenticateCredential, deletedRepos, projectOf, providerFor, admissionList, toolHome, connectVisits, mintApprovalNonce, consumeApprovalNonce, defaultProject, googleVisits } = runtime;
|
|
132
|
+
const { store, providerHome, managedRepos, consoleProjects, sendScreen, chromeFor, connectionCheck, connectionKnown, lastChecks, checkRelease, clock, modelCatalog, modelSeams, toolsViewOf, codexServers, projectViewOf, sessions, restricted, options, integrations, consoleOrigin, evidenceRoot, storagePool, visible, ssoSettings, checkLocalAgents, chatKeyFor, chatCatalog, chatCatalogKnown, leadWords, agentSignInCommand, settingsUpdates, phoneSetup, authenticateApprover, authenticateCredential, deletedRepos, projectOf, providerFor, admissionList, toolHome, connectVisits, mintApprovalNonce, consumeApprovalNonce, defaultProject, googleVisits } = runtime;
|
|
133
133
|
// v105: what agent work cost, by month, and the monthly budgets. An instance operator's page.
|
|
134
134
|
async function spendPage(ctx) {
|
|
135
135
|
const { url, who, response, now } = ctx;
|
|
@@ -225,10 +225,11 @@ ${hasGrant ? "" : html `<span class="meta">self-merging needs a merge-capable pu
|
|
|
225
225
|
if (!isProviderId(asked))
|
|
226
226
|
return refuse(response, who, 400, "Choose a known provider.", "/control");
|
|
227
227
|
const task = url.searchParams.get("task") ?? "";
|
|
228
|
-
|
|
228
|
+
// The last sign-in check: Check again posts (docs/plans/get-side-effects.md).
|
|
229
|
+
const connection = connectionKnown(asked);
|
|
229
230
|
if (view === "connection") {
|
|
230
231
|
const command = asked === "claude" ? "claude auth login" : asked === "codex" ? "codex login" : asked === "gemini" ? "gemini" : null;
|
|
231
|
-
return sendScreen(response, 200, screen(`${ASSISTANTS[asked].name} connection`, html `<h1>${ASSISTANTS[asked].name} ${connection
|
|
232
|
+
return sendScreen(response, 200, screen(`${ASSISTANTS[asked].name} connection`, html `<h1>${ASSISTANTS[asked].name} ${connection?.state === "connected" ? "is connected" : "connection"}</h1>\
|
|
232
233
|
${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On the computer running Toolroll, sign in with:</p><pre class="recap">${command}</pre>`}\
|
|
233
234
|
<p><a href="/settings#providers">Manage API keys and authentication mode</a></p><input type="hidden" name="resume-task" value="${task}">`, { chrome: chromeFor(project, "settings") }));
|
|
234
235
|
}
|
|
@@ -241,20 +242,61 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
241
242
|
const models = live.length === 0 ? modelChoices(asked, configured, providerHome)
|
|
242
243
|
: configured && !live.some(one => one.value === configured) ? [{ value: configured, label: `${configured} — current choice` }, ...live] : live;
|
|
243
244
|
const instructions = previewProjectInstructions(project);
|
|
244
|
-
|
|
245
|
+
// The model list as last loaded; Refresh models posts, and the background checks keep it current.
|
|
246
|
+
const catalog = asked === "openrouter" ? modelCatalog.known(readProviderKey("openrouter", providerHome)) ?? { ok: false, problem: "The model list has not loaded yet." } : null;
|
|
245
247
|
const inputs = { provider: asked, model: saved?.provider === asked ? saved.model ?? "" : models[0]?.value ?? "", command: setup?.command ?? (saved === null ? preparation?.command ?? "" : ""), seconds: String((setup?.timeoutMs ?? 300_000) / 1000) };
|
|
246
248
|
return sendScreen(response, 200, screen("Project setup", controlSetupHtml({ repo: project, csrf: who.via === "cookie" ? who.session.csrf : "", provider: asked, inputs, models, connection, catalog, task, preparation,
|
|
247
249
|
instructions: instructions.ok ? { installed: instructions.installed } : { installed: false, message: instructions.message } }), { chrome: chromeFor(project, "settings"), ...(catalog === null ? {} : { functional: { script: openRouterPickerScript() } }) }));
|
|
248
250
|
};
|
|
251
|
+
// Settings → Projects → Pull requests → Check setup: git and GitHub, for one project, by an approver who may turn it on.
|
|
252
|
+
async function pullRequestsCheck(ctx) {
|
|
253
|
+
const { who, response, posted } = ctx;
|
|
254
|
+
const repo = readForm(posted, CONSOLE_FORMS.pullRequestsCheck).get("repo") ?? "";
|
|
255
|
+
const known = [...new Set([...managedRepos(), ...store.knownRepos(), ...store.listProjects().map(one => one.path)])];
|
|
256
|
+
if (who.via !== "cookie" || !known.includes(repo) || !visible(repo))
|
|
257
|
+
return refuse(response, who, 404, "No such project.", "/projects");
|
|
258
|
+
if (who.role !== "approver" || store.isDemo())
|
|
259
|
+
return refuse(response, who, 403, "An approver can turn on pull requests.", "/projects");
|
|
260
|
+
await lastChecks.run(`publishing:${repo}`, () => checkPublishing(repo, options.publishExec === undefined ? {} : { exec: options.publishExec }));
|
|
261
|
+
return redirect(response, `/settings/pull-requests?repo=${encodeURIComponent(repo)}`);
|
|
262
|
+
}
|
|
263
|
+
// Settings → Storage → Check now: measure the checkouts again.
|
|
264
|
+
async function storageCheck(ctx) {
|
|
265
|
+
const databaseFile = store.databaseFile();
|
|
266
|
+
if (databaseFile !== null)
|
|
267
|
+
await measureStorage(databaseFile);
|
|
268
|
+
return redirect(ctx.response, "/settings/storage");
|
|
269
|
+
}
|
|
270
|
+
const measureStorage = (databaseFile) => lastChecks.run("storage", () => checkoutPlan(store, storagePool(databaseFile), clock(), { manual: true }));
|
|
271
|
+
// Settings → Updates → Check now: ask npm once, whether or not automatic checks are on.
|
|
272
|
+
async function updatesCheck(ctx) {
|
|
273
|
+
await checkRelease();
|
|
274
|
+
return redirect(ctx.response, "/settings/updates");
|
|
275
|
+
}
|
|
276
|
+
// Check again (Settings → providers, Project setup): ask this computer's CLI, or test the saved key, now.
|
|
277
|
+
async function connectionCheckNow(ctx) {
|
|
278
|
+
const { response, posted } = ctx;
|
|
279
|
+
const body = readForm(posted, CONSOLE_FORMS.connectionCheck);
|
|
280
|
+
const provider = body.get("provider") ?? "";
|
|
281
|
+
const back = safeReturn(body.get("return") ?? "/settings#providers");
|
|
282
|
+
if (!isProviderId(provider))
|
|
283
|
+
return redirect(response, back);
|
|
284
|
+
await connectionCheck(provider, true).catch(() => undefined);
|
|
285
|
+
return redirect(response, back);
|
|
286
|
+
}
|
|
287
|
+
// Refresh models (Project setup, OpenRouter): load the list again, then show the setup page with it.
|
|
288
|
+
async function modelsRefresh(ctx) {
|
|
289
|
+
const { response, posted } = ctx;
|
|
290
|
+
const body = readForm(posted, CONSOLE_FORMS.modelsRefresh);
|
|
291
|
+
await modelCatalog(readProviderKey("openrouter", providerHome), true);
|
|
292
|
+
const query = new URLSearchParams({ provider: "openrouter", ...(body.get("task") ? { task: body.get("task") } : {}) });
|
|
293
|
+
return redirect(response, `/control?${query.toString()}`);
|
|
294
|
+
}
|
|
249
295
|
async function modelsPage(ctx) {
|
|
250
296
|
const { url, who, response, project } = ctx;
|
|
251
297
|
const now = clock();
|
|
252
|
-
|
|
253
|
-
//
|
|
254
|
-
// the check sends nothing about the person or their projects.
|
|
255
|
-
if (who.role === "approver" && (watch.checkedAt === null || now.getTime() - Date.parse(watch.checkedAt) > 3_600_000)) {
|
|
256
|
-
await checkModels(store, now, modelSeams).catch(() => undefined);
|
|
257
|
-
}
|
|
298
|
+
// The page shows the last check and when it ran (docs/plans/get-side-effects.md): Check now, or the watch loop's
|
|
299
|
+
// automatic checks, fetch the lists and may queue a notice; opening the page never does.
|
|
258
300
|
const view = modelsView(who.role === "approver", who.via === "cookie" ? who.session.csrf : "", now, url.searchParams.get("said"), url.searchParams.get("problem"));
|
|
259
301
|
return sendScreen(response, 200, screen("Models", html `<p><a href="/settings">Settings</a></p><h1>Models</h1>${modelsHtml(view)}`, { chrome: chromeFor(project, "settings"), functional: { script: modelsScript() } }));
|
|
260
302
|
}
|
|
@@ -290,7 +332,8 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
290
332
|
if (chosen) {
|
|
291
333
|
try {
|
|
292
334
|
const approver = who.role === "approver" && who.via === "cookie";
|
|
293
|
-
|
|
335
|
+
// What Codex had configured at the last background check (docs/plans/get-side-effects.md): the page starts no codex.
|
|
336
|
+
const view = toolsViewOf(chosen, approver ? lastChecks.known(`codex-tools:${chosen}`)?.value ?? null : null, kit, wanted);
|
|
294
337
|
if (approver)
|
|
295
338
|
view.others = projects.filter(one => one !== chosen).map(one => ({ repo: one, project: projectName(one), open: connectionsOf(store, one).filter(c => c.state === "open").map(c => c.id) }));
|
|
296
339
|
content = (toolsHtml(view, who.via === "cookie" ? who.session.csrf : "", approver, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }));
|
|
@@ -376,19 +419,16 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
376
419
|
const databaseFile = store.databaseFile();
|
|
377
420
|
if (databaseFile === null)
|
|
378
421
|
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
379
|
-
// A
|
|
380
|
-
try {
|
|
381
|
-
releaseStalledUpdate(dirname(databaseFile), clock());
|
|
382
|
-
}
|
|
383
|
-
catch { /* shown as last saved */ }
|
|
422
|
+
// A stalled update is released by the background pass, never by opening this page (docs/plans/get-side-effects.md).
|
|
384
423
|
const status = runtimeUpdateStatus(dirname(databaseFile));
|
|
385
424
|
if (url.searchParams.get("fragment") === "steps")
|
|
386
425
|
return respond(response, 200, "text/html; charset=utf-8", status.journal ? updateStepsHtml(status.journal, status.running) : html `<div id="update-live" data-done="1"></div>`);
|
|
387
426
|
const active = status.journal !== null && !runtimeUpdateTerminal(status.journal.phase);
|
|
388
427
|
const current = options.updates?.current ?? PACKAGE_VERSION;
|
|
389
|
-
//
|
|
390
|
-
const checksOff = updateChecksOff(process.env, options.configDir ?? dirname(databaseFile)).off
|
|
391
|
-
const
|
|
428
|
+
// The last answer npm gave (the background checks ask while update checks are on; Check now asks once).
|
|
429
|
+
const checksOff = updateChecksOff(process.env, options.configDir ?? dirname(databaseFile)).off;
|
|
430
|
+
const known = lastChecks.known("release")?.value ?? null;
|
|
431
|
+
const view = { current, latest: active ? { version: current } : known ?? (checksOff ? { off: true } : { pending: true }), method: options.updates?.method ?? installMethod(), journal: status.journal, running: status.running, whatsNew: status.whatsNew, rollbackTo: status.lastUpdate?.to === current ? status.lastUpdate.from : null, csrf: who.session.csrf };
|
|
392
432
|
return sendScreen(response, 200, screen("Updates", html `<p><a href="/settings">Settings</a></p><h1>Updates</h1>${(updatesHtml(view, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }))}`, { chrome: chromeFor(project, "settings"), ...(active ? { functional: { script: updatesScript(), fetches: true } } : {}) }));
|
|
393
433
|
}
|
|
394
434
|
// v105: Settings → Retention, how long each kind of data is kept. An instance operator's page.
|
|
@@ -406,9 +446,16 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
406
446
|
const databaseFile = store.databaseFile();
|
|
407
447
|
if (databaseFile === null)
|
|
408
448
|
return refuse(response, who, 403, "An instance operator looks after storage.", "/settings");
|
|
409
|
-
|
|
449
|
+
// The last measurement (docs/plans/get-side-effects.md): the background checks measure the checkouts, and so do
|
|
450
|
+
// Check now and every clean-up; opening the page runs no du or git.
|
|
451
|
+
const measured = lastChecks.known("storage");
|
|
452
|
+
const check = postForm("/settings/storage/check", html `<button type="submit" class="secondary">Check now</button>`, { attrs: { class: "inline" } });
|
|
453
|
+
if (measured === null) {
|
|
454
|
+
return sendScreen(response, 200, screen("Storage", html `<p><a href="/settings">Settings</a></p><h1>Storage</h1><section class="storage"><div class="card storage-state"><h2>Checkouts not measured yet</h2>${check}</div></section>`, { chrome: chromeFor(project, "settings") }));
|
|
455
|
+
}
|
|
456
|
+
const plan = { ...measured.value, cleanup: store.checkoutCleanup() };
|
|
410
457
|
const sweep = { last: lastSweep(store), off: storageSweepOff(), schedule: sweepSchedule(store, now) };
|
|
411
|
-
return sendScreen(response, 200, screen("Storage", html `<p><a href="/settings">Settings</a></p><h1>Storage</h1>${(storageHtml({ plan, csrf: who.session.csrf, sweep, landed: landedSummary(store) }, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }))}`, { chrome: chromeFor(project, "settings") }));
|
|
458
|
+
return sendScreen(response, 200, screen("Storage", html `<p><a href="/settings">Settings</a></p><h1>Storage</h1>${(storageHtml({ plan, csrf: who.session.csrf, sweep, landed: landedSummary(store), measured: html `<p class="meta">Measured ${new Date(measured.at).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" })} ${check}</p>` }, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }))}`, { chrome: chromeFor(project, "settings") }));
|
|
412
459
|
}
|
|
413
460
|
// Settings → Projects → Pull requests: one project's setup. Off, the checks run on open, so the page says
|
|
414
461
|
// exactly what turning on would do, or what to fix first.
|
|
@@ -420,7 +467,8 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
420
467
|
return refuse(response, who, 404, "No such project.", "/projects");
|
|
421
468
|
const publishing = publishingOf(store, repo);
|
|
422
469
|
const canChange = who.role === "approver" && !store.isDemo();
|
|
423
|
-
|
|
470
|
+
// The last setup check for this project (Check setup posts); opening the page asks git and GitHub nothing.
|
|
471
|
+
const check = publishing.on || !canChange ? null : lastChecks.known(`publishing:${repo}`)?.value ?? "unchecked";
|
|
424
472
|
const body = (pullRequestSettingsHtml({ repo, name: projectName(repo), csrf: who.session.csrf, canChange, publishing, check,
|
|
425
473
|
said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }));
|
|
426
474
|
return sendScreen(response, 200, screen("Pull requests", html `<p><a href="/projects">Projects</a></p><h1>Pull requests</h1>${body}`, { chrome: chromeFor(project, "projects") }));
|
|
@@ -508,7 +556,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
508
556
|
const chosen = url.searchParams.get("repo") ?? project ?? projects[0] ?? "";
|
|
509
557
|
if (chosen && (!projects.includes(chosen) || !visible(chosen)))
|
|
510
558
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/knowledge");
|
|
511
|
-
const selector = projects.length > 1 ? html `<form class="
|
|
559
|
+
const selector = projects.length > 1 ? framedSection('Project', html `<form class="field-row" method="get" action="/settings/knowledge"><label><span class="visually-hidden">Project</span><select name="repo">${projects.map(p => html `<option value="${p}"${p === chosen ? ' selected' : ''}>${p.split('/').at(-1) ?? p}</option>`)}</select></label><button>Show project</button></form>`) : '';
|
|
512
560
|
let content = html `<p>Add a project to keep its knowledge here.</p>`;
|
|
513
561
|
if (chosen) {
|
|
514
562
|
try {
|
|
@@ -533,7 +581,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
533
581
|
}
|
|
534
582
|
content = html `<p class="meta">${MEMORY_INTRO}</p>${memorySearchHtml(chosen, memoryQuery, hits)}${proposals}${(decisionsHtml(chosen, listDecisions(store, chosen, who.name, { limit: 50 }), csrf, who.role === 'approver'))}\
|
|
535
583
|
${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf : '')}${knowledgeHtml(view, csrf, who.role === 'approver')}\
|
|
536
|
-
<details class="knowledge"><summary>Learned lessons</summary>${(learningHtml(learningView(store, evidenceRoot, chosen, who.name), csrf, who.role === 'approver'))}</details>`;
|
|
584
|
+
<details class="knowledge frame"><summary class="frame-head">Learned lessons</summary><div class="frame-panel">${(learningHtml(learningView(store, evidenceRoot, chosen, who.name), csrf, who.role === 'approver'))}</div></details>`;
|
|
537
585
|
}
|
|
538
586
|
catch {
|
|
539
587
|
content = html `<p class="problem" role="alert">Project knowledge is unavailable. Reload to retry.</p>`;
|
|
@@ -563,15 +611,13 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
563
611
|
async function leadPage(ctx) {
|
|
564
612
|
const { url, response, now, project } = ctx;
|
|
565
613
|
const who = browserCaller(ctx.who);
|
|
566
|
-
if (runtime.localSignIn === null)
|
|
567
|
-
await Promise.race([checkLocalAgents(), new Promise(done => setTimeout(done, 6_000).unref?.())]);
|
|
568
614
|
const config = store.getChatConfig();
|
|
569
615
|
const facts = {
|
|
570
616
|
keyFacts: ["anthropic-api", "openrouter-api"].map(one => {
|
|
571
617
|
const found = chatKeyFor(one);
|
|
572
618
|
return { provider: one, state: found === null ? "none" : found.source, tail: found === null || found.source === "environment" ? null : redactToken(found.key) };
|
|
573
619
|
}),
|
|
574
|
-
openrouterModels: (
|
|
620
|
+
openrouterModels: chatCatalogKnown()?.map(one => one.id) ?? null,
|
|
575
621
|
liveModels: [...modelOptions(store, "claude", now, providerHome), ...modelOptions(store, "codex", now, providerHome)],
|
|
576
622
|
csrf: who.session.csrf,
|
|
577
623
|
returnTo: "/settings/lead",
|
|
@@ -646,7 +692,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
646
692
|
provider,
|
|
647
693
|
envName: PROVIDER_KEY_ENV[provider],
|
|
648
694
|
...keyStatus(provider, providerHome),
|
|
649
|
-
|
|
695
|
+
...(connectionKnown(provider) === null ? {} : { connection: connectionKnown(provider) }),
|
|
650
696
|
ambient: (process.env[PROVIDER_KEY_ENV[provider]] ?? "") !== "",
|
|
651
697
|
mode: readAuthMode(provider, providerHome) === "api-key" ? "api-key" : "subscription",
|
|
652
698
|
subscriptionCapable: SUBSCRIPTION_CAPABLE[provider],
|
|
@@ -1114,7 +1160,9 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1114
1160
|
const databaseFile = store.databaseFile();
|
|
1115
1161
|
if (databaseFile === null)
|
|
1116
1162
|
return refuse(response, who, 403, "An instance operator looks after storage.", "/settings");
|
|
1117
|
-
|
|
1163
|
+
// What the page shows next is measured again after the change.
|
|
1164
|
+
const said = sayTo(response, "/settings/storage");
|
|
1165
|
+
const back = async (kind, words) => { await measureStorage(databaseFile).catch(() => undefined); return said(kind, words); };
|
|
1118
1166
|
const what = op === "clean" ? "clean up" : op === "discard" ? "discard a checkout" : "change checkout cleanup";
|
|
1119
1167
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1120
1168
|
return back("problem", `Enter your Toolroll password to ${what}.`);
|
|
@@ -2468,6 +2516,11 @@ ${modeWords(terms)
|
|
|
2468
2516
|
"mode": modePage,
|
|
2469
2517
|
"control": controlPage("setup"),
|
|
2470
2518
|
"control.connection": controlPage("connection"),
|
|
2519
|
+
"settings.updates-check-send": updatesCheck,
|
|
2520
|
+
"settings.storage-check-send": storageCheck,
|
|
2521
|
+
"settings.pull-requests-check-send": pullRequestsCheck,
|
|
2522
|
+
"settings.connection-check": connectionCheckNow,
|
|
2523
|
+
"control.models-refresh": modelsRefresh,
|
|
2471
2524
|
"settings.models": modelsPage,
|
|
2472
2525
|
"settings.tools": toolsPage,
|
|
2473
2526
|
"settings.project": projectPage,
|
|
@@ -487,7 +487,6 @@ export function createTaskViews(runtime) {
|
|
|
487
487
|
coordinatorProposals: (() => {
|
|
488
488
|
if (ref === null || ref.repo === null || who.role !== "approver")
|
|
489
489
|
return null;
|
|
490
|
-
runtime.store.sweepCoordinatorProposals(now);
|
|
491
490
|
const rows = runtime.store.listCoordinatorProposals({ repos: [ref.repo], taskId });
|
|
492
491
|
return { rows, decisions: decisionsFor(runtime.store, rows), now };
|
|
493
492
|
})(),
|
|
@@ -779,7 +778,7 @@ export function createTaskViews(runtime) {
|
|
|
779
778
|
return runtime.sendScreen(response, status, screen(admittedFamily.root.title, html `<h1>${admittedFamily.root.title}</h1><p>${admittedFamily.current.state}</p><p class="problem" data-history-problem>${admittedFamily.problem}</p>`, { chrome: runtime.chromeFor(admittedFamily.root.repo, "tasks") }));
|
|
780
779
|
const data = taskViewData(taskId, who, problem);
|
|
781
780
|
if (data === null)
|
|
782
|
-
return refuse(response, who, 404, "no such task", "/
|
|
781
|
+
return refuse(response, who, 404, "no such task", "/work");
|
|
783
782
|
const family = runtime.familyOf(taskId);
|
|
784
783
|
const presentedData = { ...data, rootId: family?.root.id ?? taskId, rootTitle: family?.root.title ?? data.task.title,
|
|
785
784
|
...(family === null || data.assignment != null ? {} : { history: familyHistory(family) }),
|