toolroll 0.9.52 → 0.9.54
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 +16 -0
- package/README.md +55 -80
- package/dist/agentconfig.d.ts +11 -36
- package/dist/agentconfig.js +3 -62
- package/dist/approval-policy.d.ts +1 -1
- package/dist/approval-policy.js +1 -1
- package/dist/board.d.ts +3 -19
- package/dist/board.js +4 -42
- package/dist/browser/workspace.js +18 -18
- package/dist/browser-workspace.d.ts +2 -1
- package/dist/builder.d.ts +9 -40
- package/dist/builder.js +50 -257
- package/dist/chat-actions.js +3 -14
- package/dist/chat-ask.js +8 -8
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.d.ts +171 -0
- package/dist/chat-core.js +288 -0
- package/dist/chat-decide.d.ts +10 -8
- package/dist/chat-decide.js +27 -24
- package/dist/chat-delivery-state.d.ts +51 -75
- package/dist/chat-delivery-state.js +140 -170
- package/dist/chat-delivery.d.ts +21 -4
- package/dist/chat-delivery.js +313 -195
- package/dist/chat-flow.d.ts +29 -1
- package/dist/chat-flow.js +24 -50
- package/dist/chat-messages.d.ts +319 -0
- package/dist/chat-messages.js +841 -0
- package/dist/chat-migration.d.ts +30 -0
- package/dist/chat-migration.js +802 -0
- package/dist/chat-quiet.d.ts +3 -0
- package/dist/chat-quiet.js +7 -2
- package/dist/chat-task-actions.d.ts +1 -1
- package/dist/chat-task-actions.js +0 -2
- package/dist/claim.d.ts +79 -244
- package/dist/claim.js +688 -1023
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +10 -2
- package/dist/contracts/chat-content.d.ts +71 -2
- package/dist/contracts/chat-content.js +22 -3
- package/dist/contracts/chat-tables.d.ts +11 -4
- package/dist/contracts/chat-tables.js +165 -87
- package/dist/contracts/cli.d.ts +0 -13
- package/dist/contracts/cli.js +0 -2
- package/dist/contracts/console-api.d.ts +5 -10
- package/dist/contracts/console-api.js +4 -10
- package/dist/contracts/discord-callback.js +2 -1
- package/dist/contracts/flow.d.ts +50 -0
- package/dist/contracts/flow.js +26 -1
- package/dist/contracts/lead-tools.d.ts +0 -13
- package/dist/contracts/lead-tools.js +3 -3
- package/dist/contracts/slack-callback.js +3 -2
- package/dist/contracts/store-json.d.ts +1 -29
- package/dist/contracts/store-json.js +4 -17
- package/dist/contracts/teams-callback.js +2 -1
- package/dist/converse.d.ts +1 -11
- package/dist/converse.js +6 -38
- package/dist/coordinator.d.ts +0 -1
- package/dist/coordinator.js +3 -5
- package/dist/demo.js +16 -26
- package/dist/desktop-update-gate.js +0 -1
- package/dist/desktop-update.js +1 -3
- package/dist/discord-chat.js +19 -38
- package/dist/discord-settings.js +4 -13
- package/dist/discord.js +4 -14
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/dispose.d.ts +2 -3
- package/dist/dispose.js +17 -92
- package/dist/envelope.js +0 -3
- package/dist/exec.d.ts +0 -64
- package/dist/exec.js +3 -356
- package/dist/exhaustion.d.ts +22 -76
- package/dist/exhaustion.js +14 -140
- package/dist/export.js +8 -8
- package/dist/flow-engine.js +1 -1
- package/dist/flow-schedule.d.ts +154 -0
- package/dist/flow-schedule.js +321 -0
- package/dist/flow-share.js +1 -1
- package/dist/flow-triggers.d.ts +6 -1
- package/dist/flow-triggers.js +65 -10
- package/dist/flows-cli.js +8 -4
- package/dist/flows-ui.js +2 -0
- package/dist/flows.js +1 -1
- package/dist/guides.js +9 -29
- package/dist/invoke.d.ts +1 -24
- package/dist/invoke.js +17 -259
- package/dist/lead-status.js +2 -4
- package/dist/lead-voice.js +1 -1
- package/dist/mate-doors.d.ts +1 -2
- package/dist/mate-doors.js +12 -25
- package/dist/mate-tools.d.ts +2 -2
- package/dist/mate-tools.js +13 -24
- package/dist/modes.d.ts +19 -8
- package/dist/modes.js +24 -14
- package/dist/notification-rows.d.ts +30 -0
- package/dist/notification-rows.js +102 -0
- package/dist/operate-remote-arguments.js +2 -6
- package/dist/operate-remote.js +6 -7
- package/dist/operate.d.ts +0 -6
- package/dist/operate.js +226 -2248
- package/dist/phase-routing.d.ts +13 -23
- package/dist/phase-routing.js +13 -35
- package/dist/plan-auto.js +2 -2
- package/dist/plane-review.js +5 -11
- package/dist/planner-source.js +2 -1
- package/dist/policy.d.ts +1 -4
- package/dist/policy.js +0 -8
- package/dist/process-recovery-eligibility.js +2 -3
- package/dist/project-delete.js +24 -41
- package/dist/proposal.d.ts +4 -24
- package/dist/proposal.js +3 -88
- package/dist/provider-auth.d.ts +3 -3
- package/dist/provider-auth.js +4 -6
- package/dist/provider.d.ts +11 -25
- package/dist/provider.js +4 -36
- package/dist/push.js +1 -1
- package/dist/recipe-ui.js +6 -6
- package/dist/recipes.d.ts +3 -3
- package/dist/recipes.js +27 -23
- package/dist/result-shots.js +2 -2
- package/dist/retention.js +1 -1
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +1 -4
- package/dist/scope.d.ts +11 -65
- package/dist/scope.js +17 -82
- package/dist/serve.js +15 -24
- package/dist/server/chat.js +7 -30
- package/dist/server/flows.js +11 -10
- package/dist/server/guards.js +0 -1
- package/dist/server/pages-assets.js +1 -1
- package/dist/server/pages.js +0 -2
- package/dist/server/people-tokens.js +0 -4
- package/dist/server/route-table.d.ts +1 -1
- package/dist/server/route-table.js +2 -17
- package/dist/server/settings.js +6 -12
- package/dist/server/shared.d.ts +28 -212
- package/dist/server/shared.js +71 -780
- package/dist/server/task-views.js +9 -76
- package/dist/server/tasks.d.ts +1 -50
- package/dist/server/tasks.js +36 -946
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-api.d.ts +5 -1
- package/dist/slack-api.js +2 -2
- package/dist/slack-chat.d.ts +9 -6
- package/dist/slack-chat.js +29 -53
- package/dist/slack-settings.js +6 -15
- package/dist/slack.js +6 -16
- package/dist/store.d.ts +200 -1702
- package/dist/store.js +811 -5439
- package/dist/surface.js +10 -24
- package/dist/task-composer.d.ts +1 -1
- package/dist/task-composer.js +1 -1
- package/dist/task-control.d.ts +3 -10
- package/dist/task-control.js +2 -7
- package/dist/teammate-desk.js +1 -1
- package/dist/teammate-question.js +12 -12
- package/dist/teammates-ui.js +1 -1
- package/dist/teams-chat.js +12 -9
- package/dist/teams-settings.js +2 -2
- package/dist/teams.js +4 -5
- package/dist/telegram-flow.js +5 -5
- package/dist/telegram-mate.d.ts +0 -3
- package/dist/telegram-mate.js +0 -1
- package/dist/telegram.js +4 -4
- package/dist/templates.d.ts +11 -7
- package/dist/templates.js +12 -9
- package/dist/toolroll-update.d.ts +20 -0
- package/dist/toolroll-update.js +104 -5
- package/dist/work-index.js +2 -6
- package/dist/workspace-revision.js +3 -1
- package/dist/workspace-ui.js +1 -1
- package/package.json +1 -1
- package/scripts/recovery-canary.mjs +3 -3
- package/dist/contest.d.ts +0 -318
- package/dist/contest.js +0 -753
- package/dist/held.d.ts +0 -150
- package/dist/held.js +0 -799
- package/dist/liveness.d.ts +0 -30
- package/dist/liveness.js +0 -42
- package/dist/pricing.d.ts +0 -45
- package/dist/pricing.js +0 -77
- package/dist/routine.d.ts +0 -228
- package/dist/routine.js +0 -764
- package/dist/slack-state.d.ts +0 -13
- package/dist/slack-state.js +0 -10
- package/dist/supervisor.mjs +0 -297
package/dist/templates.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The template library (adoption track, step 2) — common
|
|
3
|
-
*
|
|
2
|
+
* The template library (adoption track, step 2) — common work shipped as
|
|
3
|
+
* STATIC DATA that renders into editable, UNAPPROVED drafts.
|
|
4
4
|
*
|
|
5
5
|
* What a template is: a pre-filled form. What it is not: automation with
|
|
6
6
|
* authority. Applying one produces exactly the rows a manual filing
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* capitals, because `--file` must never read as a yes.
|
|
10
10
|
*
|
|
11
11
|
* The Codex adoption review (finding 10) drew the v1 line: templates may
|
|
12
|
-
* express ONLY what a task or
|
|
12
|
+
* express ONLY what a task proposal or a scheduled flow can carry today. A
|
|
13
|
+
* repeating template (v115: what a routine template was) makes a scheduled
|
|
14
|
+
* flow whose schedule starts paused, and whose every firing waits for
|
|
15
|
+
* approval under the project's rules. The two
|
|
13
16
|
* use cases that imply separate authenticated authority — GitHub issue
|
|
14
17
|
* intake (a grant) and CI babysitting (publish + the repair button) —
|
|
15
18
|
* ship as RECIPES: they display the existing ceremonies, and cannot be
|
|
@@ -21,10 +24,10 @@
|
|
|
21
24
|
*/
|
|
22
25
|
export const TEMPLATES = [
|
|
23
26
|
{
|
|
24
|
-
kind: "
|
|
27
|
+
kind: "scheduled",
|
|
25
28
|
name: "nightly-deps",
|
|
26
29
|
purpose: "keep dependencies fresh without surprise majors",
|
|
27
|
-
|
|
30
|
+
title: "Keep dependencies current",
|
|
28
31
|
goal: "Update dependencies conservatively: refresh the lockfile within existing semver ranges, run the full test suite, and summarize what moved in the handoff — plus anything notable you saw (majors now available, security advisories, packages that look abandoned). If the suite fails after the refresh, park with the failure rather than pinning things until it passes.",
|
|
29
32
|
outOfScope: "No major version bumps. No edits to application code beyond what the lockfile refresh itself forces. No changes to CI configuration.",
|
|
30
33
|
touches: [],
|
|
@@ -37,10 +40,10 @@ export const TEMPLATES = [
|
|
|
37
40
|
edit: ["schedule", "goal (name your package manager if it is unusual)", "ceiling (a weekly dollar cap)"],
|
|
38
41
|
},
|
|
39
42
|
{
|
|
40
|
-
kind: "
|
|
43
|
+
kind: "scheduled",
|
|
41
44
|
name: "test-coverage",
|
|
42
45
|
purpose: "grow the test suite one module at a time",
|
|
43
|
-
|
|
46
|
+
title: "Test one overlooked module",
|
|
44
47
|
goal: "Pick ONE module that is under-tested — no tests at all beats thin tests — and write focused tests for its observable behavior: inputs, outputs, and failure paths, not implementation details. One module per firing, then stop. If the module cannot be tested without refactoring it, do not refactor: park with a note naming the obstacle so a human can decide.",
|
|
45
48
|
outOfScope: "No refactoring of the code under test. No snapshot tests. No changes outside the chosen module's test file(s).",
|
|
46
49
|
touches: [],
|
|
@@ -53,10 +56,10 @@ export const TEMPLATES = [
|
|
|
53
56
|
edit: ["schedule", "touches (pin the test directory layout if yours is unusual)"],
|
|
54
57
|
},
|
|
55
58
|
{
|
|
56
|
-
kind: "
|
|
59
|
+
kind: "scheduled",
|
|
57
60
|
name: "docs-drift",
|
|
58
61
|
purpose: "keep the README and docs telling the truth weekly",
|
|
59
|
-
|
|
62
|
+
title: "Keep documentation accurate",
|
|
60
63
|
goal: "Read the README and any docs/ pages against the code as it is today. Fix what has drifted: commands that moved, flags that changed, features that shipped undocumented, claims that stopped being true. Prefer deleting a stale claim over guessing a new one. Summarize every correction in the handoff.",
|
|
61
64
|
outOfScope: "No restructuring or rewriting for style. No new documentation pages. No changes to code.",
|
|
62
65
|
touches: ["README.md", "docs/"],
|
|
@@ -103,6 +103,21 @@ export declare function releaseNotes(changelog: string, version: string): string
|
|
|
103
103
|
* by its directory), and the directory after, so a crash never leaves a torn
|
|
104
104
|
* database, catalog or command. */
|
|
105
105
|
export declare function durableRename(temp: string, target: string): void;
|
|
106
|
+
type Moved = {
|
|
107
|
+
into: string;
|
|
108
|
+
where: string;
|
|
109
|
+
rows?: string;
|
|
110
|
+
};
|
|
111
|
+
export declare const CHAT_MOVES: Record<string, Moved>;
|
|
112
|
+
/** The tables a migration may remove whole (v115's removed features). */
|
|
113
|
+
export declare const RETIRED_TABLES: readonly string[];
|
|
114
|
+
/** A saved routine, as its move into a scheduled flow must account for it. */
|
|
115
|
+
type RoutineDigest = {
|
|
116
|
+
id: number;
|
|
117
|
+
repo: string;
|
|
118
|
+
name: string;
|
|
119
|
+
live: boolean;
|
|
120
|
+
};
|
|
106
121
|
export type TableDigest = {
|
|
107
122
|
name: string;
|
|
108
123
|
columns: string[];
|
|
@@ -117,6 +132,11 @@ export type TableDigest = {
|
|
|
117
132
|
count: number;
|
|
118
133
|
nonNull: Record<string, number>;
|
|
119
134
|
};
|
|
135
|
+
routines?: RoutineDigest[];
|
|
136
|
+
moved?: {
|
|
137
|
+
rows: number;
|
|
138
|
+
found: number;
|
|
139
|
+
};
|
|
120
140
|
};
|
|
121
141
|
/** Each saved table's columns, row count and last rowid; the append-only ones hashed (deploy-browser's rehearsal check).
|
|
122
142
|
* Bounded: only the ledger and small tables are read row by row. */
|
package/dist/toolroll-update.js
CHANGED
|
@@ -166,7 +166,7 @@ export function runningWorkWords(db) {
|
|
|
166
166
|
const runs = db.prepare("SELECT tr.external_id id, COALESCE(t.title, tr.external_id) title FROM run r JOIN task_ref tr ON tr.id = r.task_ref LEFT JOIN task t ON t.id = tr.external_id WHERE r.outcome IS NULL ORDER BY r.id LIMIT 3").all()
|
|
167
167
|
.map(row => `${String(row["id"])} (${String(row["title"]).slice(0, 60)})`);
|
|
168
168
|
const more = Number(active["runs"] ?? 0) - runs.length;
|
|
169
|
-
const other = Object.entries(active).filter(([key, n]) => key !== "runs" && n > 0).map(([key, n]) => `${n} ${key === "claims" ? "claimed task" : key === "conversations" ? "chat request" : key === "
|
|
169
|
+
const other = Object.entries(active).filter(([key, n]) => key !== "runs" && n > 0).map(([key, n]) => `${n} ${key === "claims" ? "claimed task" : key === "conversations" ? "chat request" : key === "stopping" ? "run still stopping" : key === "codingDeliveries" ? "unconfirmed coding message" : "coding session"}${n === 1 ? "" : "s"}`);
|
|
170
170
|
const parts = [...(runs.length ? [`running ${runs.join(", ")}${more > 0 ? ` and ${more} more` : ""}`] : []), ...other];
|
|
171
171
|
return parts.join("; ");
|
|
172
172
|
}
|
|
@@ -322,16 +322,64 @@ export function durableRename(temp, target) {
|
|
|
322
322
|
* only when every saved value is null, or, with `carried`, when the receiving table gained exactly one receipt under
|
|
323
323
|
* that destination per row holding a value, and as many non-null values per column as the dropped columns held.
|
|
324
324
|
* `receives`: the only rows the table may gain are those receipts. `compacted`: rows may go only into summary rows,
|
|
325
|
-
* whose `sum` grows by exactly as many.
|
|
325
|
+
* whose `sum` grows by exactly as many. `retired`: a removed feature's table, which may go whole. `fromRoutines`: rows
|
|
326
|
+
* kept as they were, and the only rows the table may gain are one per saved routine, moved into a scheduled flow (its
|
|
327
|
+
* flow, or its schedule trigger naming that routine) — on only when the routine was approved and running. `moved`:
|
|
328
|
+
* the table may go only when every row it had (or the ones `rows` counts) arrived in `into`: the rows there that
|
|
329
|
+
* `where` picks grew by exactly that many. */
|
|
326
330
|
const LEGACY_DESTINATION = "legacy:single-destination";
|
|
331
|
+
/** v116: every old per-app chat table into the shared chat tables, keyed by provider (chat-migration.ts). Several old
|
|
332
|
+
* tables fan into one shared table; each picks out only its own rows there, so every count is checked on its own. */
|
|
333
|
+
const TELEGRAM_MOVES = {
|
|
334
|
+
telegram_binding: { into: "chat_binding", where: "1" },
|
|
335
|
+
telegram_pairing: { into: "chat_pair", where: "1" },
|
|
336
|
+
telegram_team_chat: { into: "chat_room", where: "1" },
|
|
337
|
+
// An applied update is a receipt; a pushed one still waiting is queued (one already applied was spent with it).
|
|
338
|
+
telegram_update: { into: "chat_event", where: "kind = 'update' AND state = 'done'" },
|
|
339
|
+
telegram_inbox: { into: "chat_event", where: "kind = 'update' AND state = 'queued'", rows: "update_id NOT IN (SELECT update_id FROM telegram_update)" },
|
|
340
|
+
telegram_conversation: { into: "chat_event", where: "kind = 'message'" },
|
|
341
|
+
telegram_conversation_part: { into: "chat_part", where: "1" },
|
|
342
|
+
telegram_proposal_action: { into: "chat_action", where: "proposal IS NOT NULL" },
|
|
343
|
+
telegram_action: { into: "chat_action", where: "decision IS NOT NULL" },
|
|
344
|
+
telegram_flow_action: { into: "chat_flow_action", where: "action IN ('approve','edit','send-back')" },
|
|
345
|
+
telegram_flow_confirm: { into: "chat_flow_action", where: "action IN ('yes','cancel')" },
|
|
346
|
+
telegram_flow_choice: { into: "chat_flow_choice", where: "1" },
|
|
347
|
+
telegram_flow_prompt: { into: "chat_flow_prompt", where: "1" },
|
|
348
|
+
telegram_question_action: { into: "chat_question_action", where: "1" },
|
|
349
|
+
telegram_question_prompt: { into: "chat_question_prompt", where: "1" },
|
|
350
|
+
telegram_decision_message: { into: "chat_message_ref", where: "kind = 'decision'" },
|
|
351
|
+
telegram_task_message: { into: "chat_message_ref", where: "kind = 'task'" },
|
|
352
|
+
telegram_outbound_message: { into: "chat_message_ref", where: "kind = 'notification'" },
|
|
353
|
+
telegram_note_draft: { into: "chat_note_draft", where: "1" },
|
|
354
|
+
// A bot's lease and its rate-limit wait share its one runtime row.
|
|
355
|
+
bridge_lease: { into: "chat_runtime", where: "lease_until IS NOT NULL" },
|
|
356
|
+
telegram_retry: { into: "chat_runtime", where: "retry_at IS NOT NULL" },
|
|
357
|
+
telegram_digest: { into: "chat_digest", where: "1", rows: "id = 1" },
|
|
358
|
+
};
|
|
359
|
+
const APP_TABLES = ["binding", "pair", "event", "part", "action", "progress", "runtime", "room", "meta",
|
|
360
|
+
"flow_action", "flow_prompt", "flow_choice", "flow_note", "question_action", "question_prompt", "ask_action"];
|
|
361
|
+
export const CHAT_MOVES = {
|
|
362
|
+
...Object.fromEntries(Object.entries(TELEGRAM_MOVES).map(([table, move]) => [table, { ...move, where: `provider = 'telegram' AND (${move.where})` }])),
|
|
363
|
+
...Object.fromEntries(["slack", "discord", "teams"].flatMap(app => APP_TABLES.map(suffix => [`${app}_${suffix}`, { into: `chat_${suffix}`, where: `provider = '${app}'` }]))),
|
|
364
|
+
};
|
|
327
365
|
const HISTORY_RULES = {
|
|
366
|
+
...Object.fromEntries(Object.entries(CHAT_MOVES).map(([table, moved]) => [table, { moved }])),
|
|
328
367
|
action_ledger: { appendOnly: true }, ledger_seal: { appendOnly: true }, ledger_checkpoint: { appendOnly: true },
|
|
329
368
|
// v114: a finished run whose process witnesses have all exited keeps one summary row in place of them.
|
|
330
369
|
run_process: { compacted: { into: "run_process_summary", sum: "witnesses" } }, run_process_summary: { appendOnly: true },
|
|
331
370
|
// v114: single-destination delivery columns may be dropped only once their values are legacy receipts.
|
|
332
371
|
notification: { dropped: ["attempts", "last_attempt_at", "last_error", "delivered_at", "receipt", "claim_owner", "claim_expires_at"], carried: { into: "notification_delivery", destination: LEGACY_DESTINATION } },
|
|
333
372
|
notification_delivery: { receives: LEGACY_DESTINATION },
|
|
373
|
+
// v115: contests, held sessions, fallback chains and routines are removed; their tables go whole.
|
|
374
|
+
tournament_terms: { retired: true }, contest: { retired: true }, contestant: { retired: true },
|
|
375
|
+
routine: { retired: true }, routine_fire: { retired: true },
|
|
376
|
+
attended_authorization: { retired: true }, session_turn: { retired: true }, held_session: { retired: true },
|
|
377
|
+
fallback_config: { retired: true }, fallback_cycle: { retired: true }, fallback_transition: { retired: true },
|
|
378
|
+
// v115: each routine becomes one scheduled flow and its schedule trigger.
|
|
379
|
+
flow: { fromRoutines: "flow" }, flow_trigger: { fromRoutines: "trigger" },
|
|
334
380
|
};
|
|
381
|
+
/** The tables a migration may remove whole (v115's removed features). */
|
|
382
|
+
export const RETIRED_TABLES = Object.freeze(Object.keys(HISTORY_RULES).filter(name => HISTORY_RULES[name].retired));
|
|
335
383
|
const tableColumns = (db, name) => db.prepare("PRAGMA table_info(" + quote(name) + ")").all().map(r => String(r["name"]));
|
|
336
384
|
const plainValue = (_, v) => v instanceof Uint8Array ? Buffer.from(v).toString("hex") : typeof v === "bigint" ? String(v) : v;
|
|
337
385
|
/** Rows streamed into a sha256, never held together: through a rowid in rowid order, or a WITHOUT ROWID table whole. */
|
|
@@ -352,6 +400,8 @@ function receipts(db, name, destination, columns) {
|
|
|
352
400
|
const r = db.prepare("SELECT count(*) AS n" + columns.map(column => ",count(" + quote(column) + ") AS " + quote(column)).join("") + " FROM " + quote(name) + " WHERE destination = ?").get(destination);
|
|
353
401
|
return { count: Number(r["n"]), nonNull: Object.fromEntries(columns.map(column => [column, Number(r[column])])) };
|
|
354
402
|
}
|
|
403
|
+
/** The rows a moved table's rule picks out in its destination (none while there is no such table). */
|
|
404
|
+
const arrived = (db, move) => db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(move.into) ? Number(db.prepare("SELECT count(*) AS n FROM " + quote(move.into) + " WHERE " + move.where).get()["n"]) : 0;
|
|
355
405
|
const summed = (db, into) => db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(into.into) ? Number(db.prepare("SELECT coalesce(sum(" + quote(into.sum) + "),0) AS n FROM " + quote(into.into)).get()["n"]) : 0;
|
|
356
406
|
/** Each saved table's columns, row count and last rowid; the append-only ones hashed (deploy-browser's rehearsal check).
|
|
357
407
|
* Bounded: only the ledger and small tables are read row by row. */
|
|
@@ -371,13 +421,53 @@ export function historySnapshot(db) {
|
|
|
371
421
|
}
|
|
372
422
|
if (rule?.receives)
|
|
373
423
|
t.received = receipts(db, name, rule.receives, columns.filter(column => column !== "notification" && column !== "destination"));
|
|
374
|
-
if (rule?.appendOnly)
|
|
424
|
+
if (rule?.appendOnly || rule?.fromRoutines)
|
|
375
425
|
t.hash = rowsHash(db, name, columns, t.last).hash;
|
|
376
426
|
if (rule?.compacted)
|
|
377
427
|
t.summed = summed(db, rule.compacted);
|
|
428
|
+
if (rule?.fromRoutines)
|
|
429
|
+
t.routines = savedRoutines(db);
|
|
430
|
+
if (rule?.moved)
|
|
431
|
+
t.moved = { found: arrived(db, rule.moved),
|
|
432
|
+
rows: rule.moved.rows === undefined ? t.count : Number(db.prepare("SELECT count(*) AS n FROM " + quote(name) + " WHERE " + rule.moved.rows).get()["n"]) };
|
|
378
433
|
return t;
|
|
379
434
|
});
|
|
380
435
|
}
|
|
436
|
+
/** Each saved routine (none when the table is gone or was never made), and whether it was approved and running. */
|
|
437
|
+
function savedRoutines(db) {
|
|
438
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='routine'").get())
|
|
439
|
+
return [];
|
|
440
|
+
const columns = new Set(tableColumns(db, "routine"));
|
|
441
|
+
const live = ["approved_at", "approved_digest", "digest", "paused", "approved_profile_json", "approved_route_json"].every(c => columns.has(c))
|
|
442
|
+
? "(approved_at IS NOT NULL AND approved_digest = digest AND approved_profile_json IS NOT NULL AND approved_route_json IS NOT NULL AND paused = 0)" : "0";
|
|
443
|
+
return db.prepare("SELECT id, repo, name, " + live + " AS live FROM routine ORDER BY id").all()
|
|
444
|
+
.map(r => ({ id: Number(r["id"]), repo: String(r["repo"]), name: String(r["name"]), live: Number(r["live"]) === 1 }));
|
|
445
|
+
}
|
|
446
|
+
/** Whether a table's rows past its saved last rowid are exactly the flows (or schedule triggers) its saved routines became. */
|
|
447
|
+
function movedRoutines(db, t, before) {
|
|
448
|
+
const routines = t.routines ?? [];
|
|
449
|
+
if (HISTORY_RULES[t.name].fromRoutines === "flow") {
|
|
450
|
+
const added = db.prepare("SELECT repo, name, state FROM flow WHERE rowid > ? ORDER BY rowid").all(t.last);
|
|
451
|
+
const key = (repo, name) => JSON.stringify([String(repo), String(name)]);
|
|
452
|
+
return added.length === routines.length && added.every(r => r["state"] === "active")
|
|
453
|
+
&& JSON.stringify(added.map(r => key(r["repo"], r["name"])).sort()) === JSON.stringify(routines.map(r => key(r.repo, r.name)).sort());
|
|
454
|
+
}
|
|
455
|
+
// A trigger names its routine in its order, and belongs to a flow the move added for that same routine.
|
|
456
|
+
const flows = before.find(one => one.name === "flow");
|
|
457
|
+
if (flows === undefined)
|
|
458
|
+
return false;
|
|
459
|
+
const added = db.prepare(`SELECT t.kind, t.state, json_extract(t.config_json, '$.order.routine') AS routine, f.id AS flow, f.repo, f.name FROM flow_trigger t
|
|
460
|
+
LEFT JOIN flow f ON f.id = t.flow AND f.rowid > ? WHERE t.rowid > ? ORDER BY t.rowid`).all(flows.last, t.last);
|
|
461
|
+
const byId = new Map(routines.map(r => [r.id, r]));
|
|
462
|
+
const seen = new Set();
|
|
463
|
+
return added.length === routines.length && added.every(r => {
|
|
464
|
+
const routine = byId.get(Number(r["routine"]));
|
|
465
|
+
if (r["kind"] !== "schedule" || routine === undefined || seen.has(routine.id) || r["flow"] === null)
|
|
466
|
+
return false;
|
|
467
|
+
seen.add(routine.id);
|
|
468
|
+
return r["repo"] === routine.repo && r["name"] === routine.name && (r["state"] === "paused" || (r["state"] === "active" && routine.live));
|
|
469
|
+
});
|
|
470
|
+
}
|
|
381
471
|
/** The tables whose saved history the migration changed beyond what HISTORY_RULES declares. */
|
|
382
472
|
export function changedHistory(db, before) {
|
|
383
473
|
const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(r => String(r["name"])));
|
|
@@ -402,8 +492,13 @@ export function changedHistory(db, before) {
|
|
|
402
492
|
return got !== undefined && want !== undefined && got.count === want.count && Object.entries(want.nonNull).every(([c, n]) => got.nonNull[c] === n) && t.carry === want.count;
|
|
403
493
|
};
|
|
404
494
|
return before.filter(t => {
|
|
405
|
-
if (!tables.has(t.name))
|
|
406
|
-
|
|
495
|
+
if (!tables.has(t.name)) {
|
|
496
|
+
const rule = HISTORY_RULES[t.name];
|
|
497
|
+
if (rule?.retired === true)
|
|
498
|
+
return false;
|
|
499
|
+
const move = rule?.moved;
|
|
500
|
+
return move === undefined || t.moved === undefined || arrived(db, move) - t.moved.found !== t.moved.rows;
|
|
501
|
+
}
|
|
407
502
|
const rule = HISTORY_RULES[t.name], present = new Set(tableColumns(db, t.name));
|
|
408
503
|
if (t.columns.some(c => !present.has(c) && (!rule?.dropped?.includes(c) || (t.nonNull?.[c] !== 0 && !accounted(rule, t)))))
|
|
409
504
|
return true;
|
|
@@ -412,6 +507,10 @@ export function changedHistory(db, before) {
|
|
|
412
507
|
const after = rowsHash(db, t.name, columns);
|
|
413
508
|
return after.count !== t.count || after.hash !== t.hash;
|
|
414
509
|
}
|
|
510
|
+
if (rule?.fromRoutines) {
|
|
511
|
+
const kept = rowsHash(db, t.name, columns, t.last);
|
|
512
|
+
return kept.count !== t.count || kept.hash !== t.hash || !movedRoutines(db, t, before);
|
|
513
|
+
}
|
|
415
514
|
if (rule?.appendOnly) {
|
|
416
515
|
const after = rowsHash(db, t.name, columns, t.last);
|
|
417
516
|
return after.count !== t.count || after.hash !== t.hash;
|
package/dist/work-index.js
CHANGED
|
@@ -72,8 +72,6 @@ const PROJECTION = `WITH RECURSIVE admitted AS MATERIALIZED (
|
|
|
72
72
|
JOIN run r ON r.id=d.run JOIN admitted a ON a.ref_id=r.task_ref
|
|
73
73
|
WHERE d.state<>'answered' AND d.answered_at IS NULL GROUP BY r.task_ref
|
|
74
74
|
), custody AS MATERIALIZED (
|
|
75
|
-
SELECT r.task_ref FROM held_session h JOIN run r ON r.id=h.run JOIN admitted a ON a.ref_id=r.task_ref WHERE h.ended_at IS NULL
|
|
76
|
-
UNION
|
|
77
75
|
SELECT r.task_ref FROM run r INDEXED BY work_spawned_run JOIN admitted a ON a.ref_id=r.task_ref
|
|
78
76
|
WHERE r.provider_started_at IS NOT NULL AND NOT EXISTS(SELECT 1 FROM run_process p WHERE p.run=r.id) AND NOT EXISTS(SELECT 1 FROM run_process_summary s WHERE s.run=r.id)
|
|
79
77
|
UNION
|
|
@@ -110,8 +108,7 @@ const PROJECTION = `WITH RECURSIVE admitted AS MATERIALIZED (
|
|
|
110
108
|
), workers AS MATERIALIZED (
|
|
111
109
|
SELECT r.name,r.capacity,r.heartbeat_at,j.value repo,
|
|
112
110
|
(SELECT COUNT(*) FROM claim c WHERE c.runner=r.name AND c.released_at IS NULL AND c.expires_at>$now
|
|
113
|
-
AND c.lease_generation=(SELECT MAX(newest.lease_generation) FROM claim newest WHERE newest.task_ref=c.task_ref)
|
|
114
|
-
AND c.lease_id NOT IN (SELECT lease_id FROM held_session WHERE ended_at IS NULL)) occupied
|
|
111
|
+
AND c.lease_generation=(SELECT MAX(newest.lease_generation) FROM claim newest WHERE newest.task_ref=c.task_ref)) occupied
|
|
115
112
|
FROM runner r,json_each(CASE WHEN json_valid(r.repos) THEN r.repos ELSE '[]' END) j WHERE r.retired_at IS NULL
|
|
116
113
|
), facts AS MATERIALIZED (
|
|
117
114
|
SELECT c.*,f.version_count,f.family_updated,f.earlier_active,f.earlier_id,f.family_running,f.question_id,
|
|
@@ -289,8 +286,7 @@ function registerValidators(store) {
|
|
|
289
286
|
* `workspace`: a finished task whose last build's workspace note is still
|
|
290
287
|
* there, which only the store can read (a held or unreadable workspace). */
|
|
291
288
|
function custodyReadings(store) {
|
|
292
|
-
const refs = store.handle.prepare(`SELECT r.task_ref FROM
|
|
293
|
-
UNION SELECT r.task_ref FROM run r WHERE r.provider_started_at IS NOT NULL AND NOT EXISTS(SELECT 1 FROM run_process p WHERE p.run=r.id) AND NOT EXISTS(SELECT 1 FROM run_process_summary s WHERE s.run=r.id)
|
|
289
|
+
const refs = store.handle.prepare(`SELECT r.task_ref FROM run r WHERE r.provider_started_at IS NOT NULL AND NOT EXISTS(SELECT 1 FROM run_process p WHERE p.run=r.id) AND NOT EXISTS(SELECT 1 FROM run_process_summary s WHERE s.run=r.id)
|
|
294
290
|
UNION SELECT r.task_ref FROM run_process p JOIN run r ON r.id=p.run WHERE p.exited_at IS NULL AND p.container_empty_at IS NULL`).all().map(row => Number(row['task_ref']));
|
|
295
291
|
const noted = store.handle.prepare(`SELECT r.task_ref,r.worktree FROM task t JOIN task_ref tr ON tr.external_id=t.id
|
|
296
292
|
JOIN run r ON r.id=(SELECT MAX(id) FROM run WHERE task_ref=tr.id AND role IN ('builder','scout') AND finished_at IS NOT NULL)
|
|
@@ -4,10 +4,12 @@ export const WORKSPACE_REVISION_KEY = 'workspace-content:v1';
|
|
|
4
4
|
const KEY = WORKSPACE_REVISION_KEY;
|
|
5
5
|
const MAX_AGE_MS = 60_000;
|
|
6
6
|
const OMIT = new Set(['schema_version', 'service_cursor', 'wake', 'notification_delivery', 'sqlite_sequence',
|
|
7
|
-
'team_read', 'team_request', 'push_delivery', '
|
|
7
|
+
'team_read', 'team_request', 'push_delivery', 'chat_message_ref']);
|
|
8
8
|
const QUIET = {
|
|
9
9
|
runner: ['heartbeat_at'], claim: ['heartbeat_at', 'expires_at'], watch_lease: ['heartbeat_at', 'expires_at'],
|
|
10
10
|
watch_episode: ['ticks'], provider_readiness: ['observed_at'],
|
|
11
|
+
// A chat worker's lease, poll cursor and rate-limit wait are its own bookkeeping; its connection and problem are news.
|
|
12
|
+
chat_runtime: ['owner', 'lease_until', 'generation', 'cursor', 'heartbeat', 'retry_at'],
|
|
11
13
|
};
|
|
12
14
|
/** A quiet beat that revives what a view already rendered as gone: the rows a beat may move, read before and after.
|
|
13
15
|
* A runner beat after its liveness window (or backwards) is a revival; a lease renewed at or past its expiry is one. */
|
package/dist/workspace-ui.js
CHANGED
|
@@ -506,7 +506,7 @@ export function workCounts(rows) {
|
|
|
506
506
|
export function compareWorkRows(a, b) {
|
|
507
507
|
return a.rank - b.rank || (a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0);
|
|
508
508
|
}
|
|
509
|
-
const WORK_KEYS = new Set(["inbox", "board", "queue", "work", "done", "activity", "review", "tasks", "runs", "workbench", "
|
|
509
|
+
const WORK_KEYS = new Set(["inbox", "board", "queue", "work", "done", "activity", "review", "tasks", "runs", "workbench", "recipes", "ledger"]);
|
|
510
510
|
const SETTINGS_KEYS = new Set(["fleet", "caps", "people", "mode", "system", "settings"]);
|
|
511
511
|
export function primaryDestinationOf(active) {
|
|
512
512
|
if (active === "chat")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.54",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|
|
@@ -27,7 +27,7 @@ for (let index = 2; index < process.argv.length; index++) {
|
|
|
27
27
|
assert(Number.isSafeInteger(rounds) && rounds > 0 && rounds <= 1000, "rounds must be an integer from 1 to 1000");
|
|
28
28
|
const { openStore } = await import(pathToFileURL(join(root, "dist/store.js")));
|
|
29
29
|
const { register, recoverDead, DEFAULT_LIVENESS_MS } = await import(pathToFileURL(join(root, "dist/runner.js")));
|
|
30
|
-
const { acquire,
|
|
30
|
+
const { acquire, finalize } = await import(pathToFileURL(join(root, "dist/claim.js")));
|
|
31
31
|
const { WorktreePool } = await import(pathToFileURL(join(root, "dist/worktree.js")));
|
|
32
32
|
const { run } = await import(pathToFileURL(join(root, "dist/exec.js")));
|
|
33
33
|
const { HANDOFF_PREFIX } = await import(pathToFileURL(join(root, "dist/evidence.js")));
|
|
@@ -101,7 +101,7 @@ try {
|
|
|
101
101
|
const blocked = await pool.lease(request);
|
|
102
102
|
assert.equal(blocked.ok, false);
|
|
103
103
|
assert.equal(blocked.reason, "in-use");
|
|
104
|
-
assert.equal(
|
|
104
|
+
assert.equal(finalize(store, "old-lease", { kind: "complete", state: "done", now: expired }).ok, false);
|
|
105
105
|
assert.notEqual(store.getTask("work").state, "done");
|
|
106
106
|
writer.kill("SIGKILL");
|
|
107
107
|
await exited;
|
|
@@ -114,7 +114,7 @@ try {
|
|
|
114
114
|
assert.equal(await readFile(join(path, "draft.txt"), "utf8"), draft);
|
|
115
115
|
assert.equal(await git(path, ["rev-parse", "HEAD"]), head);
|
|
116
116
|
assert((await readFile(resumed.reclaimed, "utf8")).includes(draft.trim()));
|
|
117
|
-
assert.equal(
|
|
117
|
+
assert.equal(finalize(store, "new-lease", { kind: "complete", state: "failed", now: expired }).ok, true);
|
|
118
118
|
await pool.release(path, expired);
|
|
119
119
|
cases.push({ round: index + 1, scenario, passed: true, oldCompletion: "fenced", liveWriter: "refused-reuse", draft: "preserved", commit: "preserved" });
|
|
120
120
|
} finally {
|