very-happy-cli 0.2.133 → 0.2.135

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 (45) hide show
  1. package/dist/{AcpBackend-DgG8o4jd.cjs → AcpBackend-C0Bwx6bc.cjs} +3 -3
  2. package/dist/{AcpBackend-BQm7dNza.mjs → AcpBackend-CkDpu1n_.mjs} +3 -3
  3. package/dist/{AcpSessionManager-C7unPEZO.mjs → AcpSessionManager-DB_mfFaH.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DDFEmhQp.cjs → AcpSessionManager-DqvUp-UU.cjs} +1 -1
  5. package/dist/{agentGuidance-BaplwDcV.mjs → agentGuidance-7MByWxKj.mjs} +1 -1
  6. package/dist/{agentGuidance-BItqIZTf.cjs → agentGuidance-DlZM_YPV.cjs} +1 -1
  7. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  8. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  9. package/dist/{config-uLywzv5t.cjs → config-BO0ZHyoB.cjs} +4 -4
  10. package/dist/{config-BZ8xEnOv.mjs → config-DzvbcL_x.mjs} +4 -4
  11. package/dist/{index-Daga0ztv.cjs → index-BEpZjXs4.cjs} +15 -9
  12. package/dist/{index-ClCeQ1FW.mjs → index-C33JB4xB.mjs} +16 -10
  13. package/dist/{index-D8i2EViA.mjs → index-CpEdOGpe.mjs} +4 -4
  14. package/dist/{index-CdH3ecC5.cjs → index-DHI6Y1xW.cjs} +4 -4
  15. package/dist/{index-6Xm0CgN_.cjs → index-DNbKbe23.cjs} +1536 -1202
  16. package/dist/{index-EsR8ayIg.mjs → index-gefLe1Bk.mjs} +1534 -1200
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.mjs +4 -4
  19. package/dist/{installTerminalHooks-NH4kiB2E.mjs → installTerminalHooks-BWoHGCew.mjs} +4 -4
  20. package/dist/{installTerminalHooks-CKG0MYVK.cjs → installTerminalHooks-gmWKACNb.cjs} +4 -4
  21. package/dist/lib.cjs +2 -2
  22. package/dist/lib.d.cts +20 -60
  23. package/dist/lib.d.mts +20 -60
  24. package/dist/lib.mjs +2 -2
  25. package/dist/{limits-Du7S6jqA.mjs → limits-CPTgiQ9A.mjs} +8 -8
  26. package/dist/{limits-XRNzIlVm.cjs → limits-K17AgMHD.cjs} +8 -8
  27. package/dist/{mcp-CAMCuudA.cjs → mcp-CK1NCZm0.cjs} +4 -4
  28. package/dist/{mcp-D8zhjyZa.mjs → mcp-DhuxpwTB.mjs} +4 -4
  29. package/dist/{runGemini-Db4rX54F.mjs → runGemini-BVJE7LZl.mjs} +6 -6
  30. package/dist/{runGemini-BIVHp0ST.cjs → runGemini-n0wD2Z9R.cjs} +6 -6
  31. package/dist/{runOpenClaw-D2MceXPO.mjs → runOpenClaw-CJaIYpB7.mjs} +5 -5
  32. package/dist/{runOpenClaw-a0f2pZZp.cjs → runOpenClaw-fYx1XweW.cjs} +5 -5
  33. package/dist/{send-DKbumEcg.cjs → send-WlJ4r3U2.cjs} +4 -4
  34. package/dist/{send-1Wjb0lIh.mjs → send-fWHOFJV9.mjs} +4 -4
  35. package/dist/{sessions-DXCCRDb5.mjs → sessions-5fJxQ2xj.mjs} +4 -4
  36. package/dist/{sessions-SIFOOlGp.cjs → sessions-Cx_3cvN6.cjs} +4 -4
  37. package/dist/{spawn-Bj3W6HR4.mjs → spawn-CF1ZV-YH.mjs} +4 -4
  38. package/dist/{spawn-C-XpSPLX.cjs → spawn-DVJtNwWN.cjs} +4 -4
  39. package/dist/{teams-ChFzXAqr.cjs → teams-BbYi0fWG.cjs} +4 -4
  40. package/dist/{teams-CqhFQLVC.mjs → teams-gzvKrMfn.mjs} +4 -4
  41. package/dist/{todo-B0gbQHH8.mjs → todo-BAK2DVLV.mjs} +3 -3
  42. package/dist/{todo-o0clp-_I.cjs → todo-DHY20tpD.cjs} +3 -3
  43. package/dist/{types-6DrEgHVx.mjs → types-BEXGahmj.mjs} +128 -35
  44. package/dist/{types-CcjVmS2i.cjs → types-DjAHWb4J.cjs} +129 -35
  45. package/package.json +8 -8
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var axios = require('axios');
4
- var persistence = require('./limits-XRNzIlVm.cjs');
4
+ var persistence = require('./limits-K17AgMHD.cjs');
5
5
  var node_events = require('node:events');
