toolroll 0.7.0 → 0.8.1
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 +435 -0
- package/README.md +15 -0
- package/dist/agent-onboard.d.ts +129 -0
- package/dist/agent-onboard.js +356 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +24 -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 +10 -2
- package/dist/daemon.js +34 -9
- 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 +57 -0
- package/dist/first-run.js +104 -0
- package/dist/install-method.d.ts +1 -1
- package/dist/install-method.js +8 -0
- package/dist/operate.d.ts +11 -0
- package/dist/operate.js +126 -7
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +12 -0
- package/dist/releases.d.ts +9 -0
- package/dist/releases.js +14 -1
- package/dist/serve.d.ts +16 -0
- package/dist/serve.js +177 -82
- package/dist/skills.d.ts +2 -0
- package/dist/skills.js +3 -1
- package/dist/store.d.ts +4 -4
- package/dist/store.js +12 -6
- package/dist/surface.js +2 -0
- package/dist/toolroll-update-cli.d.ts +15 -0
- package/dist/toolroll-update-cli.js +159 -0
- package/dist/toolroll-update-ui.d.ts +38 -0
- package/dist/toolroll-update-ui.js +90 -0
- package/dist/toolroll-update.d.ts +269 -0
- package/dist/toolroll-update.js +1253 -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: 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" ? 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,72 @@ 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
|
+
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
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3871
|
+
return firstRunSteps({ agentSignedIn: agentSignedIn(now), projects: managedRepos().length, hasTask: store.hasAnyWork(), firstResultAt: null });
|
|
3872
|
+
}
|
|
3873
|
+
/**
|
|
3874
|
+
* Whether a coding agent is signed in where the work runs. This machine's
|
|
3875
|
+
* own checks (the ones Settings makes) speak only for a worker on this
|
|
3876
|
+
* machine; a worker elsewhere speaks through its own readiness report, and
|
|
3877
|
+
* the console never claims to have checked that machine (finding 15).
|
|
3878
|
+
* Nothing here waits on a probe: this machine's answer is the last one
|
|
3879
|
+
* seen, refreshed in the background.
|
|
3880
|
+
*/
|
|
3881
|
+
function agentSignedIn(now) {
|
|
3882
|
+
const here = hostname();
|
|
3883
|
+
const workers = store.listRunners().filter(one => one.retiredAt === null && runnerAlive(one, now));
|
|
3884
|
+
if ((options.localRunner !== undefined || workers.some(one => one.host === here)) && localAgentSignedIn())
|
|
3885
|
+
return true;
|
|
3886
|
+
return workers.some(one => one.host !== here && store.providerReadiness(one.name).some(seen => seen.state === "ready"));
|
|
3887
|
+
}
|
|
3888
|
+
let localSignIn = null;
|
|
3889
|
+
let localSignInChecking = false;
|
|
3890
|
+
function localAgentSignedIn() {
|
|
3891
|
+
if (!localSignInChecking && (localSignIn === null || Date.now() - localSignIn.at > 30_000)) {
|
|
3892
|
+
localSignInChecking = true;
|
|
3893
|
+
void Promise.all(["claude", "codex"].map(one => connectionCheck(one).then(value => value.state, () => "unverified")))
|
|
3894
|
+
.then(states => { localSignIn = { at: Date.now(), signedIn: states.some(state => state === "connected" || state === "key-works" || state === "key-present") }; })
|
|
3895
|
+
.catch(() => { })
|
|
3896
|
+
.finally(() => { localSignInChecking = false; });
|
|
3897
|
+
}
|
|
3898
|
+
return localSignIn?.signedIn ?? false;
|
|
3899
|
+
}
|
|
3900
|
+
/** First tasks per project: the last ones found (safe generic ones until then), re-read in the background at most
|
|
3901
|
+
* every ten minutes. A render never waits on `gh` or `git grep`. */
|
|
3902
|
+
const firstTasks = new Map();
|
|
3903
|
+
function firstTasksFor(repo) {
|
|
3904
|
+
const at = Date.now();
|
|
3905
|
+
let entry = firstTasks.get(repo);
|
|
3906
|
+
if (entry === undefined || (entry.until <= at && !entry.reading)) {
|
|
3907
|
+
const reading = { until: at + 10 * 60_000, found: entry?.found ?? null, reading: true };
|
|
3908
|
+
firstTasks.set(repo, reading);
|
|
3909
|
+
entry = reading;
|
|
3910
|
+
void findFirstTasks(repo, options.firstTaskRunner ?? execRun)
|
|
3911
|
+
.then(found => { reading.found = found; }, () => { })
|
|
3912
|
+
.finally(() => { reading.reading = false; });
|
|
3913
|
+
}
|
|
3914
|
+
return entry.found ?? firstTaskSuggestions({ issues: [], todos: [] });
|
|
3915
|
+
}
|
|
3916
|
+
/** Chat's first run: the steps, first tasks until the first task exists, and the sandbox while no agent is signed in. */
|
|
3917
|
+
function chatFirstRun(now, project) {
|
|
3918
|
+
const steps = firstRunStepsNow(now);
|
|
3919
|
+
if (steps === null)
|
|
3920
|
+
return undefined;
|
|
3921
|
+
const taskFiled = steps.find(one => one.key === "task")?.done ?? true;
|
|
3922
|
+
const repo = project ?? managedRepos()[0] ?? null;
|
|
3923
|
+
return {
|
|
3924
|
+
steps,
|
|
3925
|
+
suggestions: taskFiled || repo === null ? [] : firstTasksFor(repo),
|
|
3926
|
+
sandbox: steps.find(one => one.key === "agent")?.done === false ? SANDBOX_COMMAND : null,
|
|
3927
|
+
};
|
|
3893
3928
|
}
|
|
3894
3929
|
// ---- fleet chat (v13) ----------------------------------------------------
|
|
3895
3930
|
const chatFetcher = options.chatFetcher ?? fetch;
|
|
@@ -4473,6 +4508,7 @@ export function createDecisionServer(options) {
|
|
|
4473
4508
|
...(s.refreshSeconds === undefined ? {} : { refreshSeconds: Math.max(5, Math.floor(s.refreshSeconds)) }),
|
|
4474
4509
|
...(s.chrome.signIn === undefined ? {} : { signIn: s.chrome.signIn }),
|
|
4475
4510
|
...(s.chrome.update === undefined ? {} : { update: s.chrome.update }),
|
|
4511
|
+
...(extras.firstRun === undefined ? {} : { firstRun: extras.firstRun }),
|
|
4476
4512
|
};
|
|
4477
4513
|
if (requestFacts.workspaceRead) {
|
|
4478
4514
|
const validator = requestFacts.workspaceValidator;
|
|
@@ -5808,8 +5844,8 @@ export function createDecisionServer(options) {
|
|
|
5808
5844
|
}
|
|
5809
5845
|
return redirect(response, safeReturn(body.get("return") ?? "/settings#updates"));
|
|
5810
5846
|
}
|
|
5811
|
-
// The daily check's switch: an installation setting, so an operator's.
|
|
5812
|
-
if (url.pathname === "/settings/updates") {
|
|
5847
|
+
// The daily check's switch: an installation setting, so an operator's. (Its own address: POST /settings/updates starts an update.)
|
|
5848
|
+
if (url.pathname === "/settings/updates/checks") {
|
|
5813
5849
|
if (options.configDir === undefined)
|
|
5814
5850
|
return refuse(response, who, 404, "Updates are not set up on this console.", "/settings");
|
|
5815
5851
|
if (!store.isInstanceOperator(who.name))
|
|
@@ -6522,6 +6558,52 @@ export function createDecisionServer(options) {
|
|
|
6522
6558
|
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
6559
|
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
6560
|
}
|
|
6561
|
+
// Settings → Updates: start the `toolroll update` job. A step-up; the job writes the ledger entries.
|
|
6562
|
+
if (url.pathname === "/settings/updates" || url.pathname === "/settings/updates/seen" || url.pathname === "/settings/updates/cancel") {
|
|
6563
|
+
const databaseFile = store.databaseFile();
|
|
6564
|
+
if (who.via !== "cookie" || !store.isInstanceOperator(who.name) || databaseFile === null)
|
|
6565
|
+
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
6566
|
+
const stateDir = dirname(databaseFile);
|
|
6567
|
+
const back = (key, words) => redirect(response, `/settings/updates?${key}=${encodeURIComponent(words)}`);
|
|
6568
|
+
if (url.pathname === "/settings/updates/seen") {
|
|
6569
|
+
markWhatsNewSeen(stateDir);
|
|
6570
|
+
return redirect(response, "/settings/updates");
|
|
6571
|
+
}
|
|
6572
|
+
if (url.pathname === "/settings/updates/cancel")
|
|
6573
|
+
return back("said", requestRuntimeUpdateCancel(stateDir));
|
|
6574
|
+
if (!authenticateApprover(store, who.name, body.get("password") ?? "").ok)
|
|
6575
|
+
return back("problem", "Enter your Toolroll password to update.");
|
|
6576
|
+
const version = body.get("version") ?? "";
|
|
6577
|
+
const when = { now: "now", "when-idle": "when-idle", tonight: "at" }[body.get("when") ?? ""] ?? null;
|
|
6578
|
+
if (!/^\d+\.\d+\.\d+$/.test(version) || when === null)
|
|
6579
|
+
return back("problem", "Choose when to update.");
|
|
6580
|
+
const current = options.updates?.current ?? PACKAGE_VERSION;
|
|
6581
|
+
if (!newerThan(version, current))
|
|
6582
|
+
return back("problem", `Toolroll ${version} is not newer than ${current}. To go back, run toolroll update --rollback.`);
|
|
6583
|
+
const method = options.updates?.method ?? installMethod();
|
|
6584
|
+
if (method.kind === "npx" || method.kind === "source" || method.kind === "desktop")
|
|
6585
|
+
return back("problem", method.kind === "npx" ? "npx runs the latest release each time; there is nothing to update." : method.kind === "desktop" ? "Update it from the Toolroll app." : "This runs from a source checkout. Update it with git.");
|
|
6586
|
+
const before = runtimeUpdateStatus(stateDir).journal;
|
|
6587
|
+
if (before !== null && !runtimeUpdateTerminal(before.phase))
|
|
6588
|
+
return back("problem", "An update is already under way.");
|
|
6589
|
+
if (when === "now") {
|
|
6590
|
+
const running = runningWorkWords(store.raw());
|
|
6591
|
+
if (running !== null)
|
|
6592
|
+
return back("problem", `Work is running: ${running}. Choose When idle to update once it finishes.`);
|
|
6593
|
+
}
|
|
6594
|
+
const prepared = prepareRuntimeUpdate({ stateDir, databaseFile, current: { version: current, dist: options.updates?.dist ?? currentRuntime(current).dist }, actor: who.name, version, when, at: "03:00" }, clock());
|
|
6595
|
+
if ("refused" in prepared)
|
|
6596
|
+
return back("problem", prepared.refused);
|
|
6597
|
+
try {
|
|
6598
|
+
await (options.updates?.launch ?? launchRuntimeUpdate)({ databaseFile, id: prepared.id });
|
|
6599
|
+
}
|
|
6600
|
+
catch (error) {
|
|
6601
|
+
abandonRuntimeUpdate(stateDir, prepared.id, `The update could not start: ${error.message}`, clock());
|
|
6602
|
+
return back("problem", `The update could not start: ${error.message}`);
|
|
6603
|
+
}
|
|
6604
|
+
// The job records its first step within moments; show it rather than the old page.
|
|
6605
|
+
return back("said", when === "at" ? `Update to ${version} scheduled for 03:00.` : `Updating to ${version}.`);
|
|
6606
|
+
}
|
|
6525
6607
|
// v105: change how long each kind of data is kept. A step-up; the ledger keeps before → after for each kind.
|
|
6526
6608
|
if (url.pathname === "/settings/retention") {
|
|
6527
6609
|
if (who.via !== "cookie" || !store.isInstanceOperator(who.name))
|
|
@@ -13984,7 +14066,7 @@ button.pick-file { min-height: 1.75rem; padding: 0 .55rem; font-size: .75rem; }
|
|
|
13984
14066
|
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
14067
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
13986
14068
|
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}');
|
|
14069
|
+
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
14070
|
/**
|
|
13989
14071
|
* The board's liveness: fetch this page's own fragment on a timer and swap
|
|
13990
14072
|
* it in place — no flicker, no scroll reset, no long-lived stream to manage,
|
|
@@ -14635,26 +14717,20 @@ function inboxPage(chrome, data) {
|
|
|
14635
14717
|
`<span class="meta">frees ${gap.unblocks.length} task${gap.unblocks.length > 1 ? "s" : ""}</span>` +
|
|
14636
14718
|
`<span class="right meta">how to fix \u2192</span></p>`)
|
|
14637
14719
|
.join("\n");
|
|
14638
|
-
// The first-run
|
|
14639
|
-
//
|
|
14640
|
-
// whole card retires permanently on the first successful run.
|
|
14720
|
+
// The first-run steps, until the first Ready result: each is done or
|
|
14721
|
+
// shows the one action that does it.
|
|
14641
14722
|
const wizard = data.wizard === null
|
|
14642
14723
|
? ""
|
|
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>` +
|
|
14724
|
+
: `<div class="card" data-first-run>` +
|
|
14725
|
+
`<p><strong>Get to your first result</strong></p>` +
|
|
14646
14726
|
data.wizard
|
|
14647
|
-
.map(step => `<p class="row"><span class="mono">${step.done ? "\u2713" : "\u25cb"}</span> <strong>${escape(step.title)}</strong
|
|
14648
|
-
|
|
14727
|
+
.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>` +
|
|
14728
|
+
(step.action === null ? ` <span class="meta">done</span>`
|
|
14729
|
+
: step.action.kind === "link" ? ` <a href="${escape(step.action.href)}">${escape(step.action.label)}</a>`
|
|
14730
|
+
: ` <code>${escape(step.action.command)}</code>`) +
|
|
14731
|
+
`</p>`)
|
|
14649
14732
|
.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>`;
|
|
14733
|
+
`</div>`;
|
|
14658
14734
|
const noWorker = data.worker.answering > 0
|
|
14659
14735
|
? ""
|
|
14660
14736
|
: `<div class="card builder-notice" data-builder-status="${data.worker.registered === 0 ? "not-connected" : "disconnected"}">` +
|
|
@@ -21413,7 +21489,7 @@ function telegramDeliveryWords(store, bot) {
|
|
|
21413
21489
|
return `Telegram pushes new messages to ${state.url}${state.problem === null ? "." : `, but ${state.problem.charAt(0).toLowerCase()}${state.problem.slice(1)}.`}`;
|
|
21414
21490
|
return `Toolroll asks Telegram for new messages every few seconds${state?.problem ? ` (${state.problem})` : ""}.`;
|
|
21415
21491
|
}
|
|
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) {
|
|
21492
|
+
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
21493
|
const permissionCard = permissionDefault === null
|
|
21418
21494
|
? ""
|
|
21419
21495
|
: [
|
|
@@ -21610,6 +21686,7 @@ function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging = null,
|
|
|
21610
21686
|
email,
|
|
21611
21687
|
workers,
|
|
21612
21688
|
updates,
|
|
21689
|
+
firstResult,
|
|
21613
21690
|
};
|
|
21614
21691
|
return screen("Settings", [
|
|
21615
21692
|
"<h1>Settings</h1>",
|
|
@@ -21620,6 +21697,7 @@ function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging = null,
|
|
|
21620
21697
|
keysCard,
|
|
21621
21698
|
workersCard(workers),
|
|
21622
21699
|
updatesCard(updates, csrf),
|
|
21700
|
+
firstResult === null ? "" : `<p class="meta" data-first-result>${escape(firstResult)}</p>`,
|
|
21623
21701
|
pushCard === "" && messagingCard === "" && digestCard === "" ? "" : `<h2>Notifications</h2>`,
|
|
21624
21702
|
messagingCard,
|
|
21625
21703
|
pushCard,
|
|
@@ -21686,7 +21764,7 @@ function updatesCard(updates, csrf) {
|
|
|
21686
21764
|
(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
21765
|
: "") +
|
|
21688
21766
|
(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>`
|
|
21767
|
+
? `<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
21768
|
: updates.check.byEnv ? `<p class="meta">Off by TOOLROLL_NO_UPDATE_CHECK.</p>` : "") +
|
|
21691
21769
|
(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
21770
|
`</section>`;
|
|
@@ -21694,6 +21772,22 @@ function updatesCard(updates, csrf) {
|
|
|
21694
21772
|
/** Choices save the moment they change; without the script the Save
|
|
21695
21773
|
* button stays and the form works the same. */
|
|
21696
21774
|
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();}});});})();`;
|
|
21775
|
+
/** The newest release, remembered for a while so the page stays quick. */
|
|
21776
|
+
let latestSeen = null;
|
|
21777
|
+
async function latestReleaseFor(latest) {
|
|
21778
|
+
if (latest === undefined && latestSeen !== null && Date.now() - latestSeen.at < ("problem" in latestSeen.value ? 60_000 : 900_000))
|
|
21779
|
+
return latestSeen.value;
|
|
21780
|
+
let value;
|
|
21781
|
+
try {
|
|
21782
|
+
value = { version: (await (latest ?? latestVersionNow)()).version };
|
|
21783
|
+
}
|
|
21784
|
+
catch (error) {
|
|
21785
|
+
value = { problem: error.message };
|
|
21786
|
+
}
|
|
21787
|
+
if (latest === undefined)
|
|
21788
|
+
latestSeen = { at: Date.now(), value };
|
|
21789
|
+
return value;
|
|
21790
|
+
}
|
|
21697
21791
|
/** Settings destinations as a scannable grid: an icon and a name each. */
|
|
21698
21792
|
function settingsTiles() {
|
|
21699
21793
|
const tiles = SETTINGS_TILE_ICONS;
|
|
@@ -21716,6 +21810,7 @@ const SETTINGS_TILE_ICONS = [
|
|
|
21716
21810
|
["/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
21811
|
["/settings/monitoring", "Monitoring", `<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>`],
|
|
21718
21812
|
["/settings/retention", "Retention", `<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>`],
|
|
21813
|
+
["/settings/updates", "Updates", `<path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/>`],
|
|
21719
21814
|
["/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
21815
|
["/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
21816
|
];
|
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
|
@@ -4337,16 +4337,16 @@ export declare class Store {
|
|
|
4337
4337
|
* createConsoleTask. Set-once: an already-stamped row never changes. */
|
|
4338
4338
|
stampFiledVia(refId: number, via: string): void;
|
|
4339
4339
|
/**
|
|
4340
|
-
* When this installation first finished a run
|
|
4340
|
+
* When this installation first finished a run with a change — the
|
|
4341
4341
|
* moment the first-run checklist retires, permanently. Derived from run
|
|
4342
4342
|
* history the first time it is observed, then stamped as an append-only
|
|
4343
4343
|
* installation fact so later pruning of those rows cannot resurrect the
|
|
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
|
@@ -14391,7 +14391,7 @@ export class Store {
|
|
|
14391
14391
|
this.db.prepare("UPDATE task_ref SET filed_via = ? WHERE id = ? AND filed_via IS NULL").run(via, refId);
|
|
14392
14392
|
}
|
|
14393
14393
|
/**
|
|
14394
|
-
* When this installation first finished a run
|
|
14394
|
+
* When this installation first finished a run with a change — the
|
|
14395
14395
|
* moment the first-run checklist retires, permanently. Derived from run
|
|
14396
14396
|
* history the first time it is observed, then stamped as an append-only
|
|
14397
14397
|
* installation fact so later pruning of those rows cannot resurrect the
|
|
@@ -14402,7 +14402,7 @@ export class Store {
|
|
|
14402
14402
|
if (fact !== null)
|
|
14403
14403
|
return fact;
|
|
14404
14404
|
const row = this.db
|
|
14405
|
-
.prepare("SELECT finished_at FROM run WHERE outcome
|
|
14405
|
+
.prepare("SELECT finished_at FROM run WHERE outcome = 'built' AND finished_at IS NOT NULL ORDER BY id LIMIT 1")
|
|
14406
14406
|
.get();
|
|
14407
14407
|
if (row === undefined)
|
|
14408
14408
|
return null;
|
|
@@ -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,11 @@ 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. A run that changed
|
|
15942
|
+
// nothing is not a first result.
|
|
15943
|
+
if (result.outcome === "built")
|
|
15944
|
+
this.firstSuccessAt(result.now);
|
|
15939
15945
|
// The park rate is *measured* — parked over concluded builder attempts —
|
|
15940
15946
|
// and maintained where attempts conclude, because the attention budget's
|
|
15941
15947
|
// 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>;
|