@chatpanel/events 0.75.0 โ†’ 0.77.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/index.js CHANGED
@@ -197,6 +197,7 @@ export { fixedPlan, parsePlan, plannerPrompt, waves, breakCycles, TEAM_PLAN_SCHE
197
197
  export { createBoard, parseFindings, boardText, findingsInstruction, toBriefClaims, FINDINGS_SCHEMA, FINDING_KINDS } from './team-board.js';
198
198
  export { runTeam, dryRunTeam, TeamRunError, RUN_STATUSES } from './team-run.js';
199
199
  export { teamToolProvider, teamToolSpec, describeTeamForApproval, TEAM_TOOL_NAME } from './team-tool.js';
200
+ export { teamLine, teamLanes } from './team-trail.js';
200
201
  export { mcpDispatchProvider, MCP_TOOL_NAME } from './mcp-dispatch.js';
201
202
  export { createManifest, ManifestError, SOURCES } from './manifest.js';
202
203
  export { createKernel, meetDecisions, KernelError, REQUIRED_PLUGINS, ALLOW_ALL } from './kernel.js';
@@ -213,7 +214,7 @@ export { SOURCE_TRUST, SkillSourceError, defineSkillSource, createSkillSourceReg
213
214
  export { SKILL_MANIFEST_VERSION, SKILL_CONTEXTS, SKILL_HISTORY_SCOPES, SKILL_MCP_MODES, SKILL_TRUST, SKILL_FILE_KINDS, SKILL_UPCASTERS, SkillManifestError, isSafeSkillPath, originOf, trustOf, skillFiles, needsBridge, declaredAccess, originLabel, sameSkillOrigin, skillIsStale, validateSkill, upcastSkill, upcastSkills, normalizeSkill } from './skill-manifest.js';
214
215
  export { SKILL_VARS, SKILL_VAR_NAMES, skillVar, skillVarPattern, parseSkillVars, lintSkillPrompt, suggestSkillVar, substituteSkillVars, expandSkillPrompt, skillVarGuidance, SkillVarError } from './skill-vars.js';
215
216
  export { lineAt, writerAffordance, INSTRUCTION_RE, instructionOnLine, GOAL_MIN_NEW, goalDraftAllowed, createSpendMeter, writerTail, draftSeparator, groundingBlock, writerRequest, AUTOCOMPLETE_SYSTEM, AUTOCOMPLETE_MAX_TOKENS, AUTOCOMPLETE_TEMPERATURE, clipCompletion, GEARS, WRITER_PREFS, WRITER_PREF_DEFAULTS, normalizeWriterPrefs, normalizeIntent } from './cowriter-writer.js';
216
- export { SLASH_TYPING_RE, enabledSkills, slashCommandItems, matchSlashSkill, matchSlashRecipe, recipeInvocationText, slashCommandInsert, skillInvocationOf, skillInvocationLabel } from './slash-commands.js';
217
+ export { SLASH_TYPING_RE, enabledSkills, slashCommandItems, matchSlashSkill, matchSlashRecipe, recipeInvocationText, matchSlashTeam, teamInvocationText, slashCommandInsert, skillInvocationOf, skillInvocationLabel } from './slash-commands.js';
217
218
  export { outlineOf, parseListItem, continueList, indentSelection, toggleWrap, toggleLinePrefix, toggleTask, toggleLink, docStats, selectionStats } from './markdown-authoring.js';
218
219
  export {
219
220
  MAX_TAG_LENGTH, MAX_TAGS, normalizeTag, normalizeTags, hasTag, addTag, removeTag, toggleTag,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/events",
3
- "version": "0.75.0",
3
+ "version": "0.77.0",
4
4
  "description": "The canonical ChatPanel event-log and capability contracts \u2014 typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -93,6 +93,7 @@
93
93
  "./team-plan.js": "./team-plan.js",
94
94
  "./team-run.js": "./team-run.js",
95
95
  "./team-tool.js": "./team-tool.js",
96
+ "./team-trail.js": "./team-trail.js",
96
97
  "./team.js": "./team.js",
97
98
  "./text-search.js": "./text-search.js",
98
99
  "./theme.js": "./theme.js",
@@ -207,6 +208,7 @@
207
208
  "team-plan.js",
208
209
  "team-run.js",
209
210
  "team-tool.js",
211
+ "team-trail.js",
210
212
  "team.js",
211
213
  "text-search.js",
212
214
  "theme.js",
package/slash-commands.js CHANGED
@@ -37,6 +37,12 @@ function recipeItem(recipe) {
37
37
  return { type: 'recipe', command: recipe.name || '', icon: '๐Ÿงฉ', description: recipe.description || 'Saved recipe', recipe };
38
38
  }
39
39
 
40
+ // A saved TEAM answers to a slash the same way: `/research <request>` is a request to run
41
+ // it, and the `team` tool does the rest.
42
+ function teamItem(team) {
43
+ return { type: 'team', command: team.name || '', icon: '๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘', description: team.description || 'Agent team', team };
44
+ }
45
+
40
46
  /** Skills that are switched on. Absence of the flag means enabled (older records have none). */
41
47
  export function enabledSkills(skills) {
42
48
  return (Array.isArray(skills) ? skills : []).filter((s) => !!s && s.enabled !== false);
@@ -58,6 +64,7 @@ export function slashCommandItems({
58
64
  builtins = [],
59
65
  skills = [],
60
66
  recipes = [],
67
+ teams = [],
61
68
  prefix = '',
62
69
  skillsAllowed = false,
63
70
  features = {},
@@ -70,7 +77,8 @@ export function slashCommandItems({
70
77
  }));
71
78
  const skillItems = skillsAllowed ? enabledSkills(skills).map(skillItem) : [];
72
79
  const recipeItems = (recipes || []).filter((r) => r && r.enabled !== false && r.name).map(recipeItem);
73
- return [...own, ...skillItems, ...recipeItems]
80
+ const teamItems = (teams || []).filter((t) => t && t.enabled !== false && t.name).map(teamItem);
81
+ return [...own, ...skillItems, ...recipeItems, ...teamItems]
74
82
  .filter((item) => item.command && item.command.toLowerCase().startsWith(normalized))
75
83
  .slice(0, 12);
76
84
  }
@@ -100,6 +108,20 @@ export function recipeInvocationText(recipe, args = '') {
100
108
  return `Run the saved recipe "${recipe.name}"${a ? ` with this input: ${a}` : ''}. Use the recipe tool; if a parameter is missing, ask for it.`;
101
109
  }
102
110
 
111
+ /** "/research compare A and B" โ†’ the team, and the rest of the line as its request. */
112
+ export function matchSlashTeam(text, teams = []) {
113
+ const m = /^\/([a-z0-9_-]+)\s*([\s\S]*)$/i.exec(String(text || ''));
114
+ if (!m) return null;
115
+ const team = (teams || []).find((t) => t && t.enabled !== false && String(t.name || '').toLowerCase() === m[1].toLowerCase());
116
+ return team ? { team, args: m[2].trim() } : null;
117
+ }
118
+
119
+ /** What the model receives for a team command: a request to run it, never a prompt expansion. */
120
+ export function teamInvocationText(team, args = '') {
121
+ const a = String(args || '').trim();
122
+ return `Run the saved team "${team.name}"${a ? ` on this request: ${a}` : ''}. Use the team tool; if the request is unclear, ask first.`;
123
+ }
124
+
103
125
  export function slashCommandInsert(item) {
104
126
  return item?.command ? `/${item.command} ` : '/';
105
127
  }
package/team-run.js CHANGED
@@ -48,7 +48,9 @@ export function dryRunTeam(team, request, { appoint = null } = {}) {
48
48
  const t = normalizeTeam(team);
49
49
  const roles = t.roles.map((r) => {
50
50
  const a = appoint ? appoint(r) : null;
51
- return { id: r.id, name: r.name, mode: r.mode, prefer: r.prefer, model: a?.model || r.model || null, appointed: !!(a?.model || r.model), grants: r.grants, ...(r.recipe ? { recipe: r.recipe } : {}) };
51
+ // `model` is what callModel will be handed (a target id in a client that resolves ids);
52
+ // `label` is what a person should read โ€” the appointer says which, when it knows.
53
+ return { id: r.id, name: r.name, mode: r.mode, prefer: r.prefer, model: a?.model || r.model || null, label: a?.label || a?.model || r.model || null, appointed: !!(a?.model || r.model), grants: r.grants, ...(r.recipe ? { recipe: r.recipe } : {}) };
52
54
  });
53
55
  const missing = roles.filter((r) => r.mode !== 'recipe' && !r.appointed).map((r) => r.id);
54
56
  return {
@@ -137,9 +139,11 @@ export async function runTeam({
137
139
  if (!budget.canAfford({ tokens: 0 })) { overBudget = true; throw new Error('over budget'); }
138
140
  const prior = boardText(board.all(), { taskIds: task.dependsOn?.length ? task.dependsOn : null });
139
141
  const prompt = [task.prompt, prior, findingsInstruction()].filter(Boolean).join('\n\n');
142
+ // A host may build a toolset asynchronously (connecting MCP servers takes time).
143
+ const tools = await toolsFor(role);
140
144
  const res = await callModel({
141
145
  runId: id, taskId: task.id, role: role.id, model: m.model, mode: m.mode || role.mode,
142
- system: role.prompt, prompt, tools: toolsFor(role), signal,
146
+ system: role.prompt, prompt, tools, signal,
143
147
  onDelta: (delta, full) => say('task.delta', { taskId: task.id, role: role.id, delta, text: full }),
144
148
  });
145
149
  usage = res?.usage || null;
package/team-tool.js CHANGED
@@ -49,7 +49,7 @@ export function teamToolSpec(teams) {
49
49
  /** The card a person approves a new team on. */
50
50
  export function describeTeamForApproval(team, dry) {
51
51
  const lines = [`${team.name}${team.description ? ` โ€” ${team.description}` : ''}`, `Plan: ${team.plan || 'fixed'} ยท merge: ${team.merge || 'concat'}${team.judge ? ` (judge: ${team.judge})` : ''}`];
52
- for (const r of dry?.roles || team.roles || []) lines.push(`โ€ข ${describeRole({ ...r, model: r.model || undefined })}${r.appointed === false ? ' โ€” NO MODEL AVAILABLE' : ''}`);
52
+ for (const r of dry?.roles || team.roles || []) lines.push(`โ€ข ${describeRole({ ...r, model: r.label || r.model || undefined })}${r.appointed === false ? ' โ€” NO MODEL AVAILABLE' : ''}`);
53
53
  const b = team.budget || {};
54
54
  lines.push(`Budget: ${Object.entries(b).map(([k, v]) => `${k} ${v}`).join(' ยท ')}`);
55
55
  lines.push('Runs go through your own models and tools; a team may not act on a page. Nothing a team produces lands without you.');
package/team-trail.js ADDED
@@ -0,0 +1,37 @@
1
+ // A team run as a trail reads it: one line per event in the trail's own vocabulary, and
2
+ // the run's lanes โ€” one per task โ€” folded from the same events. Both clients draw a run
3
+ // from these; neither decides for itself what "task.done" means in words. The event
4
+ // vocabulary is team-run.js's `emit`; the lanes are what a pane, a card or a phone renders.
5
+
6
+ /** One trail line per team event, in the trail's own vocabulary. */
7
+ export function teamLine(ev) {
8
+ const role = ev.role ? `${ev.role}` : 'team';
9
+ switch (ev.type) {
10
+ case 'run.started': return { type: 'status', text: `team ${ev.team}: ${(ev.roles || []).join(', ')}` };
11
+ case 'plan.ready': return { type: 'status', text: `plan: ${(ev.tasks || []).length} task${(ev.tasks || []).length === 1 ? '' : 's'} (${ev.by})` };
12
+ case 'task.started': return { type: 'tool', name: role, text: `${role} ยท ${ev.title || ev.taskId}` };
13
+ case 'task.tool': return { type: 'tool', name: ev.name, text: `${role} ran ${ev.name}${ev.text ? ` โ€” ${ev.text}` : ''}` };
14
+ case 'task.finding': return { type: 'status', text: `${role}: ${String(ev.finding?.text || '').slice(0, 140)}` };
15
+ case 'task.done': return { type: 'status', text: `${role} done ยท ${ev.findings || 0} finding${ev.findings === 1 ? '' : 's'}` };
16
+ case 'task.failed': return { type: 'error', text: `${role} ${ev.status || 'failed'}${ev.error ? ` โ€” ${ev.error}` : ''}` };
17
+ case 'run.merging': return { type: 'status', text: `merging (${ev.policy})` };
18
+ case 'run.done': return { type: 'status', text: `team ${ev.status}${ev.usage?.spent?.tokens ? ` ยท ${ev.usage.spent.tokens} tokens` : ''}` };
19
+ default: return null;
20
+ }
21
+ }
22
+
23
+ /** The run's lanes โ€” one per task โ€” folded from its events, for the pane. */
24
+ export function teamLanes(prev, ev) {
25
+ const lanes = prev ? { ...prev, tasks: { ...prev.tasks } } : { runId: ev.runId, team: '', status: 'running', tasks: {}, findings: 0 };
26
+ switch (ev.type) {
27
+ case 'run.started': lanes.team = ev.team; lanes.roles = ev.roles; break;
28
+ case 'plan.ready': for (const t of ev.tasks || []) lanes.tasks[t.id] = { id: t.id, role: t.role, title: t.title, status: 'pending', findings: 0 }; break;
29
+ case 'task.started': lanes.tasks[ev.taskId] = { ...(lanes.tasks[ev.taskId] || { id: ev.taskId, role: ev.role, title: ev.title }), status: 'running' }; break;
30
+ case 'task.delta': if (lanes.tasks[ev.taskId]) lanes.tasks[ev.taskId] = { ...lanes.tasks[ev.taskId], text: ev.text }; break;
31
+ case 'task.finding': lanes.findings += 1; if (lanes.tasks[ev.taskId]) lanes.tasks[ev.taskId] = { ...lanes.tasks[ev.taskId], findings: (lanes.tasks[ev.taskId].findings || 0) + 1 }; break;
32
+ case 'task.done': case 'task.failed': if (lanes.tasks[ev.taskId]) lanes.tasks[ev.taskId] = { ...lanes.tasks[ev.taskId], status: ev.status || 'ok', ms: ev.ms }; break;
33
+ case 'run.done': lanes.status = ev.status; lanes.usage = ev.usage; break;
34
+ default: break;
35
+ }
36
+ return lanes;
37
+ }