@chatpanel/events 0.102.0 → 0.104.0

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 (139) hide show
  1. package/adapters.js +1 -83
  2. package/adaptive-tool-policy.js +1 -45
  3. package/agent.js +2 -255
  4. package/attribution.js +1 -132
  5. package/backup-envelope.js +1 -221
  6. package/board-tool.js +2 -165
  7. package/budget.js +1 -116
  8. package/capability.js +1 -126
  9. package/citations.js +5 -127
  10. package/client-prefs.js +1 -148
  11. package/context-attachments.js +8 -144
  12. package/cowriter-router.js +1 -83
  13. package/cowriter-writer.js +19 -193
  14. package/cowriter.js +1 -190
  15. package/curate.js +3 -509
  16. package/distance.js +1 -124
  17. package/engine.js +1 -131
  18. package/entitlement.js +1 -332
  19. package/entity.js +1 -354
  20. package/event.js +1 -175
  21. package/extraction.js +4 -293
  22. package/failover.js +1 -93
  23. package/find-tool.js +1 -66
  24. package/flowchart.js +2 -644
  25. package/gate.js +1 -74
  26. package/harness.js +4 -101
  27. package/index.js +1 -345
  28. package/invariants.js +1 -174
  29. package/job.js +3 -147
  30. package/kernel.js +1 -255
  31. package/knowledge-derive.js +1 -267
  32. package/knowledge.js +3 -230
  33. package/library.js +11 -280
  34. package/loop.js +1 -132
  35. package/manifest.js +1 -107
  36. package/markdown-authoring.js +11 -288
  37. package/markdown-render.js +0 -0
  38. package/mcp-client.js +8 -358
  39. package/mcp-dispatch.js +1 -52
  40. package/mcp-errors.js +1 -106
  41. package/mcp-manager.js +1 -127
  42. package/media-transcript.js +8 -531
  43. package/meeting-analyzers.js +1 -83
  44. package/meeting-insights.js +7 -164
  45. package/meeting-shape.js +1 -282
  46. package/meeting-text.js +5 -94
  47. package/memory.js +2 -638
  48. package/model-candidates.js +1 -358
  49. package/model-health.js +1 -182
  50. package/model-ledger.js +1 -228
  51. package/model-picker.js +1 -188
  52. package/note-actions.js +15 -246
  53. package/note-graph.js +1 -158
  54. package/note-links.js +1 -68
  55. package/note-mentions.js +8 -114
  56. package/note-plan.js +9 -107
  57. package/note-research.js +1 -135
  58. package/observability.js +1 -119
  59. package/omni.js +1 -125
  60. package/order.js +1 -78
  61. package/package.json +4 -5
  62. package/pdf-layout.js +7 -253
  63. package/policy.js +1 -0
  64. package/project-run.js +20 -444
  65. package/project.js +1 -170
  66. package/promotion.js +1 -171
  67. package/queue.js +1 -98
  68. package/reach.js +1 -23
  69. package/recipe-tool.js +2 -161
  70. package/recipe.js +1 -264
  71. package/record-list.js +2 -110
  72. package/recruit.js +3 -456
  73. package/redaction-tokens.js +1 -61
  74. package/ref.js +1 -55
  75. package/registry.js +1 -240
  76. package/route-graph.js +1 -115
  77. package/route-strategies.js +2 -232
  78. package/router.js +2 -880
  79. package/rrf.js +1 -89
  80. package/rules.js +1 -142
  81. package/schedule.js +1 -663
  82. package/scm-connection.js +1 -180
  83. package/scopes.js +1 -12
  84. package/scorecard.js +1 -335
  85. package/search-engines.js +1 -81
  86. package/skill-manifest.js +1 -330
  87. package/skill-scan.js +3 -209
  88. package/skill-sources.js +1 -162
  89. package/skill-vars.js +2 -252
  90. package/slash-commands.js +1 -158
  91. package/source-gate.js +1 -92
  92. package/sources-retrieval.js +5 -186
  93. package/sources.js +1 -273
  94. package/store.js +1 -171
  95. package/structured.js +4 -901
  96. package/subject-kinds.js +1 -5
  97. package/subject-name.js +1 -96
  98. package/sync-plan.js +1 -213
  99. package/synthesis.js +3 -123
  100. package/tags.js +1 -203
  101. package/team-board.js +6 -337
  102. package/team-cache.js +2 -63
  103. package/team-observe.js +1 -118
  104. package/team-org.js +1 -442
  105. package/team-plan.js +4 -131
  106. package/team-record.js +1 -225
  107. package/team-run.js +9 -652
  108. package/team-subtask.js +2 -191
  109. package/team-task.js +2 -124
  110. package/team-tool.js +2 -196
  111. package/team-trail.js +2 -73
  112. package/team-worklog.js +2 -149
  113. package/team.js +1 -302
  114. package/text-search.js +1 -155
  115. package/theme.js +17 -154
  116. package/titles.js +4 -285
  117. package/tool-discovery.js +1 -87
  118. package/tool-dispatch.js +5 -230
  119. package/tool-groups.js +1 -81
  120. package/tool-hints.js +10 -185
  121. package/tool-loop-guard.js +4 -182
  122. package/tool-need.js +2 -124
  123. package/tool-result.js +2 -438
  124. package/tool-round.js +0 -0
  125. package/tool-schema.js +1 -155
  126. package/tool-traits.js +1 -182
  127. package/toolset.js +2 -88
  128. package/trajectory.js +1 -530
  129. package/turn-loop.js +4 -440
  130. package/upcast.js +1 -37
  131. package/vault.js +2 -250
  132. package/view.js +1 -88
  133. package/voice-intents.js +2 -1488
  134. package/voice-speaker.js +1 -98
  135. package/weather-tool.js +1 -65
  136. package/weather.js +2 -211
  137. package/web-search-tool.js +14 -102
  138. package/web-search.js +1 -160
  139. package/widget.js +1 -153
