@agentdeck/bridge 1.1.0 → 1.2.1

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 (199) hide show
  1. package/README.md +14 -6
  2. package/assets/fonts/NOTICE-PocketSansWorld.md +10 -0
  3. package/assets/fonts/OFL-PocketSansWorld.txt +93 -0
  4. package/assets/fonts/PocketSansWorld_12.cpfont +0 -0
  5. package/assets/fonts/pocket-sans-world.manifest.json +49 -0
  6. package/assets/learning/LICENSE-CC-BY-SA-4.0.txt +10 -0
  7. package/assets/learning/NOTICE.md +33 -0
  8. package/assets/learning/jp-n3-ko.manifest.json +27 -0
  9. package/assets/learning/jp-n3-ko.pdl +0 -0
  10. package/dist/agent-cli-diagnostics.d.ts +20 -0
  11. package/dist/agent-cli-diagnostics.d.ts.map +1 -0
  12. package/dist/agent-cli-diagnostics.js +100 -0
  13. package/dist/agent-cli-diagnostics.js.map +1 -0
  14. package/dist/apme/classifier.d.ts.map +1 -1
  15. package/dist/apme/classifier.js +18 -10
  16. package/dist/apme/classifier.js.map +1 -1
  17. package/dist/apme/claude-transcript-reader.d.ts +36 -0
  18. package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
  19. package/dist/apme/claude-transcript-reader.js +78 -2
  20. package/dist/apme/claude-transcript-reader.js.map +1 -1
  21. package/dist/apme/collector.d.ts +126 -3
  22. package/dist/apme/collector.d.ts.map +1 -1
  23. package/dist/apme/collector.js +450 -41
  24. package/dist/apme/collector.js.map +1 -1
  25. package/dist/apme/dashboard-html.d.ts.map +1 -1
  26. package/dist/apme/dashboard-html.js +278 -36
  27. package/dist/apme/dashboard-html.js.map +1 -1
  28. package/dist/apme/graph.d.ts +1 -1
  29. package/dist/apme/graph.d.ts.map +1 -1
  30. package/dist/apme/graph.js +51 -1
  31. package/dist/apme/graph.js.map +1 -1
  32. package/dist/apme/http.d.ts.map +1 -1
  33. package/dist/apme/http.js +37 -5
  34. package/dist/apme/http.js.map +1 -1
  35. package/dist/apme/index.d.ts +4 -0
  36. package/dist/apme/index.d.ts.map +1 -1
  37. package/dist/apme/index.js +19 -3
  38. package/dist/apme/index.js.map +1 -1
  39. package/dist/apme/runner.d.ts +90 -0
  40. package/dist/apme/runner.d.ts.map +1 -1
  41. package/dist/apme/runner.js +524 -48
  42. package/dist/apme/runner.js.map +1 -1
  43. package/dist/apme/sample-to-timeline.d.ts.map +1 -1
  44. package/dist/apme/sample-to-timeline.js +5 -0
  45. package/dist/apme/sample-to-timeline.js.map +1 -1
  46. package/dist/apme/settings.d.ts +8 -0
  47. package/dist/apme/settings.d.ts.map +1 -1
  48. package/dist/apme/settings.js +4 -0
  49. package/dist/apme/settings.js.map +1 -1
  50. package/dist/apme/store.d.ts +118 -9
  51. package/dist/apme/store.d.ts.map +1 -1
  52. package/dist/apme/store.js +342 -44
  53. package/dist/apme/store.js.map +1 -1
  54. package/dist/apme/task-gradeability.d.ts +82 -0
  55. package/dist/apme/task-gradeability.d.ts.map +1 -0
  56. package/dist/apme/task-gradeability.js +126 -0
  57. package/dist/apme/task-gradeability.js.map +1 -0
  58. package/dist/awaiting-overlay.d.ts +13 -26
  59. package/dist/awaiting-overlay.d.ts.map +1 -1
  60. package/dist/awaiting-overlay.js +7 -40
  61. package/dist/awaiting-overlay.js.map +1 -1
  62. package/dist/bridge-core.d.ts +15 -0
  63. package/dist/bridge-core.d.ts.map +1 -1
  64. package/dist/bridge-core.js +30 -4
  65. package/dist/bridge-core.js.map +1 -1
  66. package/dist/claude-permission-rules.d.ts +20 -32
  67. package/dist/claude-permission-rules.d.ts.map +1 -1
  68. package/dist/claude-permission-rules.js +19 -102
  69. package/dist/claude-permission-rules.js.map +1 -1
  70. package/dist/claude-usage-recovery.d.ts +54 -0
  71. package/dist/claude-usage-recovery.d.ts.map +1 -0
  72. package/dist/claude-usage-recovery.js +200 -0
  73. package/dist/claude-usage-recovery.js.map +1 -0
  74. package/dist/cli.d.ts +11 -0
  75. package/dist/cli.d.ts.map +1 -1
  76. package/dist/cli.js +81 -14
  77. package/dist/cli.js.map +1 -1
  78. package/dist/codex-rate-limits-live.d.ts +180 -10
  79. package/dist/codex-rate-limits-live.d.ts.map +1 -1
  80. package/dist/codex-rate-limits-live.js +475 -22
  81. package/dist/codex-rate-limits-live.js.map +1 -1
  82. package/dist/codex-rollout-response.d.ts +43 -0
  83. package/dist/codex-rollout-response.d.ts.map +1 -1
  84. package/dist/codex-rollout-response.js +67 -0
  85. package/dist/codex-rollout-response.js.map +1 -1
  86. package/dist/coordination-evidence.d.ts +150 -0
  87. package/dist/coordination-evidence.d.ts.map +1 -0
  88. package/dist/coordination-evidence.js +455 -0
  89. package/dist/coordination-evidence.js.map +1 -0
  90. package/dist/daemon-server.d.ts.map +1 -1
  91. package/dist/daemon-server.js +592 -35
  92. package/dist/daemon-server.js.map +1 -1
  93. package/dist/daemon-ws-client.d.ts +1 -0
  94. package/dist/daemon-ws-client.d.ts.map +1 -1
  95. package/dist/daemon-ws-client.js +25 -16
  96. package/dist/daemon-ws-client.js.map +1 -1
  97. package/dist/daemon.js +1 -1
  98. package/dist/deferred-reply-read.d.ts +24 -0
  99. package/dist/deferred-reply-read.d.ts.map +1 -0
  100. package/dist/deferred-reply-read.js +42 -0
  101. package/dist/deferred-reply-read.js.map +1 -0
  102. package/dist/esp32-serial.d.ts +15 -0
  103. package/dist/esp32-serial.d.ts.map +1 -1
  104. package/dist/esp32-serial.js +61 -2
  105. package/dist/esp32-serial.js.map +1 -1
  106. package/dist/font-pack.d.ts +15 -0
  107. package/dist/font-pack.d.ts.map +1 -0
  108. package/dist/font-pack.js +75 -0
  109. package/dist/font-pack.js.map +1 -0
  110. package/dist/glance-frame.d.ts.map +1 -1
  111. package/dist/glance-frame.js +2 -0
  112. package/dist/glance-frame.js.map +1 -1
  113. package/dist/hook-codex-sessions.d.ts +9 -0
  114. package/dist/hook-codex-sessions.d.ts.map +1 -1
  115. package/dist/hook-codex-sessions.js +35 -2
  116. package/dist/hook-codex-sessions.js.map +1 -1
  117. package/dist/hook-opencode-sessions.d.ts +62 -0
  118. package/dist/hook-opencode-sessions.d.ts.map +1 -0
  119. package/dist/hook-opencode-sessions.js +180 -0
  120. package/dist/hook-opencode-sessions.js.map +1 -0
  121. package/dist/hook-response-source.d.ts +40 -0
  122. package/dist/hook-response-source.d.ts.map +1 -0
  123. package/dist/hook-response-source.js +31 -0
  124. package/dist/hook-response-source.js.map +1 -0
  125. package/dist/http-auth-gate.d.ts.map +1 -1
  126. package/dist/http-auth-gate.js +9 -1
  127. package/dist/http-auth-gate.js.map +1 -1
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +6 -3
  130. package/dist/index.js.map +1 -1
  131. package/dist/learning-pack.d.ts +24 -0
  132. package/dist/learning-pack.d.ts.map +1 -0
  133. package/dist/learning-pack.js +125 -0
  134. package/dist/learning-pack.js.map +1 -0
  135. package/dist/mdns.js +1 -1
  136. package/dist/mdns.js.map +1 -1
  137. package/dist/native-dependency-diagnostics.d.ts +29 -0
  138. package/dist/native-dependency-diagnostics.d.ts.map +1 -0
  139. package/dist/native-dependency-diagnostics.js +95 -0
  140. package/dist/native-dependency-diagnostics.js.map +1 -0
  141. package/dist/observed-steering.d.ts +4 -4
  142. package/dist/observed-steering.d.ts.map +1 -1
  143. package/dist/observed-steering.js +28 -32
  144. package/dist/observed-steering.js.map +1 -1
  145. package/dist/openclaw-gateway-stability.d.ts +94 -0
  146. package/dist/openclaw-gateway-stability.d.ts.map +1 -0
  147. package/dist/openclaw-gateway-stability.js +176 -0
  148. package/dist/openclaw-gateway-stability.js.map +1 -0
  149. package/dist/pairing-knocks.d.ts +80 -0
  150. package/dist/pairing-knocks.d.ts.map +1 -0
  151. package/dist/pairing-knocks.js +312 -0
  152. package/dist/pairing-knocks.js.map +1 -0
  153. package/dist/passive-observer.d.ts +5 -0
  154. package/dist/passive-observer.d.ts.map +1 -1
  155. package/dist/passive-observer.js +6 -0
  156. package/dist/passive-observer.js.map +1 -1
  157. package/dist/pixoo/micro-glyphs.d.ts +4 -0
  158. package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
  159. package/dist/pixoo/micro-glyphs.js +29 -0
  160. package/dist/pixoo/micro-glyphs.js.map +1 -1
  161. package/dist/pixoo/pixoo-font.d.ts.map +1 -1
  162. package/dist/pixoo/pixoo-font.js +3 -1
  163. package/dist/pixoo/pixoo-font.js.map +1 -1
  164. package/dist/pixoo/pixoo-renderer.d.ts +1 -1
  165. package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
  166. package/dist/pixoo/pixoo-renderer.js +17 -2
  167. package/dist/pixoo/pixoo-renderer.js.map +1 -1
  168. package/dist/pty-manager.d.ts +9 -0
  169. package/dist/pty-manager.d.ts.map +1 -1
  170. package/dist/pty-manager.js +63 -4
  171. package/dist/pty-manager.js.map +1 -1
  172. package/dist/relayed-usage.d.ts +30 -0
  173. package/dist/relayed-usage.d.ts.map +1 -1
  174. package/dist/relayed-usage.js +25 -2
  175. package/dist/relayed-usage.js.map +1 -1
  176. package/dist/review-runner.js +1 -1
  177. package/dist/subagent-timeline.d.ts +27 -0
  178. package/dist/subagent-timeline.d.ts.map +1 -1
  179. package/dist/subagent-timeline.js +52 -5
  180. package/dist/subagent-timeline.js.map +1 -1
  181. package/dist/surface-protocol.d.ts.map +1 -1
  182. package/dist/surface-protocol.js +2 -0
  183. package/dist/surface-protocol.js.map +1 -1
  184. package/dist/tui/dashboard.d.ts.map +1 -1
  185. package/dist/tui/dashboard.js +8 -1
  186. package/dist/tui/dashboard.js.map +1 -1
  187. package/dist/usage-api.d.ts +3 -0
  188. package/dist/usage-api.d.ts.map +1 -1
  189. package/dist/usage-api.js +101 -36
  190. package/dist/usage-api.js.map +1 -1
  191. package/dist/usage-event.d.ts.map +1 -1
  192. package/dist/usage-event.js +2 -1
  193. package/dist/usage-event.js.map +1 -1
  194. package/dist/ws-server.d.ts +18 -0
  195. package/dist/ws-server.d.ts.map +1 -1
  196. package/dist/ws-server.js +40 -2
  197. package/dist/ws-server.js.map +1 -1
  198. package/package.json +6 -5
  199. package/src/timebox/sync_ble.py +35 -16
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/apme/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/apme/store.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,7 @@ import { debug } from '../logger.js';
15
15
  // workspace symlinks, regardless of the process CWD (vitest runs from the
