@chatpanel/events 0.89.2 → 0.89.4

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
@@ -212,7 +212,7 @@ export { DEFAULT_MIN_CALLS, cardOverride, applyCard } from './model-candidates.j
212
212
  export { SCM_KINDS, validateConnection, normalizeConnection, parseRemote, connectionFor, branchFor, worktreeDirFor, credentialEnv, describeConnection, blankConnection, connectionFromForm } from './scm-connection.js';
213
213
  export { messagesFor, mergeTranscript, clipTranscript, clipMessage, newSteps, continuationNote, createControl, STEP_MAX_CHARS, TASK_TRANSCRIPT_MAX_CHARS } from './team-task.js';
214
214
  export { runTeam, resumeTeam, dryRunTeam, isModelUnavailable, TeamRunError, RUN_STATUSES } from './team-run.js';
215
- export { teamToolProvider, teamToolSpec, describeTeamForApproval, TEAM_TOOL_NAME } from './team-tool.js';
215
+ export { teamToolProvider, teamToolSpec, teamToolTimeoutMs, describeTeamForApproval, TEAM_TOOL_NAME } from './team-tool.js';
216
216
  export { teamLine, teamLanes } from './team-trail.js';
217
217
  export { mcpDispatchProvider, MCP_TOOL_NAME } from './mcp-dispatch.js';
218
218
  export { createManifest, ManifestError, SOURCES } from './manifest.js';
package/mcp-client.js CHANGED
@@ -111,7 +111,7 @@ export class McpClient {
111
111
  'Otherwise check that the command + args run in a terminal.',
112
112
  );
113
113
  }
114
- throw new Error(`Can't reach the ChatPanel Bridge for local MCP (${e.message}). Install ChatPanel — the gateway brings the bridge (`npm i -g @chatpanel/gateway`, then `chatpanel-gateway --install`) — or start one with \`npx @chatpanel/bridge\`.`);
114
+ throw new Error(`Can't reach the ChatPanel Bridge for local MCP (${e.message}). Install ChatPanel — the gateway brings the bridge (\`npm i -g @chatpanel/gateway\`, then \`chatpanel-gateway --install\`) — or start one with \`npx @chatpanel/bridge\`.`);
115
115
  }
116
116
  if (message.id == null) return null; // notification → 202, no body
