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,9 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { D as DEFAULT_RECENT_LIMIT, M as MAX_READ_LIMIT, l as listAccountSessions, e as listSessions, r as readSessionTranscript, f as stopSession, g as resolvePermissionRequest, h as archiveSession } from './index-EsR8ayIg.mjs';
3
- import { i as isValidSessionId } from './types-6DrEgHVx.mjs';
2
+ import { D as DEFAULT_RECENT_LIMIT, M as MAX_READ_LIMIT, l as listAccountSessions, e as listSessions, r as readSessionTranscript, f as stopSession, g as resolvePermissionRequest, h as archiveSession } from './index-gefLe1Bk.mjs';
3
+ import { i as isValidSessionId } from './types-BEXGahmj.mjs';
4
4
  import 'node:os';
5
5
  import 'node:crypto';
6
- import './limits-Du7S6jqA.mjs';
6
+ import './limits-CPTgiQ9A.mjs';
7
7
  import 'node:fs/promises';
8
8
  import 'node:fs';
9
9
  import 'node:path';
@@ -12,7 +12,7 @@ import 'tweetnacl';
12
12
  import 'node:util';
13
13
  import 'cross-spawn';
14
14
  import 'node:readline';
15
- import './agentGuidance-BaplwDcV.mjs';
15
+ import './agentGuidance-7MByWxKj.mjs';
16
16
  import 'fs/promises';
17
17
  import 'ink';
18
18
  import 'react';
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var index = require('./index-6Xm0CgN_.cjs');
5
- var api = require('./types-CcjVmS2i.cjs');
4
+ var index = require('./index-DNbKbe23.cjs');
5
+ var api = require('./types-DjAHWb4J.cjs');
6
6
  require('node:os');
7
7
  require('node:crypto');
8
- require('./limits-XRNzIlVm.cjs');
8
+ require('./limits-K17AgMHD.cjs');
9
9
  require('node:fs/promises');
10
10
  require('node:fs');
11
11
  require('node:path');
@@ -14,7 +14,7 @@ require('tweetnacl');
14
14
  require('node:util');
15
15
  require('cross-spawn');
16
16
  require('node:readline');
17
- require('./agentGuidance-BItqIZTf.cjs');
17
+ require('./agentGuidance-DlZM_YPV.cjs');
18
18
  require('fs/promises');
19
19
  require('ink');
20
20
  require('react');
@@ -1,11 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync, statSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, i as isValidSpawnOrigin, b as sanitizeSpawnPermissionMode, A as ALLOWED_SPAWN_PERMISSION_MODES, S as SPAWN_AGENTS, w as waitForSessionKey, d as sendUserMessage } from './index-EsR8ayIg.mjs';
5
- import { l as logger } from './limits-Du7S6jqA.mjs';
4
+ import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, i as isValidSpawnOrigin, b as sanitizeSpawnPermissionMode, A as ALLOWED_SPAWN_PERMISSION_MODES, S as SPAWN_AGENTS, w as waitForSessionKey, d as sendUserMessage } from './index-gefLe1Bk.mjs';
5
+ import { l as logger } from './limits-CPTgiQ9A.mjs';
6
6
  import 'node:os';
7
7
  import 'node:crypto';
8
- import './types-6DrEgHVx.mjs';
8
+ import './types-BEXGahmj.mjs';
9
9
  import 'axios';
10
10
  import 'node:events';
11
11
  import 'socket.io-client';
@@ -27,7 +27,7 @@ import 'tweetnacl';
27
27
  import 'node:readline';
28
28
  import '@anthropic-ai/sandbox-runtime';
29
29
  import 'expo-server-sdk';
30
- import './agentGuidance-BaplwDcV.mjs';
30
+ import './agentGuidance-7MByWxKj.mjs';
31
31
  import 'ink';
32
32
  import 'react';
33
33
  import '@anthropic-ai/claude-agent-sdk';
@@ -3,11 +3,11 @@
3
3
  var chalk = require('chalk');
4
4
  var node_fs = require('node:fs');
5
5
  var path = require('node:path');
6
- var index = require('./index-6Xm0CgN_.cjs');
7
- var persistence = require('./limits-XRNzIlVm.cjs');
6
+ var index = require('./index-DNbKbe23.cjs');
7
+ var persistence = require('./limits-K17AgMHD.cjs');
8
8
  require('node:os');
9
9
  require('node:crypto');
10
- require('./types-CcjVmS2i.cjs');
10
+ require('./types-DjAHWb4J.cjs');
11
11
  require('axios');
12
12
  require('node:events');
13
13
  require('socket.io-client');