16
16
  // repo root, where the symlink doesn't exist).
17
17
  const require = createRequire(import.meta.url);
18
+ import { TASK_ATTENTION_WINDOW_MS, TASK_ATTENTION_RED_SCORE } from '@agentdeck/shared';
18
19
  // ─── Schema ────────────────────────────────────────────────────────────────────
19
20
  const SCORECARD_DDL = `
20
21
  -- Pre-aggregate metrics at the level they actually describe. New rows prefer
@@ -313,6 +314,7 @@ CREATE TABLE IF NOT EXISTS vibe_feedback (
313
314
  CREATE INDEX IF NOT EXISTS idx_runs_model ON runs(model_id);
314
315
  CREATE INDEX IF NOT EXISTS idx_runs_agent ON runs(agent_type);
315
316
  CREATE INDEX IF NOT EXISTS idx_runs_started ON runs(started_at);
317
+ CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session_id);
316
318
  CREATE INDEX IF NOT EXISTS idx_evals_run ON evals(run_id);
317
319
  CREATE INDEX IF NOT EXISTS idx_steps_run ON steps(run_id);
318
320
 
@@ -530,6 +532,81 @@ Examples of well-calibrated overall scores:
530
532
  notes: 'task-unit rollup (TodoWrite all-completed / /clear / session_end)',
531
533
  },
532
534
  };
535
+ /** The latest overall judge score for a task, else its composite. Inlined into
536
+ * several `TASK_VIEW_SQL` buckets so "judged" means one thing everywhere. */
537
+ const TASK_SCORE_SQL = `COALESCE((SELECT e.score FROM evals e WHERE e.task_id = t.id AND e.metric = 'overall'
538
+ ORDER BY e.created_at DESC LIMIT 1), t.composite_score)`;
539
+ /** The recency window and red-score band live in shared/src/eval-schema.ts
540
+ * (TASK_ATTENTION_WINDOW_MS / TASK_ATTENTION_RED_SCORE): both daemons build
541
+ * the same attention SQL, so the numbers are cross-daemon contracts, not
542
+ * store-local tuning knobs. Re-exported for existing importers. */
543
+ export { TASK_ATTENTION_WINDOW_MS } from '@agentdeck/shared';
544
+ /** The attention bucket, anchored at a caller-supplied cutoff (epoch ms) so
545
+ * the filter, the row flag and the badge are computed against the SAME
546
+ * instant within one request. Needs eyes = recent AND (reaper-closed, a
547
+ * closed task with an unarchived reply the judge can only partly see, or a
548
+ * judged score below the dashboard's red band). `cutoffMs` is always a
549
+ * number we computed — never user input — so inlining it is safe. */
550
+ function taskAttentionSql(cutoffMs) {
551
+ // IFNULL is load-bearing: a NULL score makes `score < 0.4` NULL, NULL OR
552
+ // false is NULL, and SQLite sorts NULL LAST under DESC — so without it,
553
+ // recent unjudged rows sank BELOW week-old rows (which get a determinate 0
554
+ // from the cutoff test) on the attention-first ordering.
555
+ return `IFNULL((t.started_at >= ${Math.floor(cutoffMs)}
556
+ AND (t.boundary_signal = 'orphaned'
557
+ OR (t.ended_at IS NOT NULL AND EXISTS (
558
+ SELECT 1 FROM turns tu WHERE tu.task_id = t.id AND tu.response IS NULL))
559
+ OR ${TASK_SCORE_SQL} < ${TASK_ATTENTION_RED_SCORE})), 0)`;
560
+ }
561
+ /** The NARROWING filters `listTaskPage` and `taskViewCounts` share — one
562
+ * builder so a filtered board's badges and its rows read the same WHERE. */
563
+ function buildTaskFilterWhere(opts) {
564
+ const where = [];
565
+ const args = [];
566
+ // `_empty` runs are bookkeeping shells, never work the user did.
567
+ where.push("COALESCE(r.task_category, '') != '_empty'");
568
+ if (opts.agentType) {
569
+ where.push('r.agent_type = ?');
570
+ args.push(opts.agentType);
571
+ }
572
+ if (opts.sessionId) {
573
+ where.push('r.session_id = ?');
574
+ args.push(opts.sessionId);
575
+ }
576
+ if (opts.projectName) {
577
+ where.push('r.project_name = ?');
578
+ args.push(opts.projectName);
579
+ }
580
+ if (opts.category) {
581
+ where.push('COALESCE(t.task_category, r.task_category) = ?');
582
+ args.push(opts.category);
583
+ }
584
+ if (opts.outcome) {
585
+ where.push('t.outcome = ?');
586
+ args.push(opts.outcome);
587
+ }
588
+ if (opts.q) {
589
+ where.push('(t.summary LIKE ? OR r.task_prompt LIKE ?)');
590
+ const like = `%${opts.q}%`;
591
+ args.push(like, like);
592
+ }
593
+ return { where, args };
594
+ }
595
+ /** ONE SQL definition per Work-board lifecycle bucket — the filter, the row's
596
+ * `attention` flag and the tab badges all read these same expressions, so a
597
+ * badge count can never disagree with the rows its tab lists.
598
+ *
599
+ * attention — see `taskAttentionSql` (recency-windowed, hence a function).
600
+ * inprogress — still accumulating turns.
601
+ * judged — carries an overall judge score or a composite.
602
+ * reported — closed but never scored (no judge configured / skipped).
603
+ * orphaned — reaper-closed; the un-windowed ledger of segmentation debt. */
604
+ const TASK_VIEW_SQL = {
605
+ inprogress: 't.ended_at IS NULL',
606
+ judged: `${TASK_SCORE_SQL} IS NOT NULL`,
607
+ reported: `(t.ended_at IS NOT NULL AND ${TASK_SCORE_SQL} IS NULL)`,
608
+ orphaned: `t.boundary_signal = 'orphaned'`,
609
+ };
533
610
  export class ApmeStore {
534
611
  db = null;
535
612
  enabled = false;
@@ -554,7 +631,8 @@ export class ApmeStore {
554
631
  Ctor = require('better-sqlite3');
555
632
  }
556
633
  catch (err) {
557
- this.lastInitError = `better-sqlite3 native binding unavailable (${String(err).slice(0, 200)}). Run \`pnpm install\` in bridge/.`;
634
+ this.lastInitError =
635
+ `better-sqlite3 native binding unavailable (${String(err).slice(0, 200)})`;
558
636
  debug('APME', this.lastInitError);
