@chatpanel/events 0.101.0 → 0.102.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 (3) hide show
  1. package/index.js +1 -1
  2. package/package.json +1 -1
  3. package/team-org.js +99 -5
package/index.js CHANGED
@@ -238,7 +238,7 @@ export { workLogFor, workLogText, workLogEvidence, describeCall, WORKLOG_KINDS }
238
238
  export { normalizeRequest, subtaskFromRequest, takeUp, takeUpLine, holdsGrants, jobFromSubtask, extendDependents, taskTree, threadRows, MAX_SUBTASKS, MAX_DEPTH, MIN_TAKEUP_FIT } from './team-subtask.js';
239
239
  export { teamLine, teamLanes } from './team-trail.js';
240
240
  // The org, derived (F8 §17): roles as cards, starters whole, a team's health and shape, the roster, one colour per agent.
241
- export { promoteRoles, starterTeam, missingStarters, teamHealth, teamShape, describeTeamShape, whereItWorks, rosterRows, agentKind, agentHue, agentColor, agentInitials, roleCardId, cardNumbers, upsertAgents, soloTeam, teamsWithSolos, SOLO_BUDGET, TEAM_SHAPES, ROSTER_KINDS } from './team-org.js';
241
+ export { promoteRoles, starterTeam, missingStarters, teamHealth, teamShape, describeTeamShape, whereItWorks, rosterRows, agentKind, agentHue, agentColor, agentInitials, roleCardId, cardNumbers, upsertAgents, soloTeam, teamsWithSolos, builtinOrg, isBuiltin, grantChoices, grantsFromChoices, skillChoices, GRANT_INFO, SOLO_BUDGET, TEAM_SHAPES, ROSTER_KINDS } from './team-org.js';
242
242
  export { observeInbox, observeStrip, runLanes, spendRows, engineStrip } from './team-observe.js';
243
243
  export { mcpDispatchProvider, MCP_TOOL_NAME } from './mcp-dispatch.js';
244
244
  export { createManifest, ManifestError, SOURCES } from './manifest.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/events",
3
- "version": "0.101.0",
3
+ "version": "0.102.0",
4
4
  "description": "The canonical ChatPanel event-log and capability contracts — 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",
package/team-org.js CHANGED
@@ -13,8 +13,8 @@
13
13
  // Nothing here renders. A client maps `columns` to boxes and arrows, `hue` to a colour, and
14
14
  // `kind` to a word; the SVG is its own.
15
15
 
16
- import { normalizeTeam, validateTeam, starterTeams, TeamError } from './team.js';
17
- import { normalizeAgent, engineOf, starterAgents, STARTER_AGENTS, ASSISTANT_ID } from './agent.js';
16
+ import { normalizeTeam, validateTeam, starterTeams, TeamError, GRANT_RE } from './team.js';
17
+ import { normalizeAgent, engineOf, starterAgents, assistantAgent, STARTER_AGENTS, ASSISTANT_ID } from './agent.js';
18
18
 
19
19
  const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
20
20
  const poolList = (pool) => (Array.isArray(pool) ? pool : []).filter((a) => a && a.id);