@@ -29,7 +29,7 @@ require('tweetnacl');
29
29
  require('node:readline');
30
30
  require('@anthropic-ai/sandbox-runtime');
31
31
  require('expo-server-sdk');
32
- require('./agentGuidance-BItqIZTf.cjs');
32
+ require('./agentGuidance-DlZM_YPV.cjs');
33
33
  require('ink');
34
34
  require('react');
35
35
  require('@anthropic-ai/claude-agent-sdk');
@@ -4,15 +4,15 @@ var os = require('node:os');
4
4
  var promises = require('node:fs/promises');
5
5
  var z = require('zod');
6
6
  var node_crypto = require('node:crypto');
7
- var agentGuidance = require('./agentGuidance-BItqIZTf.cjs');
8
- var persistence = require('./limits-XRNzIlVm.cjs');
7
+ var agentGuidance = require('./agentGuidance-DlZM_YPV.cjs');
8
+ var persistence = require('./limits-K17AgMHD.cjs');
9
9
  var path = require('node:path');
10
- var index = require('./index-6Xm0CgN_.cjs');
10
+ var index = require('./index-DNbKbe23.cjs');
11
11
  require('node:fs');
12
12
  require('tweetnacl');
13
13
  require('chalk');
14
14
  require('node:util');
15
- require('./types-CcjVmS2i.cjs');
15
+ require('./types-DjAHWb4J.cjs');
16
16
  require('axios');
17
17
  require('node:events');
18
18
  require('socket.io-client');
@@ -2,15 +2,15 @@ import { homedir } from 'node:os';
2
2
  import { unlink, rmdir, mkdir, lstat, readFile, writeFile, rename } from 'node:fs/promises';
3
3
  import { z } from 'zod';
4
4
  import { createHash, randomUUID } from 'node:crypto';
5
- import { d as teamRequest, e as createTeamsClient } from './agentGuidance-BaplwDcV.mjs';
6
- import { d as readCredentialsForConfiguredRelay } from './limits-Du7S6jqA.mjs';
5
+ import { d as teamRequest, e as createTeamsClient } from './agentGuidance-7MByWxKj.mjs';
6
+ import { d as readCredentialsForConfiguredRelay } from './limits-CPTgiQ9A.mjs';
7
7
  import { join, resolve, dirname } from 'node:path';
8
- import { T as TEAM_SKILL } from './index-EsR8ayIg.mjs';
8
+ import { T as TEAM_SKILL } from './index-gefLe1Bk.mjs';
9
9
  import 'node:fs';
10
10
  import 'tweetnacl';
11
11
  import 'chalk';
12
12
  import 'node:util';
13
- import './types-6DrEgHVx.mjs';
13
+ import './types-BEXGahmj.mjs';
14
14
  import 'axios';
15
15
  import 'node:events';
16
16
  import 'socket.io-client';
@@ -1,6 +1,6 @@
1
- import { d as readCredentialsForConfiguredRelay, c as configuration } from './limits-Du7S6jqA.mjs';
2
- import { b as createBuiltinTodoClient, B as BUILTIN_TODO_SKILL } from './types-6DrEgHVx.mjs';
3
- import { c as readTeamScope, t as teamScopePath } from './agentGuidance-BaplwDcV.mjs';
1
+ import { d as readCredentialsForConfiguredRelay, c as configuration } from './limits-CPTgiQ9A.mjs';
2
+ import { b as createBuiltinTodoClient, B as BUILTIN_TODO_SKILL } from './types-BEXGahmj.mjs';
3
+ import { c as readTeamScope, t as teamScopePath } from './agentGuidance-7MByWxKj.mjs';
4
4
  import 'node:fs/promises';
5
5
  import 'node:fs';
6
6
  import 'node:os';
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./limits-XRNzIlVm.cjs');
4
- var api = require('./types-CcjVmS2i.cjs');
5
- var agentGuidance = require('./agentGuidance-BItqIZTf.cjs');
3
+ var persistence = require('./limits-K17AgMHD.cjs');
4
+ var api = require('./types-DjAHWb4J.cjs');
5
+ var agentGuidance = require('./agentGuidance-DlZM_YPV.cjs');
6
6
  require('node:fs/promises');
7
7
  require('node:fs');
8
8
  require('node:os');
@@ -1,5 +1,5 @@
1
1
  import axios from 'axios';