559
637
  return false;
560
638
  }
@@ -929,6 +1007,17 @@ export class ApmeStore {
929
1007
  vals.push(id);
930
1008
  this.db.prepare(`UPDATE turns SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
931
1009
  }
1010
+ /** The newest closed turn of any run that `sessionId` ever owned, provided
1011
+ * it closed at or after `endedSinceMs`. The collector's fallback for a
1012
+ * reply that arrives after the run's in-memory edges are gone. */
1013
+ latestClosedTurnIdForSession(sessionId, endedSinceMs) {
1014
+ if (!this.db)
1015
+ return null;
1016
+ const row = this.db.prepare(`SELECT t.id AS id FROM turns t JOIN runs r ON r.id = t.run_id
1017
+ WHERE r.session_id = ? AND t.ended_at IS NOT NULL AND t.ended_at >= ?
1018
+ ORDER BY t.ended_at DESC LIMIT 1`).get(sessionId, endedSinceMs);
1019
+ return row?.id ?? null;
1020
+ }
932
1021
  getTurn(id) {
933
1022
  if (!this.db)
934
1023
  return null;
@@ -974,7 +1063,8 @@ export class ApmeStore {
974
1063
  SUM(t.end_source = 'interrupted') AS interrupted,
975
1064
  SUM(t.end_source = 'aborted') AS aborted,
976
1065
  SUM(t.end_source = 'superseded') AS superseded,
977
- SUM(t.end_source IN ('session_end','run_close','clear')) AS sessionEnd,
1066
+ SUM(t.end_source IN ('session_end','clear')) AS sessionEnd,
1067
+ SUM(t.end_source = 'run_close') AS runClose,
978
1068
  SUM(t.ended_at IS NULL) AS open,
979
1069
  SUM(t.ended_at IS NOT NULL AND t.end_source IS NULL) AS preInstrument
980
1070
  FROM turns t JOIN runs r ON r.id = t.run_id
@@ -991,6 +1081,7 @@ export class ApmeStore {
991
1081
  aborted: Number(r.aborted ?? 0),
992
1082
  superseded: Number(r.superseded ?? 0),
993
1083
  sessionEnd: Number(r.sessionEnd ?? 0),
1084
+ runClose: Number(r.runClose ?? 0),
994
1085
  open: Number(r.open ?? 0),
995
1086
  preInstrument: Number(r.preInstrument ?? 0),
996
1087
  }));
@@ -1081,36 +1172,18 @@ export class ApmeStore {
1081
1172
  return { total: 0, tasks: [] };
1082
1173
  const limit = Math.min(Math.max(opts.limit ?? 50, 1), 500);
1083
1174
  const offset = Math.max(opts.offset ?? 0, 0);
1084
- const where = [];
1085
- const args = [];
1086
- // `_empty` runs are bookkeeping shells, never work the user did.
1087
- where.push("COALESCE(r.task_category, '') != '_empty'");
1088
- if (opts.agentType) {
1089
- where.push('r.agent_type = ?');
1090
- args.push(opts.agentType);
1091
- }
1092
- if (opts.projectName) {
1093
- where.push('r.project_name = ?');
1094
- args.push(opts.projectName);
1095
- }
1096
- if (opts.category) {
1097
- where.push('COALESCE(t.task_category, r.task_category) = ?');
1098
- args.push(opts.category);
1099
- }
1100
- if (opts.outcome) {
1101
- where.push('t.outcome = ?');
1102
- args.push(opts.outcome);
1103
- }
1175
+ const { where, args } = buildTaskFilterWhere(opts);
1176
+ const attentionSql = taskAttentionSql(Date.now() - TASK_ATTENTION_WINDOW_MS);
1177
+ if (opts.view)
1178
+ where.push(opts.view === 'attention' ? attentionSql : TASK_VIEW_SQL[opts.view]);
1104
1179
  if (opts.state === 'closed')
1105
1180
  where.push('t.ended_at IS NOT NULL');
1106
1181
  if (opts.state === 'open')
1107
1182
  where.push('t.ended_at IS NULL');
1108
- if (opts.q) {
1109
- where.push('(t.summary LIKE ? OR r.task_prompt LIKE ?)');
1110
- const like = `%${opts.q}%`;
1111
- args.push(like, like);
1112
- }
1113
1183
  const whereSql = `WHERE ${where.join(' AND ')}`;
1184
+ const orderSql = opts.order === 'attention'
1185
+ ? 'ORDER BY attention DESC, t.started_at DESC'
1186
+ : 'ORDER BY t.started_at DESC';
1114
1187
  const total = this.db.prepare(`SELECT COUNT(*) AS n FROM tasks t JOIN runs r ON r.id = t.run_id ${whereSql}`).get(...args).n;
1115
1188
  const rows = this.db.prepare(`SELECT t.*,
1116
1189
  r.session_id, r.agent_type, r.model_id AS run_model_id, r.project_name,
@@ -1119,15 +1192,78 @@ export class ApmeStore {
1119
1192
  (SELECT COUNT(*) FROM turns tu WHERE tu.task_id = t.id AND tu.response IS NOT NULL) AS answered_turns,
1120
1193
  (SELECT COUNT(*) FROM sample_events se WHERE se.task_id = t.id) AS event_count,
1121
1194
  (SELECT COUNT(*) FROM sample_events se WHERE se.task_id = t.id AND se.kind = 'tool') AS tool_count,
1195
+ (SELECT COALESCE(SUM(COALESCE(tu.files_modified, 0) + COALESCE(tu.files_created, 0)), 0)
1196
+ FROM turns tu WHERE tu.task_id = t.id) AS files_touched,
1122
1197
  (SELECT tu.prompt FROM turns tu WHERE tu.task_id = t.id ORDER BY tu.turn_index ASC LIMIT 1) AS first_prompt,
1123
1198
  (SELECT e.score FROM evals e WHERE e.task_id = t.id AND e.metric = 'overall' ORDER BY e.created_at DESC LIMIT 1) AS overall_score,
1124
- (SELECT COUNT(*) FROM evals e WHERE e.task_id = t.id) AS eval_count
1199
+ (SELECT COUNT(*) FROM evals e WHERE e.task_id = t.id) AS eval_count,
1200
+ (${attentionSql}) AS attention
1125
1201
  FROM tasks t JOIN runs r ON r.id = t.run_id
1126
1202
  ${whereSql}
1127
- ORDER BY t.started_at DESC
1203
+ ${orderSql}
1128
1204
  LIMIT ? OFFSET ?`).all(...args, limit, offset);
1129
1205
  return { total, tasks: rows.map(rowToTaskListRow) };
1130
1206
  }
1207
+ /** 10 s TTL cache for `taskViewCounts`, keyed by the filter tuple. The Work
1208
+ * board polls every 15 s and refetches on every interaction; the badges
1209
+ * are a full-table aggregate with correlated eval subqueries, so paying it
1210
+ * at most once per TTL bounds the cost of leaving the dashboard open. */
1211
+ viewCountsCache = new Map();
1212
+ /** Per-lifecycle-bucket totals for the Work board's tab badges. Takes the
1213
+ * same NARROWING filters as `listTaskPage` (agent/project/category/
1214
+ * outcome/q) so a filtered board's badges count what its tabs would list.
1215
+ * Buckets and filters read the same SQL definitions; the TTL means a badge
1216
+ * may lag its rows by up to 10 s after a task moves buckets — a bounded
1217
+ * staleness, not a different definition. */
1218
+ taskViewCounts(filters = {}) {
1219
+ const empty = { all: 0, attention: 0, inprogress: 0, judged: 0, reported: 0, orphaned: 0 };
1220
+ if (!this.db)
1221
+ return { ...empty };
1222
+ const key = JSON.stringify([filters.agentType, filters.sessionId, filters.projectName, filters.category, filters.outcome, filters.q]);
1223
+ const cached = this.viewCountsCache.get(key);
1224
+ if (cached && Date.now() - cached.at < 10_000)
1225
+ return cached.value;
1226
+ const attentionSql = taskAttentionSql(Date.now() - TASK_ATTENTION_WINDOW_MS);
1227
+ const { where, args } = buildTaskFilterWhere(filters);
1228
+ const row = this.db.prepare(`SELECT COUNT(*) AS all_n,
1229
+ SUM(CASE WHEN ${attentionSql} THEN 1 ELSE 0 END) AS attention_n,
1230
+ SUM(CASE WHEN ${TASK_VIEW_SQL.inprogress} THEN 1 ELSE 0 END) AS inprogress_n,
1231
+ SUM(CASE WHEN ${TASK_VIEW_SQL.judged} THEN 1 ELSE 0 END) AS judged_n,
1232
+ SUM(CASE WHEN ${TASK_VIEW_SQL.reported} THEN 1 ELSE 0 END) AS reported_n,
1233
+ SUM(CASE WHEN ${TASK_VIEW_SQL.orphaned} THEN 1 ELSE 0 END) AS orphaned_n
1234
+ FROM tasks t JOIN runs r ON r.id = t.run_id
1235
+ WHERE ${where.join(' AND ')}`).get(...args);
1236
+ const value = {
1237
+ all: row.all_n ?? 0,
1238
+ attention: row.attention_n ?? 0,
1239
+ inprogress: row.inprogress_n ?? 0,
1240
+ judged: row.judged_n ?? 0,
1241
+ reported: row.reported_n ?? 0,
1242
+ orphaned: row.orphaned_n ?? 0,
1243
+ };
1244
+ if (this.viewCountsCache.size > 20)
1245
+ this.viewCountsCache.clear();
1246
+ this.viewCountsCache.set(key, { at: Date.now(), value });
1247
+ return value;
1248
+ }
1249
+ /** Per-tool call counts for a page of tasks, in one grouped query. Feeds the
1250
+ * shared `foldActionCounts` projection — the store returns raw counts, the
1251
+ * HTTP layer folds. */
1252
+ toolCountsForTasks(taskIds) {
1253
+ const out = new Map();
1254
+ if (!this.db || taskIds.length === 0)
1255
+ return out;
1256
+ const placeholders = taskIds.map(() => '?').join(',');
1257
+ const rows = this.db.prepare(`SELECT task_id, tool_name, COUNT(*) AS n FROM sample_events
1258
+ WHERE kind = 'tool' AND tool_name IS NOT NULL AND task_id IN (${placeholders})
1259
+ GROUP BY task_id, tool_name`).all(...taskIds);
1260
+ for (const r of rows) {
1261
+ const list = out.get(r.task_id) ?? [];
1262
+ list.push({ name: r.tool_name, count: r.n });
1263
+ out.set(r.task_id, list);
1264
+ }
1265
+ return out;
1266
+ }
1131
1267
  /** Distinct values behind the task list's filters, so the UI offers what the
1132
1268
  * data actually contains rather than a hardcoded menu. */
1133
1269
  taskFacets() {
@@ -1147,15 +1283,24 @@ export class ApmeStore {
1147
1283
  return [];
1148
1284
  return this.db.prepare('SELECT * FROM turns WHERE task_id = ? ORDER BY turn_index ASC').all(taskId);
1149
1285
  }
1150
- /** Ended tasks (boundary hit) that haven't been judged yet — backfill candidates. */
1151
- listTasksNeedingSummary(limit = 20) {
1286
+ /** Ended tasks (boundary hit) that haven't been judged yet — the judge
1287
+ * backlog. The daemon's eval tick drains this a few per sweep; before it
1288
+ * did (2026-09-03) nothing called it, so a task whose one judge call was
1289
+ * lost — daemon restarted mid-eval, judge offline, backend probe not yet
1290
+ * ready — stayed unjudged for good: 1,060 closed tasks sat in `reported`.
1291
+ * `sinceMs` bounds how far back the drain reaches. */
1292
+ listTasksNeedingSummary(limit = 20, sinceMs = 0) {
1152
1293
  if (!this.db)
1153
1294
  return [];
1154
1295
  const rows = this.db.prepare(`SELECT t.id, t.run_id, t.task_category FROM tasks t
1155
1296
  WHERE t.ended_at IS NOT NULL
1297
+ AND t.ended_at >= ?
1156
1298
  AND t.summary IS NULL
1299
+ -- A task the judge already declined (task-gradeability.ts) is not a
1300
+ -- backlog; re-offering it every sweep re-declines it forever.
1301
+ AND (t.notes_json IS NULL OR t.notes_json NOT LIKE '%"notGradeable"%')
1157
1302
  ORDER BY t.ended_at DESC
1158
- LIMIT ?`).all(limit);
1303
+ LIMIT ?`).all(sinceMs, limit);
1159
1304
  return rows.map((r) => ({ id: r.id, runId: r.run_id, taskCategory: r.task_category }));
1160
1305
  }
1161
1306
  insertEvalForTask(row) {
@@ -1363,27 +1508,51 @@ export class ApmeStore {
1363
1508
  LIMIT ?`).all(cutoff, cutoff, limit);
1364
1509
  return rows.map((r) => ({ id: r.id, projectPath: r.project_path, lastActivity: r.last_activity }));
1365
1510
  }
1366
- /** Finalize an abandoned run: close its dangling turns, close its tasks with
1367
- * `boundary_signal='orphaned'`, then close the run itself — all stamped at
1368
- * `endedAt` (the run's last activity), not `now`, so a run abandoned last
1369
- * night doesn't report a 12-hour turn.
1511
+ /** Finalize an abandoned run: close its dangling turns, close its open
1512
+ * tasks, then close the run itself — all stamped at `endedAt` (the run's
1513
+ * last activity), not `now`, so a run abandoned last night doesn't report a
1514
+ * 12-hour turn.
1515
+ *
1516
+ * The task's boundary says what the reaper actually found, not merely that
1517
+ * it was the reaper who found it. A task whose every turn had closed is a
1518
+ * task that went quiet after a clean finish — the definition of the
1519
+ * `idle_gap` boundary (`AGENT_IDLE_GAP_MS`), reached late because the
1520
+ * in-memory timer that would have fired did not survive the process. Only a
1521
+ * task still holding an OPEN turn is `orphaned`: the work was cut off
1522
+ * mid-turn with nothing left to tell how it ended. Stamping both
1523
+ * `orphaned` put 79% of a week's tasks in the "reaper" chip and hid that
1524
+ * the segmentation itself had been right. Tasks are classified BEFORE the
1525
+ * dangling turns are closed, since closing them is what would erase the
1526
+ * distinction.
1370
1527
  *
1371
1528
  * Tasks are backfilled with their real first/last turn index when the
1372
1529
  * in-memory close never ran, since the task rollup reads those columns.
1373
- * Returns the closed task ids so the caller can enqueue task-level evals
1374
- * the whole point of closing them. */
1530
+ * Returns the closed tasks with the boundary each was given so the
1531
+ * caller can enqueue task-level evals, the whole point of closing them. */
1375
1532
  reapAbandonedRun(runId, endedAt) {
1376
1533
  if (!this.db)
1377
1534
  return [];
1378
1535
  const bounds = this.db.prepare('SELECT MIN(turn_index) AS lo, MAX(turn_index) AS hi FROM turns WHERE run_id = ?').get(runId);
1379
- const tasks = this.db.prepare('SELECT id, task_category FROM tasks WHERE run_id = ? AND ended_at IS NULL').all(runId);
1536
+ const tasks = this.db.prepare(`SELECT k.id, k.task_category,
1537
+ EXISTS (SELECT 1 FROM turns t WHERE t.task_id = k.id AND t.ended_at IS NULL) AS has_open_turn
1538
+ FROM tasks k WHERE k.run_id = ? AND k.ended_at IS NULL`).all(runId);
1380
1539
  const tx = this.db.transaction(() => {
1381
- this.db.prepare(`UPDATE turns SET ended_at = ?, end_source = 'run_close'
1382
- WHERE run_id = ? AND ended_at IS NULL`).run(endedAt, runId);
1383
- this.db.prepare(`UPDATE tasks SET ended_at = ?, boundary_signal = 'orphaned',
1540
+ // Tasks first: the open-turn test below is destroyed by the turn UPDATE.
1541
+ this.db.prepare(`UPDATE tasks SET ended_at = ?,
1542
+ boundary_signal = CASE
1543
+ WHEN EXISTS (SELECT 1 FROM turns t WHERE t.task_id = tasks.id AND t.ended_at IS NULL)
1544
+ THEN 'orphaned' ELSE 'idle_gap' END,
1384
1545
  first_turn_index = COALESCE(first_turn_index, ?),
1385
- last_turn_index = COALESCE(last_turn_index, ?)
1546
+ last_turn_index = COALESCE(last_turn_index, ?),
1547
+ -- The in-memory close resolves a category at close time; a reaped
1548
+ -- task never had one, and the judge then fell back to the generic
1549
+ -- rubric (66 of 135 uncategorised tasks in a week sat on runs
1550
+ -- already classified 'ops'). Inherit the run's.
1551
+ task_category = COALESCE(task_category,
1552
+ (SELECT r.task_category FROM runs r WHERE r.id = tasks.run_id AND r.task_category <> 'unknown'))
1386
1553
  WHERE run_id = ? AND ended_at IS NULL`).run(endedAt, bounds?.lo ?? null, bounds?.hi ?? null, runId);
1554
+ this.db.prepare(`UPDATE turns SET ended_at = ?, end_source = 'run_close'
1555
+ WHERE run_id = ? AND ended_at IS NULL`).run(endedAt, runId);
1387
1556
  this.db.prepare('UPDATE runs SET ended_at = ? WHERE id = ? AND ended_at IS NULL').run(endedAt, runId);
1388
1557
  });
1389
1558
  try {
@@ -1393,7 +1562,109 @@ export class ApmeStore {
1393
1562
  debug('APME', `reapAbandonedRun ${runId.slice(0, 8)} failed: ${String(err)}`);
1394
1563
  return [];
1395
1564
  }
1396
- return tasks.map((t) => ({ id: t.id, category: t.task_category }));
1565
+ return tasks.map((t) => ({
1566
+ id: t.id,
1567
+ category: t.task_category ?? this.getTask(t.id)?.taskCategory ?? null,
1568
+ boundarySignal: t.has_open_turn ? 'orphaned' : 'idle_gap',
1569
+ }));
1570
+ }
1571
+ /** Closed tasks that carry a verdict, newest first — the candidates for
1572
+ * `retractTaskVerdict` when what they were scored on turns out not to
1573
+ * have been the agent's work. `sinceMs` bounds the sweep. */
1574
+ listJudgedTasks(sinceMs, limit = 2000) {
1575
+ if (!this.db)
1576
+ return [];
1577
+ return this.db.prepare(`SELECT id FROM tasks
1578
+ WHERE ended_at IS NOT NULL AND ended_at >= ?
1579
+ AND (composite_score IS NOT NULL OR summary IS NOT NULL)
1580
+ AND (notes_json IS NULL OR notes_json NOT LIKE '%"notGradeable"%')
1581
+ ORDER BY ended_at DESC LIMIT ?`).all(sinceMs, limit);
1582
+ }
1583
+ /** Closed tasks the judge declined (`notGradeable` stamped), newest first. */
1584
+ listDeclinedTasks(sinceMs, limit = 2000) {
1585
+ if (!this.db)
1586
+ return [];
1587
+ return this.db.prepare(`SELECT id FROM tasks
1588
+ WHERE ended_at IS NOT NULL AND ended_at >= ?
1589
+ AND notes_json LIKE '%"notGradeable"%'
1590
+ ORDER BY ended_at DESC LIMIT ?`).all(sinceMs, limit);
1591
+ }
1592
+ /** Clear a declined task's stamp so the backlog drain offers it again. */
1593
+ readmitTask(taskId) {
1594
+ if (!this.db)
1595
+ return;
1596
+ this.db.prepare(`UPDATE tasks SET notes_json = NULL WHERE id = ? AND notes_json LIKE '%"notGradeable"%'`).run(taskId);
1597
+ }
1598
+ /** Withdraw a task's verdict: its judge and scorer rows are deleted and the
1599
+ * summary / score / outcome cleared, and the row is stamped with why it
1600
+ * cannot be graded. A verdict reached against silence — or against a
1601
+ * usage-limit notice standing in for the reply — is not a measurement of
1602
+ * the agent, and leaving it in place keeps it ranking real work on the
1603
+ * scorecard and floating to the top of the attention sort. A manually set
1604
+ * outcome (`agentdeck task cancel`) is the user's statement and is kept. */
1605
+ retractTaskVerdict(taskId, reason) {
1606
+ if (!this.db)
1607
+ return;
1608
+ const tx = this.db.transaction(() => {
1609
+ this.db.prepare(`DELETE FROM evals WHERE task_id = ? AND layer IN ('task_judge', 'trajectory')`).run(taskId);
1610
+ this.db.prepare(`UPDATE tasks SET summary = NULL, composite_score = NULL,
1611
+ outcome = CASE WHEN outcome = 'abandoned' THEN outcome ELSE NULL END,
1612
+ notes_json = ?
1613
+ WHERE id = ?`).run(JSON.stringify({ notGradeable: reason, retractedAt: Date.now() }), taskId);
1614
+ });
1615
+ tx();
1616
+ }
1617
+ /** One-time, idempotent re-stamp of tasks the OLD reaper closed as
1618
+ * `orphaned` although every one of their turns had closed normally —
1619
+ * the `idle_gap` boundary reached late, which is what the reaper now
1620
+ * writes. Only rows with EVIDENCE move: every turn carries a known
1621
+ * `end_source`, none is `run_close` (the old reaper's mark on a turn it
1622
+ * cut open) and none is still open. Rows from before `end_source`
1623
+ * existed are left as they are — their closing signal is unknown, and a
1624
+ * guess would corrupt the chip that exists to show how much to trust the
1625
+ * segmentation. Measured 2026-09-03: 165 of 1,026 orphaned tasks qualify,
1626
+ * 861 predate the column. Returns the number of rows changed. */
1627
+ reclassifyReapedTasks() {
1628
+ if (!this.db)
1629
+ return 0;
1630
+ try {
1631
+ const info = this.db.prepare(`UPDATE tasks SET boundary_signal = 'idle_gap'
1632
+ WHERE boundary_signal = 'orphaned'
1633
+ AND ended_at IS NOT NULL
1634
+ AND EXISTS (SELECT 1 FROM turns u WHERE u.task_id = tasks.id)
1635
+ AND NOT EXISTS (SELECT 1 FROM turns u WHERE u.task_id = tasks.id
1636
+ AND (u.ended_at IS NULL OR u.end_source IS NULL OR u.end_source = 'run_close'))`).run();
1637
+ return Number(info.changes ?? 0);
1638
+ }
1639
+ catch (err) {
1640
+ debug('APME', `reclassifyReapedTasks failed: ${String(err)}`);
1641
+ return 0;
1642
+ }
1643
+ }
1644
+ /** Runs still open in the store, oldest first — what a freshly started
1645
+ * daemon must re-adopt before it handles a single hook. Only the run's
1646
+ * identity comes from here; the collector reads the turns and tasks back
1647
+ * through the existing per-run helpers. */
1648
+ listOpenRuns(limit = 500) {
1649
+ if (!this.db)
1650
+ return [];
1651
+ const rows = this.db.prepare('SELECT * FROM runs WHERE ended_at IS NULL ORDER BY started_at ASC LIMIT ?').all(limit);
1652
+ return rows.map(rowToRun);
1653
+ }
1654
+ /** Tool-call counters for one turn, recovered from the trajectory it wrote.
1655
+ * The collector keeps these in memory and flushes them only when the turn
1656
+ * closes, so a turn open across a restart would otherwise close with
1657
+ * `tool_calls = 0` whatever it did — every reaped codex turn in the measured
1658
+ * week read exactly that. `Edit`/`Write` are the two names the live counter
1659
+ * singles out (`filesModified` / `filesCreated`). */
1660
+ countToolEventsForTurn(taskId, turnIndex) {
1661
+ if (!this.db)
1662
+ return { toolCalls: 0, filesModified: 0, filesCreated: 0 };
1663
+ const row = this.db.prepare(`SELECT COUNT(*) AS n,
1664
+ COALESCE(SUM(tool_name = 'Edit'), 0) AS edits,
1665
+ COALESCE(SUM(tool_name = 'Write'), 0) AS writes
1666
+ FROM sample_events WHERE task_id = ? AND turn_index = ? AND kind = 'tool'`).get(taskId, turnIndex);
1667
+ return { toolCalls: Number(row?.n ?? 0), filesModified: Number(row?.edits ?? 0), filesCreated: Number(row?.writes ?? 0) };
1397
1668
  }
1398
1669
  /** Orphaned runs: started long ago, never closed, no turns.
1399
1670
  * Typically from session bridges that crashed without cleanup. */
@@ -1668,12 +1939,17 @@ function rowToTaskListRow(r) {
1668
1939
  projectPath: r.project_path ?? null,
1669
1940
  parentRunId: r.parent_run_id ?? null,
1670
1941
  firstPrompt: r.first_prompt ?? r.run_prompt ?? null,
1942
+ // The task's OWN first turn prompt, with NO run-prompt fallback: title
1943
+ // derivation must never name task 2+ of a split run after task 0's intent.
1944
+ ownFirstPrompt: r.first_prompt ?? null,
1671
1945
  turnCount: r.turn_count ?? 0,
1672
1946
  answeredTurns: r.answered_turns ?? 0,
1673
1947
  eventCount: r.event_count ?? 0,
1674
1948
  toolCount: r.tool_count ?? 0,
1949
+ filesTouched: r.files_touched ?? 0,
1675
1950
  evalCount: r.eval_count ?? 0,
1676
1951
  overallScore: r.overall_score ?? r.composite_score ?? null,
1952
+ attention: r.attention === 1,
1677
1953
  };
1678
1954
  }
