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.
Files changed (79) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/agent-fence.d.ts +6 -2
  3. package/dist/agent-fence.js +3 -2
  4. package/dist/assignment.js +4 -3
  5. package/dist/browser/workspace.css +1 -1
  6. package/dist/browser/workspace.js +31 -29
  7. package/dist/browser-workspace.d.ts +40 -0
  8. package/dist/chat-actions.d.ts +11 -1
  9. package/dist/chat-actions.js +53 -1
  10. package/dist/chat-channel.js +1 -0
  11. package/dist/chat-quiet.js +3 -2
  12. package/dist/claim.d.ts +3 -0
  13. package/dist/claim.js +4 -1
  14. package/dist/contracts/chat-actions.d.ts +9 -0
  15. package/dist/contracts/chat-actions.js +3 -0
  16. package/dist/contracts/console-api.d.ts +4 -1
  17. package/dist/contracts/console-api.js +4 -1
  18. package/dist/contracts/flow-propose.d.ts +4 -1
  19. package/dist/contracts/flow-propose.js +1 -1
  20. package/dist/contracts/flow.d.ts +41 -0
  21. package/dist/contracts/flow.js +4 -2
  22. package/dist/contracts/lead-tools.d.ts +21 -0
  23. package/dist/contracts/lead-tools.js +2 -0
  24. package/dist/contracts/store-json.d.ts +2 -0
  25. package/dist/contracts/trigger-payloads.d.ts +20 -0
  26. package/dist/contracts/trigger-payloads.js +4 -0
  27. package/dist/coordinator.js +1 -0
  28. package/dist/deploy-gate.js +4 -0
  29. package/dist/dispatch.d.ts +3 -0
  30. package/dist/dispatch.js +4 -1
  31. package/dist/flow-browser.d.ts +103 -0
  32. package/dist/flow-browser.js +204 -0
  33. package/dist/flow-code.d.ts +5 -1
  34. package/dist/flow-code.js +4 -1
  35. package/dist/flow-engine.js +16 -0
  36. package/dist/flow-live.js +9 -1
  37. package/dist/flow-pull-request.js +15 -7
  38. package/dist/flow-share.js +3 -1
  39. package/dist/flow-steps.d.ts +9 -1
  40. package/dist/flow-steps.js +55 -5
  41. package/dist/flow-triggers.js +55 -0
  42. package/dist/flows-cli.d.ts +19 -0
  43. package/dist/flows-cli.js +47 -2
  44. package/dist/flows-ui.js +11 -2
  45. package/dist/flows.js +5 -2
  46. package/dist/lead-tools.js +35 -6
  47. package/dist/lead-voice.js +16 -5
  48. package/dist/operate.d.ts +1 -1
  49. package/dist/operate.js +215 -6
  50. package/dist/project-delete.js +7 -3
  51. package/dist/pull-request-flow.js +35 -4
  52. package/dist/result-follow-ups.js +4 -0
  53. package/dist/scope.d.ts +5 -0
  54. package/dist/scope.js +7 -0
  55. package/dist/server/chat.js +4 -2
  56. package/dist/server/console.css +29 -0
  57. package/dist/server/flows.js +15 -0
  58. package/dist/server/render-pages.d.ts +13 -0
  59. package/dist/server/render-pages.js +45 -2
  60. package/dist/server/render-tasks.d.ts +8 -0
  61. package/dist/server/render-tasks.js +17 -6
  62. package/dist/server/route-table.js +11 -0
  63. package/dist/server/task-views.js +5 -0
  64. package/dist/server/tasks.d.ts +10 -0
  65. package/dist/server/tasks.js +54 -2
  66. package/dist/settings-proofs.js +1 -0
  67. package/dist/store.d.ts +71 -3
  68. package/dist/store.js +222 -20
  69. package/dist/surface.js +18 -2
  70. package/dist/task-archive.d.ts +10 -0
  71. package/dist/task-archive.js +16 -0
  72. package/dist/task-control.d.ts +1 -1
  73. package/dist/tidy.d.ts +130 -0
  74. package/dist/tidy.js +503 -0
  75. package/dist/work-index.d.ts +24 -0
  76. package/dist/work-index.js +52 -30
  77. package/dist/workspace-ui.d.ts +1 -1
  78. package/dist/workspace-ui.js +3 -2
  79. package/package.json +1 -1