2
- import { l as logger, i as encodeBase64, j as encrypt, k as decrypt, m as decodeBase64, c as configuration, o as decryptBlob, q as prepareClipboardText, g as readSettings, r as readPersistedSessions, s as credentialRelayProblem, p as packageJson, t as readTrackedClaudeSessionIds, u as libsodiumEncryptForPublicKey, v as libsodiumPublicKeyFromSecretKey, x as getRandomBytes, y as redactLogValue } from './limits-Du7S6jqA.mjs';
2
+ import { l as logger, i as encodeBase64, j as encrypt, k as decrypt, m as decodeBase64, c as configuration, o as decryptBlob, q as prepareClipboardText, g as readSettings, r as readPersistedSessions, s as credentialRelayProblem, p as packageJson, t as readTrackedClaudeSessionIds, u as libsodiumEncryptForPublicKey, v as libsodiumPublicKeyFromSecretKey, x as getRandomBytes, y as redactLogValue } from './limits-CPTgiQ9A.mjs';
3
3
  import { EventEmitter } from 'node:events';
4
4
  import { io } from 'socket.io-client';
5
5
  import * as z from 'zod';
@@ -31,7 +31,8 @@ const sessionRoleSchema = z.enum(["user", "agent"]);
31
31
  const sessionTextEventSchema = z.object({
32
32
  t: z.literal("text"),
33
33
  text: z.string(),
34
- thinking: z.boolean().optional()
34
+ thinking: z.boolean().optional(),
35
+ actualModel: z.string().optional()
35
36
  });