@@ -320,7 +320,7 @@ export function upsertAgents(pool, cards) {
320
320
  export const SOLO_BUDGET = Object.freeze({ tokens: 40000, ms: 300000 });
321
321
  export function soloTeam(agent, { budget = SOLO_BUDGET } = {}) {
322
322
  const a = agent && agent.id ? agent : null;
323
- if (!a || a.id === ASSISTANT_ID || a.enabled === false) return null;
323
+ if (!a || a.enabled === false) return null;
324
324
  return normalizeTeam({
325
325
  name: String(a.id).toLowerCase(),
326
326
  description: `Just ${a.name || a.id}${a.purpose ? ` — ${a.purpose}` : ''}`,
@@ -331,18 +331,112 @@ export function soloTeam(agent, { budget = SOLO_BUDGET } = {}) {
331
331
  });
332
332
  }
333
333
 
334
+
334
335
  /**
335
336
  * The teams a chat can run: the saved ones, then a solo team per enabled pool agent whose
336
337
  * id no saved team already claims. What the slash menu, the `team` tool and "run …" all read.
337
338
  */
338
339
  export function teamsWithSolos(teams = [], pool = [], opts = {}) {
339
- const saved = (Array.isArray(teams) ? teams : []).filter((t) => t && t.name);
340
+ const org = builtinOrg(teams, pool);
341
+ const saved = org.teams;
340
342
  const taken = new Set(saved.map((t) => String(t.name).toLowerCase()));
341
343
  const solos = [];
342
- for (const a of poolList(pool)) {
344
+ for (const a of org.pool) {
343
345
  if (taken.has(String(a.id).toLowerCase()) || !(Array.isArray(a.appliesTo) ? a.appliesTo : ['jobs']).includes('jobs')) continue;
344
346
  const t = soloTeam(a, opts);
345
347
  if (t) { solos.push(t); taken.add(t.name); }
346
348
  }
349
+ // The Assistant too: `/assistant <request>` runs the chat's own model as a one-role team —
350
+ // on the board, on a scorecard — where a plain turn is neither.
351
+ if (!taken.has(ASSISTANT_ID)) { const t = soloTeam(assistantAgent(), opts); if (t) solos.push({ ...t, builtin: true }); }
347
352
  return [...saved, ...solos];
348
353
  }
354
+
355
+ /**
356
+ * THE BUILT-IN ORG — the standing agents and the starter teams ship with the product, present
357
+ * and runnable without a click (the mock's "built-in" chips), and DERIVED, never stored: the
358
+ * `agents` and `teams` sections hold only what a person made, edited or switched off. A saved
359
+ * record with a built-in's id or name REPLACES it (an edit, or `enabled: false`), so the
360
+ * sections stay small and a product update reaches every unedited built-in.
361
+ *
362
+ * • every starter team a person has not saved is here, promoted (its roles as cards) and
363
+ * marked `builtin`, with the cards it stands on;
364
+ * • every starter agent not in the pool is here, marked `builtin`;
365
+ * • the Assistant stays the fixed card it is (agent.js assistantAgent).
366
+ * Returns `{ teams, pool }` — what every list, the slash menu and the runner read.
367
+ */
368
+ export function builtinOrg(teams = [], pool = []) {
369
+ const savedTeams = (Array.isArray(teams) ? teams : []).filter((t) => t && t.name);
370
+ const savedNames = new Set(savedTeams.map((t) => String(t.name).toLowerCase()));
371
+ const outPool = [...poolList(pool)];
372
+ const have = () => new Set(outPool.map((a) => String(a.id)));
373
+ const outTeams = [...savedTeams];
374
+ for (const st of starterTeams()) {
375
+ if (savedNames.has(st.name)) continue;
376
+ const { team, agents } = starterTeam(st.name, outPool, { now: () => 0 });
377
+ outTeams.push({ ...team, builtin: true });
378
+ const ids = have();
379
+ for (const a of agents) if (!ids.has(a.id)) { const { createdAt, ...card } = a; outPool.push({ ...card, builtin: true }); }
380
+ }
381
+ const ids = have();
382
+ for (const a of starterAgents()) if (!ids.has(a.id)) outPool.push({ ...a, builtin: true });
383
+ return { teams: outTeams, pool: outPool };
384
+ }
385
+
386
+ /** Is this record the product's, not a person's — drawn with the built-in chip, toggled rather than deleted. */
387
+ export const isBuiltin = (rec) => !!rec?.builtin;
388
+
389
+ /**
390
+ * The GRANTS a person can give, as choices — not a free field of ids nobody has seen. Grouped
391
+ * the way they read: what the agent may reach, which connected servers, what work it may do
392
+ * (only an agent-tool engine can use those). `servers` are the connected MCP servers
393
+ * (`{ id, name? }`), each its own `mcp:<id>` choice under the umbrella `mcp`.
394
+ */
395
+ export const GRANT_INFO = Object.freeze({
396
+ data: ['Your data', 'notes, meetings and past chats — search and read'],
397
+ web: ['Web', 'search the web and fetch pages'],
398
+ history: ['Chat history', 'search past chats only'],
399
+ mcp: ['Every connected server', 'all MCP servers you connected; pick servers below to narrow'],
400
+ shell: ['Run commands', 'a shell in its working directory'],
401
+ 'fs:write': ['Write files', 'edit files in its working directory'],
402
+ 'scm:read': ['Read the repository', 'the checkout and its hub'],
403
+ 'scm:push': ['Push its branch', 'its own cp/<project>/<job> branch, never main'],
404
+ 'scm:pr': ['Open a pull request', ''],
405
+ 'scm:merge': ['Merge', 'only where the org\'s gate allows it'],
406
+ });
407
+ export function grantChoices({ servers = [] } = {}) {
408
+ const item = (id) => ({ id, label: GRANT_INFO[id]?.[0] || id, hint: GRANT_INFO[id]?.[1] || '' });
409
+ const groups = [
410
+ { id: 'reach', label: 'May reach', items: ['data', 'web', 'history', 'mcp'].map(item) },
411
+ ];
412
+ const srv = (Array.isArray(servers) ? servers : []).filter((s) => s && s.id);
413
+ if (srv.length) groups.push({ id: 'servers', label: 'Connected servers', items: srv.map((s) => ({ id: `mcp:${s.id}`, label: s.name || s.id, hint: 'this server only' })) });
414
+ groups.push({ id: 'work', label: 'May do (agent-tool engines only)', items: ['shell', 'fs:write', 'scm:read', 'scm:push', 'scm:pr', 'scm:merge'].map(item) });
415
+ return groups;
416
+ }
417
+
418
+ /** Grants from checked ids: nothing checked is `none`; `mcp` swallows the per-server picks. */
419
+ export function grantsFromChoices(checked = []) {
420
+ const set = new Set((Array.isArray(checked) ? checked : []).filter((g) => g && g !== 'none' && GRANT_RE.test(String(g))));
421
+ if (set.has('mcp')) for (const g of [...set]) if (g.startsWith('mcp:')) set.delete(g);
422
+ return set.size ? [...set] : ['none'];
423
+ }
424
+
425
+ /**
426
+ * The SKILLS a person can attach, from the skills they have: the shared `skills` section
427
+ * (written or installed, `{ command, name?, description?, enabled? }`) and any names an
428
+ * agent already carries that are not there (kept, marked `missing`, so a card never loses a
429
+ * skill the list forgot). A skill's id is its command — what `skill_open` and `fit` match on.
430
+ */
431
+ export function skillChoices(skills = [], { current = [] } = {}) {
432
+ const out = [];
433
+ const seen = new Set();
434
+ for (const s of Array.isArray(skills) ? skills : []) {
435
+ const id = String(s?.command || s?.name || '').trim();
436
+ if (!id || seen.has(id) || s?.enabled === false) continue;
437
+ seen.add(id);
438
+ out.push({ id, label: s.name && s.name !== id ? `${s.name} (/${id})` : `/${id}`, hint: String(s.description || '').slice(0, 120) });
439
+ }
440
+ for (const id of Array.isArray(current) ? current : []) if (id && !seen.has(id)) { seen.add(id); out.push({ id, label: id, hint: 'not among your skills now', missing: true }); }
441
+ return out;
442
+ }