toolroll 0.9.59 → 0.9.60
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 +11 -0
- package/README.md +8 -6
- package/dist/assignment-adapters.d.ts +1 -1
- package/dist/assignment-adapters.js +1 -1
- package/dist/assignment-delivery.d.ts +16 -0
- package/dist/assignment-delivery.js +54 -28
- package/dist/assignment-status.d.ts +13 -4
- package/dist/assignment-status.js +70 -39
- package/dist/assignment.d.ts +6 -2
- package/dist/assignment.js +13 -8
- package/dist/bin.js +11 -2
- package/dist/brief-fence.d.ts +9 -0
- package/dist/brief-fence.js +22 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +19 -19
- package/dist/builder.js +74 -25
- package/dist/chat-channel.js +1 -1
- package/dist/cli-fast.d.ts +8 -0
- package/dist/cli-fast.js +18 -0
- package/dist/cli-help.d.ts +5 -0
- package/dist/cli-help.js +91 -0
- package/dist/cli.d.ts +6 -5
- package/dist/cli.js +17 -96
- package/dist/design/tokens.css +234 -0
- package/dist/discord.js +2 -1
- package/dist/evidence.d.ts +22 -0
- package/dist/evidence.js +76 -17
- package/dist/flow-actions.js +2 -1
- package/dist/guides.js +14 -11
- package/dist/lead-cli.js +1 -1
- package/dist/lead-contract.js +1 -1
- package/dist/lead-doors.js +5 -2
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +138 -88
- package/dist/lead-tools.js +9 -2
- package/dist/mailbox.js +3 -2
- package/dist/mcp-core.js +1 -1
- package/dist/operate.js +17 -7
- package/dist/provider.d.ts +8 -0
- package/dist/provider.js +12 -1
- package/dist/serve.js +3 -2
- package/dist/server/chrome.d.ts +6 -3
- package/dist/server/chrome.js +8 -4
- package/dist/server/console.css +9 -64
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-tasks.d.ts +2 -0
- package/dist/server/render-tasks.js +11 -13
- package/dist/server/task-views.js +1 -0
- package/dist/slack-socket.d.ts +12 -0
- package/dist/slack-socket.js +29 -0
- package/dist/slack.d.ts +0 -7
- package/dist/slack.js +2 -24
- package/dist/steer-mailbox.d.ts +39 -0
- package/dist/steer-mailbox.js +207 -0
- package/dist/steer-status.d.ts +19 -0
- package/dist/steer-status.js +30 -0
- package/dist/store.d.ts +41 -2
- package/dist/store.js +95 -3
- package/dist/surface.js +2 -2
- package/dist/sweep-gate.d.ts +10 -0
- package/dist/sweep-gate.js +18 -0
- package/dist/team-updates.js +3 -3
- package/dist/toolroll-update.js +14 -2
- package/package.json +21 -21
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 = 120;
|
|
156
156
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
157
157
|
export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
|
|
158
158
|
/**
|
|
@@ -209,7 +209,8 @@ export declare const V115_DROPPED_TABLES: readonly string[];
|
|
|
209
209
|
* coordinator_credential and lead_credential moves into credential under its kind in one transaction that counts them
|
|
210
210
|
* before the old tables drop, and the references that named them name credential in place, their rows untouched. v119 only adds four columns
|
|
211
211
|
* (planning_generation on task_ref, run and plan_revision, defaulting to 0, and decision.superseded_reason, null);
|
|
212
|
-
* the rehearsal checks every saved row reads the declared default (HISTORY_RULES' added columns).
|
|
212
|
+
* the rehearsal checks every saved row reads the declared default (HISTORY_RULES' added columns). v120 only adds
|
|
213
|
+
* three nullable task_steer columns (the running build's steering folder), checked the same way.
|
|
213
214
|
*/
|
|
214
215
|
export declare const UPDATE_SAFE_MIGRATIONS: readonly number[];
|
|
215
216
|
/** v119: the columns it adds, by table, with the value every row saved before it reads (toolroll-update's HISTORY_RULES). */
|
|
@@ -217,6 +218,11 @@ export declare const V119_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
|
|
|
217
218
|
definition: string;
|
|
218
219
|
saved: string;
|
|
219
220
|
}>>>>;
|
|
221
|
+
/** v120: the columns it adds, by table, with the value every row saved before it reads. */
|
|
222
|
+
export declare const V120_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record<string, {
|
|
223
|
+
definition: string;
|
|
224
|
+
saved: string;
|
|
225
|
+
}>>>>;
|
|
220
226
|
/** Whether a database settled at `version` reaches this build's schema through update-safe migrations alone. */
|
|
221
227
|
export declare function updateSafeSchema(version: number | null): boolean;
|
|
222
228
|
export type OAuthClientRow = {
|
|
@@ -1137,6 +1143,9 @@ export type RunPhase = (typeof RUN_PHASES)[number];
|
|
|
1137
1143
|
* One operator steering note (arc 1, v22). Attached ≠ delivered: attachment
|
|
1138
1144
|
* says which build the note rode toward; delivery settles only on the
|
|
1139
1145
|
* stream's own receipt. Superseded = the task ended with it still pending.
|
|
1146
|
+
* v120: a note filed while a build runs is mailed into that build's steering
|
|
1147
|
+
* folder (mailed), and is read only when the agent moves it to handled/
|
|
1148
|
+
* (acked, which also settles it delivered to that build).
|
|
1140
1149
|
*/
|
|
1141
1150
|
export type SteerNote = {
|
|
1142
1151
|
/** v24 (ruling 11): "verified" = credentialed authorship; anything else is history, never brief-bound. */
|
|
@@ -1151,6 +1160,9 @@ export type SteerNote = {
|
|
|
1151
1160
|
attachedAt: string | null;
|
|
1152
1161
|
deliveredAt: string | null;
|
|
1153
1162
|
supersededAt: string | null;
|
|
1163
|
+
mailedRun: number | null;
|
|
1164
|
+
mailedAt: string | null;
|
|
1165
|
+
ackedAt: string | null;
|
|
1154
1166
|
};
|
|
1155
1167
|
/** One phone enrollment for web push (arc 3, v23). A row per ACTIVATION —
|
|
1156
1168
|
* retirement is history, never deletion. */
|
|
@@ -4676,6 +4688,33 @@ export declare class Store {
|
|
|
4676
4688
|
* first receipt wins, and a replay changes nothing.
|
|
4677
4689
|
*/
|
|
4678
4690
|
settleSteerDelivered(runId: number, now: Date): number;
|
|
4691
|
+
/**
|
|
4692
|
+
* The task's build whose agent is running right now and reads its steering
|
|
4693
|
+
* folder (v120): the current live lease's open builder run, in its
|
|
4694
|
+
* agent-running phase, whose steering folder is open. Null otherwise — a
|
|
4695
|
+
* note then waits for the next attempt. Display state, never evidence.
|
|
4696
|
+
*/
|
|
4697
|
+
steeringLiveBuild(taskRef: number, now: Date): number | null;
|
|
4698
|
+
/** This run made its steering folder (v120): its agent can read a note filed now. */
|
|
4699
|
+
markSteerFolderOpened(runId: number, now: Date): void;
|
|
4700
|
+
/** This run's steering folder is gone (v120): a note filed now reaches the next attempt. */
|
|
4701
|
+
markSteerFolderClosed(runId: number, now: Date): void;
|
|
4702
|
+
/**
|
|
4703
|
+
* The notes a RUNNING build may still read (v120): verified, pending, not
|
|
4704
|
+
* already quoted in this run's brief, and not already in its folder. The
|
|
4705
|
+
* pulse mails exactly these, in filing order.
|
|
4706
|
+
*/
|
|
4707
|
+
mailableSteerNotes(taskRef: number, runId: number): SteerNote[];
|
|
4708
|
+
/** The pulse wrote this note into the run's steering folder. Mailing is not
|
|
4709
|
+
* reading: delivery settles only on the agent's own acknowledgement. */
|
|
4710
|
+
markSteerMailed(noteId: number, runId: number, now: Date): boolean;
|
|
4711
|
+
/**
|
|
4712
|
+
* The agent moved this note to handled/ (v120): it was read, by THIS run.
|
|
4713
|
+
* Acknowledgement is the only road from mailed to delivered — a hook firing
|
|
4714
|
+
* or a file written proves nothing. First acknowledgement wins; a note the
|
|
4715
|
+
* task already ended, or mailed to another run since, is left alone.
|
|
4716
|
+
*/
|
|
4717
|
+
ackSteerNote(noteId: number, runId: number, now: Date): boolean;
|
|
4679
4718
|
/** A finished task's pending notes settle superseded — shown, typed, done. */
|
|
4680
4719
|
supersedeSteerNotes(taskRef: number, now: Date): number;
|
|
4681
4720
|
/** The answers a run was given, exactly as it was given them. */
|
package/dist/store.js
CHANGED
|
@@ -562,7 +562,9 @@ const REGISTERED_AT_FILING = "again, as it was filed";
|
|
|
562
562
|
// v119 keeps planning generations: a rewritten scope starts a clean planning context (task_ref, run and plan_revision
|
|
563
563
|
// carry planning_generation, 0 for all earlier history), and a question closed without an answer says why
|
|
564
564
|
// (decision.superseded_reason: its task's scope changed, or the task was cancelled).
|
|
565
|
-
|
|
565
|
+
// v120 lets a running build read steering: a note the pulse put in the build's steering folder (task_steer.mailed_run,
|
|
566
|
+
// mailed_at) and the moment the agent moved it to handled/ (acked_at).
|
|
567
|
+
export const SCHEMA_VERSION = 120;
|
|
566
568
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
567
569
|
export const V118_MERGED_TABLES = Object.freeze({ api_token: "person", coordinator_credential: "coordinator", lead_credential: "lead" });
|
|
568
570
|
/** The foreign keys that named a merged table, exactly as their DDL wrote them; each names `credential(id)` instead. */
|
|
@@ -685,9 +687,10 @@ const V115_DROPPED_REFERENCES = /\s+REFERENCES (?:contestant|contest|tournament_
|
|
|
685
687
|
* coordinator_credential and lead_credential moves into credential under its kind in one transaction that counts them
|
|
686
688
|
* before the old tables drop, and the references that named them name credential in place, their rows untouched. v119 only adds four columns
|
|
687
689
|
* (planning_generation on task_ref, run and plan_revision, defaulting to 0, and decision.superseded_reason, null);
|
|
688
|
-
* the rehearsal checks every saved row reads the declared default (HISTORY_RULES' added columns).
|
|
690
|
+
* the rehearsal checks every saved row reads the declared default (HISTORY_RULES' added columns). v120 only adds
|
|
691
|
+
* three nullable task_steer columns (the running build's steering folder), checked the same way.
|
|
689
692
|
*/
|
|
690
|
-
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119]);
|
|
693
|
+
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120]);
|
|
691
694
|
/** v119: the columns it adds, by table, with the value every row saved before it reads (toolroll-update's HISTORY_RULES). */
|
|
692
695
|
export const V119_ADDED_COLUMNS = Object.freeze({
|
|
693
696
|
task_ref: { planning_generation: { definition: "INTEGER NOT NULL DEFAULT 0", saved: "0" } },
|
|
@@ -695,6 +698,18 @@ export const V119_ADDED_COLUMNS = Object.freeze({
|
|
|
695
698
|
plan_revision: { planning_generation: { definition: "INTEGER NOT NULL DEFAULT 0", saved: "0" } },
|
|
696
699
|
decision: { superseded_reason: { definition: "TEXT CHECK (superseded_reason IN ('scope-changed','cancelled'))", saved: "NULL" } },
|
|
697
700
|
});
|
|
701
|
+
/** v120: the columns it adds, by table, with the value every row saved before it reads. */
|
|
702
|
+
export const V120_ADDED_COLUMNS = Object.freeze({
|
|
703
|
+
task_steer: {
|
|
704
|
+
mailed_run: { definition: "INTEGER REFERENCES run(id)", saved: "NULL" },
|
|
705
|
+
mailed_at: { definition: "TEXT", saved: "NULL" },
|
|
706
|
+
acked_at: { definition: "TEXT", saved: "NULL" },
|
|
707
|
+
},
|
|
708
|
+
run: {
|
|
709
|
+
steer_opened_at: { definition: "TEXT", saved: "NULL" },
|
|
710
|
+
steer_closed_at: { definition: "TEXT", saved: "NULL" },
|
|
711
|
+
},
|
|
712
|
+
});
|
|
698
713
|
/** Whether a database settled at `version` reaches this build's schema through update-safe migrations alone. */
|
|
699
714
|
export function updateSafeSchema(version) {
|
|
700
715
|
if (version === null || !Number.isSafeInteger(version) || version <= 0 || version > SCHEMA_VERSION)
|
|
@@ -2890,6 +2905,9 @@ CREATE TABLE IF NOT EXISTS review_request (
|
|
|
2890
2905
|
-- rode toward; delivered_at settles only on the stream's own receipt (the
|
|
2891
2906
|
-- agent's first words, or a successful completion). At-least-once, honestly
|
|
2892
2907
|
-- labeled. Superseded means the task ended with the note still pending.
|
|
2908
|
+
-- v120 adds mailed_run, mailed_at and acked_at (V120_ADDED_COLUMNS): a note
|
|
2909
|
+
-- filed while a build runs is written into its steering folder, and is read
|
|
2910
|
+
-- (and delivered) only when that agent moves it to handled/.
|
|
2893
2911
|
CREATE TABLE IF NOT EXISTS task_steer (
|
|
2894
2912
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2895
2913
|
task_ref INTEGER NOT NULL REFERENCES task_ref(id) ON DELETE CASCADE,
|
|
@@ -3491,6 +3509,11 @@ function initializeStore(db, file) {
|
|
|
3491
3509
|
for (const [column, { definition }] of Object.entries(columns))
|
|
3492
3510
|
addColumn(db, table, column, definition);
|
|
3493
3511
|
}
|
|
3512
|
+
// v120: a running build's steering folder (additive only; saved notes read as never mailed).
|
|
3513
|
+
for (const [table, columns] of Object.entries(V120_ADDED_COLUMNS)) {
|
|
3514
|
+
for (const [column, { definition }] of Object.entries(columns))
|
|
3515
|
+
addColumn(db, table, column, definition);
|
|
3516
|
+
}
|
|
3494
3517
|
installLedgerTriggers(db);
|
|
3495
3518
|
// Attention/history indexes come AFTER migration: on a database whose
|
|
3496
3519
|
// constrained tables still carry a pre-rebuild shape, creating a partial
|
|
@@ -13067,6 +13090,72 @@ export class Store {
|
|
|
13067
13090
|
.run(now.toISOString(), runId);
|
|
13068
13091
|
return Number(changes);
|
|
13069
13092
|
}
|
|
13093
|
+
/**
|
|
13094
|
+
* The task's build whose agent is running right now and reads its steering
|
|
13095
|
+
* folder (v120): the current live lease's open builder run, in its
|
|
13096
|
+
* agent-running phase, whose steering folder is open. Null otherwise — a
|
|
13097
|
+
* note then waits for the next attempt. Display state, never evidence.
|
|
13098
|
+
*/
|
|
13099
|
+
steeringLiveBuild(taskRef, now) {
|
|
13100
|
+
const live = this.currentLiveLease(taskRef, now);
|
|
13101
|
+
if (live === null)
|
|
13102
|
+
return null;
|
|
13103
|
+
const row = this.db
|
|
13104
|
+
.prepare(`SELECT id FROM run WHERE task_ref = ? AND lease_id = ? AND role = 'builder'
|
|
13105
|
+
AND outcome IS NULL AND phase = 'agent-running' AND steer_opened_at IS NOT NULL AND steer_closed_at IS NULL
|
|
13106
|
+
ORDER BY id DESC LIMIT 1`)
|
|
13107
|
+
.get(taskRef, live);
|
|
13108
|
+
return row === undefined ? null : Number(row["id"]);
|
|
13109
|
+
}
|
|
13110
|
+
/** This run made its steering folder (v120): its agent can read a note filed now. */
|
|
13111
|
+
markSteerFolderOpened(runId, now) {
|
|
13112
|
+
this.db.prepare("UPDATE run SET steer_opened_at = ? WHERE id = ? AND outcome IS NULL AND steer_opened_at IS NULL").run(now.toISOString(), runId);
|
|
13113
|
+
}
|
|
13114
|
+
/** This run's steering folder is gone (v120): a note filed now reaches the next attempt. */
|
|
13115
|
+
markSteerFolderClosed(runId, now) {
|
|
13116
|
+
this.db.prepare("UPDATE run SET steer_closed_at = ? WHERE id = ? AND steer_opened_at IS NOT NULL AND steer_closed_at IS NULL").run(now.toISOString(), runId);
|
|
13117
|
+
}
|
|
13118
|
+
/**
|
|
13119
|
+
* The notes a RUNNING build may still read (v120): verified, pending, not
|
|
13120
|
+
* already quoted in this run's brief, and not already in its folder. The
|
|
13121
|
+
* pulse mails exactly these, in filing order.
|
|
13122
|
+
*/
|
|
13123
|
+
mailableSteerNotes(taskRef, runId) {
|
|
13124
|
+
return this.db
|
|
13125
|
+
.prepare(`SELECT * FROM task_steer
|
|
13126
|
+
WHERE task_ref = ? AND authorship_state = 'verified'
|
|
13127
|
+
AND delivered_at IS NULL AND superseded_at IS NULL
|
|
13128
|
+
AND (attached_run IS NULL OR attached_run <> ?)
|
|
13129
|
+
AND (mailed_run IS NULL OR mailed_run <> ?)
|
|
13130
|
+
ORDER BY id`)
|
|
13131
|
+
.all(taskRef, runId, runId)
|
|
13132
|
+
.map(readSteerNote);
|
|
13133
|
+
}
|
|
13134
|
+
/** The pulse wrote this note into the run's steering folder. Mailing is not
|
|
13135
|
+
* reading: delivery settles only on the agent's own acknowledgement. */
|
|
13136
|
+
markSteerMailed(noteId, runId, now) {
|
|
13137
|
+
const { changes } = this.db
|
|
13138
|
+
.prepare(`UPDATE task_steer SET mailed_run = ?, mailed_at = ?
|
|
13139
|
+
WHERE id = ? AND delivered_at IS NULL AND superseded_at IS NULL`)
|
|
13140
|
+
.run(runId, now.toISOString(), noteId);
|
|
13141
|
+
return Number(changes) > 0;
|
|
13142
|
+
}
|
|
13143
|
+
/**
|
|
13144
|
+
* The agent moved this note to handled/ (v120): it was read, by THIS run.
|
|
13145
|
+
* Acknowledgement is the only road from mailed to delivered — a hook firing
|
|
13146
|
+
* or a file written proves nothing. First acknowledgement wins; a note the
|
|
13147
|
+
* task already ended, or mailed to another run since, is left alone.
|
|
13148
|
+
*/
|
|
13149
|
+
ackSteerNote(noteId, runId, now) {
|
|
13150
|
+
const stamp = now.toISOString();
|
|
13151
|
+
const { changes } = this.db
|
|
13152
|
+
.prepare(`UPDATE task_steer SET acked_at = ?, delivered_at = ?, attached_run = ?, attached_at = COALESCE(mailed_at, ?)
|
|
13153
|
+
WHERE id = ? AND mailed_run = ? AND acked_at IS NULL AND delivered_at IS NULL AND superseded_at IS NULL`)
|
|
13154
|
+
.run(stamp, stamp, runId, stamp, noteId, runId);
|
|
13155
|
+
if (Number(changes) > 0)
|
|
13156
|
+
this.bumpWake();
|
|
13157
|
+
return Number(changes) > 0;
|
|
13158
|
+
}
|
|
13070
13159
|
/** A finished task's pending notes settle superseded — shown, typed, done. */
|
|
13071
13160
|
supersedeSteerNotes(taskRef, now) {
|
|
13072
13161
|
const { changes } = this.db
|
|
@@ -17649,6 +17738,9 @@ function readSteerNote(row) {
|
|
|
17649
17738
|
supersededAt: row["superseded_at"] === null ? null : String(row["superseded_at"]),
|
|
17650
17739
|
authorshipState: row["authorship_state"] === "verified" ? "verified" : "unverified-legacy",
|
|
17651
17740
|
supersededReason: row["superseded_reason"] === null || row["superseded_reason"] === undefined ? null : String(row["superseded_reason"]),
|
|
17741
|
+
mailedRun: row["mailed_run"] === null || row["mailed_run"] === undefined ? null : Number(row["mailed_run"]),
|
|
17742
|
+
mailedAt: row["mailed_at"] === null || row["mailed_at"] === undefined ? null : String(row["mailed_at"]),
|
|
17743
|
+
ackedAt: row["acked_at"] === null || row["acked_at"] === undefined ? null : String(row["acked_at"]),
|
|
17652
17744
|
};
|
|
17653
17745
|
}
|
|
17654
17746
|
function readDecision(row) {
|
package/dist/surface.js
CHANGED
|
@@ -201,7 +201,7 @@ const GUIDE_ROWS = [
|
|
|
201
201
|
...["show", "updates", "claim", "check", "brief", "inbox", "ack"].map(action => ({
|
|
202
202
|
invocation: `assignment ${action}`,
|
|
203
203
|
synopsis: action === "show" ? "read the root, current work, owner and exact receipt"
|
|
204
|
-
: action === "updates" ? "read
|
|
204
|
+
: action === "updates" ? "read the history of handoffs sent to people after a cursor; delivers nothing — receive updates with assignment inbox"
|
|
205
205
|
: action === "claim" ? "record lead ownership; with the lead token the task reads \"<the lead's name> is on it\" and leaves Needs you until done, handed over, or 2 hours idle; grants no approval or execution authority"
|
|
206
206
|
: action === "brief" ? "catch up from current assignments and project knowledge in the local database"
|
|
207
207
|
: action === "inbox" ? "receive or replay a saved batch of lead status updates"
|
|
@@ -242,7 +242,7 @@ const GUIDE_ROWS = [
|
|
|
242
242
|
positionals: [{ name: "id", required: true, meaning: "the task" }],
|
|
243
243
|
flags: [jsonFlag, dbFlag, keyFlag, { name: "undo", takesValue: false, meaning: "back to filing order" }],
|
|
244
244
|
notableReasons: ["unknown-task", "claimed"] },
|
|
245
|
-
operator("task steer", "one credentialed note
|
|
245
|
+
operator("task steer", "one credentialed note for the task's agent — a running build reads it when it next checks for notes, otherwise the next attempt does; steering speaks with the operator's voice"),
|
|
246
246
|
{ invocation: "task assign", synopsis: "reserve a task for one worker, or return it to the shared queue", audience: "agent", agentMayInvoke: true, mutation: "keyed",
|
|
247
247
|
positionals: [{ name: "id", required: true, meaning: "the task" }],
|
|
248
248
|
flags: [jsonFlag, dbFlag, keyFlag,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Assignment sweeps re-project every owned root on each pass. A pass is
|
|
2
|
+
* skipped when nothing has moved since the last full cycle began: the store's
|
|
3
|
+
* wake sequence is unchanged and that cycle started under a minute ago. The
|
|
4
|
+
* minute covers changes that don't bump wake (time-based lease expiry, some
|
|
5
|
+
* ledger-only writes). A cycle in progress (its cursor past 0) always goes on,
|
|
6
|
+
* and a restarted process sweeps at once. */
|
|
7
|
+
import type { Store } from "./store.js";
|
|
8
|
+
export declare const SWEEP_FALLBACK_MS = 60000;
|
|
9
|
+
/** Runs `sweep` unless the cycle under `cursorKey` would see what its last start saw. Returns whether it ran. */
|
|
10
|
+
export declare function sweepIfMoved(store: Store, cursorKey: string, now: Date, sweep: () => void): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const SWEEP_FALLBACK_MS = 60_000;
|
|
2
|
+
const cycles = new WeakMap();
|
|
3
|
+
/** Runs `sweep` unless the cycle under `cursorKey` would see what its last start saw. Returns whether it ran. */
|
|
4
|
+
export function sweepIfMoved(store, cursorKey, now, sweep) {
|
|
5
|
+
const seq = store.wakeSeq();
|
|
6
|
+
const startsCycle = store.serviceCursor(cursorKey) === 0;
|
|
7
|
+
const seen = cycles.get(store) ?? new Map();
|
|
8
|
+
cycles.set(store, seen);
|
|
9
|
+
const last = seen.get(cursorKey);
|
|
10
|
+
const elapsed = last === undefined ? Infinity : now.getTime() - last.at;
|
|
11
|
+
if (startsCycle && last?.seq === seq && elapsed >= 0 && elapsed < SWEEP_FALLBACK_MS)
|
|
12
|
+
return false;
|
|
13
|
+
sweep();
|
|
14
|
+
// Recorded only once the pass returns: a pass that threw is retried next time.
|
|
15
|
+
if (startsCycle)
|
|
16
|
+
seen.set(cursorKey, { seq, at: now.getTime() });
|
|
17
|
+
return true;
|
|
18
|
+
}
|
package/dist/team-updates.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TeamLeads } from './team-leads.js';
|
|
2
|
-
import { ASSIGNMENT_STATUS_ACTION, parseAssignmentStatusEvent } from './assignment-status.js';
|
|
2
|
+
import { ASSIGNMENT_STATUS_ACTION, LEAD_WAKE_STATES, parseAssignmentStatusEvent } from './assignment-status.js';
|
|
3
3
|
export const teamUpdateCursor = (conversation) => `team-updates:${conversation}`;
|
|
4
4
|
export function startTeamUpdates(store, conversation, now) {
|
|
5
5
|
// Enabling updates starts here. Past events remain available in task history.
|
|
@@ -33,10 +33,10 @@ export function deliverTeamUpdates(store, domain, authorized, now) {
|
|
|
33
33
|
for (const row of rows) {
|
|
34
34
|
const event = parseAssignmentStatusEvent(String(row['outcome']));
|
|
35
35
|
// Keep an unreadable row pending instead of silently losing delivery.
|
|
36
|
-
if (!event || event.assignment.rootId !== row['task_id'] || event.assignment.owner
|
|
36
|
+
if (!event || event.assignment.rootId !== row['task_id'] || event.assignment.owner?.kind !== 'lead' || event.assignment.owner.id !== current.lead.id)
|
|
37
37
|
break;
|
|
38
38
|
through = Number(row['id']);
|
|
39
|
-
if (!
|
|
39
|
+
if (!LEAD_WAKE_STATES.includes(event.assignment.state))
|
|
40
40
|
continue;
|
|
41
41
|
changes.push(`${event.assignment.rootId}: ${event.assignment.title.slice(0, 100)} — ${event.assignment.state}. ${event.assignment.detail.slice(0, 180)}`);
|
|
42
42
|
if (changes.length === 4)
|
package/dist/toolroll-update.js
CHANGED
|
@@ -35,7 +35,7 @@ import { processMayBeAlive } from "./process-liveness.js";
|
|
|
35
35
|
import { assertCodingUpdateStopped, backupCodingCatalog, codingCatalogExists, releaseStaleCodingOwner, removeCodingUpdateGate } from "./coding-update.js";
|
|
36
36
|
import { installLaunchdService, launchdPlist, stopLaunchdService, writeFileDurably } from "./daemon.js";
|
|
37
37
|
import { NAME } from "./names.js";
|
|
38
|
-
import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS } from "./store.js";
|
|
38
|
+
import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS } from "./store.js";
|
|
39
39
|
import { isNewer, REGISTRY } from "./releases.js";
|
|
40
40
|
import { markNeverIndex } from "./never-index.js";
|
|
41
41
|
import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
|
|
@@ -363,6 +363,16 @@ export const CHAT_MOVES = {
|
|
|
363
363
|
...Object.fromEntries(Object.entries(TELEGRAM_MOVES).map(([table, move]) => [table, { ...move, where: `provider = 'telegram' AND (${move.where})` }])),
|
|
364
364
|
...Object.fromEntries(["slack", "discord", "teams"].flatMap(app => APP_TABLES.map(suffix => [`${app}_${suffix}`, { into: `chat_${suffix}`, where: `provider = '${app}'` }]))),
|
|
365
365
|
};
|
|
366
|
+
/** Each table's added columns across versions, merged: a table two versions both extend keeps every column. */
|
|
367
|
+
function addedColumns(...versions) {
|
|
368
|
+
const rules = {};
|
|
369
|
+
for (const version of versions) {
|
|
370
|
+
for (const [table, columns] of Object.entries(version)) {
|
|
371
|
+
rules[table] = { added: { ...rules[table]?.added, ...Object.fromEntries(Object.entries(columns).map(([column, { saved }]) => [column, saved])) } };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return rules;
|
|
375
|
+
}
|
|
366
376
|
const HISTORY_RULES = {
|
|
367
377
|
...Object.fromEntries(Object.entries(CHAT_MOVES).map(([table, moved]) => [table, { moved }])),
|
|
368
378
|
action_ledger: { appendOnly: true }, ledger_seal: { appendOnly: true }, ledger_checkpoint: { appendOnly: true },
|
|
@@ -386,7 +396,9 @@ const HISTORY_RULES = {
|
|
|
386
396
|
...Object.fromEntries(Object.entries(V118_MERGED_TABLES).map(([table, kind]) => [table, { moved: { into: "credential", where: `kind = '${kind}'` } }])),
|
|
387
397
|
// v119: planning generations and a question's closure reason are new columns; every row saved before them reads
|
|
388
398
|
// the declared value (generation 0, no closure reason).
|
|
389
|
-
|
|
399
|
+
// v120: a steering note's mailing and acknowledgement, and a run's steering folder, are new columns; every row
|
|
400
|
+
// saved before them reads none.
|
|
401
|
+
...addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS),
|
|
390
402
|
};
|
|
391
403
|
/** The tables a migration may remove whole (v115's removed features). */
|
|
392
404
|
export const RETIRED_TABLES = Object.freeze(Object.keys(HISTORY_RULES).filter(name => HISTORY_RULES[name].retired));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.60",
|
|
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"
|
|
@@ -62,16 +62,36 @@
|
|
|
62
62
|
"node": ">=22.13.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
+
"@radix-ui/react-collapsible": "1.1.20",
|
|
66
|
+
"@radix-ui/react-dialog": "1.1.23",
|
|
67
|
+
"@radix-ui/react-dropdown-menu": "2.1.24",
|
|
68
|
+
"@radix-ui/react-label": "2.1.15",
|
|
69
|
+
"@radix-ui/react-radio-group": "1.4.7",
|
|
70
|
+
"@radix-ui/react-select": "2.3.7",
|
|
71
|
+
"@radix-ui/react-separator": "1.1.15",
|
|
72
|
+
"@radix-ui/react-slot": "1.3.3",
|
|
73
|
+
"@radix-ui/react-switch": "1.3.7",
|
|
74
|
+
"@radix-ui/react-tabs": "1.1.21",
|
|
75
|
+
"@radix-ui/react-tooltip": "1.2.16",
|
|
65
76
|
"@tailwindcss/cli": "4.3.3",
|
|
66
77
|
"@types/mailparser": "3.9.0",
|
|
67
78
|
"@types/node": "26.6.4",
|
|
68
79
|
"@types/react": "19.3.0",
|
|
69
80
|
"@types/react-dom": "19.3.0",
|
|
81
|
+
"@xyflow/react": "12.12.0",
|
|
82
|
+
"class-variance-authority": "0.7.1",
|
|
83
|
+
"clsx": "2.1.1",
|
|
70
84
|
"esbuild": "0.28.2",
|
|
71
85
|
"happy-dom": "20.14.5",
|
|
86
|
+
"lucide-react": "1.52.0",
|
|
87
|
+
"react": "19.3.0",
|
|
88
|
+
"react-dom": "19.3.0",
|
|
89
|
+
"sonner": "2.0.8",
|
|
90
|
+
"tailwind-merge": "3.7.0",
|
|
72
91
|
"tailwindcss": "4.3.3",
|
|
73
92
|
"tsx": "4.23.15",
|
|
74
93
|
"typescript": "7.0.2",
|
|
94
|
+
"use-stick-to-bottom": "1.1.6",
|
|
75
95
|
"vitest": "4.1.11"
|
|
76
96
|
},
|
|
77
97
|
"files": [
|
|
@@ -96,32 +116,12 @@
|
|
|
96
116
|
}
|
|
97
117
|
},
|
|
98
118
|
"dependencies": {
|
|
99
|
-
"@radix-ui/react-collapsible": "1.1.20",
|
|
100
|
-
"@radix-ui/react-dialog": "1.1.23",
|
|
101
|
-
"@radix-ui/react-dropdown-menu": "2.1.24",
|
|
102
|
-
"@radix-ui/react-label": "2.1.15",
|
|
103
|
-
"@radix-ui/react-radio-group": "1.4.7",
|
|
104
|
-
"@radix-ui/react-select": "2.3.7",
|
|
105
|
-
"@radix-ui/react-separator": "1.1.15",
|
|
106
|
-
"@radix-ui/react-slot": "1.3.3",
|
|
107
|
-
"@radix-ui/react-switch": "1.3.7",
|
|
108
|
-
"@radix-ui/react-tabs": "1.1.21",
|
|
109
|
-
"@radix-ui/react-tooltip": "1.2.16",
|
|
110
119
|
"@slack/socket-mode": "3.1.0",
|
|
111
|
-
"@xyflow/react": "12.12.0",
|
|
112
|
-
"class-variance-authority": "0.7.1",
|
|
113
|
-
"clsx": "2.1.1",
|
|
114
120
|
"discord.js": "14.27.0",
|
|
115
121
|
"imapflow": "2.2.5",
|
|
116
|
-
"lucide-react": "1.52.0",
|
|
117
122
|
"mailparser": "3.9.36",
|
|
118
123
|
"nodemailer": "10.0.15",
|
|
119
|
-
"react": "19.3.0",
|
|
120
|
-
"react-dom": "19.3.0",
|
|
121
|
-
"sonner": "2.0.8",
|
|
122
|
-
"tailwind-merge": "3.7.0",
|
|
123
124
|
"typescript-parser": "npm:typescript@6.0.3",
|
|
124
|
-
"use-stick-to-bottom": "1.1.6",
|
|
125
125
|
"yaml": "2.9.1",
|
|
126
126
|
"zod": "^4.6.5"
|
|
127
127
|
}
|