@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.
- package/adapters.js +1 -83
- package/adaptive-tool-policy.js +1 -45
- package/agent.js +2 -255
- package/attribution.js +1 -132
- package/backup-envelope.js +1 -221
- package/board-tool.js +2 -165
- package/budget.js +1 -116
- package/capability.js +1 -126
- package/citations.js +5 -127
- package/client-prefs.js +1 -148
- package/context-attachments.js +8 -144
- package/cowriter-router.js +1 -83
- package/cowriter-writer.js +19 -193
- package/cowriter.js +1 -190
- package/curate.js +3 -509
- package/distance.js +1 -124
- package/engine.js +1 -131
- package/entitlement.js +1 -332
- package/entity.js +1 -354
- package/event.js +1 -175
- package/extraction.js +4 -293
- package/failover.js +1 -93
- package/find-tool.js +1 -66
- package/flowchart.js +2 -644
- package/gate.js +1 -74
- package/harness.js +4 -101
- package/index.js +1 -345
- package/invariants.js +1 -174
- package/job.js +3 -147
- package/kernel.js +1 -255
- package/knowledge-derive.js +1 -267
- package/knowledge.js +3 -230
- package/library.js +11 -280
- package/loop.js +1 -132
- package/manifest.js +1 -107
- package/markdown-authoring.js +11 -288
- package/markdown-render.js +0 -0
- package/mcp-client.js +8 -358
- package/mcp-dispatch.js +1 -52
- package/mcp-errors.js +1 -106
- package/mcp-manager.js +1 -127
- package/media-transcript.js +8 -531
- package/meeting-analyzers.js +1 -83
- package/meeting-insights.js +7 -164
- package/meeting-shape.js +1 -282
- package/meeting-text.js +5 -94
- package/memory.js +2 -638
- package/model-candidates.js +1 -358
- package/model-health.js +1 -182
- package/model-ledger.js +1 -228
- package/model-picker.js +1 -188
- package/note-actions.js +15 -246
- package/note-graph.js +1 -158
- package/note-links.js +1 -68
- package/note-mentions.js +8 -114
- package/note-plan.js +9 -107
- package/note-research.js +1 -135
- package/observability.js +1 -119
- package/omni.js +1 -125
- package/order.js +1 -78
- package/package.json +4 -5
- package/pdf-layout.js +7 -253
- package/policy.js +1 -0
- package/project-run.js +20 -444
- package/project.js +1 -170
- package/promotion.js +1 -171
- package/queue.js +1 -98
- package/reach.js +1 -23
- package/recipe-tool.js +2 -161
- package/recipe.js +1 -264
- package/record-list.js +2 -110
- package/recruit.js +3 -456
- package/redaction-tokens.js +1 -61
- package/ref.js +1 -55
- package/registry.js +1 -240
- package/route-graph.js +1 -115
- package/route-strategies.js +2 -232
- package/router.js +2 -880
- package/rrf.js +1 -89
- package/rules.js +1 -142
- package/schedule.js +1 -663
- package/scm-connection.js +1 -180
- package/scopes.js +1 -12
- package/scorecard.js +1 -335
- package/search-engines.js +1 -81
- package/skill-manifest.js +1 -330
- package/skill-scan.js +3 -209
- package/skill-sources.js +1 -162
- package/skill-vars.js +2 -252
- package/slash-commands.js +1 -158
- package/source-gate.js +1 -92
- package/sources-retrieval.js +5 -186
- package/sources.js +1 -273
- package/store.js +1 -171
- package/structured.js +4 -901
- package/subject-kinds.js +1 -5
- package/subject-name.js +1 -96
- package/sync-plan.js +1 -213
- package/synthesis.js +3 -123
- package/tags.js +1 -203
- package/team-board.js +6 -337
- package/team-cache.js +2 -63
- package/team-observe.js +1 -118
- package/team-org.js +1 -442
- package/team-plan.js +4 -131
- package/team-record.js +1 -225
- package/team-run.js +9 -652
- package/team-subtask.js +2 -191
- package/team-task.js +2 -124
- package/team-tool.js +2 -196
- package/team-trail.js +2 -73
- package/team-worklog.js +2 -149
- package/team.js +1 -302
- package/text-search.js +1 -155
- package/theme.js +17 -154
- package/titles.js +4 -285
- package/tool-discovery.js +1 -87
- package/tool-dispatch.js +5 -230
- package/tool-groups.js +1 -81
- package/tool-hints.js +10 -185
- package/tool-loop-guard.js +4 -182
- package/tool-need.js +2 -124
- package/tool-result.js +2 -438
- package/tool-round.js +0 -0
- package/tool-schema.js +1 -155
- package/tool-traits.js +1 -182
- package/toolset.js +2 -88
- package/trajectory.js +1 -530
- package/turn-loop.js +4 -440
- package/upcast.js +1 -37
- package/vault.js +2 -250
- package/view.js +1 -88
- package/voice-intents.js +2 -1488
- package/voice-speaker.js +1 -98
- package/weather-tool.js +1 -65
- package/weather.js +2 -211
- package/web-search-tool.js +14 -102
- package/web-search.js +1 -160
- package/widget.js +1 -153
package/team-record.js
CHANGED
|
@@ -1,225 +1 @@
|
|
|
1
|
-
|
|
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};
|