package/team-record.js CHANGED
@@ -1,225 +1 @@
1
- // The run record a team run folded from its events, the same way on the gateway's store
2
- // and in either client. One fold, so what the desktop shows, what the extension shows and
3
- // what the store holds never disagree; and enough on the record to RESUME the run from
4
- // anywhere: the plan, every task's status and transcript, the board, the spend.
5
- //
6
- // The gateway vendors this file (its store used to carry a mirror of it, which is the copy
7
- // that drifts). `checkpointFrom(run)` is what a client hands `resumeTeam` after reading a
8
- // run the process that started it no longer runs.
9
-
10
- import { foldBoard, emptyBoardState } from './team-board.js';
11
-
12
- export const LIVE_RUN_STATUSES = Object.freeze(['planning', 'running', 'merging', 'waiting']);
13
- export const RESUMABLE_RUN_STATUSES = Object.freeze(['waiting', 'stopped', 'failed', 'partial', 'over-budget', 'answered']);
14
- const TASK_TEXT_MAX = 20_000;
15
-
16
- export function emptyRun({ id, client = '', now = Date.now() } = {}) {
17
- return { id, client: String(client || '').slice(0, 40), createdAt: now, lastEventAt: now, status: 'planning', team: '', request: '', roles: [], plan: null, tasks: [], jobs: [], board: [], threads: emptyBoardState(), checkpoint: null, proposal: null, usage: null, stopRequested: null, startedAt: null, endedAt: null };
18
- }
19
-
20
- const taskOf = (run, id) => run.tasks.find((x) => x.id === id);
21
-
22
- /** Fold one event (`{ type, at, payload }`, or a flat `{ type, at, ...payload }`) into the record. */
23
- export function foldRun(run, ev) {
24
- const type = String(ev?.type || '');
25
- const p = ev?.payload && typeof ev.payload === 'object' ? ev.payload : (ev || {});
26
- const at = Number(ev?.at) || Date.now();
27
- run.lastEventAt = at;
28
- switch (type) {
29
- case 'run.started':
30
- case 'run.resumed':
31
- run.team = p.team || run.team; run.request = p.request ?? run.request; run.budget = p.budget || run.budget;
32
- run.roles = Array.isArray(p.roles) ? p.roles : run.roles; run.status = 'planning'; run.startedAt = run.startedAt || at;
33
- if (type === 'run.resumed') { run.endedAt = null; run.checkpoint = null; run.resumedAt = at; }
34
- break;
35
- case 'plan.ready':
36
- run.plan = { by: p.by || 'fixed', tasks: Array.isArray(p.tasks) ? p.tasks : [] };
37
- // A resume replays the plan: keep what the tasks already hold (transcripts, attempts).
38
- run.tasks = run.plan.tasks.map((t) => ({ ...(taskOf(run, t.id) || {}), id: t.id, role: t.role, title: t.title, status: taskOf(run, t.id)?.status === 'ok' ? 'ok' : (t.parent && !t.role ? 'unassigned' : 'pending'), findings: taskOf(run, t.id)?.findings || 0, ...(t.parent ? { parent: t.parent, requestedBy: t.requestedBy || null } : {}), ...(t.grants ? { grants: t.grants, why: t.why || '' } : {}), ...(t.kind ? { kind: t.kind } : {}) }));
39
- run.status = 'running';
40
- break;
41
- // A SUB-TASK (§15.2): requested by a member mid-run, it joins the plan under its parent;
42
- // taken by a member, recruited from the pool, created on a person's approval — or not.
43
- case 'task.requested':
44
- if (p.taskId && !taskOf(run, p.taskId)) {
45
- const task = { id: p.taskId, role: null, title: p.title || p.taskId, prompt: p.prompt || '', dependsOn: Array.isArray(p.dependsOn) ? p.dependsOn : [], parent: p.parent || null, requestedBy: p.by || null, needs: p.needs || null, depth: p.depth || 1, wait: p.wait !== false };
46
- if (run.plan) run.plan.tasks = [...(run.plan.tasks || []), task];
47
- run.tasks.push({ id: task.id, role: null, title: task.title, status: 'requested', findings: 0, parent: task.parent, requestedBy: task.requestedBy, needs: task.needs, requestedAt: at });
48
- }
49
- break;
50
- // A TASK ADDED AFTER THE PLAN — the merge (the judge's task, opened when the members are
51
- // done). It joins the plan so a resume carries it and the board draws its thread and log.
52
- case 'task.added':
53
- if (p.taskId && !taskOf(run, p.taskId)) {
54
- const task = { id: p.taskId, role: p.role || null, title: p.title || p.taskId, dependsOn: Array.isArray(p.dependsOn) ? p.dependsOn : [], ...(p.kind ? { kind: p.kind } : {}) };
55
- if (run.plan) run.plan.tasks = [...(run.plan.tasks || []), task];
56
- run.tasks.push({ id: task.id, role: task.role, title: task.title, status: 'pending', findings: 0, ...(p.kind ? { kind: p.kind } : {}) });
57
- }
58
- break;
59
- case 'task.taken': { const t = taskOf(run, p.taskId); if (t) { t.role = p.role; t.status = 'pending'; t.takenBy = { by: p.by || 'fit', role: p.role, fit: p.fit ?? null, reasons: p.reasons || [], agentId: p.agentId || null, engine: p.engine || null, why: p.why || '', at }; } const pt = run.plan?.tasks?.find((x) => x.id === p.taskId); if (pt) pt.role = p.role; break; }
60
- case 'task.posted': { const t = taskOf(run, p.taskId); if (t) t.job = p.job || null; if (p.job && !run.jobs.some((j) => j.id === p.job.id)) run.jobs.push({ ...p.job, taskId: p.taskId, at }); break; }
61
- case 'task.proposed': { const t = taskOf(run, p.taskId); if (t) t.proposal = { agent: p.agent || null, threadId: p.threadId || null, postId: p.postId || null, why: p.why || '', at }; break; }
62
- case 'task.unassigned': { const t = taskOf(run, p.taskId); if (t) { t.status = 'unassigned'; t.error = p.why || null; t.endedAt = at; } const j = run.jobs.find((x) => x.taskId === p.taskId); if (j) j.status = 'failed'; break; }
63
- case 'task.nudged': { const t = taskOf(run, p.taskId); if (t) t.nudged = [...(t.nudged || []), { grants: p.grants || [], at }]; break; }
64
- case 'run.role-added': if (p.role?.id && !run.roles.includes(p.role.id)) { run.roles.push(p.role.id); run.recruited = [...(run.recruited || []), { ...p.role, jobId: p.jobId || null, at }]; const j = run.jobs.find((x) => x.id === p.jobId); if (j) { j.status = 'recruited'; j.recruited = { agentId: p.role.agent || p.role.id, engine: p.role.engine || null, at }; } } break;
65
- case 'task.started': {
66
- // A start the plan never named (a record from a build whose merge was not a task) gets
67
- // its row here rather than being dropped — the fold never loses a task that ran.
68
- let t = taskOf(run, p.taskId);
69
- if (!t && p.taskId) { t = { id: p.taskId, role: p.role || null, title: p.title || p.taskId, status: 'pending', findings: 0, ...(p.taskId === 'merge' ? { kind: 'merge' } : {}) }; run.tasks.push(t); }
70
- if (t) { t.status = 'running'; t.startedAt = at; t.error = null; }
71
- run.status = 'running'; break;
72
- }
73
- case 'task.model': { const t = taskOf(run, p.taskId); if (t) { t.model = p.model; t.attempts = [...(t.attempts || []), { model: p.model, ...(p.label ? { label: p.label } : {}), at, attempt: p.attempt }]; } break; }
74
- // Which engine the attempt runs on (pillars §13): kept on the attempt, so a record folded
75
- // from events names the model the way the runner's own `attempts` do.
76
- case 'task.routed': { const t = taskOf(run, p.taskId); const a = t?.attempts?.at?.(-1); if (a && p.engine && (a.attempt == null || a.attempt === p.attempt)) a.engine = p.engine; break; }
77
- case 'task.step': { const t = taskOf(run, p.taskId); if (t && Array.isArray(p.steps)) t.transcript = [...(t.transcript || []), ...p.steps]; break; }
78
- case 'task.handoff': { const t = taskOf(run, p.taskId); if (t) { t.model = p.to; t.handoffs = [...(t.handoffs || []), { from: p.from, to: p.to, by: p.by, reason: p.reason, at }]; } break; }
79
- case 'task.tool': { const t = taskOf(run, p.taskId); if (t) t.tools = (t.tools || 0) + 1; break; }
80
- case 'task.delta': { const t = taskOf(run, p.taskId); if (t) t.text = String(p.text || '').slice(0, TASK_TEXT_MAX); break; }
81
- case 'task.finding':
82
- if (p.finding && p.finding.text) { run.board.push({ ...p.finding, at }); const t = taskOf(run, p.taskId); if (t) t.findings += 1; }
83
- break;
84
- case 'task.waiting': { const t = taskOf(run, p.taskId); if (t) { t.status = 'waiting'; t.waitingOn = p.threadId; } break; }
85
- case 'task.done':
86
- case 'task.failed': {
87
- const t = taskOf(run, p.taskId);
88
- if (t) { t.status = p.status || (type === 'task.done' ? 'ok' : 'failed'); t.error = p.error || null; t.ms = p.ms; t.endedAt = at; }
89
- // A job posting's outcome is the sub-task's (§15.2.5): taken by, finished how.
90
- const j = run.jobs.find((x) => x.taskId === p.taskId);
91
- if (j) { j.status = type === 'task.done' ? 'done' : 'failed'; j.endedAt = at; }
92
- break;
93
- }
94
- case 'run.merging': run.status = 'merging'; break;
95
- case 'run.waiting': run.status = 'running'; break;
96
- case 'run.usage': run.usage = p.usage || run.usage; break;
97
- case 'run.done':
98
- run.status = p.status || 'completed'; run.usage = p.usage || run.usage; run.proposal = p.proposal ?? run.proposal; run.endedAt = at;
99
- if (p.checkpoint) run.checkpoint = p.checkpoint;
100
- break;
101
- case 'run.stop-requested': run.stopRequested = at; break;
102
- case 'board.thread': case 'board.post': case 'board.decision': case 'board.thread-status': case 'board.thread-removed':
103
- run.threads = foldBoard(run.threads || emptyBoardState(), ev);
104
- if (type === 'board.thread-status' && p.status !== 'waiting' && run.status === 'waiting' && !(run.threads.threads || []).some((t) => t.kind === 'ask' && t.status === 'waiting')) run.status = 'answered';
105
- break;
106
- default: break;
107
- }
108
- return run;
109
- }
110
-
111
- /** Fold a whole event list into a fresh record. */
112
- export function runFromEvents(id, events, opts = {}) {
113
- const run = emptyRun({ id, ...opts });
114
- for (const ev of events || []) foldRun(run, ev);
115
- return run;
116
- }
117
-
118
- /**
119
- * What `resumeTeam` needs, from the record alone — the runner's own checkpoint when the run
120
- * ended with one, else one built from the folded tasks: whatever the process that ran it
121
- * managed to write before it went. A task recorded `running` (its process died) resumes
122
- * from its transcript like a waiting one.
123
- */
124
- export function checkpointFrom(run) {
125
- if (!run?.plan?.tasks?.length) return null;
126
- if (run.checkpoint?.plan?.tasks?.length) return { ...run.checkpoint, board: run.threads && run.threads.threads?.length >= (run.checkpoint.board?.threads?.length || 0) ? run.threads : run.checkpoint.board };
127
- return {
128
- runId: run.id,
129
- startedAt: run.startedAt || run.createdAt,
130
- plan: run.plan,
131
- tasks: run.tasks.map((t) => ({ id: t.id, role: t.role, title: t.title, status: t.status === 'running' || t.status === 'pending' ? 'stopped' : t.status, text: t.text || '', error: t.error || null, transcript: t.transcript || [], attempts: t.attempts || [], usage: null })),
132
- board: run.threads || emptyBoardState(),
133
- budget: { cap: run.usage?.cap || run.budget || {}, spent: run.usage?.spent || {} },
134
- budgetAsked: false,
135
- };
136
- }
137
-
138
- /** Can this record be picked up again? Not one that completed, not one still being run by a live client. */
139
- export function isResumable(run) {
140
- if (!run?.plan?.tasks?.length) return false;
141
- if (RESUMABLE_RUN_STATUSES.includes(run.status)) return true;
142
- return !!run.stale && LIVE_RUN_STATUSES.includes(run.status); // its client went away mid-run
143
- }
144
-
145
- /**
146
- * The run's spend against its cap, as a board shows it: the record's last `run.usage` (or
147
- * nothing spent yet) with `ms` measured LIVE for a run still going — the stored figure is as
148
- * of the last task's end, and a board read "0 s" through a ten-minute research task.
149
- */
150
- export function spendOf(run, { now = Date.now() } = {}) {
151
- const cap = run?.usage?.cap || run?.budget || null;
152
- if (!cap || !Object.keys(cap).length) return null;
153
- const spent = { tokens: 0, calls: 0, usd: 0, ms: 0, ...(run?.usage?.spent || {}) };
154
- // The clock runs while the client is writing, and STOPS where it stopped: a run whose
155
- // record says `running` because its client died (or its end never landed) is not still
156
- // spending — it read "18m13s / 5m00s" and counting for a member that had finished.
157
- const st = runState(run, { now });
158
- if (LIVE_RUN_STATUSES.includes(run?.status) && run?.startedAt) spent.ms = Math.max(spent.ms || 0, (st.key === 'stalled' ? Number(run.lastEventAt) || now : now) - run.startedAt);
159
- const pct = cap.tokens ? Math.min(100, Math.round(((spent.tokens || 0) / cap.tokens) * 100)) : cap.ms ? Math.min(100, Math.round(((spent.ms || 0) / cap.ms) * 100)) : null;
160
- const over = BUDGET_KEYS.filter((k) => cap[k] && (spent[k] || 0) >= cap[k]);
161
- return { cap, spent, pct, exhausted: run?.usage?.exhausted || over[0] || null, over };
162
- }
163
- const BUDGET_KEYS = ['tokens', 'calls', 'usd', 'ms'];
164
-
165
- export const STALLED_AFTER_MS = 2 * 60_000;
166
-
167
- /**
168
- * What a run IS right now, for a person: the record's status read against the clock. A
169
- * record that says `running` with no event for minutes is STALLED — its client stopped
170
- * writing (died, or its end never landed) — not running. `{ key, label, tone, detail }`;
171
- * `tone` is the chip: on · warn · ok · err · muted.
172
- */
173
- export function runState(run, { now = Date.now(), stalledAfterMs = STALLED_AFTER_MS } = {}) {
174
- const s = String(run?.status || 'planning');
175
- const quiet = Number.isFinite(run?.quietMs) ? run.quietMs : Math.max(0, now - (Number(run?.lastEventAt) || now));
176
- const agoText = (ms) => { const sec = Math.round(ms / 1000); return sec < 60 ? `${sec} s` : sec < 3600 ? `${Math.round(sec / 60)} min` : `${Math.round(sec / 3600)} h`; };
177
- if (s === 'waiting') return { key: 'waiting', label: 'waiting on you', tone: 'warn', detail: '' };
178
- if (LIVE_RUN_STATUSES.includes(s)) {
179
- if (run?.stale === true || quiet > stalledAfterMs) return { key: 'stalled', label: 'stalled', tone: 'err', detail: `no events for ${agoText(quiet)} — its client stopped writing; Resume here picks it up from the record` };
180
- return { key: 'running', label: s === 'merging' ? 'merging' : s === 'planning' ? 'planning' : 'running', tone: 'on', detail: `last event ${agoText(quiet)} ago` };
181
- }
182
- if (s === 'completed') return { key: 'done', label: 'done', tone: 'ok', detail: '' };
183
- if (s === 'partial') return { key: 'partial', label: 'done with failures', tone: 'warn', detail: 'a member failed; the rest merged' };
184
- if (s === 'answered') return { key: 'answered', label: 'answered — resume to continue', tone: 'warn', detail: '' };
185
- if (s === 'over-budget') return { key: 'over-budget', label: 'over budget', tone: 'err', detail: 'stopped with what it had' };
186
- if (s === 'stopped') return { key: 'stopped', label: 'stopped', tone: 'muted', detail: '' };
187
- if (s === 'failed') return { key: 'failed', label: 'failed', tone: 'err', detail: '' };
188
- return { key: s, label: s, tone: 'muted', detail: '' };
189
- }
190
-
191
- /**
192
- * Earlier runs whose work a new run should read before repeating it (§12.2.6, the librarian's
193
- * first step): the same team (or any, when `team` is empty), a request that says the same
194
- * thing (word overlap ≥ `minSimilarity`), findings on the record, not the run itself, newest
195
- * first. `runs` is the store's list (`findings` is a count there). Returns
196
- * `[{ id, at, similarity, findings }]`; the host fetches the record for the findings.
197
- */
198
- export function priorWorkFor(runs, { team = '', request = '', excludeId = null, minSimilarity = 0.6, maxAgeMs = 7 * 24 * 3600_000, now = Date.now(), limit = 3 } = {}) {
199
- const words = (t) => new Set(String(t || '').toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, ' ').split(/\s+/).filter((w) => w.length > 2));
200
- const want = words(request);
201
- if (!want.size) return [];
202
- const sim = (t) => { const have = words(t); if (!have.size) return 0; let hit = 0; for (const w of want) if (have.has(w)) hit += 1; return hit / Math.max(want.size, have.size); };
203
- return (runs || [])
204
- .filter((r) => r && r.id && r.id !== excludeId && (!team || r.team === team) && !LIVE_RUN_STATUSES.includes(r.status) && (Number(r.findings) || (Array.isArray(r.board) ? r.board.length : 0)) > 0 && now - (Number(r.createdAt) || 0) <= maxAgeMs)
205
- .map((r) => ({ id: r.id, at: r.createdAt, similarity: Math.round(sim(r.request) * 100) / 100, findings: Number(r.findings) || (Array.isArray(r.board) ? r.board.length : 0), status: r.status }))
206
- .filter((r) => r.similarity >= minSimilarity)
207
- .sort((a, b) => b.similarity - a.similarity || b.at - a.at)
208
- .slice(0, limit);
209
- }
210
-
211
- const secs = (ms) => { const s = Math.max(0, Math.round((Number(ms) || 0) / 1000)); return s >= 60 ? `${Math.floor(s / 60)}m${String(s % 60).padStart(2, '0')}s` : `${s}s`; };
212
- const num = (n) => (Number(n) || 0).toLocaleString('en-US');
213
-
214
- /** One line: `1,240 / 40,000 tokens · 3 / 20 calls · 2m10s / 15m00s`. Only the capped dimensions. */
215
- export function describeSpend(spend) {
216
- if (!spend?.cap) return '';
217
- const { cap, spent } = spend;
218
- const over = (k) => ((spend.over || []).includes(k) ? ' (over)' : '');
219
- return [
220
- cap.tokens ? `${num(spent.tokens)} / ${num(cap.tokens)} tokens${over('tokens')}` : '',
221
- cap.calls ? `${num(spent.calls)} / ${num(cap.calls)} calls${over('calls')}` : '',
222
- cap.usd ? `$${(Number(spent.usd) || 0).toFixed(2)} / $${Number(cap.usd).toFixed(2)}${over('usd')}` : '',
223
- cap.ms ? `${secs(spent.ms)} / ${secs(cap.ms)}${over('ms')}` : '',
224
- ].filter(Boolean).join(' · ');
225
- }
1
+ import{foldBoard as I,emptyBoardState as g}from"./team-board.js";const p=Object.freeze(["planning","running","merging","waiting"]),A=Object.freeze(["waiting","stopped","failed","partial","over-budget","answered"]),w=2e4;function x({id:e,client:a="",now:n=Date.now()}={}){return{id:e,client:String(a||"").slice(0,40),createdAt:n,lastEventAt:n,status:"planning",team:"",request:"",roles:[],plan:null,tasks:[],jobs:[],board:[],threads:g(),checkpoint:null,proposal:null,usage:null,stopRequested:null,startedAt:null,endedAt:null}}const r=(e,a)=>e.tasks.find(n=>n.id===a);function S(e,a){const n=String(a?.type||""),t=a?.payload&&typeof a.payload=="object"?a.payload:a||{},d=Number(a?.at)||Date.now();switch(e.lastEventAt=d,n){case"run.started":case"run.resumed":e.team=t.team||e.team,e.request=t.request??e.request,e.budget=t.budget||e.budget,e.roles=Array.isArray(t.roles)?t.roles:e.roles,e.status="planning",e.startedAt=e.startedAt||d,n==="run.resumed"&&(e.endedAt=null,e.checkpoint=null,e.resumedAt=d);break;case"plan.ready":e.plan={by:t.by||"fixed",tasks:Array.isArray(t.tasks)?t.tasks:[]},e.tasks=e.plan.tasks.map(s=>({...r(e,s.id)||{},id:s.id,role:s.role,title:s.title,status:r(e,s.id)?.status==="ok"?"ok":s.parent&&!s.role?"unassigned":"pending",findings:r(e,s.id)?.findings||0,...s.parent?{parent:s.parent,requestedBy:s.requestedBy||null}:{},...s.grants?{grants:s.grants,why:s.why||""}:{},...s.kind?{kind:s.kind}:{}})),e.status="running";break;case"task.requested":if(t.taskId&&!r(e,t.taskId)){const s={id:t.taskId,role:null,title:t.title||t.taskId,prompt:t.prompt||"",dependsOn:Array.isArray(t.dependsOn)?t.dependsOn:[],parent:t.parent||null,requestedBy:t.by||null,needs:t.needs||null,depth:t.depth||1,wait:t.wait!==!1};e.plan&&(e.plan.tasks=[...e.plan.tasks||[],s]),e.tasks.push({id:s.id,role:null,title:s.title,status:"requested",findings:0,parent:s.parent,requestedBy:s.requestedBy,needs:s.needs,requestedAt:d})}break;case"task.added":if(t.taskId&&!r(e,t.taskId)){const s={id:t.taskId,role:t.role||null,title:t.title||t.taskId,dependsOn:Array.isArray(t.dependsOn)?t.dependsOn:[],...t.kind?{kind:t.kind}:{}};e.plan&&(e.plan.tasks=[...e.plan.tasks||[],s]),e.tasks.push({id:s.id,role:s.role,title:s.title,status:"pending",findings:0,...t.kind?{kind:t.kind}:{}})}break;case"task.taken":{const s=r(e,t.taskId);s&&(s.role=t.role,s.status="pending",s.takenBy={by:t.by||"fit",role:t.role,fit:t.fit??null,reasons:t.reasons||[],agentId:t.agentId||null,engine:t.engine||null,why:t.why||"",at:d});const o=e.plan?.tasks?.find(l=>l.id===t.taskId);o&&(o.role=t.role);break}case"task.posted":{const s=r(e,t.taskId);s&&(s.job=t.job||null),t.job&&!e.jobs.some(o=>o.id===t.job.id)&&e.jobs.push({...t.job,taskId:t.taskId,at:d});break}case"task.proposed":{const s=r(e,t.taskId);s&&(s.proposal={agent:t.agent||null,threadId:t.threadId||null,postId:t.postId||null,why:t.why||"",at:d});break}case"task.unassigned":{const s=r(e,t.taskId);s&&(s.status="unassigned",s.error=t.why||null,s.endedAt=d);const o=e.jobs.find(l=>l.taskId===t.taskId);o&&(o.status="failed");break}case"task.nudged":{const s=r(e,t.taskId);s&&(s.nudged=[...s.nudged||[],{grants:t.grants||[],at:d}]);break}case"run.role-added":if(t.role?.id&&!e.roles.includes(t.role.id)){e.roles.push(t.role.id),e.recruited=[...e.recruited||[],{...t.role,jobId:t.jobId||null,at:d}];const s=e.jobs.find(o=>o.id===t.jobId);s&&(s.status="recruited",s.recruited={agentId:t.role.agent||t.role.id,engine:t.role.engine||null,at:d})}break;case"task.started":{let s=r(e,t.taskId);!s&&t.taskId&&(s={id:t.taskId,role:t.role||null,title:t.title||t.taskId,status:"pending",findings:0,...t.taskId==="merge"?{kind:"merge"}:{}},e.tasks.push(s)),s&&(s.status="running",s.startedAt=d,s.error=null),e.status="running";break}case"task.model":{const s=r(e,t.taskId);s&&(s.model=t.model,s.attempts=[...s.attempts||[],{model:t.model,...t.label?{label:t.label}:{},at:d,attempt:t.attempt}]);break}case"task.routed":{const o=r(e,t.taskId)?.attempts?.at?.(-1);o&&t.engine&&(o.attempt==null||o.attempt===t.attempt)&&(o.engine=t.engine);break}case"task.step":{const s=r(e,t.taskId);s&&Array.isArray(t.steps)&&(s.transcript=[...s.transcript||[],...t.steps]);break}case"task.handoff":{const s=r(e,t.taskId);s&&(s.model=t.to,s.handoffs=[...s.handoffs||[],{from:t.from,to:t.to,by:t.by,reason:t.reason,at:d}]);break}case"task.tool":{const s=r(e,t.taskId);s&&(s.tools=(s.tools||0)+1);break}case"task.delta":{const s=r(e,t.taskId);s&&(s.text=String(t.text||"").slice(0,w));break}case"task.finding":if(t.finding&&t.finding.text){e.board.push({...t.finding,at:d});const s=r(e,t.taskId);s&&(s.findings+=1)}break;case"task.waiting":{const s=r(e,t.taskId);s&&(s.status="waiting",s.waitingOn=t.threadId);break}case"task.done":case"task.failed":{const s=r(e,t.taskId);s&&(s.status=t.status||(n==="task.done"?"ok":"failed"),s.error=t.error||null,s.ms=t.ms,s.endedAt=d);const o=e.jobs.find(l=>l.taskId===t.taskId);o&&(o.status=n==="task.done"?"done":"failed",o.endedAt=d);break}case"run.merging":e.status="merging";break;case"run.waiting":e.status="running";break;case"run.usage":e.usage=t.usage||e.usage;break;case"run.done":e.status=t.status||"completed",e.usage=t.usage||e.usage,e.proposal=t.proposal??e.proposal,e.endedAt=d,t.checkpoint&&(e.checkpoint=t.checkpoint);break;case"run.stop-requested":e.stopRequested=d;break;case"board.thread":case"board.post":case"board.decision":case"board.thread-status":case"board.thread-removed":e.threads=I(e.threads||g(),a),n==="board.thread-status"&&t.status!=="waiting"&&e.status==="waiting"&&!(e.threads.threads||[]).some(s=>s.kind==="ask"&&s.status==="waiting")&&(e.status="answered");break;default:break}return e}function E(e,a,n={}){const t=x({id:e,...n});for(const d of a||[])S(t,d);return t}function N(e){return e?.plan?.tasks?.length?e.checkpoint?.plan?.tasks?.length?{...e.checkpoint,board:e.threads&&e.threads.threads?.length>=(e.checkpoint.board?.threads?.length||0)?e.threads:e.checkpoint.board}:{runId:e.id,startedAt:e.startedAt||e.createdAt,plan:e.plan,tasks:e.tasks.map(a=>({id:a.id,role:a.role,title:a.title,status:a.status==="running"||a.status==="pending"?"stopped":a.status,text:a.text||"",error:a.error||null,transcript:a.transcript||[],attempts:a.attempts||[],usage:null})),board:e.threads||g(),budget:{cap:e.usage?.cap||e.budget||{},spent:e.usage?.spent||{}},budgetAsked:!1}:null}function v(e){return e?.plan?.tasks?.length?A.includes(e.status)?!0:!!e.stale&&p.includes(e.status):!1}function O(e,{now:a=Date.now()}={}){const n=e?.usage?.cap||e?.budget||null;if(!n||!Object.keys(n).length)return null;const t={tokens:0,calls:0,usd:0,ms:0,...e?.usage?.spent||{}},d=q(e,{now:a});p.includes(e?.status)&&e?.startedAt&&(t.ms=Math.max(t.ms||0,(d.key==="stalled"&&Number(e.lastEventAt)||a)-e.startedAt));const s=n.tokens?Math.min(100,Math.round((t.tokens||0)/n.tokens*100)):n.ms?Math.min(100,Math.round((t.ms||0)/n.ms*100)):null,o=j.filter(l=>n[l]&&(t[l]||0)>=n[l]);return{cap:n,spent:t,pct:s,exhausted:e?.usage?.exhausted||o[0]||null,over:o}}const j=["tokens","calls","usd","ms"],$=2*6e4;function q(e,{now:a=Date.now(),stalledAfterMs:n=$}={}){const t=String(e?.status||"planning"),d=Number.isFinite(e?.quietMs)?e.quietMs:Math.max(0,a-(Number(e?.lastEventAt)||a)),s=o=>{const l=Math.round(o/1e3);return l<60?`${l} s`:l<3600?`${Math.round(l/60)} min`:`${Math.round(l/3600)} h`};return t==="waiting"?{key:"waiting",label:"waiting on you",tone:"warn",detail:""}:p.includes(t)?e?.stale===!0||d>n?{key:"stalled",label:"stalled",tone:"err",detail:`no events for ${s(d)} — its client stopped writing; Resume here picks it up from the record`}:{key:"running",label:t==="merging"?"merging":t==="planning"?"planning":"running",tone:"on",detail:`last event ${s(d)} ago`}:t==="completed"?{key:"done",label:"done",tone:"ok",detail:""}:t==="partial"?{key:"partial",label:"done with failures",tone:"warn",detail:"a member failed; the rest merged"}:t==="answered"?{key:"answered",label:"answered — resume to continue",tone:"warn",detail:""}:t==="over-budget"?{key:"over-budget",label:"over budget",tone:"err",detail:"stopped with what it had"}:t==="stopped"?{key:"stopped",label:"stopped",tone:"muted",detail:""}:t==="failed"?{key:"failed",label:"failed",tone:"err",detail:""}:{key:t,label:t,tone:"muted",detail:""}}function _(e,{team:a="",request:n="",excludeId:t=null,minSimilarity:d=.6,maxAgeMs:s=168*36e5,now:o=Date.now(),limit:l=3}={}){const f=i=>new Set(String(i||"").toLowerCase().replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(c=>c.length>2)),u=f(n);if(!u.size)return[];const h=i=>{const c=f(i);if(!c.size)return 0;let b=0;for(const y of u)c.has(y)&&(b+=1);return b/Math.max(u.size,c.size)};return(e||[]).filter(i=>i&&i.id&&i.id!==t&&(!a||i.team===a)&&!p.includes(i.status)&&(Number(i.findings)||(Array.isArray(i.board)?i.board.length:0))>0&&o-(Number(i.createdAt)||0)<=s).map(i=>({id:i.id,at:i.createdAt,similarity:Math.round(h(i.request)*100)/100,findings:Number(i.findings)||(Array.isArray(i.board)?i.board.length:0),status:i.status})).filter(i=>i.similarity>=d).sort((i,c)=>c.similarity-i.similarity||c.at-i.at).slice(0,l)}const m=e=>{const a=Math.max(0,Math.round((Number(e)||0)/1e3));return a>=60?`${Math.floor(a/60)}m${String(a%60).padStart(2,"0")}s`:`${a}s`},k=e=>(Number(e)||0).toLocaleString("en-US");function B(e){if(!e?.cap)return"";const{cap:a,spent:n}=e,t=d=>(e.over||[]).includes(d)?" (over)":"";return[a.tokens?`${k(n.tokens)} / ${k(a.tokens)} tokens${t("tokens")}`:"",a.calls?`${k(n.calls)} / ${k(a.calls)} calls${t("calls")}`:"",a.usd?`$${(Number(n.usd)||0).toFixed(2)} / $${Number(a.usd).toFixed(2)}${t("usd")}`:"",a.ms?`${m(n.ms)} / ${m(a.ms)}${t("ms")}`:""].filter(Boolean).join(" · ")}export{p as LIVE_RUN_STATUSES,A as RESUMABLE_RUN_STATUSES,$ as STALLED_AFTER_MS,N as checkpointFrom,B as describeSpend,x as emptyRun,S as foldRun,v as isResumable,_ as priorWorkFor,E as runFromEvents,q as runState,O as spendOf};