36
37
  const sessionServiceMessageEventSchema = z.object({
37
38
  t: z.literal("service"),
@@ -778,6 +779,25 @@ const ContextUsageSchema = z$1.object({
778
779
  contextWindow: z$1.number().finite().positive(),
779
780
  updatedAt: z$1.number().finite().nonnegative()
780
781
  });
782
+ const AgentVersionsSchema = z$1.object({
783
+ checkedAt: z$1.number(),
784
+ daemonPid: z$1.number(),
785
+ agents: z$1.array(z$1.object({
786
+ id: z$1.string(),
787
+ installed: z$1.string().nullable(),
788
+ latest: z$1.string().nullable(),
789
+ status: z$1.string()
790
+ }))
791
+ });
792
+ const AGENT_VERSION_SOURCES = [
793
+ { id: "claude", label: "Claude Code", command: "claude", package: "@anthropic-ai/claude-code", docs: "https://code.claude.com/docs/en/setup" },
794
+ { id: "codex", label: "Codex", command: "codex", package: "@openai/codex", docs: "https://github.com/openai/codex#installation-and-usage" },
795
+ { id: "pi", label: "Pi", command: "pi", package: "@mariozechner/pi-coding-agent", docs: "https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent" },
796
+ { id: "gemini", label: "Gemini CLI", command: "gemini", package: "@google/gemini-cli", docs: "https://github.com/google-gemini/gemini-cli#installation" },
797
+ { id: "opencode", label: "OpenCode", command: "opencode", package: "opencode-ai", docs: "https://opencode.ai/docs/" },
798
+ { id: "openclaw", label: "OpenClaw", command: "openclaw", package: "openclaw", docs: "https://docs.openclaw.ai/install/updating" },
799
+ { id: "claude-sdk", label: "Claude Agent SDK", command: null, package: "@anthropic-ai/claude-agent-sdk", docs: "https://github.com/anthropics/claude-agent-sdk-typescript" }
800
+ ];
781
801
 
782
802
  z$1.object({
783
803
  teamsVersion: z$1.number().int().positive().optional(),
@@ -857,6 +877,8 @@ const WebTerminalListItemSchema = z$1.object({
857
877
  createdAt: z$1.number().optional(),
858
878
  activityAt: z$1.number().optional(),
859
879
  agentState: z$1.enum(["working", "needs_input", "idle", "shell"]).optional(),
880
+ agentKind: z$1.string().optional(),
881
+ agentObservedAt: z$1.number().optional(),
860
882
  /** B-105: mirror session of the claude running inside the terminal. */
861
883
  mirrorSessionId: z$1.string().optional(),
862
884
  /** B-150: auto-restored after a restart (ms epoch) — the directory and the
@@ -887,6 +909,8 @@ const ClosedTerminalRecordSchema = z$1.object({
887
909
  closedAt: z$1.number()
888
910
  });
889
911
  z$1.object({
912
+ agentVersions: AgentVersionsSchema.optional(),
913
+ agentVersionEpoch: z$1.number().optional(),
890
914
  status: z$1.union([
891
915
  z$1.enum(["running", "shutting-down"]),
892
916
  z$1.string()
@@ -2424,7 +2448,12 @@ function mapTaskLifecycleSystemMessage(message, state, envelopes) {
2424
2448
  if (!subtype.startsWith("task_")) return;
2425
2449
  if (raw.skip_transcript === true) return;
2426
2450
  const toolUseId = typeof raw.tool_use_id === "string" && raw.tool_use_id.length > 0 ? raw.tool_use_id : null;
2427
- if (!toolUseId || !getAgentToolCalls(state).has(toolUseId)) return;
2451
+ if (!toolUseId) return;
2452
+ if (subtype === "task_started" && raw.task_type === "local_bash" && state.bashToolCalls?.has(toolUseId)) {
2453
+ (state.backgroundBashCalls ??= /* @__PURE__ */ new Set()).add(toolUseId);
2454
+ }
2455
+ const backgroundBash = state.backgroundBashCalls?.has(toolUseId) === true;
2456
+ if (!getAgentToolCalls(state).has(toolUseId) && !backgroundBash) return;
2428
2457
  const subagent = ensureSessionSubagentIdForProviderSubagent(state, toolUseId);
2429
2458
  const usage = raw.usage && typeof raw.usage === "object" ? {
2430
2459
  ...typeof raw.usage.tool_uses === "number" ? { toolUses: raw.usage.tool_uses } : {},
@@ -2432,7 +2461,7 @@ function mapTaskLifecycleSystemMessage(message, state, envelopes) {
2432
2461
  ...typeof raw.usage.duration_ms === "number" ? { durationMs: raw.usage.duration_ms } : {}
2433
2462
  } : void 0;
2434
2463
  if (subtype === "task_started") {
2435
- rememberSubagentMeta(state, subagent, { description: raw.description, subagentType: raw.subagent_type });
2464
+ rememberSubagentMeta(state, subagent, { description: raw.description, subagentType: backgroundBash ? "background-command" : raw.subagent_type });
2436
2465
  const turn = ensureTurn(state, envelopes);
2437
2466
  getStartedSubagents(state).delete(subagent);
2438
2467
  maybeEmitSubagentStart(state, turn, subagent, envelopes);
@@ -2521,14 +2550,15 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2521
2550
  blockCursor = state.streamBlockCursor ?? 0;
2522
2551
  state.streamBlockCursor = blockCursor + blocks.length;
2523
2552
  }
2553
+ const actualModel = subagent && typeof message.message?.model === "string" && message.message.model.trim() ? { actualModel: message.message.model.trim() } : {};
2524
2554
  const draftKey = (index) => apiMessageId ? streamKeyOf(apiMessageId, blockCursor + index) : void 0;
2525
2555
  for (const [blockIndex, block] of blocks.entries()) {
2526
2556
  if (block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) {
2527
- envelopes.push(createEnvelope("agent", { t: "text", text: block.text }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2557
+ envelopes.push(createEnvelope("agent", { t: "text", text: block.text, ...actualModel }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2528
2558
  continue;
2529
2559
  }
2530
2560
  if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim().length > 0) {
2531
- envelopes.push(createEnvelope("agent", { t: "text", text: block.thinking, thinking: true }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2561
+ envelopes.push(createEnvelope("agent", { t: "text", text: block.thinking, thinking: true, ...actualModel }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2532
2562
  continue;
2533
2563
  }
2534
2564
  if (block.type === "tool_use") {
@@ -2556,7 +2586,8 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2556
2586
  }
2557
2587
  continue;
2558
2588
  }
2559
- const args = isSubagentTool(name) ? { ...baseArgs, sessionSubagent: sessionSubagentForCall } : baseArgs;
2589
+ if (name === "Bash") (state.bashToolCalls ??= /* @__PURE__ */ new Set()).add(call);
2590
+ const args = isSubagentTool(name) || name === "Bash" ? { ...baseArgs, sessionSubagent: sessionSubagentForCall } : baseArgs;
2560
2591
  envelopes.push(createEnvelope("agent", {
2561
2592
  t: "tool-call-start",
2562
2593
  call,
@@ -2657,7 +2688,7 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2657
2688
  continue;
2658
2689
  }
2659
2690
  }
2660
- if (sessionSubagentForToolResult) {
2691
+ if (sessionSubagentForToolResult && !state.backgroundBashCalls?.has(block.tool_use_id)) {
2661
2692
  maybeEmitSubagentStop(state, turnId, sessionSubagentForToolResult, envelopes);
2662
2693
  }
2663
2694
  }
@@ -4337,6 +4368,58 @@ function registerTodoHandlers(manager) {
4337
4368
  });
4338
4369
  }
4339
4370
 
4371
+ function codingAgentFromProcessTree(panePid, snapshot) {
4372
+ const rows = snapshot.split("\n").flatMap((line) => {
4373
+ const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.+)$/);
4374
+ return m ? [{ pid: Number(m[1]), parent: Number(m[2]), command: m[3] }] : [];
4375
+ });
4376
+ const visit = (pid, seen = /* @__PURE__ */ new Set()) => {
4377
+ if (seen.has(pid)) return void 0;
4378
+ seen.add(pid);
4379
+ const row = rows.find((r) => r.pid === pid);
4380
+ if (!row) return void 0;
4381
+ for (const child of rows.filter((r) => r.parent === pid)) {
4382
+ const kind = visit(child.pid, seen);
4383
+ if (kind) return kind;
4384
+ }
4385
+ const words = row.command.split(/\s+/);
4386
+ const executable = words[0].split("/").pop();
4387
+ const candidate = executable === "node" ? words[1]?.split("/").pop() : executable;
4388
+ if (candidate === "pi" || candidate === "pi-coding-agent") return "pi";
4389
+ if (candidate === "codex") return "codex";
4390
+ if (candidate === "claude" || /^\d+\.\d+\.\d+$/.test(candidate ?? "")) return "claude";
4391
+ return void 0;
4392
+ };
4393
+ return visit(panePid);
4394
+ }
4395
+
4396
+ const shells = /* @__PURE__ */ new Set(["zsh", "bash", "fish", "sh", "dash", "ksh", "tcsh", "csh"]);
4397
+ function classifyAgentPane(command, tail) {
4398
+ const cmd = command.trim().replace(/^-/, "").split(/[\\/]/).pop().toLowerCase();
4399
+ const lines = tail.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\r/g, "").trimEnd().split("\n");
4400
+ const screen = lines.join("\n");
4401
+ const footer = lines.slice(-20).join("\n");
4402
+ if (shells.has(cmd)) return { agentState: "shell" };
4403
+ if (!["node", "claude", "codex", "pi"].includes(cmd) && !/^\d+\.\d+\.\d+$/.test(cmd)) return {};
4404
+ 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;
4405
+ if (!agentKind) return {};
4406
+ const result = (agentState) => ({ agentKind, agentState });
4407
+ if (agentKind === "pi") {
4408
+ 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");
4409
+ if (/^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◓◑◒]\s+(?:Working|Thinking|Retrying|Compacting context|Auto-compacting|Summarizing branch).*$/m.test(footer)) return result("working");
4410
+ if (/^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◓◑◒]\s/m.test(footer)) return result();
4411
+ const editorRules = footer.match(/^\s*─{8,}\s*$/gm) ?? [];
4412
+ if (editorRules.length >= 2 && /(?:\d+(?:\.\d+)?|\?)%\/\d+(?:\.\d+)?[km]?.* • (?:thinking off|minimal|low|medium|high|xhigh)/i.test(footer)) return result("idle");
4413
+ return result();
4414
+ }
4415
+ const choice = /^[\s│]*[❯›>]\s*\d+\.\s/m.test(footer);
4416
+ 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");
4417
+ if (/esc to interrupt/i.test(footer)) return result("working");
4418
+ if (agentKind === "codex" && /^\s*gpt-[\w.-]+ (?:minimal|low|medium|high|xhigh|fast)[^\n]* · (?:~|\/|[A-Z]:[\\/])/m.test(footer) && /^› /m.test(footer)) return result("idle");
4419
+ if (/\? for shortcuts|bypass permissions on|⏵⏵|\d+% context left/.test(footer)) return result("idle");
4420
+ return result();
4421
+ }
4422
+
4340
4423
  function sanitizeGeometry(cols, rows) {
4341
4424
  const ok = (v) => typeof v === "number" && Number.isInteger(v) && v >= 2 && v <= 1e4;
4342
4425
  return ok(cols) && ok(rows) ? { cols, rows } : void 0;
@@ -9546,10 +9629,12 @@ class TerminalNotifyTracker {
9546
9629
  * Feed one observation for a terminal. Returns the notification event this
9547
9630
  * observation triggers, or null. Call once per terminal per tick.
9548
9631
  */
9549
- observe(terminalId, state, now) {
9632
+ observe(terminalId, state, now, agentKind) {
9550
9633
  let t = this.tracks.get(terminalId);
9551
- if (!t) {
9634
+ if (!t || t.agentKind !== agentKind || now - t.lastObservedAt > 45e3) {
9552
9635
  t = {
9636
+ agentKind,
9637
+ lastObservedAt: now,
9553
9638
  armed: state === "working",
9554
9639
  confirmed: state,
9555
9640
  candidate: state,
@@ -9560,6 +9645,7 @@ class TerminalNotifyTracker {
9560
9645
  this.tracks.set(terminalId, t);
9561
9646
  return null;
9562
9647
  }
9648
+ t.lastObservedAt = now;
9563
9649
  if (state === "working") t.armed = true;
9564
9650
  if (state === t.confirmed) {
9565
9651
  t.candidate = state;
@@ -9892,6 +9978,8 @@ function terminalListSignature(items) {
9892
9978
  t.createdAt ?? 0,
9893
9979
  Math.floor((t.activityAt ?? 0) / ACTIVITY_SIGNATURE_BUCKET_MS),
9894
9980
  t.agentState ?? "",
9981
+ t.agentKind ?? "",
9982
+ Math.floor((t.agentObservedAt ?? 0) / 1e4),
9895
9983
  // B-105: a mirror binding appearing/disappearing MUST push the
9896
9984
  // list, or the web never learns the toggle became available.
9897
9985
  t.mirrorSessionId ?? "",
@@ -9940,7 +10028,7 @@ const LIST_SESSIONS_FORMAT = [
9940
10028
  // command only separates shell/idle). MUST stay before pane_title: that
9941
10029
  // field is deliberately last so a pathological 0x1f inside a title can only
9942
10030
  // garble the title, never shift the fields.
9943
- "#{pane_current_command}",
10031
+ "#{pane_current_command} #{pane_pid}",
9944
10032
  "#{pane_title}"
9945
10033
  ].join(LIST_FIELD_SEP);
9946
10034
  function parseSessionListLine(line) {
@@ -9958,7 +10046,8 @@ function parseSessionListLine(line) {
9958
10046
  vhTitle: vhTitle.trim() || void 0,
9959
10047
  manual: manual.trim().length > 0,
9960
10048
  tags: parseTerminalTags(vhTags),
9961
- paneCurrentCommand: paneCommand.trim() || void 0,
10049
+ paneCurrentCommand: paneCommand.trim().replace(/ \d+$/, "") || void 0,
10050
+ .../ (\d+)$/.test(paneCommand) ? { panePid: Number(paneCommand.match(/ (\d+)$/)[1]) } : {},
9962
10051
  // Verbatim (no trim): tmux allows edge spaces in a session name and the
9963
10052
  // restore lookup / attach echo compare it exactly.
9964
10053
  attachTmux: isSafeTmuxSessionName(vhAttach) ? vhAttach : void 0,
@@ -10000,22 +10089,6 @@ function parseTerminalTags(raw) {
10000
10089
  return [];
10001
10090
  }
10002
10091
  }
10003
- function classifyPane(currentCommand, tail) {
10004
- const cmd = (currentCommand || "").trim().replace(/^-/, "").toLowerCase();
10005
- const isShell = SHELL_COMMANDS.has(cmd);
10006
- const lines = tail.replace(/\r/g, "").split("\n").map((l) => l.trimEnd());
10007
- while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
10008
- const text = lines.join("\n");
10009
- const last15 = lines.slice(-15).join("\n");
10010
- 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);
10011
- if (hasDialog) return "needs_input";
10012
- if (text.includes("esc to interrupt")) return "working";
10013
- const looksLikeClaude = looksLikeClaudeCommand(cmd);
10014
- const hasIdleFooter = text.includes("? for shortcuts") || text.includes("bypass permissions on") || text.includes("\u23F5\u23F5");
10015
- if (looksLikeClaude || hasIdleFooter) return "idle";
10016
- if (isShell) return "shell";
10017
- return void 0;
10018
- }
10019
10092
  function looksLikeClaudeCommand(currentCommand) {
10020
10093
  const cmd = (currentCommand || "").trim().replace(/^-/, "").toLowerCase();
10021
10094
  return cmd === "claude" || cmd === "node" || /^\d+\.\d+(\.\d+)?$/.test(cmd);
@@ -11026,7 +11099,7 @@ class WebTerminalManager {
11026
11099
  const now = Date.now();
11027
11100
  this.notifyTracker.prune(list.map((t) => t.id));
11028
11101
  for (const item of list) {
11029
- const event = this.notifyTracker.observe(item.id, item.agentState, now);
11102
+ const event = this.notifyTracker.observe(item.id, item.agentState, now, item.agentKind);
11030
11103
  if (!event) continue;
11031
11104
  try {
11032
11105
  this.onNotify({ terminalId: item.id, title: item.title || "Terminal", event });
@@ -11758,6 +11831,7 @@ class WebTerminalManager {
11758
11831
  if (r.status !== 0 || !r.stdout) return [];
11759
11832
  const hostname = os$1.hostname();
11760
11833
  const out = [];
11834
+ let processSnapshot;
11761
11835
  for (const line of r.stdout.split("\n")) {
11762
11836
  const s = parseSessionListLine(line);
11763
11837
  if (!s || !s.name.startsWith("vh-")) continue;
@@ -11775,7 +11849,15 @@ class WebTerminalManager {
11775
11849
  } catch {
11776
11850
  }
11777
11851
  }
11778
- const probe = this.probeAgentState(s.name, s.paneCurrentCommand);
11852
+ let identity = s.paneCurrentCommand;
11853
+ if (identity === "node" && s.panePid) {
11854
+ if (processSnapshot === void 0) {
11855
+ const ps = spawnSync("ps", ["-axo", "pid=,ppid=,args="], { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS });
11856
+ processSnapshot = ps.status === 0 ? ps.stdout : "";
11857
+ }
11858
+ identity = codingAgentFromProcessTree(s.panePid, processSnapshot) ?? identity;
11859
+ }
11860
+ const probe = this.probeAgentState(s.name, identity);
11779
11861
  out.push({
11780
11862
  id,
11781
11863
  title,
@@ -11787,6 +11869,8 @@ class WebTerminalManager {
11787
11869
  // simply omit it and web clients fall back to createdAt.
11788
11870
  activityAt: s.activity,
11789
11871
  agentState: probe.agentState,
11872
+ agentKind: probe.agentKind,
11873
+ agentObservedAt: probe.agentObservedAt,
11790
11874
  // Daemon-internal (not pushed to web / not in the signature):
11791
11875
  // the mirror reconciler's stricter "claude is really here" gate.
11792
11876
  claudeConfident: probe.claudeConfident,
@@ -11813,16 +11897,17 @@ class WebTerminalManager {
11813
11897
  if (live) {
11814
11898
  try {
11815
11899
  const { command, tail } = live.agentProbeInput();
11816
- return { agentState: classifyPane(command || polledCommand || "", tail), claudeConfident: isClaudeConfident(tail) };
11900
+ const observation = classifyAgentPane(command === "node" ? polledCommand || command : command || polledCommand || "", tail);
11901
+ return { ...observation, agentObservedAt: Date.now(), claudeConfident: observation.agentKind === "claude" && isClaudeConfident(tail) };
11817
11902
  } catch {
11818
11903
  }
11819
11904
  }
11820
- return this.probeAgentStateViaTmux(sessionName);
11905
+ return this.probeAgentStateViaTmux(sessionName, polledCommand);
11821
11906
  }
11822
11907
  /** Fallback probe for sessions with no live headless: 2 short tmux calls
11823
11908
  * (foreground command + pane tail) fed into classifyPane. Any failure or
11824
11909
  * timeout → undefined (the field is omitted), never an error. */
11825
- probeAgentStateViaTmux(sessionName) {
11910
+ probeAgentStateViaTmux(sessionName, polledCommand) {
11826
11911
  try {
11827
11912
  const cmd = spawnSync(
11828
11913
  "tmux",
@@ -11836,7 +11921,8 @@ class WebTerminalManager {
11836
11921
  { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11837
11922
  );
11838
11923
  if (cap.status !== 0 || typeof cap.stdout !== "string") return { agentState: void 0, claudeConfident: false };
11839
- return { agentState: classifyPane(cmd.stdout.trim(), cap.stdout), claudeConfident: isClaudeConfident(cap.stdout) };
11924
+ const observation = classifyAgentPane(cmd.stdout.trim() === "node" ? polledCommand || "node" : cmd.stdout.trim(), cap.stdout);
11925
+ return { ...observation, agentObservedAt: Date.now(), claudeConfident: observation.agentKind === "claude" && isClaudeConfident(cap.stdout) };
11840
11926
  } catch {
11841
11927
  return { agentState: void 0, claudeConfident: false };
11842
11928
  }
@@ -12972,6 +13058,10 @@ class CodexAppServerClient {
12972
13058
  if (!item || typeof item !== "object") {
12973
13059
  return method.startsWith("item/");
12974
13060
  }
13061
+ if ((method === "item/started" || method === "item/completed") && (item.type === "collabAgentToolCall" || item.type === "subAgentActivity")) {
13062
+ this.eventHandler?.({ type: "collab_agent_item", item });
13063
+ return true;
13064
+ }
12975
13065
  if (method === "item/started" && item.type === "commandExecution") {
12976
13066
  const callId = typeof item.id === "string" ? item.id : "";
12977
13067
  this.eventHandler?.({
@@ -14082,6 +14172,8 @@ class ApiMachineClient {
14082
14172
  * stale" so the FIRST keep-alive tick always probes and populates the
14083
14173
  * machine metadata that a connecting/reconnecting web client reads. */
14084
14174
  lastCliProbeAt = Date.now() - CLI_AVAILABILITY_RECHECK_MS;
14175
+ /** Stable across socket reconnects; a new client/run invalidates old version checks. */
14176
+ agentVersionEpoch = Date.now();
14085
14177
  cliUpdateState = null;
14086
14178
  cliUpdatePushChain = Promise.resolve();
14087
14179
  claudeAuthState = null;
@@ -14842,6 +14934,7 @@ class ApiMachineClient {
14842
14934
  pid: process.pid,
14843
14935
  httpPort: this.machine.daemonState?.httpPort,
14844
14936
  startedAt: now,
14937
+ agentVersionEpoch: this.agentVersionEpoch,
14845
14938
  webTerminals: { updatedAt: now, terminals: initialTerminals },
14846
14939
  // B-265 capability flag; restamped every connect so the
14847
14940
  // web's `detectedAt >= startedAt` trust rule holds.
@@ -16206,4 +16299,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
16206
16299
  }).passthrough()
16207
16300
  ]);
16208
16301
 
16209
- export { api as $, ApiClient as A, BUILTIN_TODO_SKILL as B, scrubTmuxClientEnv as C, decideMirrorBinding as D, MIRROR_BACKFILL_LINES_DEFAULT as E, mirrorLineKey as F, mirrorLocalId as G, detectCLIAvailability as H, InvalidateSync as I, detectResumeSupport as J, summarizeSpawnSessionForLog as K, shellescape as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, fetchCliUpdateState as N, resolveCliUpdateCheckInterval as O, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as P, listVhTerminals as Q, RawJSONLinesSchema as R, STREAM_DELTA_MAX_CHARS as S, isValidTerminalId as T, readVhTerminal as U, sendToVhTerminal as V, ContextUsageSchema as W, TITLE_PROMPT_PREFIX as X, BOARD_ANALYZER_PROMPT_PREFIX as Y, startOfflineReconnection as Z, CodexAppServerClient as _, ApiSessionClient as a, createBuiltinTodoClient as b, createEnvelope as c, contentLogMetadata as d, errorLogMetadata as e, connectionState as f, BUILTIN_TODO_DISCOVERY as g, delay as h, isValidSessionId as i, getProjectPath as j, initializeSandbox as k, AsyncLock as l, commandOnPath as m, deriveLocalCliUpdateSummary as n, isClaudeSessionId as o, projectPath as p, getLocalHappyAgentCredentialPath as q, readLocalHappyAgentCredentials as r, streamKeyOf as s, parseExactVersion as t, compareExactVersions as u, extractCompleteLines as v, wrapCommand as w, decideBackfill as x, parseTerminalHookPayload as y, tmuxArgs as z };
16302
+ export { CodexAppServerClient as $, ApiClient as A, BUILTIN_TODO_SKILL as B, tmuxArgs as C, scrubTmuxClientEnv as D, decideMirrorBinding as E, MIRROR_BACKFILL_LINES_DEFAULT as F, mirrorLineKey as G, mirrorLocalId as H, InvalidateSync as I, detectCLIAvailability as J, detectResumeSupport as K, summarizeSpawnSessionForLog as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, shellescape as N, fetchCliUpdateState as O, resolveCliUpdateCheckInterval as P, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as Q, RawJSONLinesSchema as R, STREAM_DELTA_MAX_CHARS as S, listVhTerminals as T, isValidTerminalId as U, readVhTerminal as V, sendToVhTerminal as W, ContextUsageSchema as X, TITLE_PROMPT_PREFIX as Y, BOARD_ANALYZER_PROMPT_PREFIX as Z, startOfflineReconnection as _, ApiSessionClient as a, api as a0, createBuiltinTodoClient as b, createEnvelope as c, contentLogMetadata as d, errorLogMetadata as e, connectionState as f, BUILTIN_TODO_DISCOVERY as g, delay as h, isValidSessionId as i, getProjectPath as j, initializeSandbox as k, AsyncLock as l, commandOnPath as m, deriveLocalCliUpdateSummary as n, AGENT_VERSION_SOURCES as o, projectPath as p, isClaudeSessionId as q, getLocalHappyAgentCredentialPath as r, streamKeyOf as s, readLocalHappyAgentCredentials as t, parseExactVersion as u, compareExactVersions as v, wrapCommand as w, extractCompleteLines as x, decideBackfill as y, parseTerminalHookPayload as z };