toolroll 0.7.0 → 0.8.0
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 +413 -0
- package/README.md +15 -0
- package/dist/agent-onboard.d.ts +106 -0
- package/dist/agent-onboard.js +282 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +23 -23
- package/dist/browser-shell.js +5 -3
- package/dist/browser-workspace.d.ts +11 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +8 -1
- package/dist/control-setup.d.ts +4 -4
- package/dist/daemon.d.ts +4 -0
- package/dist/daemon.js +28 -4
- package/dist/desktop-host.js +2 -2
- package/dist/desktop-update.d.ts +8 -2
- package/dist/desktop-update.js +39 -22
- package/dist/evidence.d.ts +24 -0
- package/dist/evidence.js +103 -5
- package/dist/first-run.d.ts +54 -0
- package/dist/first-run.js +101 -0
- package/dist/install-method.d.ts +1 -1
- package/dist/install-method.js +4 -0
- package/dist/operate.d.ts +9 -0
- package/dist/operate.js +68 -4
- package/dist/releases.d.ts +9 -0
- package/dist/releases.js +14 -1
- package/dist/serve.d.ts +16 -0
- package/dist/serve.js +151 -82
- package/dist/skills.d.ts +2 -0
- package/dist/skills.js +3 -1
- package/dist/store.d.ts +3 -3
- package/dist/store.js +9 -4
- package/dist/surface.js +2 -0
- package/dist/toolroll-update-cli.d.ts +15 -0
- package/dist/toolroll-update-cli.js +155 -0
- package/dist/toolroll-update-ui.d.ts +38 -0
- package/dist/toolroll-update-ui.js +89 -0
- package/dist/toolroll-update.d.ts +235 -0
- package/dist/toolroll-update.js +929 -0
- package/package.json +2 -1
package/dist/serve.js
CHANGED
|
@@ -125,7 +125,6 @@ import { openRouterModelsCache, openRouterPickerScript } from "./openrouter-mode
|
|
|
125
125
|
import { checkModels, isNewModel, livePin, modelOptions, modelWords, RUNTIME_TOOLS, runtimeStates, seenModels, setWatch, updateRuntime, watchState } from "./model-catalog.js";
|
|
126
126
|
import { MODELS_CSS, modelsHtml, modelsScript } from "./models-ui.js";
|
|
127
127
|
import { ASSISTANTS, modelChoices, detectPreparation, previewProjectInstructions, addProjectInstructions } from "./setup-guide.js";
|
|
128
|
-
import { projectSkillInstalled } from "./skills.js";
|
|
129
128
|
import { previewSetup, approveSetup } from "./control-setup.js";
|
|
130
129
|
import { controlSetupHtml, setupPreviewHtml, connectionHtml, connectionWords, hiddenFields } from "./control-ui.js";
|
|
131
130
|
import { composerSchedule, scheduleEditorHtml, scheduleEditorScript } from "./task-composer.js";
|
|
@@ -171,6 +170,7 @@ import { parseGithubRepo, previewGithubRepo, cloneGithubRepo, listGithubRepos, i
|
|
|
171
170
|
import { verifiedAuthor, LEAD_THREAD } from "./store.js";
|
|
172
171
|
import { updateRepos, addRepos, removeRepos } from "./repos.js";
|
|
173
172
|
import { run as execRun } from "./exec.js";
|
|
173
|
+
import { findFirstTasks, firstResultWords, firstRunSteps, firstTaskSuggestions, SANDBOX_COMMAND } from "./first-run.js";
|
|
174
174
|
/** A user agent, reduced to safe display words — never echoed raw. */
|
|
175
175
|
function oneLineUa(raw) {
|
|
176
176
|
const text = Array.isArray(raw) ? (raw[0] ?? "") : (raw ?? "");
|
|
@@ -206,6 +206,9 @@ import { BACKUP_EVERY_HOURS, MAX_KEEP, backupFolderOf, backupNow, checkBackupFol
|
|
|
206
206
|
import { prometheusMetrics } from "./metrics.js";
|
|
207
207
|
import { SPEND_CSS, spendCsv, spendHtml } from "./spend-ui.js";
|
|
208
208
|
import { RETENTION_CSS, retentionHtml } from "./retention-ui.js";
|
|
209
|
+
import { UPDATES_CSS, newerThan, updateStepsHtml, updatesHtml, updatesScript } from "./toolroll-update-ui.js";
|
|
210
|
+
import { abandonRuntimeUpdate, launchRuntimeUpdate, markWhatsNewSeen, prepareRuntimeUpdate, requestRuntimeUpdateCancel, runningWorkWords, runtimeUpdateStatus, runtimeUpdateTerminal, currentRuntime } from "./toolroll-update.js";
|
|
211
|
+
import { latestVersionNow } from "./releases.js";
|
|
209
212
|
import { RETENTION_KINDS, lastSweepAt, parsePeriod, retentionPlan } from "./retention.js";
|
|
210
213
|
import { EXPORT_CSS, dataExportHtml } from "./export-ui.js";
|
|
211
214
|
import { buildExport, exportZip } from "./export.js";
|
|
@@ -1880,7 +1883,7 @@ export function createDecisionServer(options) {
|
|
|
1880
1883
|
})),
|
|
1881
1884
|
cancelledBlockers: cancelled,
|
|
1882
1885
|
gaps: project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0).slice(0, 10),
|
|
1883
|
-
wizard:
|
|
1886
|
+
wizard: await firstRunStepsNow(now),
|
|
1884
1887
|
worker: (() => {
|
|
1885
1888
|
// The one fact the inbox must never hide (install review): with
|
|
1886
1889
|
// no worker answering, nothing here will ever build, and every
|
|
@@ -3373,10 +3376,13 @@ export function createDecisionServer(options) {
|
|
|
3373
3376
|
// A failed briefing query must not make the conversation vanish.
|
|
3374
3377
|
}
|
|
3375
3378
|
}
|
|
3379
|
+
// The first run belongs to the lead conversation, for someone who can act on it.
|
|
3380
|
+
const firstRun = focusTask === null && who.role === "approver" ? await chatFirstRun(now, chatProject) : undefined;
|
|
3381
|
+
const withFirstRun = (shown) => firstRun === undefined ? shown : { ...shown, workspace: { ...shown.workspace, firstRun } };
|
|
3376
3382
|
if (enabled.ok && mateSession !== null && principal !== null && !ceilingStale) {
|
|
3377
3383
|
{
|
|
3378
3384
|
const said = takeMateNote(who.session.csrf, mateSession.id);
|
|
3379
|
-
return sendScreen(response, 200, matePage(chromeFor(null, "chat", undefined, "all"), {
|
|
3385
|
+
return sendScreen(response, 200, withFirstRun(matePage(chromeFor(null, "chat", undefined, "all"), {
|
|
3380
3386
|
session: mateSession,
|
|
3381
3387
|
...mateConversationRows(who, principal, focusTask, now, chatProject),
|
|
3382
3388
|
chatProject,
|
|
@@ -3394,10 +3400,10 @@ export function createDecisionServer(options) {
|
|
|
3394
3400
|
now,
|
|
3395
3401
|
resultPanel,
|
|
3396
3402
|
resultRunId: resultRun?.id ?? null,
|
|
3397
|
-
}));
|
|
3403
|
+
})));
|
|
3398
3404
|
}
|
|
3399
3405
|
}
|
|
3400
|
-
return sendScreen(response, 200, chatPage(chromeFor(null, "chat", undefined, "all"), {
|
|
3406
|
+
return sendScreen(response, 200, withFirstRun(chatPage(chromeFor(null, "chat", undefined, "all"), {
|
|
3401
3407
|
settingsOpen: settingsView,
|
|
3402
3408
|
enabled,
|
|
3403
3409
|
pending,
|
|
@@ -3430,7 +3436,7 @@ export function createDecisionServer(options) {
|
|
|
3430
3436
|
resultPanel,
|
|
3431
3437
|
...(enabled.ok && who.role === "approver" ? { mateMint: mateMintCard(who.session.csrf, enabled, focusTask === null ? "/chat" : taskChatHref(focusTask.id)) } : {}),
|
|
3432
3438
|
...(who.role === "approver" ? { coordinatorProposals: coordinatorProposalsSection(coordinatorRows, decisionsFor(store, coordinatorRows), who.session.csrf, now, true, focusTask === null ? null : taskChatHref(focusTask.id)) } : {}),
|
|
3433
|
-
}));
|
|
3439
|
+
})));
|
|
3434
3440
|
}
|
|
3435
3441
|
const chatAck = /^\/chat\/ack\/([0-9]{1,15})$/.exec(url.pathname);
|
|
3436
3442
|
if (chatAck !== null) {
|
|
@@ -3617,6 +3623,21 @@ export function createDecisionServer(options) {
|
|
|
3617
3623
|
const view = { settings: readMonitoring(options.configDir), status: store.monitoringStatus(), head: store.ledgerHeadId(), origin: consoleOrigin(request.headers.host) };
|
|
3618
3624
|
return sendScreen(response, 200, screen("Monitoring", `<p><a href="/settings">Settings</a></p><h1>Monitoring</h1>${monitoringHtml(view, who.session.csrf, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") })}`, { chrome: chromeFor(project, "settings") }));
|
|
3619
3625
|
}
|
|
3626
|
+
// Settings → Updates: the installed version, three ways to update, the update's steps live, then What's new. An instance operator's page.
|
|
3627
|
+
if (url.pathname === "/settings/updates") {
|
|
3628
|
+
const databaseFile = store.databaseFile();
|
|
3629
|
+
if (who.via !== "cookie" || !store.isInstanceOperator(who.name) || databaseFile === null)
|
|
3630
|
+
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
3631
|
+
const status = runtimeUpdateStatus(dirname(databaseFile));
|
|
3632
|
+
if (url.searchParams.get("fragment") === "steps")
|
|
3633
|
+
return respond(response, 200, "text/html; charset=utf-8", status.journal ? updateStepsHtml(status.journal, status.running) : `<div id="update-live" data-done="1"></div>`);
|
|
3634
|
+
const active = status.journal !== null && !runtimeUpdateTerminal(status.journal.phase);
|
|
3635
|
+
const current = options.updates?.current ?? PACKAGE_VERSION;
|
|
3636
|
+
// While update checks are off the page asks npm nothing until Check now.
|
|
3637
|
+
const checksOff = updateChecksOff(process.env, options.configDir ?? dirname(databaseFile)).off && url.searchParams.get("check") !== "now";
|
|
3638
|
+
const view = { current, latest: active ? { version: current } : checksOff ? { off: true } : await latestReleaseFor(options.updates?.latest), method: options.updates?.method ?? installMethod(), journal: status.journal, running: status.running, whatsNew: status.whatsNew, csrf: who.session.csrf };
|
|
3639
|
+
return sendScreen(response, 200, screen("Updates", `<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 } } : {}) }));
|
|
3640
|
+
}
|
|
3620
3641
|
// v105: Settings → Retention, how long each kind of data is kept. An instance operator's page.
|
|
3621
3642
|
if (url.pathname === "/settings/retention") {
|
|
3622
3643
|
if (who.via !== "cookie" || !store.isInstanceOperator(who.name))
|
|
@@ -3794,7 +3815,11 @@ export function createDecisionServer(options) {
|
|
|
3794
3815
|
const googleView = { connected: googleConnected(options.configDir ?? null)?.address ?? null, clientId: google?.clientId ?? "", redirect: origin === null ? null : `${origin}${GOOGLE_CALLBACK}` };
|
|
3795
3816
|
return email === null ? { set: false, host: "", port: 587, secure: false, user: "", from: "", imapHost: "", imapPort: 993, google: googleView }
|
|
3796
3817
|
: { set: true, host: email.host, port: email.port, secure: email.secure, user: email.user, from: email.from, imapHost: email.imap?.host ?? "", imapPort: email.imap?.port ?? 993, google: googleView };
|
|
3797
|
-
})() : null, telegramDeliveryWords(store, loadBotToken(process.env, options.telegramTokenFile)), settingsWorkers(store, now), settingsUpdates(who.name, csrf))
|
|
3818
|
+
})() : null, telegramDeliveryWords(store, loadBotToken(process.env, options.telegramTokenFile)), settingsWorkers(store, now), settingsUpdates(who.name, csrf), (() => {
|
|
3819
|
+
// The installation fact: when the first Ready result arrived, measured from the first account.
|
|
3820
|
+
const at = store.firstSuccessAt(now), since = store.installationStartedAt();
|
|
3821
|
+
return at === null || since === null ? null : firstResultWords(since, at);
|
|
3822
|
+
})()));
|
|
3798
3823
|
}
|
|
3799
3824
|
if (url.pathname === "/push/key") {
|
|
3800
3825
|
// Session-gated: the key is not secret, but strangers get nothing.
|
|
@@ -3834,62 +3859,46 @@ export function createDecisionServer(options) {
|
|
|
3834
3859
|
return refuse(response, who, 404, "There's no page at this address.", "/chat");
|
|
3835
3860
|
}
|
|
3836
3861
|
/**
|
|
3837
|
-
* The first
|
|
3838
|
-
* state on every render, never a stored cursor, and retired
|
|
3839
|
-
* by the first-success installation fact (Codex adoption
|
|
3840
|
-
* finding 14).
|
|
3841
|
-
*
|
|
3842
|
-
* checklist instructs, it never gains authority (finding e).
|
|
3862
|
+
* The first run (adoption track, step 3): three plain steps derived from
|
|
3863
|
+
* live state on every render, never a stored cursor, and retired
|
|
3864
|
+
* PERMANENTLY by the first-success installation fact (Codex adoption
|
|
3865
|
+
* review, finding 14). Each step is either something this console can do,
|
|
3866
|
+
* or the exact command where only the CLI can (finding e).
|
|
3843
3867
|
*/
|
|
3844
|
-
function
|
|
3845
|
-
if (restricted() || store.firstSuccessAt(now) !== null)
|
|
3868
|
+
async function firstRunStepsNow(now) {
|
|
3869
|
+
if (restricted() || store.isDemo() || store.firstSuccessAt(now) !== null)
|
|
3846
3870
|
return null;
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
{
|
|
3879
|
-
done: repos.length > 0 && skillDone === repos.length,
|
|
3880
|
-
title: "teach the repo's agents this queue exists",
|
|
3881
|
-
detail: skillDone > 0
|
|
3882
|
-
? `skill installed${counted(skillDone)}`
|
|
3883
|
-
: `<code>toolroll skills install --repo <path></code> previews; add <code>--yes</code> to write the skill file`,
|
|
3884
|
-
},
|
|
3885
|
-
{
|
|
3886
|
-
done: store.hasAnyWork(),
|
|
3887
|
-
title: "file the first standing order",
|
|
3888
|
-
detail: store.hasAnyWork()
|
|
3889
|
-
? `work is filed — approve its scope and the machine takes it from there`
|
|
3890
|
-
: `start from a template below, capture a one-off task underneath, or browse <a href="/routines">routines</a>`,
|
|
3891
|
-
},
|
|
3892
|
-
];
|
|
3871
|
+
return firstRunSteps({ agentSignedIn: await agentSignedIn(), projects: managedRepos().length, hasTask: store.hasAnyWork(), firstResultAt: null });
|
|
3872
|
+
}
|
|
3873
|
+
/** Whether any coding agent on this computer is signed in: the same
|
|
3874
|
+
* non-spending checks Settings makes, cached briefly. */
|
|
3875
|
+
async function agentSignedIn() {
|
|
3876
|
+
const states = await Promise.all(["claude", "codex"].map(one => connectionCheck(one).then(value => value.state, () => "unverified")));
|
|
3877
|
+
return states.some(state => state === "connected" || state === "key-works" || state === "key-present");
|
|
3878
|
+
}
|
|
3879
|
+
/** First tasks per project, read at most every ten minutes: a `gh`
|
|
3880
|
+
* round trip and a `git grep` never run on every render. */
|
|
3881
|
+
const firstTasks = new Map();
|
|
3882
|
+
function firstTasksFor(repo) {
|
|
3883
|
+
const at = Date.now(), hit = firstTasks.get(repo);
|
|
3884
|
+
if (hit !== undefined && hit.until > at)
|
|
3885
|
+
return hit.value;
|
|
3886
|
+
const value = findFirstTasks(repo, options.firstTaskRunner ?? execRun).catch(() => firstTaskSuggestions({ issues: [], todos: [] }));
|
|
3887
|
+
firstTasks.set(repo, { until: at + 10 * 60_000, value });
|
|
3888
|
+
return value;
|
|
3889
|
+
}
|
|
3890
|
+
/** Chat's first run: the steps, first tasks until the first task exists, and the sandbox while no agent is signed in. */
|
|
3891
|
+
async function chatFirstRun(now, project) {
|
|
3892
|
+
const steps = await firstRunStepsNow(now);
|
|
3893
|
+
if (steps === null)
|
|
3894
|
+
return undefined;
|
|
3895
|
+
const taskFiled = steps.find(one => one.key === "task")?.done ?? true;
|
|
3896
|
+
const repo = project ?? managedRepos()[0] ?? null;
|
|
3897
|
+
return {
|
|
3898
|
+
steps,
|
|
3899
|
+
suggestions: taskFiled || repo === null ? [] : await firstTasksFor(repo),
|
|
3900
|
+
sandbox: steps.find(one => one.key === "agent")?.done === false ? SANDBOX_COMMAND : null,
|
|
3901
|
+
};
|
|
3893
3902
|
}
|
|
3894
3903
|
// ---- fleet chat (v13) ----------------------------------------------------
|
|
3895
3904
|
const chatFetcher = options.chatFetcher ?? fetch;
|
|
@@ -4473,6 +4482,7 @@ export function createDecisionServer(options) {
|
|
|
4473
4482
|
...(s.refreshSeconds === undefined ? {} : { refreshSeconds: Math.max(5, Math.floor(s.refreshSeconds)) }),
|
|
4474
4483
|
...(s.chrome.signIn === undefined ? {} : { signIn: s.chrome.signIn }),
|
|
4475
4484
|
...(s.chrome.update === undefined ? {} : { update: s.chrome.update }),
|
|
4485
|
+
...(extras.firstRun === undefined ? {} : { firstRun: extras.firstRun }),
|
|
4476
4486
|
};
|
|
4477
4487
|
if (requestFacts.workspaceRead) {
|
|
4478
4488
|
const validator = requestFacts.workspaceValidator;
|
|
@@ -5808,8 +5818,8 @@ export function createDecisionServer(options) {
|
|
|
5808
5818
|
}
|
|
5809
5819
|
return redirect(response, safeReturn(body.get("return") ?? "/settings#updates"));
|
|
5810
5820
|
}
|
|
5811
|
-
// The daily check's switch: an installation setting, so an operator's.
|
|
5812
|
-
if (url.pathname === "/settings/updates") {
|
|
5821
|
+
// The daily check's switch: an installation setting, so an operator's. (Its own address: POST /settings/updates starts an update.)
|
|
5822
|
+
if (url.pathname === "/settings/updates/checks") {
|
|
5813
5823
|
if (options.configDir === undefined)
|
|
5814
5824
|
return refuse(response, who, 404, "Updates are not set up on this console.", "/settings");
|
|
5815
5825
|
if (!store.isInstanceOperator(who.name))
|
|
@@ -6522,6 +6532,52 @@ export function createDecisionServer(options) {
|
|
|
6522
6532
|
store.recordAction({ at: now.toISOString(), actor: who.name, repo: null, taskId: null, runId: null, action: before === null ? "sign-in with a provider turned on" : "sign-in with a provider changed", outcome: "changed", source: "policy", detail: ssoChangeWords(before, saved.settings) });
|
|
6523
6533
|
return found.ok ? back("said", `Saved. People can sign in with ${saved.settings.label}.`) : back("problem", `Saved, but ${saved.settings.label} didn't answer: ${found.said}`);
|
|
6524
6534
|
}
|
|
6535
|
+
// Settings → Updates: start the `toolroll update` job. A step-up; the job writes the ledger entries.
|
|
6536
|
+
if (url.pathname === "/settings/updates" || url.pathname === "/settings/updates/seen" || url.pathname === "/settings/updates/cancel") {
|
|
6537
|
+
const databaseFile = store.databaseFile();
|
|
6538
|
+
if (who.via !== "cookie" || !store.isInstanceOperator(who.name) || databaseFile === null)
|
|
6539
|
+
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
6540
|
+
const stateDir = dirname(databaseFile);
|
|
6541
|
+
const back = (key, words) => redirect(response, `/settings/updates?${key}=${encodeURIComponent(words)}`);
|
|
6542
|
+
if (url.pathname === "/settings/updates/seen") {
|
|
6543
|
+
markWhatsNewSeen(stateDir);
|
|
6544
|
+
return redirect(response, "/settings/updates");
|
|
6545
|
+
}
|
|
6546
|
+
if (url.pathname === "/settings/updates/cancel")
|
|
6547
|
+
return back("said", requestRuntimeUpdateCancel(stateDir));
|
|
6548
|
+
if (!authenticateApprover(store, who.name, body.get("password") ?? "").ok)
|
|
6549
|
+
return back("problem", "Enter your Toolroll password to update.");
|
|
6550
|
+
const version = body.get("version") ?? "";
|
|
6551
|
+
const when = { now: "now", "when-idle": "when-idle", tonight: "at" }[body.get("when") ?? ""] ?? null;
|
|
6552
|
+
if (!/^\d+\.\d+\.\d+$/.test(version) || when === null)
|
|
6553
|
+
return back("problem", "Choose when to update.");
|
|
6554
|
+
const current = options.updates?.current ?? PACKAGE_VERSION;
|
|
6555
|
+
if (!newerThan(version, current))
|
|
6556
|
+
return back("problem", `Toolroll ${version} is not newer than ${current}. To go back, run toolroll update --rollback.`);
|
|
6557
|
+
const method = options.updates?.method ?? installMethod();
|
|
6558
|
+
if (method.kind === "npx" || method.kind === "source")
|
|
6559
|
+
return back("problem", method.kind === "npx" ? "npx runs the latest release each time; there is nothing to update." : "This runs from a source checkout. Update it with git.");
|
|
6560
|
+
const before = runtimeUpdateStatus(stateDir).journal;
|
|
6561
|
+
if (before !== null && !runtimeUpdateTerminal(before.phase))
|
|
6562
|
+
return back("problem", "An update is already under way.");
|
|
6563
|
+
if (when === "now") {
|
|
6564
|
+
const running = runningWorkWords(store.raw());
|
|
6565
|
+
if (running !== null)
|
|
6566
|
+
return back("problem", `Work is running: ${running}. Choose When idle to update once it finishes.`);
|
|
6567
|
+
}
|
|
6568
|
+
const prepared = prepareRuntimeUpdate({ stateDir, databaseFile, current: { version: current, dist: options.updates?.dist ?? currentRuntime(current).dist }, actor: who.name, version, when, at: "03:00" }, clock());
|
|
6569
|
+
if ("refused" in prepared)
|
|
6570
|
+
return back("problem", prepared.refused);
|
|
6571
|
+
try {
|
|
6572
|
+
await (options.updates?.launch ?? launchRuntimeUpdate)({ databaseFile, id: prepared.id });
|
|
6573
|
+
}
|
|
6574
|
+
catch (error) {
|
|
6575
|
+
abandonRuntimeUpdate(stateDir, prepared.id, `The update could not start: ${error.message}`, clock());
|
|
6576
|
+
return back("problem", `The update could not start: ${error.message}`);
|
|
6577
|
+
}
|
|
6578
|
+
// The job records its first step within moments; show it rather than the old page.
|
|
6579
|
+
return back("said", when === "at" ? `Update to ${version} scheduled for 03:00.` : `Updating to ${version}.`);
|
|
6580
|
+
}
|
|
6525
6581
|
// v105: change how long each kind of data is kept. A step-up; the ledger keeps before → after for each kind.
|
|
6526
6582
|
if (url.pathname === "/settings/retention") {
|
|
6527
6583
|
if (who.via !== "cookie" || !store.isInstanceOperator(who.name))
|
|
@@ -13984,7 +14040,7 @@ button.pick-file { min-height: 1.75rem; padding: 0 .55rem; font-size: .75rem; }
|
|
|
13984
14040
|
const THEME_CONTROLS_CSS = `.task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-warning-soft);color:var(--so-warning)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem;margin:0 0 2rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed="true"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}`;
|
|
13985
14041
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
13986
14042
|
export const PAGE_CSS = STYLE + THEME_CONTROLS_CSS;
|
|
13987
|
-
const WORKSPACE_STYLE = styleAsset(STYLE + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + LIMITS_CSS + MONITORING_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + SSO_CSS + CREDENTIALS_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + LEAD_CONTEXT_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
|
|
14043
|
+
const WORKSPACE_STYLE = styleAsset(STYLE + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + SSO_CSS + CREDENTIALS_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + LEAD_CONTEXT_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
|
|
13988
14044
|
/**
|
|
13989
14045
|
* The board's liveness: fetch this page's own fragment on a timer and swap
|
|
13990
14046
|
* it in place — no flicker, no scroll reset, no long-lived stream to manage,
|
|
@@ -14635,26 +14691,20 @@ function inboxPage(chrome, data) {
|
|
|
14635
14691
|
`<span class="meta">frees ${gap.unblocks.length} task${gap.unblocks.length > 1 ? "s" : ""}</span>` +
|
|
14636
14692
|
`<span class="right meta">how to fix \u2192</span></p>`)
|
|
14637
14693
|
.join("\n");
|
|
14638
|
-
// The first-run
|
|
14639
|
-
//
|
|
14640
|
-
// whole card retires permanently on the first successful run.
|
|
14694
|
+
// The first-run steps, until the first Ready result: each is done or
|
|
14695
|
+
// shows the one action that does it.
|
|
14641
14696
|
const wizard = data.wizard === null
|
|
14642
14697
|
? ""
|
|
14643
|
-
: `<div class="card">` +
|
|
14644
|
-
`<p><strong>
|
|
14645
|
-
`<p class="meta">Keep <span class="mono">toolroll up</span> running on this machine \u2014 it opens the app and reconnects every saved project's builder.</p>` +
|
|
14698
|
+
: `<div class="card" data-first-run>` +
|
|
14699
|
+
`<p><strong>Get to your first result</strong></p>` +
|
|
14646
14700
|
data.wizard
|
|
14647
|
-
.map(step => `<p class="row"><span class="mono">${step.done ? "\u2713" : "\u25cb"}</span> <strong>${escape(step.title)}</strong
|
|
14648
|
-
|
|
14701
|
+
.map(step => `<p class="row" data-step="${step.key}"><span class="mono" aria-hidden="true">${step.done ? "\u2713" : "\u25cb"}</span> <strong>${escape(step.title)}</strong>` +
|
|
14702
|
+
(step.action === null ? ` <span class="meta">done</span>`
|
|
14703
|
+
: step.action.kind === "link" ? ` <a href="${escape(step.action.href)}">${escape(step.action.label)}</a>`
|
|
14704
|
+
: ` <code>${escape(step.action.command)}</code>`) +
|
|
14705
|
+
`</p>`)
|
|
14649
14706
|
.join("\n") +
|
|
14650
|
-
|
|
14651
|
-
`<p class="meta">templates \u2014 edit, then approve; nothing a template files carries authority: ` +
|
|
14652
|
-
TEMPLATES.map(one => one.kind === "routine"
|
|
14653
|
-
? `<a href="/routines?template=${escape(one.name)}">${escape(one.name)}</a>`
|
|
14654
|
-
: one.kind === "task"
|
|
14655
|
-
? `<a href="/tasks?template=${escape(one.name)}">${escape(one.name)}</a>`
|
|
14656
|
-
: `<span title="a recipe \u2014 walk it in the terminal: toolroll template show ${escape(one.name)}">${escape(one.name)} (recipe)</span>`).join(" \u00b7 ") +
|
|
14657
|
-
`</p></div>`;
|
|
14707
|
+
`</div>`;
|
|
14658
14708
|
const noWorker = data.worker.answering > 0
|
|
14659
14709
|
? ""
|
|
14660
14710
|
: `<div class="card builder-notice" data-builder-status="${data.worker.registered === 0 ? "not-connected" : "disconnected"}">` +
|
|
@@ -21413,7 +21463,7 @@ function telegramDeliveryWords(store, bot) {
|
|
|
21413
21463
|
return `Telegram pushes new messages to ${state.url}${state.problem === null ? "." : `, but ${state.problem.charAt(0).toLowerCase()}${state.problem.slice(1)}.`}`;
|
|
21414
21464
|
return `Toolroll asks Telegram for new messages every few seconds${state?.problem ? ` (${state.problem})` : ""}.`;
|
|
21415
21465
|
}
|
|
21416
|
-
function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging = null, push = null, providerKeys = null, digest = null, permissionDefault = null, qualityDefault = null, email = null, telegramDelivery = null, workers = null, updates = null) {
|
|
21466
|
+
function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging = null, push = null, providerKeys = null, digest = null, permissionDefault = null, qualityDefault = null, email = null, telegramDelivery = null, workers = null, updates = null, firstResult = null) {
|
|
21417
21467
|
const permissionCard = permissionDefault === null
|
|
21418
21468
|
? ""
|
|
21419
21469
|
: [
|
|
@@ -21610,6 +21660,7 @@ function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging = null,
|
|
|
21610
21660
|
email,
|
|
21611
21661
|
workers,
|
|
21612
21662
|
updates,
|
|
21663
|
+
firstResult,
|
|
21613
21664
|
};
|
|
21614
21665
|
return screen("Settings", [
|
|
21615
21666
|
"<h1>Settings</h1>",
|
|
@@ -21620,6 +21671,7 @@ function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging = null,
|
|
|
21620
21671
|
keysCard,
|
|
21621
21672
|
workersCard(workers),
|
|
21622
21673
|
updatesCard(updates, csrf),
|
|
21674
|
+
firstResult === null ? "" : `<p class="meta" data-first-result>${escape(firstResult)}</p>`,
|
|
21623
21675
|
pushCard === "" && messagingCard === "" && digestCard === "" ? "" : `<h2>Notifications</h2>`,
|
|
21624
21676
|
messagingCard,
|
|
21625
21677
|
pushCard,
|
|
@@ -21686,7 +21738,7 @@ function updatesCard(updates, csrf) {
|
|
|
21686
21738
|
(latest.notes === "" ? "" : `<details class="settings-more"><summary>What's new in ${escape(latest.version)}</summary><pre class="update-notes">${escape(latest.notes)}</pre></details>`)
|
|
21687
21739
|
: "") +
|
|
21688
21740
|
(updates.check.canManage && !updates.check.byEnv
|
|
21689
|
-
? `<form method="post" action="/settings/updates" class="inline"><input type="hidden" name="csrf" value="${escape(csrf)}"><input type="hidden" name="check" value="${updates.check.on ? "off" : "on"}"><button type="submit">${updates.check.on ? "Turn off daily check" : "Turn on daily check"}</button></form>`
|
|
21741
|
+
? `<form method="post" action="/settings/updates/checks" class="inline"><input type="hidden" name="csrf" value="${escape(csrf)}"><input type="hidden" name="check" value="${updates.check.on ? "off" : "on"}"><button type="submit">${updates.check.on ? "Turn off daily check" : "Turn on daily check"}</button></form>`
|
|
21690
21742
|
: updates.check.byEnv ? `<p class="meta">Off by TOOLROLL_NO_UPDATE_CHECK.</p>` : "") +
|
|
21691
21743
|
(updates.workers.length === 0 ? "" : `<ul>${updates.workers.map(one => `<li data-worker-version="${escape(one.name)}">${escape(one.name)} <span class="mono">${escape(one.version ?? "unknown")}</span>${one.older ? " \u00b7 older" : ""}</li>`).join("")}</ul>`) +
|
|
21692
21744
|
`</section>`;
|
|
@@ -21694,6 +21746,22 @@ function updatesCard(updates, csrf) {
|
|
|
21694
21746
|
/** Choices save the moment they change; without the script the Save
|
|
21695
21747
|
* button stays and the form works the same. */
|
|
21696
21748
|
const SETTINGS_AUTOSAVE_SCRIPT = `(function(){document.querySelectorAll('form[data-autosave]').forEach(function(form){form.classList.add('js-autosave');form.addEventListener('change',function(ev){var t=ev.target;if(t&&(t.type==='radio'||t.tagName==='SELECT')){if(form.requestSubmit)form.requestSubmit();else form.submit();}});});})();`;
|
|
21749
|
+
/** The newest release, remembered for a while so the page stays quick. */
|
|
21750
|
+
let latestSeen = null;
|
|
21751
|
+
async function latestReleaseFor(latest) {
|
|
21752
|
+
if (latest === undefined && latestSeen !== null && Date.now() - latestSeen.at < ("problem" in latestSeen.value ? 60_000 : 900_000))
|
|
21753
|
+
return latestSeen.value;
|
|
21754
|
+
let value;
|
|
21755
|
+
try {
|
|
21756
|
+
value = { version: (await (latest ?? latestVersionNow)()).version };
|
|
21757
|
+
}
|
|
21758
|
+
catch (error) {
|
|
21759
|
+
value = { problem: error.message };
|
|
21760
|
+
}
|
|
21761
|
+
if (latest === undefined)
|
|
21762
|
+
latestSeen = { at: Date.now(), value };
|
|
21763
|
+
return value;
|
|
21764
|
+
}
|
|
21697
21765
|
/** Settings destinations as a scannable grid: an icon and a name each. */
|
|
21698
21766
|
function settingsTiles() {
|
|
21699
21767
|
const tiles = SETTINGS_TILE_ICONS;
|
|
@@ -21716,6 +21784,7 @@ const SETTINGS_TILE_ICONS = [
|
|
|
21716
21784
|
["/settings/approval", "Approval rules", `<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9 12 2 2 4-4"/>`],
|
|
21717
21785
|
["/settings/monitoring", "Monitoring", `<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>`],
|
|
21718
21786
|
["/settings/retention", "Retention", `<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>`],
|
|
21787
|
+
["/settings/updates", "Updates", `<path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/>`],
|
|
21719
21788
|
["/settings/backups", "Backups", `<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>`],
|
|
21720
21789
|
["/settings/data", "Data", `<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>`],
|
|
21721
21790
|
];
|
package/dist/skills.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ export declare function applyInstall(repo: string, writeContext: boolean): Insta
|
|
|
68
68
|
*/
|
|
69
69
|
export declare const CLAUDE_CODE_MANAGED_MARK = "<!-- standing-orders:claude-code-skill:v1 -->";
|
|
70
70
|
export declare const CLAUDE_CODE_GUIDES: readonly ["console", "operating", "runner"];
|
|
71
|
+
/** The thin user-level skill `toolroll onboard` writes (agent-onboard.ts). */
|
|
72
|
+
export declare const OPERATOR_SKILL_MARK = "<!-- toolroll:operator-skill:v1";
|
|
71
73
|
export type ClaudeCodeSkillFile = {
|
|
72
74
|
name: string;
|
|
73
75
|
path: string;
|
package/dist/skills.js
CHANGED
|
@@ -162,6 +162,8 @@ export function applyInstall(repo, writeContext) {
|
|
|
162
162
|
*/
|
|
163
163
|
export const CLAUDE_CODE_MANAGED_MARK = "<!-- standing-orders:claude-code-skill:v1 -->";
|
|
164
164
|
export const CLAUDE_CODE_GUIDES = ["console", "operating", "runner"];
|
|
165
|
+
/** The thin user-level skill `toolroll onboard` writes (agent-onboard.ts). */
|
|
166
|
+
export const OPERATOR_SKILL_MARK = "<!-- toolroll:operator-skill:v1";
|
|
165
167
|
export function defaultClaudeCodeSkillDir(home = homedir()) {
|
|
166
168
|
return join(home, ".claude", "skills", "toolroll");
|
|
167
169
|
}
|
|
@@ -232,7 +234,7 @@ function claudeCodeFiles(directory) {
|
|
|
232
234
|
}
|
|
233
235
|
function isClaudeCodeManaged(name, content) {
|
|
234
236
|
if (name === "SKILL.md") {
|
|
235
|
-
return ownHeader(content) && content.includes(`\n${CLAUDE_CODE_MANAGED_MARK}\n`);
|
|
237
|
+
return ownHeader(content) && (content.includes(`\n${CLAUDE_CODE_MANAGED_MARK}\n`) || content.includes(`\n${OPERATOR_SKILL_MARK} `));
|
|
236
238
|
}
|
|
237
239
|
return content.startsWith(`${CLAUDE_CODE_MANAGED_MARK}\n\n`);
|
|
238
240
|
}
|
package/dist/store.d.ts
CHANGED
|
@@ -4344,9 +4344,9 @@ export declare class Store {
|
|
|
4344
4344
|
* checklist (adoption review, finding 14).
|
|
4345
4345
|
*/
|
|
4346
4346
|
firstSuccessAt(now: Date): string | null;
|
|
4347
|
-
/**
|
|
4348
|
-
*
|
|
4349
|
-
|
|
4347
|
+
/** When this installation began: its first account. Accounts are never
|
|
4348
|
+
* deleted (revocation is a stamp), so this never moves. */
|
|
4349
|
+
installationStartedAt(): string | null;
|
|
4350
4350
|
/** The jump palette's food: open (queued/running/failed) tasks, admission
|
|
4351
4351
|
* inside the query before its LIMIT, newest first. */
|
|
4352
4352
|
paletteTasks(repo: string | null, limit: number, admitted?: string[] | null, includeUnplaced?: boolean): {
|
package/dist/store.js
CHANGED
|
@@ -14410,10 +14410,11 @@ export class Store {
|
|
|
14410
14410
|
this.recordInstallationFact("first-success-at", when, now);
|
|
14411
14411
|
return when;
|
|
14412
14412
|
}
|
|
14413
|
-
/**
|
|
14414
|
-
*
|
|
14415
|
-
|
|
14416
|
-
|
|
14413
|
+
/** When this installation began: its first account. Accounts are never
|
|
14414
|
+
* deleted (revocation is a stamp), so this never moves. */
|
|
14415
|
+
installationStartedAt() {
|
|
14416
|
+
const row = this.db.prepare("SELECT MIN(added_at) AS at FROM approver").get();
|
|
14417
|
+
return row === undefined || row["at"] == null ? null : String(row["at"]);
|
|
14417
14418
|
}
|
|
14418
14419
|
/** The jump palette's food: open (queued/running/failed) tasks, admission
|
|
14419
14420
|
* inside the query before its LIMIT, newest first. */
|
|
@@ -15936,6 +15937,10 @@ export class Store {
|
|
|
15936
15937
|
// stop after an orphan or held supervisor finishes shutdown.
|
|
15937
15938
|
this.recordRunProcessExits(id, result.now);
|
|
15938
15939
|
this.settleRunStop(id, result.stopSettlement ?? "finished", result.now);
|
|
15940
|
+
// The first Ready result is an installation fact the moment it lands,
|
|
15941
|
+
// not only when something later looks for it.
|
|
15942
|
+
if (result.outcome === "built" || result.outcome === "no-change")
|
|
15943
|
+
this.firstSuccessAt(result.now);
|
|
15939
15944
|
// The park rate is *measured* — parked over concluded builder attempts —
|
|
15940
15945
|
// and maintained where attempts conclude, because the attention budget's
|
|
15941
15946
|
// gate reads it inside a claim transaction and must never trust a number
|
package/dist/surface.js
CHANGED
|
@@ -105,6 +105,7 @@ export const COMMAND_GUIDE = [
|
|
|
105
105
|
},
|
|
106
106
|
operator("link", "put toolroll on PATH"),
|
|
107
107
|
operator("unlink", "take it off PATH"),
|
|
108
|
+
operator("update", "update to the latest release: verified provenance, drained work, backup, rehearsal, health check, automatic restore; --rollback undoes it"),
|
|
108
109
|
operator("demo", "a seeded throwaway sandbox"),
|
|
109
110
|
{ invocation: 'session capabilities', synopsis: 'Show executable session schemas and operator authority requirements; no credentials needed', audience: 'agent', agentMayInvoke: true, mutation: 'none', flags: [jsonFlag] },
|
|
110
111
|
...SESSION_DESCRIPTORS.map(spec => ({
|
|
@@ -348,5 +349,6 @@ export const COMMAND_GUIDE = [
|
|
|
348
349
|
operator("serve", "the operations console (HTTP)"),
|
|
349
350
|
operator("watch", "the unattended loop, kept running"),
|
|
350
351
|
operator("up", "console + worker + browser, one command"),
|
|
352
|
+
operator("onboard", "add this repository, report signed-in agents, install or --remove the operator skill (--yes), and print the handoff"),
|
|
351
353
|
operator("daemon", "install the loop under the OS service manager"),
|
|
352
354
|
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { installMethod } from "./install-method.js";
|
|
2
|
+
import { type RuntimeRef, type UpdateSystem } from "./toolroll-update.js";
|
|
3
|
+
export declare const UPDATE_HELP = "toolroll update preview updating to the latest release\ntoolroll update --yes update: verify, let running work finish, back up, rehearse, switch, restart, check\n --version X a specific release instead of the latest\n --when-idle pause new work and wait for running work (the default)\n --now refuse if any work is running\n --at HH:MM wait until then (e.g. --at 03:00)\n --allow-downgrade with --version, go back to an older release\ntoolroll update --rollback [--yes] return to the previous version and its database backup\ntoolroll update --status | --resume | --cancel";
|
|
4
|
+
export type UpdateCliDeps = {
|
|
5
|
+
system?: UpdateSystem;
|
|
6
|
+
current?: RuntimeRef;
|
|
7
|
+
method?: ReturnType<typeof installMethod>;
|
|
8
|
+
latest?: () => Promise<{
|
|
9
|
+
version: string;
|
|
10
|
+
}>;
|
|
11
|
+
databaseFile?: string;
|
|
12
|
+
/** Where the console's one-off update job's definition lives (tests). */
|
|
13
|
+
home?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function runUpdateCommand(args: readonly string[], write: (line: string) => void, deps?: UpdateCliDeps): Promise<number>;
|