117
117
  if (!res.ok) throw new Error(`Bridge MCP HTTP ${res.status}: ${(await res.text().catch(() => '')).slice(0, 200)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/events",
3
- "version": "0.89.2",
3
+ "version": "0.89.4",
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",
package/team-board.js CHANGED
@@ -74,7 +74,9 @@ export function parseFindings(text, { role, taskId } = {}) {
74
74
  // ── threads, posts, asks ────────────────────────────────────────────────────────────────
75
75
 
76
76
  export const THREAD_KINDS = Object.freeze(['task', 'ask', 'discussion', 'proposal']);
77
- export const THREAD_STATUSES = Object.freeze(['open', 'waiting', 'resolved', 'approved', 'rejected']);
77
+ // `failed`: the task behind the thread ended without an answer (every model on the roster
78
+ // tried, or a hard error) — not `resolved`, which read as "done" on the board.
79
+ export const THREAD_STATUSES = Object.freeze(['open', 'waiting', 'resolved', 'failed', 'approved', 'rejected']);
78
80
  export const POST_KINDS = Object.freeze(['finding', 'note', 'question', 'answer', 'draft', 'decision']);
79
81
  export const POST_STATUSES = Object.freeze(['open', 'proposed', 'approved', 'rejected']);
80
82
  export const ASK_TYPES = Object.freeze(['info', 'budget', 'permission', 'direction']);
package/team-run.js CHANGED
@@ -58,7 +58,11 @@ export function isModelUnavailable(error) {
58
58
  // Also: a relayed agent that exited, a provider that closed the stream, and a turn that
59
59
  // came back with nothing — the model did not answer, and the next one might. A refusal,
60
60
  // a timeout the caller set, a bad request or a budget stop are not this.
61
- return /model[_ ]not[_ ]found|not found|not deployed|inaccessible|does not exist|no such model|unknown model|unsupported model|not available|unavailable|no api key|not configured|"status":\s*(404|401|403|500|502|503)\b|\b(404|401|403|502|503)\b|exited \d+|returned no answer|did not answer|closed the connection|couldn't reach|could not reach|ECONNREFUSED|overloaded|capacity/i.test(m);
61
+ // A server that is not there: Node says ECONNREFUSED, a browser says only "Failed to
62
+ // fetch" (the extension reports it as "network error") — a local model killed mid-run
63
+ // arrived as the latter and ended the task on its first attempt with Claude Code sitting
64
+ // idle on the roster.
65
+ return /model[_ ]not[_ ]found|not found|not deployed|inaccessible|does not exist|no such model|unknown model|unsupported model|not available|unavailable|no api key|not configured|"status":\s*(404|401|403|500|502|503)\b|\b(404|401|403|502|503)\b|exited \d+|returned no answer|did not answer|closed the connection|couldn't reach|could not reach|ECONNREFUSED|ECONNRESET|ENOTFOUND|EHOSTUNREACH|socket hang up|fetch failed|failed to fetch|load failed|network ?error|overloaded|capacity/i.test(m);
62
66
  }
63
67
 
64
68
  export function dryRunTeam(team, request, { appoint = null } = {}) {
@@ -355,7 +359,13 @@ export async function runTeam({
355
359
  const findings = status === 'ok' ? parseFindings(text, { role: role.id, taskId: task.id }) : [];
356
360
  if (findings.length) { board.add(findings); for (const f of findings) say('task.finding', { taskId: task.id, role: role.id, finding: f }); }
357
361
  const thread = board.threadForTask(task.id);
358
- if (thread && status !== 'waiting') board.setThreadStatus(thread.id, 'resolved');
362
+ // The thread says how the task ended. A failure is posted in it as well — a person reading
363
+ // the board sees "researcher failed: network error" where it happened, and what was tried.
364
+ if (thread && status === 'ok') board.setThreadStatus(thread.id, 'resolved');
365
+ else if (thread && status !== 'waiting') {
366
+ board.post({ threadId: thread.id, by: RUNNER, kind: 'note', text: `${role.id} ${status === 'over-budget' ? 'stopped at the budget' : 'failed'}${error ? `: ${String(error).slice(0, 300)}` : ''}${attempts.length > 1 ? ` (after ${attempts.length} models: ${attempts.map((a) => a.model).join(', ')})` : ''}.` });
367
+ board.setThreadStatus(thread.id, 'failed');
368
+ }
359
369
  const row = { id: task.id, role: task.role, title: task.title, status, text, error, usage, ms: now() - t0, findings, transcript: clipTranscript(transcript), attempts, ...(routed ? { routed } : {}), ...(scm ? { scm } : {}), ...(askedAndWaiting ? { waitingOn: askedAndWaiting } : {}) };
360
370
  tasksOut.push(row);
361
371
  say(status === 'ok' ? 'task.done' : 'task.failed', { taskId: task.id, role: task.role, status, error, ms: row.ms, findings: findings.length, ...(askedAndWaiting ? { threadId: askedAndWaiting } : {}) });
package/team-tool.js CHANGED
@@ -26,10 +26,23 @@ function catalogue(teams) {
26
26
  return `Saved teams: ${list.map((t) => `${t.name} (${(t.roles || []).map((r) => r.id).join(', ')})${t.description ? ` — ${t.description}` : ''}`).join('; ')}.`;
27
27
  }
28
28
 
29
+ /**
30
+ * How long one call of the tool may take: the longest budget among the teams plus the merge,
31
+ * never under two minutes. A relay between a CLI agent and this tool (the bridge's MCP
32
+ * server) times a call by this; without it a 300 s team hit the relay's 120 s default,
33
+ * Claude Code was told "tool call timed out" and ran the team AGAIN while the first run was
34
+ * still working.
35
+ */
36
+ export function teamToolTimeoutMs(teams) {
37
+ const longest = Math.max(0, ...(teams || []).map((t) => Number(t?.budget?.ms) || 0));
38
+ return Math.max(120_000, (longest || 10 * 60_000) + 60_000);
39
+ }
40
+
29
41
  export function teamToolSpec(teams) {
30
42
  return {
31
43
  name: TEAM_TOOL_NAME,
32
44
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
45
+ timeoutMs: teamToolTimeoutMs(teams),
33
46
  description:
34
47
  `Saved agent teams — several roles working a request in parallel, merged into one answer. ${catalogue(teams)} `
35
48
  + 'Actions: {"action":"run","name":"<team>","request":"<what to do>"} runs one (streams; may take a while); '