toolroll 0.9.85 → 0.9.86
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 +24 -0
- package/dist/agent-fence.d.ts +6 -2
- package/dist/agent-fence.js +3 -2
- package/dist/assignment.js +4 -3
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +31 -29
- package/dist/browser-workspace.d.ts +40 -0
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.js +1 -0
- package/dist/chat-quiet.js +3 -2
- package/dist/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/console-api.d.ts +4 -1
- package/dist/contracts/console-api.js +4 -1
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +21 -0
- package/dist/contracts/lead-tools.js +2 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/coordinator.js +1 -0
- package/dist/deploy-gate.js +4 -0
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +47 -2
- package/dist/flows-ui.js +11 -2
- package/dist/flows.js +5 -2
- package/dist/lead-tools.js +35 -6
- package/dist/lead-voice.js +16 -5
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +215 -6
- package/dist/project-delete.js +7 -3
- package/dist/pull-request-flow.js +35 -4
- package/dist/result-follow-ups.js +4 -0
- package/dist/scope.d.ts +5 -0
- package/dist/scope.js +7 -0
- package/dist/server/chat.js +4 -2
- package/dist/server/console.css +29 -0
- package/dist/server/flows.js +15 -0
- package/dist/server/render-pages.d.ts +13 -0
- package/dist/server/render-pages.js +45 -2
- package/dist/server/render-tasks.d.ts +8 -0
- package/dist/server/render-tasks.js +17 -6
- package/dist/server/route-table.js +11 -0
- package/dist/server/task-views.js +5 -0
- package/dist/server/tasks.d.ts +10 -0
- package/dist/server/tasks.js +54 -2
- package/dist/settings-proofs.js +1 -0
- package/dist/store.d.ts +71 -3
- package/dist/store.js +222 -20
- package/dist/surface.js +18 -2
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/work-index.d.ts +24 -0
- package/dist/work-index.js +52 -30
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +3 -2
- package/package.json +1 -1
package/dist/store.js
CHANGED
|
@@ -9,6 +9,8 @@ import { isAcpProfile, isAcpProvider } from "./acp-agents.js";
|
|
|
9
9
|
import { ACP_CONFORMANCE_VERSION, launchFromJson } from "./acp-attest.js";
|
|
10
10
|
import { reclaimDatabase } from "./database-reclaim.js";
|
|
11
11
|
import { BASELINE_SCHEMA, BASELINE_SCHEMA_VERSION } from "./store-baseline.js";
|
|
12
|
+
import { refArchived, taskArchived } from "./task-archive.js";
|
|
13
|
+
export { refArchived, taskArchived } from "./task-archive.js";
|
|
12
14
|
import { CHAT_PROVIDERS, CHAT_SCHEMA } from "./contracts/chat-tables.js";
|
|
13
15
|
import {} from "./chat-core.js";
|
|
14
16
|
import { ChatMessages, TELEGRAM_HOLD_REASONS, TELEGRAM_SKIPPED_ELSEWHERE, TELEGRAM_SKIPPED_OTHER_CHAT, TELEGRAM_SKIPPED_QUIET, TELEGRAM_UNSETTLED } from "./chat-messages.js";
|
|
@@ -94,6 +96,7 @@ import { LEDGER_COLUMNS, LEDGER_SCHEMA, LEDGER_TABLE, LEDGER_V99_TABLE, installL
|
|
|
94
96
|
import { PLAN_AUTO_SCHEMA } from "./plan-auto.js";
|
|
95
97
|
import { CONSOLE_INSTANCE_SCHEMA } from "./console-instance.js";
|
|
96
98
|
import { endUnattendedGrants, FLOW_UNATTENDED_SCHEMA, grantStandsSql } from "./flow-unattended.js";
|
|
99
|
+
import { FLOW_BROWSER_SCHEMA } from "./flow-browser.js";
|
|
97
100
|
import { RECIPE_SCHEMA } from "./recipes.js";
|
|
98
101
|
import { POLICY_SCHEMA, agentRefusal, approvalRefusal, policyParts, readPolicy, underCeiling, sessionCeilingRefusal } from "./policy.js";
|
|
99
102
|
import { PROVIDER_AUTH_SCHEMA } from "./provider-auth.js";
|
|
@@ -1092,7 +1095,13 @@ const REGISTERED_AT_FILING = "again, as it was filed";
|
|
|
1092
1095
|
// project's intake choice and last answer, and each request taken, with the task it became.
|
|
1093
1096
|
// v143 adds, and changes no saved row: optional exact-screenshot context on result feedback
|
|
1094
1097
|
// (diff_comment.image_json, V143_ADDED_COLUMNS; saved notes read NULL and keep their file/line target).
|
|
1095
|
-
|
|
1098
|
+
// v144 adds, and changes no saved row: a person's approval of browser use in a flow's script zones (flow_browser_grant;
|
|
1099
|
+
// flow-browser.ts), the one sandbox rule a project check gets for Chromium; saved flows have none and get nothing.
|
|
1100
|
+
// v145 adds, and changes no saved row (docs/plans/task-tidy.md): put-away tasks (task_archive, one row per archived task
|
|
1101
|
+
// version; no row is every saved task: not archived), the completed workflow runs on a watched branch the checks
|
|
1102
|
+
// trigger saw (branch_check_run), and the reservations that hold work back from being put away (merge_in_flight,
|
|
1103
|
+
// deploy_in_flight, flow_step_task; V145_SCHEMA, new tables only).
|
|
1104
|
+
export const SCHEMA_VERSION = 145;
|
|
1096
1105
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
1097
1106
|
export const V118_MERGED_TABLES = Object.freeze({ api_token: "person", coordinator_credential: "coordinator", lead_credential: "lead" });
|
|
1098
1107
|
/** The foreign keys that named a merged table, exactly as their DDL wrote them; each names `credential(id)` instead. */
|
|
@@ -1225,7 +1234,7 @@ const V115_DROPPED_REFERENCES = /\s+REFERENCES (?:contestant|contest|tournament_
|
|
|
1225
1234
|
* unchanged), and adds the nullable column in V125_ADDED_COLUMNS, checked the same way. v126 only adds a new table
|
|
1226
1235
|
* (task_source) and the columns in V126_ADDED_COLUMNS (null or 0), checked the same way.
|
|
1227
1236
|
*/
|
|
1228
|
-
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143]);
|
|
1237
|
+
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145]);
|
|
1229
1238
|
/** v119: the columns it adds, by table, with the value every row saved before it reads (toolroll-update's HISTORY_RULES). */
|
|
1230
1239
|
export const V119_ADDED_COLUMNS = Object.freeze({
|
|
1231
1240
|
task_ref: { planning_generation: { definition: "INTEGER NOT NULL DEFAULT 0", saved: "0" } },
|
|
@@ -1365,6 +1374,55 @@ export const V140_ADDED_COLUMNS = Object.freeze({
|
|
|
1365
1374
|
export const V143_ADDED_COLUMNS = Object.freeze({
|
|
1366
1375
|
diff_comment: { image_json: { definition: "TEXT", saved: "NULL" } },
|
|
1367
1376
|
});
|
|
1377
|
+
/** v145 (docs/plans/task-tidy.md): `task_archive` is a put-away task version. A row hides it from every list and count
|
|
1378
|
+
* and refuses every door that would dispatch, claim or approve it; restore deletes the row. Its history, results,
|
|
1379
|
+
* approvals and ledger stay exactly as they were. `merge_in_flight` is a direct merge between its last check and GitHub's
|
|
1380
|
+
* answer, owned by its token: a task can't be put away under it (a row older than 15 minutes is a crash, and no
|
|
1381
|
+
* longer counts). `deploy_in_flight` is a deployment whose candidate passed its gate (30 minutes, refreshed by every
|
|
1382
|
+
* phase that proves it again). `flow_step_task` is the tasks a flow step was admitted for, kept whatever the card does
|
|
1383
|
+
* next, so a step still running holds them. `branch_check_run` keeps each completed GitHub workflow run (pushed to
|
|
1384
|
+
* the watched branch of the watched repository) the "checks failed" trigger saw, green or red, so tidy can tell a
|
|
1385
|
+
* failure notice was fixed by a later commit. */
|
|
1386
|
+
export const V145_SCHEMA = `
|
|
1387
|
+
CREATE TABLE IF NOT EXISTS task_archive (
|
|
1388
|
+
task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),
|
|
1389
|
+
by TEXT NOT NULL,
|
|
1390
|
+
by_kind TEXT NOT NULL CHECK (by_kind IN ('person','tidy')),
|
|
1391
|
+
rule TEXT,
|
|
1392
|
+
reason TEXT NOT NULL,
|
|
1393
|
+
at TEXT NOT NULL
|
|
1394
|
+
);
|
|
1395
|
+
CREATE TABLE IF NOT EXISTS branch_check_run (
|
|
1396
|
+
repo TEXT NOT NULL,
|
|
1397
|
+
run_id INTEGER NOT NULL,
|
|
1398
|
+
branch TEXT NOT NULL,
|
|
1399
|
+
workflow TEXT NOT NULL,
|
|
1400
|
+
head_sha TEXT NOT NULL,
|
|
1401
|
+
conclusion TEXT NOT NULL CHECK (conclusion IN ('success','failure')),
|
|
1402
|
+
run_created_at TEXT NOT NULL,
|
|
1403
|
+
observed_at TEXT NOT NULL,
|
|
1404
|
+
PRIMARY KEY (repo, run_id)
|
|
1405
|
+
);
|
|
1406
|
+
CREATE INDEX IF NOT EXISTS branch_check_run_by_workflow ON branch_check_run (repo, branch, workflow, run_created_at);
|
|
1407
|
+
CREATE TABLE IF NOT EXISTS merge_in_flight (
|
|
1408
|
+
publication INTEGER PRIMARY KEY REFERENCES publication(id),
|
|
1409
|
+
task_ref INTEGER NOT NULL REFERENCES task_ref(id),
|
|
1410
|
+
token TEXT NOT NULL,
|
|
1411
|
+
by TEXT NOT NULL,
|
|
1412
|
+
at TEXT NOT NULL
|
|
1413
|
+
);
|
|
1414
|
+
CREATE TABLE IF NOT EXISTS deploy_in_flight (
|
|
1415
|
+
task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),
|
|
1416
|
+
run INTEGER NOT NULL,
|
|
1417
|
+
at TEXT NOT NULL
|
|
1418
|
+
);
|
|
1419
|
+
CREATE TABLE IF NOT EXISTS flow_step_task (
|
|
1420
|
+
card INTEGER NOT NULL,
|
|
1421
|
+
entry INTEGER NOT NULL,
|
|
1422
|
+
task_id TEXT NOT NULL,
|
|
1423
|
+
PRIMARY KEY (card, entry, task_id)
|
|
1424
|
+
);
|
|
1425
|
+
`;
|
|
1368
1426
|
/**
|
|
1369
1427
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
1370
1428
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -4622,6 +4680,10 @@ CREATE INDEX IF NOT EXISTS run_checkpoint_by_task ON run_checkpoint (task_ref, i
|
|
|
4622
4680
|
for (const [column, { definition }] of Object.entries(columns))
|
|
4623
4681
|
addColumn(db, table, column, definition);
|
|
4624
4682
|
}
|
|
4683
|
+
// v144: approvals of browser use in flows' script zones (a new table only).
|
|
4684
|
+
db.exec(FLOW_BROWSER_SCHEMA);
|
|
4685
|
+
// v145: put-away tasks, green checks per branch and the reservations that hold work back (new tables only).
|
|
4686
|
+
db.exec(V145_SCHEMA);
|
|
4625
4687
|
// Keep the epoch until reclamation succeeds, including a retry after compaction already committed. A new file has
|
|
4626
4688
|
// nothing to reclaim.
|
|
4627
4689
|
if (!fresh)
|
|
@@ -6361,6 +6423,7 @@ export class Store {
|
|
|
6361
6423
|
FROM task JOIN task_ref ON task_ref.backend = ? AND task_ref.external_id = task.id
|
|
6362
6424
|
WHERE task.state = 'queued'
|
|
6363
6425
|
AND (? IS NULL OR task_ref.repo IS NULL OR task_ref.repo = ?)
|
|
6426
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)
|
|
6364
6427
|
ORDER BY task.priority DESC, task.created_at, task_ref.id
|
|
6365
6428
|
LIMIT 200`)
|
|
6366
6429
|
.all(stamp, BUILT_IN, repo, repo)
|
|
@@ -6385,7 +6448,8 @@ export class Store {
|
|
|
6385
6448
|
.prepare(`SELECT task.priority AS priority, task.created_at AS created_at, task_ref.id AS ref,
|
|
6386
6449
|
task_ref.repo AS repo, task_ref.assigned_runner AS assigned
|
|
6387
6450
|
FROM task JOIN task_ref ON task_ref.backend = ? AND task_ref.external_id = task.id
|
|
6388
|
-
WHERE task.id = ? AND task.state = 'queued'
|
|
6451
|
+
WHERE task.id = ? AND task.state = 'queued'
|
|
6452
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)`)
|
|
6389
6453
|
.get(BUILT_IN, taskId);
|
|
6390
6454
|
if (me === undefined)
|
|
6391
6455
|
return null;
|
|
@@ -6397,7 +6461,8 @@ export class Store {
|
|
|
6397
6461
|
OR (task.priority = ? AND task.created_at = ? AND task_ref.id < ?)
|
|
6398
6462
|
THEN 1 ELSE 0 END) AS ahead
|
|
6399
6463
|
FROM task JOIN task_ref ON task_ref.backend = ? AND task_ref.external_id = task.id
|
|
6400
|
-
WHERE task.state = 'queued' AND (task_ref.assigned_runner IS ?) AND (task_ref.repo IS ?)
|
|
6464
|
+
WHERE task.state = 'queued' AND (task_ref.assigned_runner IS ?) AND (task_ref.repo IS ?)
|
|
6465
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)`)
|
|
6401
6466
|
.get(Number(me["priority"]), Number(me["priority"]), String(me["created_at"]), Number(me["priority"]), String(me["created_at"]), Number(me["ref"]), BUILT_IN, me["assigned"], me["repo"]);
|
|
6402
6467
|
return {
|
|
6403
6468
|
position: Number(row?.["ahead"] ?? 0) + 1,
|
|
@@ -6547,6 +6612,8 @@ export class Store {
|
|
|
6547
6612
|
JOIN task_ref ON task_ref.backend = ? AND task_ref.external_id = task.id
|
|
6548
6613
|
WHERE task.state = 'queued'
|
|
6549
6614
|
${reservation}
|
|
6615
|
+
-- A put-away task (v145) never dispatches.
|
|
6616
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)
|
|
6550
6617
|
AND NOT EXISTS (
|
|
6551
6618
|
SELECT 1 FROM hold
|
|
6552
6619
|
WHERE hold.task_ref = task_ref.id
|
|
@@ -7328,6 +7395,9 @@ export class Store {
|
|
|
7328
7395
|
// road it came by.
|
|
7329
7396
|
if (basis === undefined && machineActing())
|
|
7330
7397
|
return false;
|
|
7398
|
+
// A PUT-AWAY TASK (v145, task-tidy.md) is never approved, by any road: restore it first.
|
|
7399
|
+
if (taskArchived(this, taskId))
|
|
7400
|
+
return false;
|
|
7331
7401
|
// A RELEASE WAITING FOR CI (Codex review round 1, finding 4): filed while its pull request's checks still ran,
|
|
7332
7402
|
// only the automatic road (which proves CI itself) seals it until the plane read exact-head CI passing.
|
|
7333
7403
|
if (basis?.kind !== "auto-check" && basis?.kind !== "auto-green" && releaseCiHoldOf(this, taskId) !== null)
|
|
@@ -10587,7 +10657,7 @@ export class Store {
|
|
|
10587
10657
|
versions.sort((a, b) => a.id === root.id ? -1 : b.id === root.id ? 1 : a.createdAt.localeCompare(b.createdAt) || a.refId - b.refId);
|
|
10588
10658
|
const current = versions.at(-1);
|
|
10589
10659
|
return { root, current, versions,
|
|
10590
|
-
otherActive: versions.filter(one => one.id !== current.id && (one.state === "queued" || one.state === "running")),
|
|
10660
|
+
otherActive: versions.filter(one => one.id !== current.id && (one.state === "queued" || one.state === "running") && !refArchived(this, one.refId)),
|
|
10591
10661
|
problem: Number(rows[0]["broken"]) === 0 ? null : "Task history is unavailable or incomplete. This task is shown separately." };
|
|
10592
10662
|
}
|
|
10593
10663
|
/** Group and filter BEFORE the family limit; hydrate only that page's
|
|
@@ -10597,6 +10667,8 @@ export class Store {
|
|
|
10597
10667
|
const order = options.order === "updated" ? "updated_at" : "created_at";
|
|
10598
10668
|
const rows = this.db.prepare(`${projection.sql}, selected AS MATERIALIZED (
|
|
10599
10669
|
SELECT * FROM ordered WHERE version_rank = 1
|
|
10670
|
+
-- A put-away family (v145) is out of every list, before the page is cut.
|
|
10671
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = ordered.ref_id)
|
|
10600
10672
|
${options.states === undefined ? "" : `AND state IN (${options.states.map(() => "?").join(",")})`}
|
|
10601
10673
|
ORDER BY ${order} DESC, ref_id DESC LIMIT ?
|
|
10602
10674
|
) SELECT task.*, members.repo, members.ref_id, members.root_ref, members.broken
|
|
@@ -10687,7 +10759,8 @@ export class Store {
|
|
|
10687
10759
|
COUNT(CASE WHEN live_families.root_ref IS NOT NULL THEN 1 END) AS running,
|
|
10688
10760
|
COUNT(CASE WHEN live_families.root_ref IS NULL AND ordered.state = 'queued' THEN 1 END) AS queued,
|
|
10689
10761
|
COUNT(CASE WHEN live_families.root_ref IS NULL AND ordered.state = 'done' AND ordered.updated_at >= ? THEN 1 END) AS doneRecently
|
|
10690
|
-
FROM ordered LEFT JOIN live_families ON live_families.root_ref = ordered.root_ref WHERE version_rank = 1
|
|
10762
|
+
FROM ordered LEFT JOIN live_families ON live_families.root_ref = ordered.root_ref WHERE version_rank = 1
|
|
10763
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = ordered.ref_id)`)
|
|
10691
10764
|
.get(...projection.params, JSON.stringify(liveTasks), new Date(now.getTime() - 24 * 60 * 60_000).toISOString());
|
|
10692
10765
|
return { running: Number(row["running"]), queued: Number(row["queued"]), doneRecently: Number(row["doneRecently"]) };
|
|
10693
10766
|
}
|
|
@@ -11121,6 +11194,9 @@ export class Store {
|
|
|
11121
11194
|
if (ref === undefined)
|
|
11122
11195
|
return { ok: false, reason: "unknown-task" };
|
|
11123
11196
|
const taskRef = Number(ref["id"]);
|
|
11197
|
+
// A put-away task (v145) is restored before it runs again.
|
|
11198
|
+
if (refArchived(this, taskRef))
|
|
11199
|
+
return { ok: false, reason: "archived" };
|
|
11124
11200
|
const stamp = now.toISOString();
|
|
11125
11201
|
if (this.hasLiveClaim(taskRef, now))
|
|
11126
11202
|
return { ok: false, reason: "claimed" };
|
|
@@ -12732,10 +12808,12 @@ export class Store {
|
|
|
12732
12808
|
: source !== undefined && "project" in source && source.project !== "" ? "project"
|
|
12733
12809
|
: source !== undefined && "installation" in source ? "installation" : "unknown";
|
|
12734
12810
|
const project = scope === "task" ? String(ref["repo"]) : scope === "project" && source !== undefined && "project" in source ? source.project : null;
|
|
12811
|
+
// A put-away task (v145) nudges nobody: no notification about it is enqueued, whoever produced it. The check is
|
|
12812
|
+
// part of the insert itself, so a put away that commits first always wins.
|
|
12735
12813
|
const { changes, lastInsertRowid } = this.db
|
|
12736
12814
|
.prepare(`INSERT OR IGNORE INTO notification (dedupe_key, kind, subject, body, created_at, push_class, link, provenance_scope, project, task_ref, task_id, source_run, recipient)
|
|
12737
|
-
|
|
12738
|
-
.run(notification.dedupeKey, notification.kind, notification.subject, notification.body, now.toISOString(), notification.pushClass ?? null, notification.link ?? null, scope, project, ref === undefined ? null : taskRef, ref === undefined ? null : String(ref["external_id"]), run?.id ?? null, notification.recipient ?? null);
|
|
12815
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = ?)`)
|
|
12816
|
+
.run(notification.dedupeKey, notification.kind, notification.subject, notification.body, now.toISOString(), notification.pushClass ?? null, notification.link ?? null, scope, project, ref === undefined ? null : taskRef, ref === undefined ? null : String(ref["external_id"]), run?.id ?? null, notification.recipient ?? null, ref === undefined ? null : Number(ref["id"]));
|
|
12739
12817
|
// Whose act made this fact: pings follow it (see pingAllowed).
|
|
12740
12818
|
const actor = currentActor();
|
|
12741
12819
|
if (Number(changes) > 0 && actor !== null) {
|
|
@@ -14227,6 +14305,7 @@ export class Store {
|
|
|
14227
14305
|
JOIN task_ref ON task_ref.backend = ? AND task_ref.external_id = task.id
|
|
14228
14306
|
WHERE (? IS NULL OR task_ref.repo IS NULL OR task_ref.repo = ?)
|
|
14229
14307
|
AND (? IS NULL OR task.state = ?)
|
|
14308
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)
|
|
14230
14309
|
ORDER BY task.created_at DESC, task.id DESC LIMIT ?
|
|
14231
14310
|
)
|
|
14232
14311
|
UNION
|
|
@@ -14395,8 +14474,10 @@ export class Store {
|
|
|
14395
14474
|
resolvePlanRevision(id, outcome, resolvedBy, now) {
|
|
14396
14475
|
const { changes } = this.db
|
|
14397
14476
|
.prepare(`UPDATE plan_revision SET status = ?, resolved_at = ?, resolved_by = ?
|
|
14398
|
-
WHERE id = ? AND status = 'blocked'
|
|
14399
|
-
|
|
14477
|
+
WHERE id = ? AND status = 'blocked'
|
|
14478
|
+
-- A put-away task's (v145) plan change is never applied; rejecting it still is.
|
|
14479
|
+
AND (? = 'rejected' OR NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = plan_revision.task_ref))`)
|
|
14480
|
+
.run(outcome, now.toISOString(), resolvedBy, id, outcome);
|
|
14400
14481
|
return Number(changes) > 0;
|
|
14401
14482
|
}
|
|
14402
14483
|
/** Stamp the exact plan revision and authority snapshot a run's brief was
|
|
@@ -15626,6 +15707,12 @@ export class Store {
|
|
|
15626
15707
|
if (row === undefined || String(row["state"]) !== "claimed" || Number(row["generation"]) !== generation) {
|
|
15627
15708
|
return { ok: false, outcome: "raced", why: "the claim moved" };
|
|
15628
15709
|
}
|
|
15710
|
+
// A put-away task (v145) merges nothing: the intent waits for a person, who restores it first.
|
|
15711
|
+
const archivedTask = this.db.prepare("SELECT 1 FROM publication p JOIN task_archive a ON a.task_ref = p.task_ref WHERE p.id = ?").get(Number(row["publication"]));
|
|
15712
|
+
if (archivedTask !== undefined) {
|
|
15713
|
+
this.settleMergeIntent(id, generation, "waiting-human", now, { error: "the task is put away", from: "claimed", bumpGeneration: true });
|
|
15714
|
+
return { ok: false, outcome: "waiting-human", why: "the task is put away" };
|
|
15715
|
+
}
|
|
15629
15716
|
const supersede = (why) => {
|
|
15630
15717
|
this.settleMergeIntent(id, generation, "superseded", now, { error: why, from: "claimed" });
|
|
15631
15718
|
return { ok: false, outcome: "superseded", why };
|
|
@@ -17384,9 +17471,10 @@ export class Store {
|
|
|
17384
17471
|
const page = Math.max(1, Math.min(Math.floor(limit), 50));
|
|
17385
17472
|
// Roll-up admission binds BEFORE the LIMIT (Codex roll-up review,
|
|
17386
17473
|
// finding 2): a repo outside the ceiling must not consume the page.
|
|
17387
|
-
|
|
17474
|
+
// A put-away task (v145, task-tidy.md) is left out here, as from every list and count.
|
|
17475
|
+
const admission = (admitted === null
|
|
17388
17476
|
? "(? IS NULL OR task_ref.repo IS NULL OR task_ref.repo = ?)"
|
|
17389
|
-
: `(task_ref.repo IS NULL${admitted.length === 0 ? "" : ` OR task_ref.repo IN (${admitted.map(() => "?").join(",")})`})
|
|
17477
|
+
: `(task_ref.repo IS NULL${admitted.length === 0 ? "" : ` OR task_ref.repo IN (${admitted.map(() => "?").join(",")})`})`) + " AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)";
|
|
17390
17478
|
const params = admitted === null ? [repo, repo] : admitted;
|
|
17391
17479
|
return this.db
|
|
17392
17480
|
.prepare(`SELECT task.id AS task_id, task.title, task_scope.goal, task_scope.digest, task_scope.proposed_at, task_ref.repo AS task_repo
|
|
@@ -17416,9 +17504,10 @@ export class Store {
|
|
|
17416
17504
|
*/
|
|
17417
17505
|
listRequeueablesScoped(repo, now, limit = 10, admitted = null) {
|
|
17418
17506
|
const page = Math.max(1, Math.min(Math.floor(limit), 50));
|
|
17419
|
-
|
|
17507
|
+
// A put-away task (v145, task-tidy.md) is left out here, as from every list and count.
|
|
17508
|
+
const admission = (admitted === null
|
|
17420
17509
|
? "(? IS NULL OR task_ref.repo IS NULL OR task_ref.repo = ?)"
|
|
17421
|
-
: `(task_ref.repo IS NULL${admitted.length === 0 ? "" : ` OR task_ref.repo IN (${admitted.map(() => "?").join(",")})`})
|
|
17510
|
+
: `(task_ref.repo IS NULL${admitted.length === 0 ? "" : ` OR task_ref.repo IN (${admitted.map(() => "?").join(",")})`})`) + " AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)";
|
|
17422
17511
|
const params = admitted === null ? [repo, repo] : admitted;
|
|
17423
17512
|
return this.db
|
|
17424
17513
|
.prepare(`WITH open_incidents AS (
|
|
@@ -17482,6 +17571,8 @@ export class Store {
|
|
|
17482
17571
|
JOIN task_ref AS blocker_ref ON blocker_ref.backend = ? AND blocker_ref.external_id = task_edge.blocker
|
|
17483
17572
|
WHERE ${admissionOf("dependent_ref")}
|
|
17484
17573
|
AND ${admissionOf("blocker_ref")}
|
|
17574
|
+
-- Put-away tasks (v145) need no repair card, on either side.
|
|
17575
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref IN (dependent_ref.id, blocker_ref.id))
|
|
17485
17576
|
GROUP BY task_edge.blocker, dependent_ref.repo, blocker_ref.repo
|
|
17486
17577
|
ORDER BY task_edge.blocker LIMIT ?`)
|
|
17487
17578
|
.all(BUILT_IN, BUILT_IN, ...paramsOf(), ...paramsOf(), page)
|
|
@@ -17514,6 +17605,7 @@ export class Store {
|
|
|
17514
17605
|
WHERE task.state = 'done'
|
|
17515
17606
|
AND (? IS NULL OR task_ref.repo IS NULL OR task_ref.repo = ?)
|
|
17516
17607
|
${admission}
|
|
17608
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)
|
|
17517
17609
|
)
|
|
17518
17610
|
SELECT completed.*, run.id AS run_id, run.outcome, run.handoff, run.cost_usd, run.provider, run.auth_mode, run.started_at, run.finished_at,
|
|
17519
17611
|
publication.state AS pub_state, publication.pr_number, publication.pr_url,
|
|
@@ -17620,9 +17712,10 @@ export class Store {
|
|
|
17620
17712
|
// finding 11): in the roll-up, rows outside the ceiling must not
|
|
17621
17713
|
// consume the page and crowd out admitted cards. Serve still filters
|
|
17622
17714
|
// per-row afterward — this is the bound, that is the law.
|
|
17623
|
-
|
|
17715
|
+
// A put-away task (v145, task-tidy.md) is left out here, as from every list and count.
|
|
17716
|
+
const admission = (admitted === null
|
|
17624
17717
|
? "(? IS NULL OR task_ref.repo IS NULL OR task_ref.repo = ?)"
|
|
17625
|
-
: `(task_ref.repo IS NULL OR task_ref.repo IN (${admitted.map(() => "?").join(",")}))
|
|
17718
|
+
: `(task_ref.repo IS NULL OR task_ref.repo IN (${admitted.map(() => "?").join(",")}))`) + " AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)";
|
|
17626
17719
|
const admissionParams = admitted === null ? [repo, repo] : admitted;
|
|
17627
17720
|
const columns = `
|
|
17628
17721
|
SELECT task.id, task.title, task.state, task.updated_at, task.priority, task_ref.strikes, task_ref.repo AS task_repo,
|
|
@@ -17826,14 +17919,15 @@ export class Store {
|
|
|
17826
17919
|
SUM(CASE WHEN task.state = 'queued' THEN 1 ELSE 0 END) AS queued,
|
|
17827
17920
|
SUM(CASE WHEN task.state = 'running' THEN 1 ELSE 0 END) AS running
|
|
17828
17921
|
FROM task JOIN task_ref ON task_ref.backend = 'built-in' AND task_ref.external_id = task.id
|
|
17829
|
-
WHERE task_ref.repo =
|
|
17922
|
+
WHERE task_ref.repo = ? AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)`)
|
|
17830
17923
|
.get(repo);
|
|
17831
17924
|
const since = new Date(now.getTime() - 24 * 60 * 60_000).toISOString();
|
|
17832
17925
|
const done = this.db
|
|
17833
17926
|
.prepare(`SELECT COUNT(*) AS n FROM run
|
|
17834
17927
|
JOIN task_ref ON task_ref.id = run.task_ref
|
|
17835
17928
|
WHERE task_ref.repo = ? AND run.role IN ('builder','scout')
|
|
17836
|
-
AND run.outcome IN ('built','no-change') AND run.finished_at IS NOT NULL AND run.finished_at >=
|
|
17929
|
+
AND run.outcome IN ('built','no-change') AND run.finished_at IS NOT NULL AND run.finished_at >= ?
|
|
17930
|
+
AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)`)
|
|
17837
17931
|
.get(repo, since);
|
|
17838
17932
|
return { waiting: waiting, queued: Number(q.queued ?? 0), running: Number(q.running ?? 0), doneRecently: Number(done.n) };
|
|
17839
17933
|
}
|
|
@@ -17842,9 +17936,10 @@ export class Store {
|
|
|
17842
17936
|
// review, finding 11): with repo NULL the old predicate admitted every
|
|
17843
17937
|
// stored project, ceiling or not. An empty admitted list reduces to
|
|
17844
17938
|
// unplaced rows only — never IN ().
|
|
17845
|
-
|
|
17939
|
+
// A put-away task (v145, task-tidy.md) is left out here, as from every list and count.
|
|
17940
|
+
const admission = (admitted === null
|
|
17846
17941
|
? "(? IS NULL OR task_ref.repo IS NULL OR task_ref.repo = ?)"
|
|
17847
|
-
: `(task_ref.repo IS NULL${admitted.length === 0 ? "" : ` OR task_ref.repo IN (${admitted.map(() => "?").join(",")})`})
|
|
17942
|
+
: `(task_ref.repo IS NULL${admitted.length === 0 ? "" : ` OR task_ref.repo IN (${admitted.map(() => "?").join(",")})`})`) + " AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)";
|
|
17848
17943
|
const params = () => (admitted === null ? [repo, repo] : [...admitted]);
|
|
17849
17944
|
const row = this.db
|
|
17850
17945
|
.prepare(`SELECT COUNT(*) AS n FROM (
|
|
@@ -18697,6 +18792,8 @@ export class Store {
|
|
|
18697
18792
|
*/
|
|
18698
18793
|
resumeRunStop(args, now) {
|
|
18699
18794
|
return this.transact(() => {
|
|
18795
|
+
if (refArchived(this, args.taskRef))
|
|
18796
|
+
return { ok: false, reason: "archived", detail: "this task is put away — restore it first" };
|
|
18700
18797
|
const stop = this.stopOf(args.runId);
|
|
18701
18798
|
const run = this.getRun(args.runId);
|
|
18702
18799
|
if (stop === null || run === null)
|
|
@@ -21669,3 +21766,108 @@ export function dropFeedbackImage(store, runId, commentId, actor, unusable, now)
|
|
|
21669
21766
|
return { ok: true, id };
|
|
21670
21767
|
});
|
|
21671
21768
|
}
|
|
21769
|
+
/** The put-away record of this task, or null when it is not put away. */
|
|
21770
|
+
export function archiveOf(store, taskId) {
|
|
21771
|
+
const row = store.handle.prepare(`SELECT a.*, r.external_id FROM task_archive a JOIN task_ref r ON r.id = a.task_ref
|
|
21772
|
+
WHERE r.backend = 'built-in' AND r.external_id = ?`).get(taskId);
|
|
21773
|
+
return row === undefined ? null : { taskRef: Number(row["task_ref"]), taskId: String(row["external_id"]), by: String(row["by"]),
|
|
21774
|
+
byKind: row["by_kind"] === "tidy" ? "tidy" : "person", rule: row["rule"] == null ? null : String(row["rule"]), reason: String(row["reason"]), at: String(row["at"]) };
|
|
21775
|
+
}
|
|
21776
|
+
/** Every version of a task's family, by the same proven lineage the task page reads (`taskFamilyOf`: same project, each
|
|
21777
|
+
* revision bound to its parent's own run by its brief). A family whose lineage doesn't prove is just this task. */
|
|
21778
|
+
export function taskFamilyRefs(store, taskId) {
|
|
21779
|
+
const family = store.taskFamilyOf(taskId, null, true);
|
|
21780
|
+
return family === null ? [] : family.versions.map(one => ({ id: one.refId, taskId: one.id })).sort((a, b) => a.id - b.id);
|
|
21781
|
+
}
|
|
21782
|
+
/** The first family version with something still going on, or null: running, a live newest claim, an unfinished run,
|
|
21783
|
+
* a process Toolroll hasn't seen exit, a build whose processes were never recorded, a stop still settling, a follow-up
|
|
21784
|
+
* check asked for and not finished, a flow step running for its card, a release in flight (a live release approval
|
|
21785
|
+
* that names it, or a deploy or merge reserved, running or unknown), or a pull request merging (an intent claimed or
|
|
21786
|
+
* firing, or a person's merge between its last check and GitHub's answer). */
|
|
21787
|
+
export function liveFamilyAttempt(store, refs, now) {
|
|
21788
|
+
if (refs.length === 0)
|
|
21789
|
+
return null;
|
|
21790
|
+
const row = store.handle.prepare(`SELECT r.external_id FROM task_ref r JOIN task t ON t.id = r.external_id
|
|
21791
|
+
WHERE r.id IN (SELECT value FROM json_each(?)) AND (t.state = 'running'
|
|
21792
|
+
OR EXISTS (SELECT 1 FROM claim c WHERE c.task_ref = r.id AND c.released_at IS NULL AND c.expires_at > ?
|
|
21793
|
+
AND c.lease_generation = (SELECT MAX(n.lease_generation) FROM claim n WHERE n.task_ref = r.id))
|
|
21794
|
+
OR EXISTS (SELECT 1 FROM run x WHERE x.task_ref = r.id AND x.outcome IS NULL)
|
|
21795
|
+
OR EXISTS (SELECT 1 FROM run_process p JOIN run x ON x.id = p.run WHERE x.task_ref = r.id AND p.exited_at IS NULL AND p.container_empty_at IS NULL)
|
|
21796
|
+
OR EXISTS (SELECT 1 FROM run x WHERE x.task_ref = r.id AND x.provider_started_at IS NOT NULL
|
|
21797
|
+
AND NOT EXISTS (SELECT 1 FROM run_process p WHERE p.run = x.id) AND NOT EXISTS (SELECT 1 FROM run_process_summary s WHERE s.run = x.id))
|
|
21798
|
+
OR EXISTS (SELECT 1 FROM run_stop s WHERE s.task_ref = r.id AND s.resumed_at IS NULL AND s.settled_at IS NULL)
|
|
21799
|
+
OR EXISTS (SELECT 1 FROM action_ledger q JOIN run x ON x.id = q.run_id WHERE x.task_ref = r.id AND q.action = 'checks requested'
|
|
21800
|
+
AND NOT EXISTS (SELECT 1 FROM action_ledger f WHERE f.run_id = q.run_id AND f.action = 'checks finished' AND f.outcome LIKE q.id || ':%'))
|
|
21801
|
+
OR EXISTS (SELECT 1 FROM flow_card c JOIN flow_step_run s ON s.card = c.id
|
|
21802
|
+
WHERE s.state = 'running' AND (c.task = r.external_id OR c.primary_task = r.external_id))
|
|
21803
|
+
OR EXISTS (SELECT 1 FROM flow_step_task t JOIN flow_step_run s ON s.card = t.card AND s.entry = t.entry
|
|
21804
|
+
WHERE s.state = 'running' AND t.task_id = r.external_id)
|
|
21805
|
+
OR EXISTS (SELECT 1 FROM deploy_in_flight d WHERE d.task_ref = r.id AND d.at > ?)
|
|
21806
|
+
OR EXISTS (SELECT 1 FROM release_authority a WHERE a.state IN ('open','deploying','merge-reserved') AND (a.anchor_task = r.external_id OR a.tip_task = r.external_id))
|
|
21807
|
+
OR EXISTS (SELECT 1 FROM release_operation o WHERE o.task_id = r.external_id AND o.state IN ('reserved','running','unknown'))
|
|
21808
|
+
OR EXISTS (SELECT 1 FROM merge_intent i JOIN publication p ON p.id = i.publication WHERE p.task_ref = r.id AND i.state IN ('claimed','firing'))
|
|
21809
|
+
OR EXISTS (SELECT 1 FROM merge_in_flight m WHERE m.task_ref = r.id AND m.at > ?))
|
|
21810
|
+
LIMIT 1`).get(JSON.stringify(refs), now.toISOString(), new Date(now.getTime() - 30 * 60_000).toISOString(), new Date(now.getTime() - 15 * 60_000).toISOString());
|
|
21811
|
+
return row === undefined ? null : String(row["external_id"]);
|
|
21812
|
+
}
|
|
21813
|
+
/** Puts a task away (v145): every version of its family, in one write transaction that re-checks it has no live
|
|
21814
|
+
* attempt, that its versions are exactly `expect` when the caller showed a person a family (a preview), and that
|
|
21815
|
+
* `guard` (the caller's own rule, re-read under the lock) still allows it. Its state, history, results, approvals and
|
|
21816
|
+
* ledger are untouched; its open notifications are resolved; one ledger row records who and why. */
|
|
21817
|
+
export function archiveTaskFamily(store, taskId, by, now, options = {}) {
|
|
21818
|
+
return store.transact(() => {
|
|
21819
|
+
const family = taskFamilyRefs(store, taskId);
|
|
21820
|
+
if (family.length === 0)
|
|
21821
|
+
return { ok: false, reason: "unknown-task", message: `No task ${taskId}.` };
|
|
21822
|
+
const refs = family.map(one => one.id);
|
|
21823
|
+
if (options.expect !== undefined && [...options.expect].sort((a, b) => a - b).join(",") !== refs.join(",")) {
|
|
21824
|
+
return { ok: false, reason: "blocked", message: `${taskId}'s versions changed since the list was shown.` };
|
|
21825
|
+
}
|
|
21826
|
+
// Already put away, every version: nothing to do. A family a person revived with a new revision puts away the rest.
|
|
21827
|
+
const remaining = refs.filter(ref => !refArchived(store, ref));
|
|
21828
|
+
if (remaining.length === 0)
|
|
21829
|
+
return { ok: false, reason: "archived", message: `${taskId} is already put away.` };
|
|
21830
|
+
const live = liveFamilyAttempt(store, refs, now);
|
|
21831
|
+
if (live !== null)
|
|
21832
|
+
return { ok: false, reason: "live", message: `${taskId} still has work going on. Stop it or let it finish, then put it away.` };
|
|
21833
|
+
const blocked = options.guard?.(refs) ?? null;
|
|
21834
|
+
if (blocked !== null)
|
|
21835
|
+
return { ok: false, reason: "blocked", message: blocked };
|
|
21836
|
+
const reason = by.reason.replace(/[\u0000-\u001f\u007f]+/g, " ").trim().slice(0, 300) || "Put away";
|
|
21837
|
+
const at = now.toISOString();
|
|
21838
|
+
const insert = store.handle.prepare("INSERT INTO task_archive (task_ref, by, by_kind, rule, reason, at) VALUES (?, ?, ?, ?, ?, ?)");
|
|
21839
|
+
for (const ref of remaining)
|
|
21840
|
+
insert.run(ref, by.by, by.byKind, by.rule, reason, at);
|
|
21841
|
+
store.handle.prepare("UPDATE notification SET resolved_at = ? WHERE resolved_at IS NULL AND task_ref IN (SELECT value FROM json_each(?))")
|
|
21842
|
+
.run(at, JSON.stringify(refs));
|
|
21843
|
+
// The family's flow cards hold while it is put away, so what they queued for people is settled too.
|
|
21844
|
+
store.handle.prepare(`UPDATE notification SET resolved_at = ? WHERE resolved_at IS NULL AND EXISTS (SELECT 1 FROM flow_card c
|
|
21845
|
+
WHERE (c.task IN (SELECT value FROM json_each(?)) OR c.primary_task IN (SELECT value FROM json_each(?)))
|
|
21846
|
+
AND (notification.dedupe_key LIKE 'flow:' || c.id || ':%' OR notification.dedupe_key LIKE 'flow-card:' || c.id || ':%'))`)
|
|
21847
|
+
.run(at, JSON.stringify(family.map(one => one.taskId)), JSON.stringify(family.map(one => one.taskId)));
|
|
21848
|
+
const repo = store.refById(refs[0])?.repo ?? null;
|
|
21849
|
+
store.recordAction({ at, actor: by.byKind === "tidy" ? "tidy" : `operator:${by.by}`, repo, taskId, runId: null, action: "task archived",
|
|
21850
|
+
outcome: by.rule ?? "by hand", source: "work", detail: `${reason}${refs.length > 1 ? ` (${refs.length} versions)` : ""}` });
|
|
21851
|
+
return { ok: true, refs };
|
|
21852
|
+
});
|
|
21853
|
+
}
|
|
21854
|
+
/** Brings a put-away task back (v145): every version of its family, with a ledger row. Tidy never puts a restored
|
|
21855
|
+
* task away again (tidy.ts reads the `task restored` row). */
|
|
21856
|
+
export function restoreTaskFamily(store, taskId, by, now, guard) {
|
|
21857
|
+
return store.transact(() => {
|
|
21858
|
+
const family = taskFamilyRefs(store, taskId);
|
|
21859
|
+
if (family.length === 0)
|
|
21860
|
+
return { ok: false, reason: "unknown-task", message: `No task ${taskId}.` };
|
|
21861
|
+
const refs = family.map(one => one.id).filter(ref => refArchived(store, ref));
|
|
21862
|
+
if (refs.length === 0)
|
|
21863
|
+
return { ok: false, reason: "not-archived", message: `${taskId} isn't put away.` };
|
|
21864
|
+
const blocked = guard?.(refs) ?? null;
|
|
21865
|
+
if (blocked !== null)
|
|
21866
|
+
return { ok: false, reason: "blocked", message: blocked };
|
|
21867
|
+
store.handle.prepare("DELETE FROM task_archive WHERE task_ref IN (SELECT value FROM json_each(?))").run(JSON.stringify(refs));
|
|
21868
|
+
const at = now.toISOString();
|
|
21869
|
+
store.recordAction({ at, actor: `operator:${by}`, repo: store.refById(refs[0])?.repo ?? null, taskId, runId: null, action: "task restored",
|
|
21870
|
+
outcome: "recorded", source: "work", detail: refs.length > 1 ? `${refs.length} versions` : null });
|
|
21871
|
+
return { ok: true, refs };
|
|
21872
|
+
});
|
|
21873
|
+
}
|
package/dist/surface.js
CHANGED
|
@@ -171,7 +171,7 @@ const GUIDE_ROWS = [
|
|
|
171
171
|
notableReasons: ["no-result"] },
|
|
172
172
|
{ invocation: "task list", synopsis: "a page of saved task status", audience: "agent", agentMayInvoke: true, mutation: "none",
|
|
173
173
|
flags: [jsonFlag, dbFlag, { name: "state", takesValue: true, meaning: "queued|running|done|failed|cancelled" },
|
|
174
|
-
{ name: "view", takesValue: true, meaning: "all|needs-you|running|completed" },
|
|
174
|
+
{ name: "view", takesValue: true, meaning: "all|needs-you|running|completed|archived" },
|
|
175
175
|
{ name: "limit", takesValue: true, meaning: "page size, 1–100; default 40" },
|
|
176
176
|
{ name: "cursor", takesValue: true, meaning: "nextCursor from the previous response" },
|
|
177
177
|
{ name: "repo", takesValue: true, meaning: "filter to one project" }] },
|
|
@@ -299,6 +299,18 @@ const GUIDE_ROWS = [
|
|
|
299
299
|
operator("task route", "which configured agents plan, build and repair this task; phase and size changes require renewed approval"),
|
|
300
300
|
operator("task reopen", "resume external work its tracker closed and has been SEEN open again"),
|
|
301
301
|
operator("task stop", "stop ONE exact live attempt (--run <id>): durable before any process is signalled, answers 'stopping' until its own processes are established gone; work, branch, and evidence preserved; no strike, no retry"),
|
|
302
|
+
{ ...operator("task archive", "put tasks away (a person's act): out of every list, count and nudge, history and approvals kept; nothing claims, approves or dispatches them until restored"),
|
|
303
|
+
positionals: [{ name: "id", required: true, meaning: "one or more tasks (any version: the whole family goes)" }],
|
|
304
|
+
flags: [jsonFlag, dbFlag, { name: "reason", takesValue: true, meaning: "why, shown on the task page" }], notableReasons: ["unknown-task", "not-an-approver"] },
|
|
305
|
+
{ ...operator("task restore", "bring put-away tasks back exactly as they were (a person's act)"),
|
|
306
|
+
positionals: [{ name: "id", required: true, meaning: "one or more put-away tasks" }], flags: [jsonFlag, dbFlag], notableReasons: ["unknown-task", "not-an-approver"] },
|
|
307
|
+
{ ...operator("task tidy", "Put away old tasks: --preview lists what would go and why (read-only), --apply --digest <d> puts away exactly that list (a person's act)"),
|
|
308
|
+
flags: [jsonFlag, dbFlag, { name: "preview", takesValue: false, meaning: "show the list and the digest; changes nothing" },
|
|
309
|
+
{ name: "apply", takesValue: false, meaning: "put away exactly the previewed list (needs --digest)" },
|
|
310
|
+
{ name: "digest", takesValue: true, meaning: "the digest --preview printed" },
|
|
311
|
+
{ name: "days", takesValue: true, meaning: "also finished tasks untouched this many days (1–365, default 14)" },
|
|
312
|
+
{ name: "except", takesValue: true, meaning: "comma-separated tasks from the list to leave out" }],
|
|
313
|
+
notableReasons: ["usage", "not-an-approver", "empty"] },
|
|
302
314
|
operator("run settle", "last resort for a finished run whose process witness can't be proven either way: records your --why in the ledger; refuses while any of the run's processes is alive"),
|
|
303
315
|
operator("task resume", "resume the exact stopped attempt (--run <id>): refuses until it is quiescent, lifts only that stop's hold, approves nothing — the next pass re-proves the scope and inherits the draft with fresh proof"),
|
|
304
316
|
// ---- leases (runner surface) ----
|
|
@@ -443,11 +455,15 @@ const REMOTE_NO = [
|
|
|
443
455
|
"enroll", "revoke",
|
|
444
456
|
// Flow steps that fetch addresses or run checks and scripts here.
|
|
445
457
|
"flows import", "flows trigger check", "flows script save",
|
|
458
|
+
// Lets a flow script on this machine start Chromium in its sandbox: a person here, with their own password.
|
|
459
|
+
"flows browser",
|
|
446
460
|
// Reads this machine owner's own Claude and Codex session files (~/.claude, ~/.codex).
|
|
447
461
|
"memory propose",
|
|
448
462
|
];
|
|
449
463
|
const REMOTE_STEP_UP = [
|
|
450
|
-
"proposals", "decide", "task regate", "task approve", "task accept", "task merge", "run settle",
|
|
464
|
+
"proposals", "decide", "task regate", "task approve", "task accept", "task merge", "run settle",
|
|
465
|
+
// Putting tasks away and restoring them: a person's act, in the console or on the server's machine.
|
|
466
|
+
"task archive", "task restore", "task tidy", "knowledge apply", "memory apply",
|
|
451
467
|
"flows card approve", "flows card send-back", "flows script approve", "flows unattended",
|
|
452
468
|
"approver list", "approver add", "people list", "people invite", "people projects", "people revoke",
|
|
453
469
|
// A person's own API tokens: a password step-up on the server's machine, or the console. Never with a token.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Put-away checks (v145, docs/plans/task-tidy.md), in a leaf module so every door can ask without loading the store. */
|
|
2
|
+
import type { Store } from "./store.js";
|
|
3
|
+
/** Whether this task version is put away. Every door that dispatches, claims or approves asks this (fail closed: a
|
|
4
|
+
* row means no). */
|
|
5
|
+
export declare function refArchived(store: Store, taskRef: number): boolean;
|
|
6
|
+
/** Whether the built-in task with this id is put away. */
|
|
7
|
+
export declare function taskArchived(store: Store, taskId: string): boolean;
|
|
8
|
+
/** Whether a flow card's work is put away: its current task or its first build task (a later stage keeps the first
|
|
9
|
+
* and may clear the current one). Read from the card's own row now, so a put away that committed first holds it. */
|
|
10
|
+
export declare function cardTaskArchived(store: Store, cardId: number): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Whether this task version is put away. Every door that dispatches, claims or approves asks this (fail closed: a
|
|
2
|
+
* row means no). */
|
|
3
|
+
export function refArchived(store, taskRef) {
|
|
4
|
+
return store.handle.prepare("SELECT 1 FROM task_archive WHERE task_ref = ?").get(taskRef) !== undefined;
|
|
5
|
+
}
|
|
6
|
+
/** Whether the built-in task with this id is put away. */
|
|
7
|
+
export function taskArchived(store, taskId) {
|
|
8
|
+
return store.handle.prepare(`SELECT 1 FROM task_archive a JOIN task_ref r ON r.id = a.task_ref
|
|
9
|
+
WHERE r.backend = 'built-in' AND r.external_id = ?`).get(taskId) !== undefined;
|
|
10
|
+
}
|
|
11
|
+
/** Whether a flow card's work is put away: its current task or its first build task (a later stage keeps the first
|
|
12
|
+
* and may clear the current one). Read from the card's own row now, so a put away that committed first holds it. */
|
|
13
|
+
export function cardTaskArchived(store, cardId) {
|
|
14
|
+
return store.handle.prepare(`SELECT 1 FROM flow_card c JOIN task_ref r ON r.backend = 'built-in' AND r.external_id IN (c.task, c.primary_task)
|
|
15
|
+
JOIN task_archive a ON a.task_ref = r.id WHERE c.id = ?`).get(cardId) !== undefined;
|
|
16
|
+
}
|
package/dist/task-control.d.ts
CHANGED
|
@@ -102,7 +102,7 @@ export type ResumeOutcome = {
|
|
|
102
102
|
gate: DispatchDiagnosis | null;
|
|
103
103
|
} | {
|
|
104
104
|
ok: false;
|
|
105
|
-
reason: "no-task" | "no-stop" | "wrong-task" | "stopping" | "already-resumed" | "superseded" | "busy" | "review" | "occupied";
|
|
105
|
+
reason: "no-task" | "no-stop" | "wrong-task" | "stopping" | "already-resumed" | "superseded" | "busy" | "review" | "occupied" | "archived";
|
|
106
106
|
detail: string;
|
|
107
107
|
};
|
|
108
108
|
/**
|