1679
1955
  function rowToSampleEvent(r) {
@@ -1718,10 +1994,32 @@ function sampleEventRowToTrajectory(r) {
1718
1994
  return { ...base, kind: 'model', model: r.model ?? 'unknown', inputTokens: r.inputTokens ?? 0, outputTokens: r.outputTokens ?? 0, costUsd: r.costUsd ?? 0, latencyMs: r.latencyMs ?? 0 };
1719
1995
  case 'tool':
1720
1996
  return { ...base, kind: 'tool', name: r.toolName ?? 'tool', input: p.input, output: p.output, error: r.toolError ?? null, status: r.toolStatus ?? undefined };
1997
+ case 'subagent':
1998
+ return {
1999
+ ...base,
2000
+ kind: 'subagent',
2001
+ id: p.id ?? 'unknown',
2002
+ name: p.name ?? 'Subagent',
2003
+ phase: p.phase === 'completed' ? 'completed' : 'started',
2004
+ summary: p.summary ?? null,
2005
+ durationMs: typeof p.durationMs === 'number' ? p.durationMs : null,
2006
+ };
1721
2007
  case 'state':
1722
2008
  return { ...base, kind: 'state', from: p.from ?? null, to: p.to ?? 'unknown' };
1723
2009
  case 'info':
1724
2010
  return { ...base, kind: 'info', label: p.label ?? 'info', detail: p.detail ?? null };
2011
+ case 'relation':
2012
+ return {
2013
+ ...base,
2014
+ kind: 'relation',
2015
+ relation: p.relation === 'spawned' || p.relation === 'messaged' ? p.relation : 'waiting_on',
2016
+ direction: p.direction === 'in' ? 'in' : 'out',
2017
+ phase: p.phase === 'closed' ? 'closed' : 'open',
2018
+ peerSessionId: p.peerSessionId ?? null,
2019
+ peerName: p.peerName ?? null,
2020
+ evidence: p.evidence ?? 'unknown',
2021
+ detail: p.detail ?? null,
2022
+ };
1725
2023
  default:
1726
2024
  return null;
1727
2025
  }