6
6
  var socket_ioClient = require('socket.io-client');
7
7
  var z = require('zod');
@@ -53,7 +53,8 @@ const sessionRoleSchema = z__namespace.enum(["user", "agent"]);
53
53
  const sessionTextEventSchema = z__namespace.object({
54
54
  t: z__namespace.literal("text"),
55
55
  text: z__namespace.string(),
56
- thinking: z__namespace.boolean().optional()
56
+ thinking: z__namespace.boolean().optional(),
57
+ actualModel: z__namespace.string().optional()
57
58
  });
58
59
  const sessionServiceMessageEventSchema = z__namespace.object({
59
60
  t: z__namespace.literal("service"),
@@ -800,6 +801,25 @@ const ContextUsageSchema = z.z.object({
800
801
  contextWindow: z.z.number().finite().positive(),
801
802
  updatedAt: z.z.number().finite().nonnegative()
802
803
  });
804
+ const AgentVersionsSchema = z.z.object({
805
+ checkedAt: z.z.number(),
806
+ daemonPid: z.z.number(),
807
+ agents: z.z.array(z.z.object({
808
+ id: z.z.string(),
809
+ installed: z.z.string().nullable(),
810
+ latest: z.z.string().nullable(),
811
+ status: z.z.string()
812
+ }))
813
+ });
814
+ const AGENT_VERSION_SOURCES = [
815
+ { id: "claude", label: "Claude Code", command: "claude", package: "@anthropic-ai/claude-code", docs: "https://code.claude.com/docs/en/setup" },
816
+ { id: "codex", label: "Codex", command: "codex", package: "@openai/codex", docs: "https://github.com/openai/codex#installation-and-usage" },
817
+ { id: "pi", label: "Pi", command: "pi", package: "@mariozechner/pi-coding-agent", docs: "https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent" },
818
+ { id: "gemini", label: "Gemini CLI", command: "gemini", package: "@google/gemini-cli", docs: "https://github.com/google-gemini/gemini-cli#installation" },
819
+ { id: "opencode", label: "OpenCode", command: "opencode", package: "opencode-ai", docs: "https://opencode.ai/docs/" },
820
+ { id: "openclaw", label: "OpenClaw", command: "openclaw", package: "openclaw", docs: "https://docs.openclaw.ai/install/updating" },
821
+ { id: "claude-sdk", label: "Claude Agent SDK", command: null, package: "@anthropic-ai/claude-agent-sdk", docs: "https://github.com/anthropics/claude-agent-sdk-typescript" }
822
+ ];
803
823
 
804
824
  z.z.object({
805
825
  teamsVersion: z.z.number().int().positive().optional(),
@@ -879,6 +899,8 @@ const WebTerminalListItemSchema = z.z.object({
879
899
  createdAt: z.z.number().optional(),
880
900
  activityAt: z.z.number().optional(),
881
901
  agentState: z.z.enum(["working", "needs_input", "idle", "shell"]).optional(),
902
+ agentKind: z.z.string().optional(),
903
+ agentObservedAt: z.z.number().optional(),
882
904
  /** B-105: mirror session of the claude running inside the terminal. */
883
905
  mirrorSessionId: z.z.string().optional(),
884
906
  /** B-150: auto-restored after a restart (ms epoch) — the directory and the
@@ -909,6 +931,8 @@ const ClosedTerminalRecordSchema = z.z.object({
909
931
  closedAt: z.z.number()
910
932
  });
911
933
  z.z.object({
934
+ agentVersions: AgentVersionsSchema.optional(),
935
+ agentVersionEpoch: z.z.number().optional(),
912
936
  status: z.z.union([
913
937
  z.z.enum(["running", "shutting-down"]),
914
938
  z.z.string()
@@ -1345,7 +1369,7 @@ class RpcHandlerManager {
1345
1369
  }
1346
1370
  }
1347
1371
 
1348
- const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-CcjVmS2i.cjs', document.baseURI).href))));
1372
+ const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-DjAHWb4J.cjs', document.baseURI).href))));
1349
1373
  function projectPath() {
1350
1374
  const path$1 = path.resolve(__dirname$1, "..");
1351
1375
  return path$1;
@@ -2446,7 +2470,12 @@ function mapTaskLifecycleSystemMessage(message, state, envelopes) {
2446
2470
  if (!subtype.startsWith("task_")) return;
2447
2471
  if (raw.skip_transcript === true) return;
2448
2472
  const toolUseId = typeof raw.tool_use_id === "string" && raw.tool_use_id.length > 0 ? raw.tool_use_id : null;
2449
- if (!toolUseId || !getAgentToolCalls(state).has(toolUseId)) return;
2473
+ if (!toolUseId) return;
2474
+ if (subtype === "task_started" && raw.task_type === "local_bash" && state.bashToolCalls?.has(toolUseId)) {
2475
+ (state.backgroundBashCalls ??= /* @__PURE__ */ new Set()).add(toolUseId);
2476
+ }
2477
+ const backgroundBash = state.backgroundBashCalls?.has(toolUseId) === true;
2478
+ if (!getAgentToolCalls(state).has(toolUseId) && !backgroundBash) return;
2450
2479
  const subagent = ensureSessionSubagentIdForProviderSubagent(state, toolUseId);
2451
2480
  const usage = raw.usage && typeof raw.usage === "object" ? {
2452
2481
  ...typeof raw.usage.tool_uses === "number" ? { toolUses: raw.usage.tool_uses } : {},
@@ -2454,7 +2483,7 @@ function mapTaskLifecycleSystemMessage(message, state, envelopes) {
2454
2483
  ...typeof raw.usage.duration_ms === "number" ? { durationMs: raw.usage.duration_ms } : {}
2455
2484
  } : void 0;
2456
2485
  if (subtype === "task_started") {
2457
- rememberSubagentMeta(state, subagent, { description: raw.description, subagentType: raw.subagent_type });
2486
+ rememberSubagentMeta(state, subagent, { description: raw.description, subagentType: backgroundBash ? "background-command" : raw.subagent_type });
2458
2487
  const turn = ensureTurn(state, envelopes);
2459
2488
  getStartedSubagents(state).delete(subagent);
2460
2489
  maybeEmitSubagentStart(state, turn, subagent, envelopes);
@@ -2543,14 +2572,15 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2543
2572
  blockCursor = state.streamBlockCursor ?? 0;
2544
2573
  state.streamBlockCursor = blockCursor + blocks.length;
2545
2574
  }
2575
+ const actualModel = subagent && typeof message.message?.model === "string" && message.message.model.trim() ? { actualModel: message.message.model.trim() } : {};
2546
2576
  const draftKey = (index) => apiMessageId ? streamKeyOf(apiMessageId, blockCursor + index) : void 0;
2547
2577
  for (const [blockIndex, block] of blocks.entries()) {
2548
2578
  if (block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) {
2549
- envelopes.push(createEnvelope("agent", { t: "text", text: block.text }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2579
+ envelopes.push(createEnvelope("agent", { t: "text", text: block.text, ...actualModel }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2550
2580
  continue;
2551
2581
  }
2552
2582
  if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim().length > 0) {
2553
- envelopes.push(createEnvelope("agent", { t: "text", text: block.thinking, thinking: true }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2583
+ envelopes.push(createEnvelope("agent", { t: "text", text: block.thinking, thinking: true, ...actualModel }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2554
2584
  continue;
2555
2585
  }
2556
2586
  if (block.type === "tool_use") {
@@ -2578,7 +2608,8 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2578
2608
  }
2579
2609
  continue;
2580
2610
  }
2581
- const args = isSubagentTool(name) ? { ...baseArgs, sessionSubagent: sessionSubagentForCall } : baseArgs;
2611
+ if (name === "Bash") (state.bashToolCalls ??= /* @__PURE__ */ new Set()).add(call);
2612
+ const args = isSubagentTool(name) || name === "Bash" ? { ...baseArgs, sessionSubagent: sessionSubagentForCall } : baseArgs;
2582
2613
  envelopes.push(createEnvelope("agent", {
2583
2614
  t: "tool-call-start",
2584
2615
  call,
@@ -2679,7 +2710,7 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2679
2710
  continue;
2680
2711
  }
2681
2712
  }
2682
- if (sessionSubagentForToolResult) {
2713
+ if (sessionSubagentForToolResult && !state.backgroundBashCalls?.has(block.tool_use_id)) {
2683
2714
  maybeEmitSubagentStop(state, turnId, sessionSubagentForToolResult, envelopes);
2684
2715
  }
2685
2716
  }
@@ -4359,6 +4390,58 @@ function registerTodoHandlers(manager) {
4359
4390
  });
4360
4391
  }
4361
4392
 
4393
+ function codingAgentFromProcessTree(panePid, snapshot) {
4394
+ const rows = snapshot.split("\n").flatMap((line) => {
4395
+ const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.+)$/);
4396
+ return m ? [{ pid: Number(m[1]), parent: Number(m[2]), command: m[3] }] : [];
4397
+ });
4398
+ const visit = (pid, seen = /* @__PURE__ */ new Set()) => {
4399
+ if (seen.has(pid)) return void 0;
4400
+ seen.add(pid);
4401
+ const row = rows.find((r) => r.pid === pid);
4402
+ if (!row) return void 0;
4403
+ for (const child of rows.filter((r) => r.parent === pid)) {
4404
+ const kind = visit(child.pid, seen);
4405
+ if (kind) return kind;
4406
+ }
4407
+ const words = row.command.split(/\s+/);
4408
+ const executable = words[0].split("/").pop();
4409
+ const candidate = executable === "node" ? words[1]?.split("/").pop() : executable;
4410
+ if (candidate === "pi" || candidate === "pi-coding-agent") return "pi";
4411
+ if (candidate === "codex") return "codex";
4412
+ if (candidate === "claude" || /^\d+\.\d+\.\d+$/.test(candidate ?? "")) return "claude";
4413
+ return void 0;
4414
+ };
4415
+ return visit(panePid);
4416
+ }
4417
+
4418
+ const shells = /* @__PURE__ */ new Set(["zsh", "bash", "fish", "sh", "dash", "ksh", "tcsh", "csh"]);
4419
+ function classifyAgentPane(command, tail) {
4420
+ const cmd = command.trim().replace(/^-/, "").split(/[\\/]/).pop().toLowerCase();
4421
+ const lines = tail.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\r/g, "").trimEnd().split("\n");
4422
+ const screen = lines.join("\n");
4423
+ const footer = lines.slice(-20).join("\n");
4424
+ if (shells.has(cmd)) return { agentState: "shell" };
4425
+ if (!["node", "claude", "codex", "pi"].includes(cmd) && !/^\d+\.\d+\.\d+$/.test(cmd)) return {};
4426
+ const agentKind = cmd === "pi" ? "pi" : cmd === "codex" ? "codex" : cmd === "claude" || /^\d+\.\d+\.\d+$/.test(cmd) ? "claude" : /OpenAI Codex/.test(screen) ? "codex" : /(?:^|\n)\s*pi v\d|Pi can explain its own features/.test(screen) ? "pi" : /Claude Code|tell Claude what to do|bypass permissions on|⏵⏵|[·✻✽✶✳] [^\n]+… \(esc to interrupt\)/.test(footer) ? "claude" : void 0;
4427
+ if (!agentKind) return {};
4428
+ const result = (agentState) => ({ agentKind, agentState });
4429
+ if (agentKind === "pi") {
4430
+ if (/^\s*→ \S/m.test(screen) && /(?:↑↓ navigate.*(?:enter|return) select.*esc cancel|Enter to select.*Esc to cancel)/i.test(footer)) return result("needs_input");
4431
+ if (/^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◓◑◒]\s+(?:Working|Thinking|Retrying|Compacting context|Auto-compacting|Summarizing branch).*$/m.test(footer)) return result("working");
4432
+ if (/^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◓◑◒]\s/m.test(footer)) return result();
4433
+ const editorRules = footer.match(/^\s*─{8,}\s*$/gm) ?? [];
4434
+ if (editorRules.length >= 2 && /(?:\d+(?:\.\d+)?|\?)%\/\d+(?:\.\d+)?[km]?.* • (?:thinking off|minimal|low|medium|high|xhigh)/i.test(footer)) return result("idle");
4435
+ return result();
4436
+ }
4437
+ const choice = /^[\s│]*[❯›>]\s*\d+\.\s/m.test(footer);
4438
+ if (choice && /Do you want|Would you like|approve|Yes,|Yes\b|Enter to select.*navigate.*Esc to cancel/i.test(footer)) return result("needs_input");
4439
+ if (/esc to interrupt/i.test(footer)) return result("working");
4440
+ if (agentKind === "codex" && /^\s*gpt-[\w.-]+ (?:minimal|low|medium|high|xhigh|fast)[^\n]* · (?:~|\/|[A-Z]:[\\/])/m.test(footer) && /^› /m.test(footer)) return result("idle");
4441
+ if (/\? for shortcuts|bypass permissions on|⏵⏵|\d+% context left/.test(footer)) return result("idle");
4442
+ return result();
4443
+ }
4444
+
4362
4445
  function sanitizeGeometry(cols, rows) {
4363
4446
  const ok = (v) => typeof v === "number" && Number.isInteger(v) && v >= 2 && v <= 1e4;
4364
4447
  return ok(cols) && ok(rows) ? { cols, rows } : void 0;
@@ -9568,10 +9651,12 @@ class TerminalNotifyTracker {
9568
9651
  * Feed one observation for a terminal. Returns the notification event this
9569
9652
  * observation triggers, or null. Call once per terminal per tick.
9570
9653
  */
9571
- observe(terminalId, state, now) {
9654
+ observe(terminalId, state, now, agentKind) {
9572
9655
  let t = this.tracks.get(terminalId);
9573
- if (!t) {
9656
+ if (!t || t.agentKind !== agentKind || now - t.lastObservedAt > 45e3) {
9574
9657
  t = {
9658
+ agentKind,
9659
+ lastObservedAt: now,
9575
9660
  armed: state === "working",
9576
9661
  confirmed: state,
9577
9662
  candidate: state,
@@ -9582,6 +9667,7 @@ class TerminalNotifyTracker {
9582
9667
  this.tracks.set(terminalId, t);
9583
9668
  return null;
9584
9669
  }
9670
+ t.lastObservedAt = now;
9585
9671
  if (state === "working") t.armed = true;
9586
9672
  if (state === t.confirmed) {
9587
9673
  t.candidate = state;
@@ -9914,6 +10000,8 @@ function terminalListSignature(items) {
9914
10000
  t.createdAt ?? 0,
9915
10001
  Math.floor((t.activityAt ?? 0) / ACTIVITY_SIGNATURE_BUCKET_MS),
9916
10002
  t.agentState ?? "",
10003
+ t.agentKind ?? "",
10004
+ Math.floor((t.agentObservedAt ?? 0) / 1e4),
9917
10005
  // B-105: a mirror binding appearing/disappearing MUST push the
9918
10006
  // list, or the web never learns the toggle became available.
9919
10007
  t.mirrorSessionId ?? "",
@@ -9962,7 +10050,7 @@ const LIST_SESSIONS_FORMAT = [
9962
10050
  // command only separates shell/idle). MUST stay before pane_title: that
9963
10051
  // field is deliberately last so a pathological 0x1f inside a title can only
9964
10052
  // garble the title, never shift the fields.
9965
- "#{pane_current_command}",
10053
+ "#{pane_current_command} #{pane_pid}",
9966
10054
  "#{pane_title}"
9967
10055
  ].join(LIST_FIELD_SEP);
9968
10056
  function parseSessionListLine(line) {
@@ -9980,7 +10068,8 @@ function parseSessionListLine(line) {
9980
10068
  vhTitle: vhTitle.trim() || void 0,
9981
10069
  manual: manual.trim().length > 0,
9982
10070
  tags: parseTerminalTags(vhTags),
9983
- paneCurrentCommand: paneCommand.trim() || void 0,
10071
+ paneCurrentCommand: paneCommand.trim().replace(/ \d+$/, "") || void 0,
10072
+ .../ (\d+)$/.test(paneCommand) ? { panePid: Number(paneCommand.match(/ (\d+)$/)[1]) } : {},
9984
10073
  // Verbatim (no trim): tmux allows edge spaces in a session name and the
9985
10074
  // restore lookup / attach echo compare it exactly.
9986
10075
  attachTmux: isSafeTmuxSessionName(vhAttach) ? vhAttach : void 0,
@@ -10022,22 +10111,6 @@ function parseTerminalTags(raw) {
10022
10111
  return [];
10023
10112
  }
10024
10113
  }
10025
- function classifyPane(currentCommand, tail) {
10026
- const cmd = (currentCommand || "").trim().replace(/^-/, "").toLowerCase();
10027
- const isShell = SHELL_COMMANDS.has(cmd);
10028
- const lines = tail.replace(/\r/g, "").split("\n").map((l) => l.trimEnd());
10029
- while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
10030
- const text = lines.join("\n");
10031
- const last15 = lines.slice(-15).join("\n");
10032
- const hasDialog = last15.includes("Do you want") || last15.includes("Would you like to proceed") || /^[\s│]*[❯>]\s*1\.\s/m.test(last15) || /\(y\/n\)/i.test(last15);
10033
- if (hasDialog) return "needs_input";
10034
- if (text.includes("esc to interrupt")) return "working";
10035
- const looksLikeClaude = looksLikeClaudeCommand(cmd);
10036
- const hasIdleFooter = text.includes("? for shortcuts") || text.includes("bypass permissions on") || text.includes("\u23F5\u23F5");
10037
- if (looksLikeClaude || hasIdleFooter) return "idle";
10038
- if (isShell) return "shell";
10039
- return void 0;
10040
- }
10041
10114
  function looksLikeClaudeCommand(currentCommand) {
10042
10115
  const cmd = (currentCommand || "").trim().replace(/^-/, "").toLowerCase();
10043
10116
  return cmd === "claude" || cmd === "node" || /^\d+\.\d+(\.\d+)?$/.test(cmd);
@@ -11048,7 +11121,7 @@ class WebTerminalManager {
11048
11121
  const now = Date.now();
11049
11122
  this.notifyTracker.prune(list.map((t) => t.id));
11050
11123
  for (const item of list) {
11051
- const event = this.notifyTracker.observe(item.id, item.agentState, now);
11124
+ const event = this.notifyTracker.observe(item.id, item.agentState, now, item.agentKind);
11052
11125
  if (!event) continue;
11053
11126
  try {
11054
11127
  this.onNotify({ terminalId: item.id, title: item.title || "Terminal", event });
@@ -11780,6 +11853,7 @@ class WebTerminalManager {
11780
11853
  if (r.status !== 0 || !r.stdout) return [];
11781
11854
  const hostname = os$1.hostname();
11782
11855
  const out = [];
11856
+ let processSnapshot;
11783
11857
  for (const line of r.stdout.split("\n")) {
11784
11858
  const s = parseSessionListLine(line);
11785
11859
  if (!s || !s.name.startsWith("vh-")) continue;
@@ -11797,7 +11871,15 @@ class WebTerminalManager {
11797
11871
  } catch {
11798
11872
  }
11799
11873
  }
11800
- const probe = this.probeAgentState(s.name, s.paneCurrentCommand);
11874
+ let identity = s.paneCurrentCommand;
11875
+ if (identity === "node" && s.panePid) {
11876
+ if (processSnapshot === void 0) {
11877
+ const ps = node_child_process.spawnSync("ps", ["-axo", "pid=,ppid=,args="], { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS });
11878
+ processSnapshot = ps.status === 0 ? ps.stdout : "";
11879
+ }
11880
+ identity = codingAgentFromProcessTree(s.panePid, processSnapshot) ?? identity;
11881
+ }
11882
+ const probe = this.probeAgentState(s.name, identity);
11801
11883
  out.push({
11802
11884
  id,
11803
11885
  title,
@@ -11809,6 +11891,8 @@ class WebTerminalManager {
11809
11891
  // simply omit it and web clients fall back to createdAt.
11810
11892
  activityAt: s.activity,
11811
11893
  agentState: probe.agentState,
11894
+ agentKind: probe.agentKind,
11895
+ agentObservedAt: probe.agentObservedAt,
11812
11896
  // Daemon-internal (not pushed to web / not in the signature):
11813
11897
  // the mirror reconciler's stricter "claude is really here" gate.
11814
11898
  claudeConfident: probe.claudeConfident,
@@ -11835,16 +11919,17 @@ class WebTerminalManager {
11835
11919
  if (live) {
11836
11920
  try {
11837
11921
  const { command, tail } = live.agentProbeInput();
11838
- return { agentState: classifyPane(command || polledCommand || "", tail), claudeConfident: isClaudeConfident(tail) };
11922
+ const observation = classifyAgentPane(command === "node" ? polledCommand || command : command || polledCommand || "", tail);
11923
+ return { ...observation, agentObservedAt: Date.now(), claudeConfident: observation.agentKind === "claude" && isClaudeConfident(tail) };
11839
11924
  } catch {
11840
11925
  }
11841
11926
  }
11842
- return this.probeAgentStateViaTmux(sessionName);
11927
+ return this.probeAgentStateViaTmux(sessionName, polledCommand);
11843
11928
  }
11844
11929
  /** Fallback probe for sessions with no live headless: 2 short tmux calls
11845
11930
  * (foreground command + pane tail) fed into classifyPane. Any failure or
11846
11931
  * timeout → undefined (the field is omitted), never an error. */
11847
- probeAgentStateViaTmux(sessionName) {
11932
+ probeAgentStateViaTmux(sessionName, polledCommand) {
11848
11933
  try {
11849
11934
  const cmd = node_child_process.spawnSync(
11850
11935
  "tmux",
@@ -11858,7 +11943,8 @@ class WebTerminalManager {
11858
11943
  { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11859
11944
  );
11860
11945
  if (cap.status !== 0 || typeof cap.stdout !== "string") return { agentState: void 0, claudeConfident: false };
11861
- return { agentState: classifyPane(cmd.stdout.trim(), cap.stdout), claudeConfident: isClaudeConfident(cap.stdout) };
11946
+ const observation = classifyAgentPane(cmd.stdout.trim() === "node" ? polledCommand || "node" : cmd.stdout.trim(), cap.stdout);
11947
+ return { ...observation, agentObservedAt: Date.now(), claudeConfident: observation.agentKind === "claude" && isClaudeConfident(cap.stdout) };
11862
11948
  } catch {
11863
11949
  return { agentState: void 0, claudeConfident: false };
11864
11950
  }
@@ -12994,6 +13080,10 @@ class CodexAppServerClient {
12994
13080
  if (!item || typeof item !== "object") {
12995
13081
  return method.startsWith("item/");
12996
13082
  }
13083
+ if ((method === "item/started" || method === "item/completed") && (item.type === "collabAgentToolCall" || item.type === "subAgentActivity")) {
13084
+ this.eventHandler?.({ type: "collab_agent_item", item });
13085
+ return true;
13086
+ }
12997
13087
  if (method === "item/started" && item.type === "commandExecution") {
12998
13088
  const callId = typeof item.id === "string" ? item.id : "";
12999
13089
  this.eventHandler?.({
@@ -14104,6 +14194,8 @@ class ApiMachineClient {
14104
14194
  * stale" so the FIRST keep-alive tick always probes and populates the
14105
14195
  * machine metadata that a connecting/reconnecting web client reads. */
14106
14196
  lastCliProbeAt = Date.now() - CLI_AVAILABILITY_RECHECK_MS;
14197
+ /** Stable across socket reconnects; a new client/run invalidates old version checks. */
14198
+ agentVersionEpoch = Date.now();
14107
14199
  cliUpdateState = null;
14108
14200
  cliUpdatePushChain = Promise.resolve();
14109
14201
  claudeAuthState = null;
@@ -14864,6 +14956,7 @@ class ApiMachineClient {
14864
14956
  pid: process.pid,
14865
14957
  httpPort: this.machine.daemonState?.httpPort,
14866
14958
  startedAt: now,
14959
+ agentVersionEpoch: this.agentVersionEpoch,
14867
14960
  webTerminals: { updatedAt: now, terminals: initialTerminals },
14868
14961
  // B-265 capability flag; restamped every connect so the
14869
14962
  // web's `detectedAt >= startedAt` trust rule holds.
@@ -16228,6 +16321,7 @@ const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
16228
16321
  }).passthrough()
16229
16322
  ]);
16230
16323
 
16324
+ exports.AGENT_VERSION_SOURCES = AGENT_VERSION_SOURCES;
16231
16325
  exports.ApiClient = ApiClient;
16232
16326
  exports.ApiSessionClient = ApiSessionClient;
16233
16327
  exports.AsyncLock = AsyncLock;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "very-happy-cli",
3
- "version": "0.2.133",
3
+ "version": "0.2.135",
4
4
  "description": "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "contributors": [
@@ -79,7 +79,7 @@
79
79
  },
80
80
  "dependencies": {
81
81
  "@agentclientprotocol/sdk": "^0.14.1",
82
- "@anthropic-ai/claude-agent-sdk": "^0.3.232",
82
+ "@anthropic-ai/claude-agent-sdk": "0.3.267",
83
83
  "@anthropic-ai/sandbox-runtime": "^0.0.37",
84
84
  "@modelcontextprotocol/sdk": "1.30.0",
85
85
  "@noble/ed25519": "^3.0.0",
@@ -113,12 +113,12 @@
113
113
  "zod": "^4.0.0"
114
114
  },
115
115
  "optionalDependencies": {
116
- "very-happy-tools-arm64-darwin": "0.2.133",
117
- "very-happy-tools-x64-darwin": "0.2.133",
118
- "very-happy-tools-arm64-linux": "0.2.133",
119
- "very-happy-tools-x64-linux": "0.2.133",
120
- "very-happy-tools-arm64-win32": "0.2.133",
121
- "very-happy-tools-x64-win32": "0.2.133"
116
+ "very-happy-tools-arm64-darwin": "0.2.135",
117
+ "very-happy-tools-x64-darwin": "0.2.135",
118
+ "very-happy-tools-arm64-linux": "0.2.135",
119
+ "very-happy-tools-x64-linux": "0.2.135",
120
+ "very-happy-tools-arm64-win32": "0.2.135",
121
+ "very-happy-tools-x64-win32": "0.2.135"
122
122
  },
123
123
  "devDependencies": {
124
124
  "@slopus/happy-wire": "workspace:*",