toolroll 0.9.51 → 0.9.52
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 +12 -0
- package/dist/backup.d.ts +8 -0
- package/dist/backup.js +10 -1
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +1 -1
- package/dist/browser-workspace.d.ts +1 -0
- package/dist/database-reclaim.d.ts +4 -0
- package/dist/database-reclaim.js +13 -0
- package/dist/demo.d.ts +1 -1
- package/dist/demo.js +1 -1
- package/dist/desktop-update.d.ts +1 -1
- package/dist/live-bus.d.ts +3 -3
- package/dist/live-bus.js +3 -3
- package/dist/monitoring.js +32 -8
- package/dist/notification-delivery.d.ts +19 -0
- package/dist/notification-delivery.js +37 -0
- package/dist/operate-remote-arguments.js +1 -1
- package/dist/operate-remote.js +2 -2
- package/dist/operate.js +49 -119
- package/dist/process-recovery-settlement.js +1 -0
- package/dist/retention.js +6 -5
- package/dist/serve.js +1 -1
- package/dist/server/settings.js +3 -1
- package/dist/server/shared.d.ts +1 -0
- package/dist/server/shared.js +2 -0
- package/dist/store.d.ts +38 -43
- package/dist/store.js +255 -134
- package/dist/surface.js +1 -2
- package/dist/toolroll-update.d.ts +14 -3
- package/dist/toolroll-update.js +105 -14
- package/dist/webhooks.d.ts +10 -22
- package/dist/webhooks.js +43 -81
- package/dist/work-index.js +2 -2
- package/dist/workspace-revision.d.ts +28 -1
- package/dist/workspace-revision.js +276 -49
- package/package.json +1 -3
|
@@ -123,6 +123,7 @@ export async function recoverPreparedObserverGap(store, input) {
|
|
|
123
123
|
const actionId = store.recordAction({ at: new Date().toISOString(), actor: "system", repo: current.binding.run.repo,
|
|
124
124
|
taskId: current.binding.run.taskId, runId: receipt.targetRun, action: "process absence recovered",
|
|
125
125
|
outcome: digest, source: "work" });
|
|
126
|
+
store.compactRunProcesses(new Date(), receipt.targetRun);
|
|
126
127
|
return { ok: true, recorded: true, ...result, actionId, certificatePath };
|
|
127
128
|
}));
|
|
128
129
|
}
|
package/dist/retention.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reclaimDatabase } from "./database-reclaim.js";
|
|
1
2
|
/**
|
|
2
3
|
* Retention (v105): how long Toolroll keeps run evidence and logs,
|
|
3
4
|
* finished checkouts' records, chat messages and notifications. Until an
|
|
@@ -56,7 +57,7 @@ export const RETENTION_KINDS = [
|
|
|
56
57
|
{ kind: "evidence", label: "Run evidence and logs", detail: "Diffs, check logs, screenshots and reports saved for each run" },
|
|
57
58
|
{ kind: "checkouts", label: "Finished checkout records", detail: "Records of build checkouts that were let go and are no longer on disk" },
|
|
58
59
|
{ kind: "chat", label: "Chat messages", detail: "Messages in lead and team chats" },
|
|
59
|
-
{ kind: "notifications", label: "Notifications", detail: "
|
|
60
|
+
{ kind: "notifications", label: "Notifications", detail: "Resolved notifications" },
|
|
60
61
|
];
|
|
61
62
|
/** The periods the page offers for each kind; the command line takes any number of days from 1 to 3650. */
|
|
62
63
|
const LONG_CHOICES = [30, 90, 180, 365, 730, null];
|
|
@@ -183,12 +184,11 @@ export function retentionPlan(store, evidenceRoot, now, periods = store.retentio
|
|
|
183
184
|
items.chat = rows.slice(0, SWEEP_BATCH).map(row => ({ id: Number(row["id"]), bytes: Number(row["bytes"]) }));
|
|
184
185
|
}
|
|
185
186
|
if (periods.notifications !== null) {
|
|
186
|
-
const rows = db.prepare(`SELECT n.id AS id, length(n.subject) + length(n.body) +
|
|
187
|
-
WHERE n.created_at < ? AND
|
|
188
|
-
AND NOT (n.claim_owner IS NOT NULL AND n.claim_expires_at > ?)
|
|
187
|
+
const rows = db.prepare(`SELECT n.id AS id, length(n.subject) + length(n.body) + 64 AS bytes FROM notification n
|
|
188
|
+
WHERE n.created_at < ? AND n.resolved_at IS NOT NULL
|
|
189
189
|
AND NOT EXISTS (SELECT 1 FROM notification_delivery d WHERE d.notification = n.id AND d.claim_owner IS NOT NULL AND d.claim_expires_at > ?)
|
|
190
190
|
AND NOT (n.resolved_at IS NULL AND n.task_ref IS NOT NULL AND NOT EXISTS (SELECT 1 FROM task_ref r WHERE r.id = n.task_ref AND ${finishedTask("r")}))
|
|
191
|
-
ORDER BY n.id LIMIT ?`).all(cutoff(now, periods.notifications), now.toISOString(),
|
|
191
|
+
ORDER BY n.id LIMIT ?`).all(cutoff(now, periods.notifications), now.toISOString(), SWEEP_BATCH + 1);
|
|
192
192
|
more.notifications = rows.length > SWEEP_BATCH;
|
|
193
193
|
items.notifications = rows.slice(0, SWEEP_BATCH).map(row => ({ id: Number(row["id"]), bytes: Number(row["bytes"]) }));
|
|
194
194
|
}
|
|
@@ -297,6 +297,7 @@ export function sweepRetention(store, evidenceRoot, now, actor = "worker") {
|
|
|
297
297
|
const counts = plan.counts.map(one => ({ ...one, count: done[one.kind].count, bytes: done[one.kind].bytes }));
|
|
298
298
|
const freed = counts.reduce((sum, one) => sum + one.bytes, 0);
|
|
299
299
|
const ledgerId = store.recordAction({ at: now.toISOString(), actor, repo: null, taskId: null, runId: null, action: "retention sweep", outcome: freed > 0 || counts.some(one => one.count > 0) ? "removed" : "nothing due", source: "policy", detail: sweepWords(counts, freed) });
|
|
300
|
+
reclaimDatabase(store.handle, "retention");
|
|
300
301
|
return { at: now.toISOString(), counts, freed, ledgerId };
|
|
301
302
|
}
|
|
302
303
|
/** Whether a day has passed since the last sweep. */
|
package/dist/serve.js
CHANGED
|
@@ -429,7 +429,7 @@ export function createDecisionServer(options) {
|
|
|
429
429
|
/** One-click connections on their way: the service's sign-in page and back, 15 minutes at most. */
|
|
430
430
|
const connectVisits = new Map();
|
|
431
431
|
/** Flows open in a browser (v88): who's here, and a nudge when one changes. */
|
|
432
|
-
// Live views push on write (live-bus.ts): the revision
|
|
432
|
+
// Live views push on write (live-bus.ts): each commit moves the workspace revision, the rooms hear it.
|
|
433
433
|
const liveBus = createLiveBus();
|
|
434
434
|
const liveFollower = followWorkspace(store, () => workspaceRevision.current(), liveBus, { file: store.databaseFile() });
|
|
435
435
|
const flowRooms = createFlowRooms(flow => flowFingerprint(store, flow), { bus: liveBus });
|
package/dist/server/settings.js
CHANGED
|
@@ -1111,6 +1111,8 @@ export function createSettingsHandlers(runtime) {
|
|
|
1111
1111
|
if (who.via !== "cookie" || !store.isInstanceOperator(who.name) || databaseFile === null)
|
|
1112
1112
|
return refuse(response, who, 403, "An instance operator looks after backups.", "/settings");
|
|
1113
1113
|
const made = await backupNow(store, databaseFile, "manual", clock);
|
|
1114
|
+
if (made.ok && made.checkpoint !== null && "problem" in made.checkpoint)
|
|
1115
|
+
return redirect(response, `/settings/backups?problem=${encodeURIComponent(`Backed up. The activity log failed its tamper check, so it was not anchored: ${made.checkpoint.problem}`)}`);
|
|
1114
1116
|
return redirect(response, made.ok ? `/settings/backups?said=${encodeURIComponent("Backed up.")}` : `/settings/backups?problem=${encodeURIComponent(`The backup failed: ${made.error}`)}`);
|
|
1115
1117
|
}
|
|
1116
1118
|
// Sprint 8: change the backup schedule. A step-up; the ledger keeps before → after.
|
|
@@ -1978,7 +1980,7 @@ export function createSettingsHandlers(runtime) {
|
|
|
1978
1980
|
const existing = loadBotToken({}, options.telegramTokenFile);
|
|
1979
1981
|
const hasEnv = (envValue(process.env, "TELEGRAM_TOKEN") ?? "") !== "";
|
|
1980
1982
|
const csrf = who.via === "cookie" ? who.session.csrf : "";
|
|
1981
|
-
return sendScreen(response, 400, settingsPage(chromeFor(who.via === "cookie" ? who.session.project : defaultProject, "settings"), existing, hasEnv, csrf, saved.message, null, null, null, null, {
|
|
1983
|
+
return sendScreen(response, 400, settingsPage(chromeFor(who.via === "cookie" ? who.session.project : defaultProject, "settings"), existing, hasEnv, csrf, saved.message, options.configDir === undefined ? null : effectivePrimary(process.env, options.configDir, loadBotToken(process.env, options.telegramTokenFile) !== null), null, null, null, {
|
|
1982
1984
|
...store.permissionDefault(),
|
|
1983
1985
|
canManage: who.role === "approver",
|
|
1984
1986
|
}));
|
package/dist/server/shared.d.ts
CHANGED
|
@@ -3138,6 +3138,7 @@ export declare function settingsPage(chrome: Chrome, existing: TokenSource | nul
|
|
|
3138
3138
|
channel: string | null;
|
|
3139
3139
|
implicit: boolean;
|
|
3140
3140
|
configured: string[];
|
|
3141
|
+
legacyWarning?: string;
|
|
3141
3142
|
} | null, push?: {
|
|
3142
3143
|
available: boolean;
|
|
3143
3144
|
devices: PushSubscription[];
|
package/dist/server/shared.js
CHANGED
|
@@ -12313,6 +12313,7 @@ export function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging
|
|
|
12313
12313
|
kind: "settings",
|
|
12314
12314
|
said: problem,
|
|
12315
12315
|
groups: settingsGroups(messaging, telegramFailing),
|
|
12316
|
+
...(messaging?.legacyWarning ? { legacyWebhookWarning: messaging.legacyWarning } : {}),
|
|
12316
12317
|
theme,
|
|
12317
12318
|
accent: requestContext.getStore()?.accent ?? DEFAULT_ACCENT,
|
|
12318
12319
|
accentPresets: ACCENT_PRESETS,
|
|
@@ -12346,6 +12347,7 @@ export function settingsPage(chrome, existing, hasEnv, csrf, problem, messaging
|
|
|
12346
12347
|
};
|
|
12347
12348
|
return screen("Settings", [
|
|
12348
12349
|
"<h1>Settings</h1>",
|
|
12350
|
+
messaging?.legacyWarning ? `<p data-legacy-webhooks>Legacy webhooks are deprecated. Connect <a href="/settings/slack" style="display:inline-flex;min-height:44px;align-items:center">Slack</a> or <a href="/settings/discord" style="display:inline-flex;min-height:44px;align-items:center">Discord</a> in Chat settings.</p>` : "",
|
|
12349
12351
|
settingsTiles(view.groups),
|
|
12350
12352
|
phone === null ? "" : phoneSetupHtml(phone),
|
|
12351
12353
|
appearanceCard(csrf),
|
package/dist/store.d.ts
CHANGED
|
@@ -152,7 +152,7 @@ export type SealedLedgerEntry = LedgerEntry & {
|
|
|
152
152
|
hash: string;
|
|
153
153
|
} | null;
|
|
154
154
|
};
|
|
155
|
-
export declare const SCHEMA_VERSION =
|
|
155
|
+
export declare const SCHEMA_VERSION = 114;
|
|
156
156
|
/**
|
|
157
157
|
* The migrations `toolroll update` may carry a database through in place: each
|
|
158
158
|
* one only adds (a table, a column, an accepted value) and leaves every saved row
|
|
@@ -166,6 +166,11 @@ export declare const SCHEMA_VERSION = 113;
|
|
|
166
166
|
* api_token columns (project limit, rotation); existing tokens read as before.
|
|
167
167
|
* v112 only adds the two request-budget tables.
|
|
168
168
|
* v113 adds MCP sign-in tables and API-token purpose metadata.
|
|
169
|
+
* v114 is the one declared exception to "only adds", and the rehearsal checks
|
|
170
|
+
* it as such: a settled run's exited process witnesses become one summary row
|
|
171
|
+
* (rows removed equal the witnesses summarized), and notification drops its
|
|
172
|
+
* delivery columns only after every value moves into one legacy receipt per row
|
|
173
|
+
* (rows and ids carried whole; legacy receipts added equal the rows carried).
|
|
169
174
|
*/
|
|
170
175
|
export declare const UPDATE_SAFE_MIGRATIONS: readonly number[];
|
|
171
176
|
/** Whether a database settled at `version` reaches this build's schema through update-safe migrations alone. */
|
|
@@ -1030,19 +1035,15 @@ export type Notification = {
|
|
|
1030
1035
|
subject: string;
|
|
1031
1036
|
body: string;
|
|
1032
1037
|
createdAt: string;
|
|
1033
|
-
attempts: number;
|
|
1034
|
-
lastAttemptAt: string | null;
|
|
1035
|
-
lastError: string | null;
|
|
1036
|
-
deliveredAt: string | null;
|
|
1037
|
-
receipt: string | null;
|
|
1038
1038
|
/** The closed push attention class, or null = this fact never pushes. */
|
|
1039
1039
|
pushClass: "decision" | "pick" | "merge" | "attention" | "progress" | null;
|
|
1040
1040
|
/** The machine-minted console path a push may deep-link — never free text. */
|
|
1041
1041
|
link: string | null;
|
|
1042
1042
|
/**
|
|
1043
1043
|
* When the fact stopped wanting a person — a decision answered, an incident
|
|
1044
|
-
* resolved. Distinct from delivery, and it never deletes the row:
|
|
1045
|
-
* are the audit trail of
|
|
1044
|
+
* resolved. Distinct from delivery, and it never deletes the row: the
|
|
1045
|
+
* per-destination receipts (notification_delivery) are the audit trail of
|
|
1046
|
+
* what was actually sent.
|
|
1046
1047
|
*/
|
|
1047
1048
|
resolvedAt: string | null;
|
|
1048
1049
|
scope: "unknown" | "installation" | "project" | "task";
|
|
@@ -1051,6 +1052,14 @@ export type Notification = {
|
|
|
1051
1052
|
taskId: string | null;
|
|
1052
1053
|
run: number | null;
|
|
1053
1054
|
};
|
|
1055
|
+
/** One notification's delivery state at one destination (notification_delivery). */
|
|
1056
|
+
export type NotificationReceipt = Notification & {
|
|
1057
|
+
attempts: number;
|
|
1058
|
+
lastAttemptAt: string | null;
|
|
1059
|
+
lastError: string | null;
|
|
1060
|
+
deliveredAt: string | null;
|
|
1061
|
+
receipt: string | null;
|
|
1062
|
+
};
|
|
1054
1063
|
export type TelegramDelivery = Notification & {
|
|
1055
1064
|
destination: string;
|
|
1056
1065
|
claimGeneration: number;
|
|
@@ -1868,6 +1877,9 @@ export declare function openStoreNoMigrate(file: string, options?: OpenOptions):
|
|
|
1868
1877
|
* retries. Correction children (the same-session structured-output
|
|
1869
1878
|
* repairs) are not attempts; they continue one. */
|
|
1870
1879
|
export declare const REVIEW_ROOT_ATTEMPTS = 3;
|
|
1880
|
+
/** The receipt v114 keeps a notification's old single-destination delivery under. No sender claims it: every real
|
|
1881
|
+
* destination is a kind and a 64-character hash, or a Telegram binding. */
|
|
1882
|
+
export declare const LEGACY_NOTIFICATION_DESTINATION = "legacy:single-destination";
|
|
1871
1883
|
/** v34: artifact.kind admits 'report'. A table already rebuilt to the v38
|
|
1872
1884
|
* shape (migrate() runs unconditionally on every open, including a fresh
|
|
1873
1885
|
* database created straight at the current shape) is a DONE shape too —
|
|
@@ -2489,8 +2501,16 @@ export declare class Store {
|
|
|
2489
2501
|
fresh?: number;
|
|
2490
2502
|
}): LedgerChainReport;
|
|
2491
2503
|
/** A checkpoint of the chain as it stands: its head, recorded here and handed back to be copied off the machine.
|
|
2492
|
-
* Never over a chain that doesn't verify: that would vouch for what broke it.
|
|
2493
|
-
|
|
2504
|
+
* Never over a chain that doesn't verify: that would vouch for what broke it. `automatic` (before a backup, after
|
|
2505
|
+
* a monitoring delivery): at the verified head, or at `head` (one that was sent) once the walk reached it, only past
|
|
2506
|
+
* the newest checkpoint (null otherwise), and with no ledger entry of its own, so a checkpoint never makes new
|
|
2507
|
+
* activity for the next backup or delivery to checkpoint again. */
|
|
2508
|
+
ledgerCheckpoint(by: string, now: Date, automatic?: {
|
|
2509
|
+
head?: {
|
|
2510
|
+
through: number;
|
|
2511
|
+
hash: string;
|
|
2512
|
+
};
|
|
2513
|
+
}): {
|
|
2494
2514
|
through: number;
|
|
2495
2515
|
hash: string;
|
|
2496
2516
|
} | {
|
|
@@ -4869,13 +4889,6 @@ export declare class Store {
|
|
|
4869
4889
|
notificationById(id: number): Notification | null;
|
|
4870
4890
|
notificationsAfter(id: number, limit?: number): Notification[];
|
|
4871
4891
|
listNotifications(only?: "pending" | "all"): Notification[];
|
|
4872
|
-
recordDelivery(id: number, outcome: {
|
|
4873
|
-
ok: true;
|
|
4874
|
-
receipt: string | null;
|
|
4875
|
-
} | {
|
|
4876
|
-
ok: false;
|
|
4877
|
-
error: string;
|
|
4878
|
-
}, now: Date): void;
|
|
4879
4892
|
/**
|
|
4880
4893
|
* Close a gap's notification episode. Called when its capability verifies:
|
|
4881
4894
|
* the next failure is a *new* fact, and a dedupe key that suppressed it
|
|
@@ -7448,6 +7461,12 @@ export declare class Store {
|
|
|
7448
7461
|
* every finished run is visited, a batch at a time. Never signals. */
|
|
7449
7462
|
recordFinishedRunExits(now: Date, batch?: number): number;
|
|
7450
7463
|
finishUnspawnedProcess(witness: number, now: Date): void;
|
|
7464
|
+
/** Settled custody, compacted (v114; compactSettledRunProcesses): a finished run whose witnesses all exited keeps
|
|
7465
|
+
* one summary row. A settled stop, an approver's settlement and a recovery compact their run at once; the worker's
|
|
7466
|
+
* reconcile pass (recordFinishedRunExits) compacts every other settled run, including one that settled at run end. */
|
|
7467
|
+
compactRunProcesses(now: Date, only?: number): number;
|
|
7468
|
+
/** Whether a run's witnesses were compacted into its summary: they existed, and all exited. */
|
|
7469
|
+
processesSummarized(runId: number): boolean;
|
|
7451
7470
|
/** The reconcile road for a pid-less witness a crash or a thrown spawn
|
|
7452
7471
|
* left behind: once its run has finished, no live child is tracked for it
|
|
7453
7472
|
* here, every other witness of the run has exited and none of the run's
|
|
@@ -7739,24 +7758,8 @@ export declare class Store {
|
|
|
7739
7758
|
error: string;
|
|
7740
7759
|
retryAt?: string;
|
|
7741
7760
|
}, now: Date): boolean;
|
|
7742
|
-
/**
|
|
7743
|
-
telegramDeliveries(binding: TelegramBinding):
|
|
7744
|
-
/**
|
|
7745
|
-
* Claim pending notifications for one deliverer. Two deliverers — the
|
|
7746
|
-
* bridge and `outbox deliver` — select-then-send-then-record, and without
|
|
7747
|
-
* a claim both can send the same row in the gap. The claim is a short
|
|
7748
|
-
* lease on the act of sending; a deliverer that dies mid-send leaves rows
|
|
7749
|
-
* that unclaim themselves by expiry.
|
|
7750
|
-
*/
|
|
7751
|
-
claimDeliveries(owner: string, ttlMs: number, now: Date, only?: "all" | "urgent"): Notification[];
|
|
7752
|
-
/** Finalize only what this owner still holds. A lapsed claim writes nothing. */
|
|
7753
|
-
finalizeDelivery(id: number, owner: string, outcome: {
|
|
7754
|
-
ok: true;
|
|
7755
|
-
receipt: string | null;
|
|
7756
|
-
} | {
|
|
7757
|
-
ok: false;
|
|
7758
|
-
error: string;
|
|
7759
|
-
}, now: Date): boolean;
|
|
7761
|
+
/** The notifications this pairing holds, each with its delivery state there. */
|
|
7762
|
+
telegramDeliveries(binding: TelegramBinding): NotificationReceipt[];
|
|
7760
7763
|
/** The digest cadence: null everyMs = off. */
|
|
7761
7764
|
telegramDigest(): TelegramDigest;
|
|
7762
7765
|
/** Set (or clear, with null) the cadence. Turning it on starts the
|
|
@@ -7879,14 +7882,6 @@ export declare class Store {
|
|
|
7879
7882
|
/** The message now showing this card, and what it shows. */
|
|
7880
7883
|
setChatCardMessage(id: number, message: string, digest: string): void;
|
|
7881
7884
|
markTelegramDigestSent(now: Date): void;
|
|
7882
|
-
/** Every batched row finalized delivered AND the anchor moved, in ONE
|
|
7883
|
-
* transaction (v4 review, finding 5): a digest is either recorded whole
|
|
7884
|
-
* or not at all. Rows whose claim lapsed meanwhile are reported, not
|
|
7885
|
-
* silently skipped. */
|
|
7886
|
-
finalizeDigest(ids: readonly number[], owner: string, receipt: string | null, now: Date): {
|
|
7887
|
-
finalized: number;
|
|
7888
|
-
lapsed: number;
|
|
7889
|
-
};
|
|
7890
7885
|
/** Routine rows waiting for the next digest: pending, and not urgent.
|
|
7891
7886
|
* A row the live pairing delivered, or skipped as pre-pairing history,
|
|
7892
7887
|
* is settled and not waiting. */
|