@@ -37,6 +37,13 @@ export type WorkIndexItem = {
37
37
  evidence: 'recorded';
38
38
  /** The viewer's own lead took it on (lead-voice.ts): "<name> is on it.", by the name they gave it, read now. */
39
39
  lead?: string;
40
+ /** Put away (v145, task-tidy.md): by whom (a person's name, or tidy), why and when. Only the Archived view lists it. */
41
+ archived?: {
42
+ by: string;
43
+ byKind: 'person' | 'tidy';
44
+ reason: string;
45
+ at: string;
46
+ };
40
47
  };
41
48
  /** `root`: the evidence root, so a result whose checks were Off can read the release check that covers it. */
42
49
  export type WorkIndexOptions = {
@@ -129,3 +136,20 @@ export declare function workReadyForReview(store: Store, now: Date, access: Work
129
136
  run: number | null;
130
137
  }[];
131
138
  };
139
+ /** One row per task family for tidy (docs/plans/task-tidy.md): the Tasks list's own projection, so tidy's "unfinished",
140
+ * "live" and "complete" are the words the list shows. `members` are every version's task_ref ids. */
141
+ export type WorkTidyRow = {
142
+ rootId: string;
143
+ currentId: string;
144
+ title: string;
145
+ repo: string | null;
146
+ state: TaskState;
147
+ code: string;
148
+ live: boolean;
149
+ archived: boolean;
150
+ broken: boolean;
151
+ updatedAt: string;
152
+ members: number[];
153
+ memberIds: string[];
154
+ };
155
+ export declare function workTidyRows(store: Store, now: Date, access: WorkSummaryAccess): WorkTidyRow[];
@@ -22,10 +22,12 @@ import { ASKS, NEED_ASK, NEEDS, askChipOf, failedAttemptSentence, processNeedOf,
22
22
  import { LEAD_CLAIM_SQL, LEAD_IDLE_MS } from './lead-voice.js';
23
23
  export const WORK_INDEX_PAGE_LIMIT = 40;
24
24
  export const WORK_INDEX_MAX_LIMIT = 100;
25
- const EMPTY = { all: 0, 'needs-you': 0, running: 0, completed: 0 };
25
+ const EMPTY = { all: 0, 'needs-you': 0, running: 0, completed: 0, archived: 0 };
26
26
  const NO_GROUPS = { decide: 0, review: 0, unblock: 0, building: 0, rest: 0 };
27
27
  const FAMILY_PROBLEM = 'Task history is unavailable or incomplete. This task is shown separately.';
28
- const VIEWS = ['all', 'needs-you', 'running', 'completed'];
28
+ const VIEWS = ['all', 'needs-you', 'running', 'completed', 'archived'];
29
+ /** The family's current version is put away (v145): it waits on nobody and only the Archived view lists it. */
30
+ const ARCHIVED = `EXISTS(SELECT 1 FROM task_archive ta WHERE ta.task_ref=c.ref_id)`;
29
31
  const n = (row, key) => Number(row[key] ?? 0);
30
32
  const s = (row, key) => row[key] == null ? null : String(row[key]);
31
33
  /** The running agent's oldest open question (agent-board.ts), from its open newest attempt only. */
@@ -93,24 +95,25 @@ const PROJECTION = `WITH RECURSIVE admitted AS MATERIALIZED (
93
95
  (SELECT id FROM run WHERE task_ref=a.ref_id AND outcome IS NULL ORDER BY id DESC LIMIT 1) unfinished,
94
96
  CASE WHEN a.state='done' THEN (SELECT id FROM run WHERE task_ref=a.ref_id AND finished_at IS NOT NULL AND role IN ('builder','scout') ORDER BY id DESC LIMIT 1) END result_id,
95
97
  (SELECT h.id FROM hold h WHERE h.task_ref=a.ref_id AND (h.until IS NULL OR h.until>$now) ORDER BY h.held_at,h.id LIMIT 1) hold_id,
96
- q.decision_id,${AGENT_ASK} agent_ask,(cu.task_ref IS NOT NULL OR a.ref_id IN (SELECT value FROM json_each($workspaceCustody)))
98
+ q.decision_id,${AGENT_ASK} agent_ask,EXISTS(SELECT 1 FROM task_archive ta WHERE ta.task_ref=a.ref_id) member_archived,(cu.task_ref IS NOT NULL OR a.ref_id IN (SELECT value FROM json_each($workspaceCustody)))
97
99
  AND a.ref_id NOT IN (SELECT value FROM json_each($settledCustody)) custody_unresolved
98
100
  FROM admitted a LEFT JOIN lineage l ON l.ref_id=a.ref_id
99
101
  LEFT JOIN questions q ON q.task_ref=a.ref_id LEFT JOIN custody cu ON cu.task_ref=a.ref_id
100
102
  ), family_heads AS MATERIALIZED (
101
103
  SELECT root_ref,COUNT(*) version_count,MAX(updated_at) family_updated,
102
104
  CAST(substr(MAX((CASE WHEN ref_id=root_ref THEN '0' ELSE '1' END)||created_at||printf('%020d',ref_id)),-20) AS INTEGER) current_ref,
103
- SUM(state IN ('queued','running') OR live_lease IS NOT NULL OR unfinished IS NOT NULL OR custody_unresolved) active_count,
104
- MIN(CASE WHEN state IN ('queued','running') OR live_lease IS NOT NULL OR unfinished IS NOT NULL OR custody_unresolved THEN ref_id END) first_active,
105
- MAX(CASE WHEN state IN ('queued','running') OR live_lease IS NOT NULL OR unfinished IS NOT NULL OR custody_unresolved THEN ref_id END) last_active,
106
- MAX(live_lease IS NOT NULL) family_running,MIN(decision_id) question_id
105
+ -- A put-away version (v145) is history: it never counts as an earlier active version or brings its question along.
106
+ SUM(NOT member_archived AND (state IN ('queued','running') OR live_lease IS NOT NULL OR unfinished IS NOT NULL OR custody_unresolved)) active_count,
107
+ MIN(CASE WHEN NOT member_archived AND (state IN ('queued','running') OR live_lease IS NOT NULL OR unfinished IS NOT NULL OR custody_unresolved) THEN ref_id END) first_active,
108
+ MAX(CASE WHEN NOT member_archived AND (state IN ('queued','running') OR live_lease IS NOT NULL OR unfinished IS NOT NULL OR custody_unresolved) THEN ref_id END) last_active,
109
+ MAX(live_lease IS NOT NULL) family_running,MIN(CASE WHEN NOT member_archived THEN decision_id END) question_id
107
110
  FROM members GROUP BY root_ref
108
111
  ), ordered AS MATERIALIZED (
109
112
  SELECT m.*,CASE WHEN m.ref_id=f.current_ref THEN 1 ELSE 2 END version_rank
110
113
  FROM members m JOIN family_heads f USING(root_ref)
111
114
  ), families AS MATERIALIZED (
112
115
  SELECT f.root_ref,f.version_count,f.family_updated,f.family_running,
113
- f.active_count-(c.state IN ('queued','running') OR c.live_lease IS NOT NULL OR c.unfinished IS NOT NULL OR c.custody_unresolved) earlier_active,
116
+ f.active_count-(NOT c.member_archived AND (c.state IN ('queued','running') OR c.live_lease IS NOT NULL OR c.unfinished IS NOT NULL OR c.custody_unresolved)) earlier_active,
114
117
  earlier.id earlier_id,COALESCE(c.decision_id,f.question_id) question_id
115
118
  FROM family_heads f JOIN members c ON c.ref_id=f.current_ref
116
119
  LEFT JOIN admitted earlier ON earlier.ref_id=CASE WHEN f.first_active=f.current_ref THEN f.last_active ELSE f.first_active END
@@ -236,8 +239,9 @@ const PROJECTION = `WITH RECURSIVE admitted AS MATERIALIZED (
236
239
  SELECT c.*,CASE WHEN code IN ('cancelled','complete') THEN 0 ELSE ${LEAD_CLAIM_SQL} END lead_claim FROM classified c
237
240
  ), ranked AS MATERIALIZED (
238
241
  -- A task the person's lead is on (lead-voice.ts) waits on nobody: it leaves Needs you until the claim ends or lapses.
239
- SELECT c.*,code NOT IN ('cancelled','complete','running','updating','retry-scheduled','waiting-dependency','worker-at-capacity','planning-ready','scouting-ready','queued') AND NOT ${CUSTODY_WAITS} AND lead_claim<>1 needs,
240
- code NOT IN ('cancelled','complete','running','updating','retry-scheduled','waiting-dependency','worker-at-capacity','planning-ready','scouting-ready','queued') AND NOT ${CUSTODY_WAITS} person_need,
242
+ SELECT c.*,code NOT IN ('cancelled','complete','running','updating','retry-scheduled','waiting-dependency','worker-at-capacity','planning-ready','scouting-ready','queued') AND NOT ${CUSTODY_WAITS} AND lead_claim<>1 AND NOT ${ARCHIVED} needs,
243
+ code NOT IN ('cancelled','complete','running','updating','retry-scheduled','waiting-dependency','worker-at-capacity','planning-ready','scouting-ready','queued') AND NOT ${CUSTODY_WAITS} AND NOT ${ARCHIVED} person_need,
244
+ ${ARCHIVED} archived,
241
245
  CASE WHEN code='complete' THEN 3 WHEN code='cancelled' THEN 4 WHEN code='running' THEN 1
242
246
  WHEN ${CUSTODY_WAITS} OR code IN ('updating','retry-scheduled','waiting-dependency','worker-at-capacity','planning-ready','scouting-ready','queued') THEN 2 ELSE 0 END rank,
243
247
  ${ASK_RANK} ask_rank,
@@ -329,11 +333,13 @@ function parameters(now, access, options, custody) {
329
333
  $leadOf: access.principal === 'operator' && access.viewer != null && access.viewer !== '' ? `lead for ${access.viewer}` : null };
330
334
  }
331
335
  function counts(row) {
332
- return { all: n(row, 'all_count'), 'needs-you': n(row, 'needs_count'), running: n(row, 'running_count'), completed: n(row, 'completed_count') };
336
+ return { all: n(row, 'all_count'), 'needs-you': n(row, 'needs_count'), running: n(row, 'running_count'), completed: n(row, 'completed_count'), archived: n(row, 'archived_count') };
333
337
  }
334
- const TOTALS = `COUNT(*) all_count,COALESCE(SUM(needs),0) needs_count,COALESCE(SUM(family_running),0) running_count,COALESCE(SUM(code='complete'),0) completed_count`;
335
- const GROUP_TOTALS = `COALESCE(SUM(rank=0 AND ask_rank=0),0) decide_count,COALESCE(SUM(rank=0 AND ask_rank=1),0) review_count,COALESCE(SUM(rank=0 AND ask_rank=2),0) unblock_count,`
336
- + `COALESCE(SUM(rank=1),0) building_count,COALESCE(SUM(rank>1),0) rest_count`;
338
+ // Put-away families (v145) count only as archived.
339
+ const TOTALS = `COALESCE(SUM(archived=0),0) all_count,COALESCE(SUM(needs),0) needs_count,COALESCE(SUM(family_running AND archived=0),0) running_count,`
340
+ + `COALESCE(SUM(code='complete' AND archived=0),0) completed_count,COALESCE(SUM(archived),0) archived_count`;
341
+ const GROUP_TOTALS = `COALESCE(SUM(rank=0 AND ask_rank=0 AND archived=0),0) decide_count,COALESCE(SUM(rank=0 AND ask_rank=1 AND archived=0),0) review_count,COALESCE(SUM(rank=0 AND ask_rank=2 AND archived=0),0) unblock_count,`
342
+ + `COALESCE(SUM(rank=1 AND archived=0),0) building_count,COALESCE(SUM(rank>1 AND archived=0),0) rest_count`;
337
343
  function groupCounts(row) {
338
344
  return { decide: n(row, 'decide_count'), review: n(row, 'review_count'), unblock: n(row, 'unblock_count'), building: n(row, 'building_count'), rest: n(row, 'rest_count') };
339
345
  }
@@ -383,18 +389,20 @@ function readWorkIndexPage(store, now, access, options, taskId = null) {
383
389
  const view = VIEWS.includes(options.view ?? 'all') ? options.view ?? 'all' : 'all';
384
390
  const limit = Number.isFinite(options.limit) ? Math.max(1, Math.min(WORK_INDEX_MAX_LIMIT, Math.floor(options.limit))) : WORK_INDEX_PAGE_LIMIT;
385
391
  const scope = cursorScope(access, options, view), cursor = readCursor(options.cursor, scope);
386
- const filter = view === 'needs-you' ? 'needs=1' : view === 'running' ? 'family_running=1' : view === 'completed' ? "code='complete'" : '1';
392
+ const filter = view === 'archived' ? 'archived=1' : view === 'needs-you' ? 'needs=1' : view === 'running' ? 'family_running=1 AND archived=0' : view === 'completed' ? "code='complete' AND archived=0" : 'archived=0';
387
393
  const rows = prepared(store, `${projection}, selected_page AS MATERIALIZED (
388
- SELECT * FROM ranked WHERE ${filter} AND ($taskId IS NULL OR root_ref IN (SELECT root_ref FROM members WHERE id=$taskId)) AND ($roots IS NULL OR root_id IN (SELECT value FROM json_each($roots))) AND ($cursorRoot=0 OR
394
+ SELECT * FROM ranked WHERE ($taskId IS NOT NULL OR ${filter}) AND ($taskId IS NULL OR root_ref IN (SELECT root_ref FROM members WHERE id=$taskId)) AND ($roots IS NULL OR root_id IN (SELECT value FROM json_each($roots))) AND ($cursorRoot=0 OR
389
395
  ${ORDER}>$cursorRank OR (${ORDER}=$cursorRank AND (sort_at<$cursorAt OR (sort_at=$cursorAt AND root_ref<$cursorRoot))))
390
396
  ORDER BY ${ORDER},sort_at DESC,root_ref DESC LIMIT $limit
391
397
  ), page AS (SELECT p.*,COALESCE(p.result_id,(SELECT id FROM run WHERE task_ref=p.ref_id AND finished_at IS NOT NULL AND role IN ('builder','scout') ORDER BY id DESC LIMIT 1)) page_result_id FROM selected_page p), totals AS (SELECT ${TOTALS},${GROUP_TOTALS} FROM ranked), project_totals AS (
392
- SELECT repo,${TOTALS},SUM(state='queued' AND family_running=0) queued,
393
- SUM(state='done' AND family_running=0 AND updated_at>=$recent) done_recently FROM ranked GROUP BY repo
398
+ SELECT repo,${TOTALS},SUM(state='queued' AND family_running=0 AND archived=0) queued,
399
+ SUM(state='done' AND family_running=0 AND archived=0 AND updated_at>=$recent) done_recently FROM ranked GROUP BY repo
394
400
  )
395
401
  SELECT 0 record_kind,totals.*,(SELECT json_group_array(json_object('repo',repo,'all_count',all_count,'needs_count',needs_count,
396
- 'running_count',running_count,'completed_count',completed_count,'queued',queued,'done_recently',done_recently)) FROM project_totals) row_json FROM totals
397
- UNION ALL SELECT 1,0,0,0,0,0,0,0,0,0,json_object('root_ref',root_ref,'root_id',root_id,'id',id,'root_title',root_title,'repo',repo,
402
+ 'running_count',running_count,'completed_count',completed_count,'archived_count',archived_count,'queued',queued,'done_recently',done_recently)) FROM project_totals) row_json FROM totals
403
+ UNION ALL SELECT 1,0,0,0,0,0,0,0,0,0,0,json_object('archived',archived,'archived_by',(SELECT by FROM task_archive WHERE task_ref=page.ref_id),
404
+ 'archived_kind',(SELECT by_kind FROM task_archive WHERE task_ref=page.ref_id),'archived_reason',(SELECT reason FROM task_archive WHERE task_ref=page.ref_id),
405
+ 'archived_at',(SELECT at FROM task_archive WHERE task_ref=page.ref_id),'root_ref',root_ref,'root_id',root_id,'id',id,'root_title',root_title,'repo',repo,
398
406
  'state',state,'created_at',created_at,'family_updated',family_updated,'version_count',version_count,'earlier_active',earlier_active,
399
407
  'earlier_id',earlier_id,'broken',broken,'live_run',live_run,'result_id',page_result_id,'result_outcome',(SELECT outcome FROM run WHERE id=page_result_id),'attempt_reason',(SELECT substr(reason,1,1000) FROM run WHERE task_ref IN (SELECT ref_id FROM members WHERE root_ref=page.root_ref) AND (finished_at IS NOT NULL OR outcome IS NOT NULL) AND role NOT IN ('planner','reviewer') ORDER BY id DESC LIMIT 1),
400
408
  'publication_url',(SELECT pr_url FROM publication WHERE run=page_result_id),
@@ -434,8 +442,8 @@ function readWorkIndexPage(store, now, access, options, taskId = null) {
434
442
  }
435
443
  export function workCountsByProject(store, now, access) {
436
444
  const projection = registerValidators(store);
437
- return store.handle.prepare(`${projection} SELECT repo,${TOTALS},SUM(state='queued' AND family_running=0) queued,
438
- SUM(state='done' AND family_running=0 AND updated_at>=$recent) done_recently FROM ranked GROUP BY repo ORDER BY repo`)
445
+ return store.handle.prepare(`${projection} SELECT repo,${TOTALS},SUM(state='queued' AND family_running=0 AND archived=0) queued,
446
+ SUM(state='done' AND family_running=0 AND archived=0 AND updated_at>=$recent) done_recently FROM ranked GROUP BY repo ORDER BY repo`)
439
447
  .all({ ...parameters(now, access, {}, custodyReadings(store)), $recent: new Date(now.getTime() - 86_400_000).toISOString() })
440
448
  .map(row => ({ repo: s(row, 'repo'), totals: counts(row), queued: n(row, 'queued'), doneRecently: n(row, 'done_recently') }));
441
449
  }
@@ -447,7 +455,7 @@ export function workStateRows(store, now, access) {
447
455
  COALESCE((SELECT created_at FROM decision WHERE id=question_id),''),COALESCE((SELECT asked_at FROM agent_question WHERE id=agent_ask),''),
448
456
  COALESCE((SELECT held_at FROM hold WHERE id=hold_id),''),COALESCE((SELECT requested_at FROM run_stop WHERE run=stop_run),'')),''),sort_at) ask_at,
449
457
  COALESCE(live_run,(SELECT run FROM decision WHERE id=question_id),(SELECT run FROM agent_question WHERE id=agent_ask)) ask_run,
450
- (SELECT json_group_array(m.ref_id) FROM members m WHERE m.root_ref=ranked.root_ref) members FROM ranked`)
458
+ (SELECT json_group_array(m.ref_id) FROM members m WHERE m.root_ref=ranked.root_ref) members FROM ranked WHERE archived=0`)
451
459
  .all(parameters(now, access, {}, custodyReadings(store)))
452
460
  .map(row => ({ repo: s(row, 'repo'), rootId: String(row['root_id']), code: String(row['code']), needs: n(row, 'needs') === 1, personNeed: n(row, 'person_need') === 1,
453
461
  leadClaim: n(row, 'lead_claim') === 1, liveRun: row['live_run'] == null ? null : n(row, 'live_run'), unfinished: row['unfinished'] == null ? null : n(row, 'unfinished'),
@@ -458,8 +466,8 @@ export function workStateRows(store, now, access) {
458
466
  * attempts are aggregated in SQLite, never loaded as an unbounded history. */
459
467
  export function workOutcomeRows(store, now, access, project, since, limit) {
460
468
  return store.handle.prepare(`${registerValidators(store)}, selected AS MATERIALIZED (
461
- SELECT * FROM ranked WHERE code='ready-to-check' OR code='history-problem'
462
- OR (code='complete' AND checked_at >= $since AND checked_at <= $now)
469
+ SELECT * FROM ranked WHERE archived=0 AND (code='ready-to-check' OR code='history-problem'
470
+ OR (code='complete' AND checked_at >= $since AND checked_at <= $now))
463
471
  ORDER BY sort_at DESC,root_ref DESC LIMIT $outcomeLimit
464
472
  ), amounts AS (
465
473
  -- reported-usage.ts runAmountOf, row for row: only evidenced billing on a run whose cost was reported is known;
@@ -701,12 +709,14 @@ function itemOf(row, principal, probe, leadName, readiness, readable = false) {
701
709
  if (asked !== null)
702
710
  detail = `The agent asks: ${asked}`;
703
711
  }
704
- const views = ['all'];
705
- if (need)
712
+ const archived = n(row, 'archived') === 1 ? { by: String(row['archived_by'] ?? ''), byKind: row['archived_kind'] === 'tidy' ? 'tidy' : 'person',
713
+ reason: String(row['archived_reason'] ?? ''), at: String(row['archived_at'] ?? '') } : null;
714
+ const views = archived !== null ? ['archived'] : ['all'];
715
+ if (archived === null && need)
706
716
  views.push('needs-you');
707
- if (running)
717
+ if (archived === null && running)
708
718
  views.push('running');
709
- if (code === 'complete')
719
+ if (archived === null && code === 'complete')
710
720
  views.push('completed');
711
721
  return { rootId: String(row['root_id']), activeTaskId: id, title: String(row['root_title']), repo: s(row, 'repo'),
712
722
  state: String(row['state']), assignmentState, createdAt: String(row['created_at']), updatedAt: String(row['sort_at']),
@@ -718,5 +728,17 @@ function itemOf(row, principal, probe, leadName, readiness, readable = false) {
718
728
  ask: need ? ASKS[n(row, 'ask_rank')] ?? 'unblock' : null,
719
729
  chip: need ? askChipOf({ headline: shared.headline, need: shared.need?.key ?? null, planChanged, mismatch: verdict === 'refuted' && n(row, 'proof_failed_check') !== 1, ask: ASKS[n(row, 'ask_rank')] ?? 'unblock' }) : null,
720
730
  primaryAction, completion: code === 'complete' ? { actor: String(row['checked_actor']), at: String(row['checked_at']), digest: String(row['checked_digest']), ...(landed === null ? {} : { landed }) } : null,
721
- evidence: 'recorded', ...(lead === 'on-it' ? { lead: leadOnIt(leadName) } : {}) };
731
+ evidence: 'recorded', ...(lead === 'on-it' ? { lead: leadOnIt(leadName) } : {}), ...(archived === null ? {} : { archived }) };
732
+ }
733
+ export function workTidyRows(store, now, access) {
734
+ const projection = registerValidators(store);
735
+ return store.handle.prepare(`${projection} SELECT root_id,id,root_title,repo,state,code,archived,broken,family_updated,
736
+ (family_running OR live_lease IS NOT NULL OR unfinished IS NOT NULL OR custody_unresolved OR stop_run IS NOT NULL AND stop_settled IS NULL OR state='running') live,
737
+ (SELECT json_group_array(m.ref_id) FROM members m WHERE m.root_ref=ranked.root_ref) members,
738
+ (SELECT json_group_array(m.id) FROM members m WHERE m.root_ref=ranked.root_ref) member_ids FROM ranked`)
739
+ .all(parameters(now, access, {}, custodyReadings(store)))
740
+ .map(row => ({ rootId: String(row['root_id']), currentId: String(row['id']), title: String(row['root_title']), repo: s(row, 'repo'),
741
+ state: String(row['state']), code: String(row['code']), live: n(row, 'live') === 1, archived: n(row, 'archived') === 1, broken: n(row, 'broken') === 1,
742
+ updatedAt: String(row['family_updated'] ?? ''), members: JSON.parse(String(row['members'] ?? '[]')).map(Number),
743
+ memberIds: JSON.parse(String(row['member_ids'] ?? '[]')).map(String) }));
722
744
  }
@@ -26,7 +26,7 @@ import { type ChecksFact } from "./task-status.js";
26
26
  import { type Html } from "./html.js";
27
27
  /** The Work destination's views — shortcuts over the same rows, never a
28
28
  * persisted state. All is the default. */
29
- export type WorkView = "all" | "needs-you" | "running" | "completed";
29
+ export type WorkView = "all" | "needs-you" | "running" | "completed" | "archived";
30
30
  export declare const WORK_VIEWS: readonly {
31
31
  key: WorkView;
32
32
  label: string;
@@ -27,9 +27,10 @@ export const WORK_VIEWS = [
27
27
  { key: "needs-you", label: "Needs you", hint: "Tasks waiting on an answer, approval, or your inspection.", empty: "Nothing needs you right now. Queued and running work continues on its own." },
28
28
  { key: "running", label: "Building", hint: "Attempts a builder owns right now.", empty: "Nothing is building right now. Approved tasks start when a builder with capacity is connected." },
29
29
  { key: "completed", label: "Complete", hint: "Tasks the lead or user has marked complete. Recorded check results remain available.", empty: "No tasks have been marked complete in this view." },
30
+ { key: "archived", label: "Archived", hint: "Tasks put away by you or by tidy. Their history and results are kept.", empty: "Nothing is put away." },
30
31
  ];
31
32
  export function parseWorkView(raw) {
32
- return raw === "needs-you" || raw === "running" || raw === "completed" ? raw : "all";
33
+ return raw === "needs-you" || raw === "running" || raw === "completed" || raw === "archived" ? raw : "all";
33
34
  }
34
35
  /** The review facts from the store's bounded retry projection — the same
35
36
  * reading `diagnoseTaskDispatch` makes, so every surface starts from one
@@ -525,7 +526,7 @@ function workStatusWordsOf(facts, resultDisplay) {
525
526
  }
526
527
  /** The counts each view tab wears — from the same rows the page lists. */
527
528
  export function workCounts(rows) {
528
- const counts = { all: 0, "needs-you": 0, running: 0, completed: 0 };
529
+ const counts = { all: 0, "needs-you": 0, running: 0, completed: 0, archived: 0 };
529
530
  for (const row of rows)
530
531
  for (const view of row.views)
531
532
  counts[view] += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.85",
3
+ "version": "0.9.86",
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"