very-happy-cli 0.2.77 → 0.2.79

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 (31) hide show
  1. package/dist/{AcpBackend-CsLnh1So.cjs → AcpBackend-BuxJntPo.cjs} +1 -1
  2. package/dist/{AcpBackend-BFFC77vV.mjs → AcpBackend-DPcRh_3n.mjs} +1 -1
  3. package/dist/{AcpSessionManager--CixPN-p.cjs → AcpSessionManager-BOZPQZUQ.cjs} +1 -1
  4. package/dist/{AcpSessionManager-CU5vsxgF.mjs → AcpSessionManager-DVGWPtBC.mjs} +1 -1
  5. package/dist/{config-pv17N6Kg.cjs → config-DB0v5eoc.cjs} +2 -2
  6. package/dist/{config-CRFTYuTN.mjs → config-T7eM_8eT.mjs} +2 -2
  7. package/dist/{index-Dcv1RKe8.cjs → index-BGvGqzc1.cjs} +252 -73
  8. package/dist/{index-BkvUwyIU.mjs → index-CuLe9SB7.mjs} +5 -5
  9. package/dist/{index-C1OnwMx0.cjs → index-D_ZjCYNI.cjs} +4 -4
  10. package/dist/{index-DOb57EGb.mjs → index-HYOkniru.mjs} +249 -70
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/{installTerminalHooks-YyT3fv6w.cjs → installTerminalHooks-CAViJyYh.cjs} +2 -2
  14. package/dist/{installTerminalHooks-IXvZMGJq.mjs → installTerminalHooks-CPKThY28.mjs} +2 -2
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +11 -2
  17. package/dist/lib.d.mts +11 -2
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-CsKnmeE5.cjs → mcp-DhQcmKO7.cjs} +2 -2
  20. package/dist/{mcp-B-GdhlKD.mjs → mcp-DhUA22wj.mjs} +2 -2
  21. package/dist/{runGemini-D0lddF43.mjs → runGemini-BVxviS8T.mjs} +4 -4
  22. package/dist/{runGemini-DuUdmlVZ.cjs → runGemini-CfWCL9K7.cjs} +4 -4
  23. package/dist/{runOpenClaw-BiwwYnYf.mjs → runOpenClaw-DVTaZJtj.mjs} +3 -3
  24. package/dist/{runOpenClaw-DEMCYSoD.cjs → runOpenClaw-kSy7Os3C.cjs} +3 -3
  25. package/dist/{send-DM8oqJFp.mjs → send-DjjJQXJt.mjs} +2 -2
  26. package/dist/{send-DijSXAMp.cjs → send-jzwPToBN.cjs} +2 -2
  27. package/dist/{spawn-nISXbZD5.mjs → spawn-DBpgsEkS.mjs} +2 -2
  28. package/dist/{spawn-De3b5Hsu.cjs → spawn-DFnYIiKP.cjs} +2 -2
  29. package/dist/{types-D2qSKjsO.mjs → types-BRDN6zN2.mjs} +92 -14
  30. package/dist/{types-NwtcBgfS.cjs → types-Dy7P_1bh.cjs} +93 -15
  31. package/package.json +7 -7
package/dist/lib.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as _anthropic_ai_claude_agent_sdk from '@anthropic-ai/claude-agent-sdk';
1
2
  import * as z from 'zod';
2
3
  import { z as z$1 } from 'zod';
3
4
  import { EventEmitter } from 'node:events';
@@ -67,6 +68,7 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
67
68
  failed: "failed";
68
69
  cancelled: "cancelled";
69
70
  }>;
71
+ error: z.ZodOptional<z.ZodString>;
70
72
  costUsd: z.ZodOptional<z.ZodNumber>;
71
73
  durationMs: z.ZodOptional<z.ZodNumber>;
72
74
  numTurns: z.ZodOptional<z.ZodNumber>;
@@ -273,6 +275,7 @@ declare const DaemonStateSchema: z$1.ZodObject<{
273
275
  terminals: z$1.ZodArray<z$1.ZodObject<{
274
276
  id: z$1.ZodString;
275
277
  title: z$1.ZodOptional<z$1.ZodString>;
278
+ tags: z$1.ZodOptional<z$1.ZodArray<z$1.ZodString>>;
276
279
  cwd: z$1.ZodOptional<z$1.ZodString>;
277
280
  createdAt: z$1.ZodOptional<z$1.ZodNumber>;
278
281
  activityAt: z$1.ZodOptional<z$1.ZodNumber>;
@@ -475,6 +478,8 @@ type AgentState$1 = {
475
478
  tool: string;
476
479
  arguments: any;
477
480
  createdAt: number;
481
+ kind?: 'tool' | 'elicitation' | 'user_dialog';
482
+ permissionSuggestions?: _anthropic_ai_claude_agent_sdk.PermissionUpdate[];
478
483
  };
479
484
  };
480
485
  completedRequests?: {
@@ -487,7 +492,7 @@ type AgentState$1 = {
487
492
  reason?: string;
488
493
  mode?: PermissionMode;
489
494
  decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort';
490
- allowTools?: string[];
495
+ allowedTools?: string[];
491
496
  };
492
497
  };
493
498
  };
@@ -733,7 +738,9 @@ declare class ApiSessionClient extends EventEmitter {
733
738
  * unique constraint swallow every envelope after the first. */
734
739
  localIdFor?: (envelopeIndex: number) => string;
735
740
  }): void;
736
- closeClaudeSessionTurn(status?: SessionTurnEndStatus): void;
741
+ closeClaudeSessionTurn(status?: SessionTurnEndStatus, meta?: {
742
+ error?: string;
743
+ }): void;
737
744
  sendCodexMessage(body: any): void;
738
745
  private enqueueSessionProtocolEnvelope;
739
746
  sendSessionProtocolMessage(envelope: SessionEnvelope, localId?: string): void;
@@ -974,6 +981,8 @@ type AgentState = 'working' | 'needs_input' | 'idle' | 'shell';
974
981
  interface TerminalListItem {
975
982
  id: string;
976
983
  title?: string;
984
+ /** Always present in new daemon output; optional for old persisted state. */
985
+ tags?: string[];
977
986
  cwd?: string;
978
987
  createdAt?: number;
979
988
  activityAt?: number;
package/dist/lib.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as _anthropic_ai_claude_agent_sdk from '@anthropic-ai/claude-agent-sdk';
1
2
  import * as z from 'zod';
2
3
  import { z as z$1 } from 'zod';
3
4
  import { EventEmitter } from 'node:events';
@@ -67,6 +68,7 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
67
68
  failed: "failed";
68
69
  cancelled: "cancelled";
69
70
  }>;
71
+ error: z.ZodOptional<z.ZodString>;
70
72
  costUsd: z.ZodOptional<z.ZodNumber>;
71
73
  durationMs: z.ZodOptional<z.ZodNumber>;
72
74
  numTurns: z.ZodOptional<z.ZodNumber>;
@@ -273,6 +275,7 @@ declare const DaemonStateSchema: z$1.ZodObject<{
273
275
  terminals: z$1.ZodArray<z$1.ZodObject<{
274
276
  id: z$1.ZodString;
275
277
  title: z$1.ZodOptional<z$1.ZodString>;
278
+ tags: z$1.ZodOptional<z$1.ZodArray<z$1.ZodString>>;
276
279
  cwd: z$1.ZodOptional<z$1.ZodString>;
277
280
  createdAt: z$1.ZodOptional<z$1.ZodNumber>;
278
281
  activityAt: z$1.ZodOptional<z$1.ZodNumber>;
@@ -475,6 +478,8 @@ type AgentState$1 = {
475
478
  tool: string;
476
479
  arguments: any;
477
480
  createdAt: number;
481
+ kind?: 'tool' | 'elicitation' | 'user_dialog';
482
+ permissionSuggestions?: _anthropic_ai_claude_agent_sdk.PermissionUpdate[];
478
483
  };
479
484
  };
480
485
  completedRequests?: {
@@ -487,7 +492,7 @@ type AgentState$1 = {
487
492
  reason?: string;
488
493
  mode?: PermissionMode;
489
494
  decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort';
490
- allowTools?: string[];
495
+ allowedTools?: string[];
491
496
  };
492
497
  };
493
498
  };
@@ -733,7 +738,9 @@ declare class ApiSessionClient extends EventEmitter {
733
738
  * unique constraint swallow every envelope after the first. */
734
739
  localIdFor?: (envelopeIndex: number) => string;
735
740
  }): void;
736
- closeClaudeSessionTurn(status?: SessionTurnEndStatus): void;
741
+ closeClaudeSessionTurn(status?: SessionTurnEndStatus, meta?: {
742
+ error?: string;
743
+ }): void;
737
744
  sendCodexMessage(body: any): void;
738
745
  private enqueueSessionProtocolEnvelope;
739
746
  sendSessionProtocolMessage(envelope: SessionEnvelope, localId?: string): void;
@@ -974,6 +981,8 @@ type AgentState = 'working' | 'needs_input' | 'idle' | 'shell';
974
981
  interface TerminalListItem {
975
982
  id: string;
976
983
  title?: string;
984
+ /** Always present in new daemon output; optional for old persisted state. */
985
+ tags?: string[];
977
986
  cwd?: string;
978
987
  createdAt?: number;
979
988
  activityAt?: number;
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-D2qSKjsO.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-BRDN6zN2.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'node:util';
@@ -3,9 +3,9 @@
3
3
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
4
4
  var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
5
5
  var z = require('zod');
6
- var index = require('./index-Dcv1RKe8.cjs');
6
+ var index = require('./index-BGvGqzc1.cjs');
7
7
  var limits = require('./limits-KdWKmwtH.cjs');
8
- var persistence = require('./types-NwtcBgfS.cjs');
8
+ var persistence = require('./types-Dy7P_1bh.cjs');
9
9
  require('chalk');
10
10
  require('node:os');
11
11
  require('node:crypto');
@@ -1,9 +1,9 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { z } from 'zod';
4
- import { p as pushClipboardViaDaemon } from './index-DOb57EGb.mjs';
4
+ import { p as pushClipboardViaDaemon } from './index-HYOkniru.mjs';
5
5
  import { C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, c as CLIPBOARD_MAX_BYTES } from './limits-CwFb5S2b.mjs';
6
- import { l as logger } from './types-D2qSKjsO.mjs';
6
+ import { l as logger } from './types-BRDN6zN2.mjs';
7
7
  import 'chalk';
8
8
  import 'node:os';
9
9
  import 'node:crypto';
@@ -2,10 +2,10 @@ import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
- import { l as logger, h as connectionState, A as ApiClient, i as readSettings, e as errorLogMetadata, j as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-D2qSKjsO.mjs';
6
- import { g as GEMINI_API_KEY_ENV, j as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, k as BaseReasoningProcessor, i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, l as MessageBuffer, C as CHANGE_TITLE_INSTRUCTION, r as registerKillSessionHandler, f as startHappyServer } from './index-DOb57EGb.mjs';
7
- import { A as AcpBackend } from './AcpBackend-BFFC77vV.mjs';
8
- import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-CRFTYuTN.mjs';
5
+ import { l as logger, h as connectionState, A as ApiClient, i as readSettings, e as errorLogMetadata, j as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-BRDN6zN2.mjs';
6
+ import { g as GEMINI_API_KEY_ENV, j as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, k as BaseReasoningProcessor, i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, l as MessageBuffer, C as CHANGE_TITLE_INSTRUCTION, r as registerKillSessionHandler, f as startHappyServer } from './index-HYOkniru.mjs';
7
+ import { A as AcpBackend } from './AcpBackend-DPcRh_3n.mjs';
8
+ import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-T7eM_8eT.mjs';
9
9
  import 'axios';
10
10
  import 'chalk';
11
11
  import 'node:util';
@@ -4,10 +4,10 @@ var ink = require('ink');
4
4
  var React = require('react');
5
5
  var node_crypto = require('node:crypto');
6
6
  var path = require('node:path');
7
- var persistence = require('./types-NwtcBgfS.cjs');
8
- var index = require('./index-Dcv1RKe8.cjs');
9
- var AcpBackend = require('./AcpBackend-CsLnh1So.cjs');
10
- var config = require('./config-pv17N6Kg.cjs');
7
+ var persistence = require('./types-Dy7P_1bh.cjs');
8
+ var index = require('./index-BGvGqzc1.cjs');
9
+ var AcpBackend = require('./AcpBackend-BuxJntPo.cjs');
10
+ var config = require('./config-DB0v5eoc.cjs');
11
11
  require('axios');
12
12
  require('chalk');
13
13
  require('node:util');
@@ -3,9 +3,9 @@ import { execFileSync } from 'node:child_process';
3
3
  import os from 'node:os';
4
4
  import { existsSync, readFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
- import { k as ensurePrivateDirectorySync, m as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, c as configuration } from './types-D2qSKjsO.mjs';
7
- import { A as AcpSessionManager } from './AcpSessionManager-CU5vsxgF.mjs';
8
- import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, r as registerKillSessionHandler } from './index-DOb57EGb.mjs';
6
+ import { k as ensurePrivateDirectorySync, m as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, c as configuration } from './types-BRDN6zN2.mjs';
7
+ import { A as AcpSessionManager } from './AcpSessionManager-DVGWPtBC.mjs';
8
+ import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, r as registerKillSessionHandler } from './index-HYOkniru.mjs';
9
9
  import WebSocket from 'ws';
10
10
  import * as ed from '@noble/ed25519';
11
11
  import { sha512 } from '@noble/hashes/sha2.js';
@@ -5,9 +5,9 @@ var node_child_process = require('node:child_process');
5
5
  var os = require('node:os');
6
6
  var node_fs = require('node:fs');
7
7
  var path = require('node:path');
8
- var persistence = require('./types-NwtcBgfS.cjs');
9
- var AcpSessionManager = require('./AcpSessionManager--CixPN-p.cjs');
10
- var index = require('./index-Dcv1RKe8.cjs');
8
+ var persistence = require('./types-Dy7P_1bh.cjs');
9
+ var AcpSessionManager = require('./AcpSessionManager-BOZPQZUQ.cjs');
10
+ var index = require('./index-BGvGqzc1.cjs');
11
11
  var WebSocket = require('ws');
12
12
  var ed = require('@noble/ed25519');
13
13
  var sha2_js = require('@noble/hashes/sha2.js');
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { r as readPersistedSessions, c as configuration } from './types-D2qSKjsO.mjs';
5
- import { b as sendUserMessage, a as sessionWebUrl } from './index-DOb57EGb.mjs';
4
+ import { r as readPersistedSessions, c as configuration } from './types-BRDN6zN2.mjs';
5
+ import { b as sendUserMessage, a as sessionWebUrl } from './index-HYOkniru.mjs';
6
6
  import 'axios';
7
7
  import 'node:util';
8
8
  import 'node:os';
@@ -3,8 +3,8 @@
3
3
  var chalk = require('chalk');
4
4
  var node_fs = require('node:fs');
5
5
  var path = require('node:path');
6
- var persistence = require('./types-NwtcBgfS.cjs');
7
- var index = require('./index-Dcv1RKe8.cjs');
6
+ var persistence = require('./types-Dy7P_1bh.cjs');
7
+ var index = require('./index-BGvGqzc1.cjs');
8
8
  require('axios');
9
9
  require('node:util');
10
10
  require('node:os');
@@ -1,8 +1,8 @@
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, w as waitForSessionKey, b as sendUserMessage } from './index-DOb57EGb.mjs';
5
- import { l as logger } from './types-D2qSKjsO.mjs';
4
+ import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, w as waitForSessionKey, b as sendUserMessage } from './index-HYOkniru.mjs';
5
+ import { l as logger } from './types-BRDN6zN2.mjs';
6
6
  import 'node:os';
7
7
  import 'node:crypto';
8
8
  import 'cross-spawn';
@@ -3,8 +3,8 @@
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-Dcv1RKe8.cjs');
7
- var persistence = require('./types-NwtcBgfS.cjs');
6
+ var index = require('./index-BGvGqzc1.cjs');
7
+ var persistence = require('./types-Dy7P_1bh.cjs');
8
8
  require('node:os');
9
9
  require('node:crypto');
10
10
  require('cross-spawn');
@@ -29,7 +29,7 @@ import { SandboxManager } from '@anthropic-ai/sandbox-runtime';
29
29
  import { Expo } from 'expo-server-sdk';
30
30
 
31
31
  var name = "very-happy-cli";
32
- var version = "0.2.77";
32
+ var version = "0.2.79";
33
33
  var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
34
34
  var author = "Kirill Dubovitskiy";
35
35
  var contributors = [
@@ -142,12 +142,12 @@ var dependencies = {
142
142
  zod: "^4.0.0"
143
143
  };
144
144
  var optionalDependencies = {
145
- "very-happy-tools-arm64-darwin": "0.2.77",
146
- "very-happy-tools-x64-darwin": "0.2.77",
147
- "very-happy-tools-arm64-linux": "0.2.77",
148
- "very-happy-tools-x64-linux": "0.2.77",
149
- "very-happy-tools-arm64-win32": "0.2.77",
150
- "very-happy-tools-x64-win32": "0.2.77"
145
+ "very-happy-tools-arm64-darwin": "0.2.79",
146
+ "very-happy-tools-x64-darwin": "0.2.79",
147
+ "very-happy-tools-arm64-linux": "0.2.79",
148
+ "very-happy-tools-x64-linux": "0.2.79",
149
+ "very-happy-tools-arm64-win32": "0.2.79",
150
+ "very-happy-tools-x64-win32": "0.2.79"
151
151
  };
152
152
  var devDependencies = {
153
153
  "@slopus/happy-wire": "workspace:*",
@@ -672,6 +672,9 @@ const sessionTurnUsageSchema = z.object({
672
672
  const sessionTurnEndEventSchema = z.object({
673
673
  t: z.literal("turn-end"),
674
674
  status: sessionTurnEndStatusSchema,
675
+ // Human-readable SDK failure detail. Optional for backward compatibility;
676
+ // successful and cancelled turns normally omit it.
677
+ error: z.string().optional(),
675
678
  // Optional per-turn metadata sourced from the SDK result message. Present
676
679
  // only on turns ended by a result event; absent on lazily-closed turns.
677
680
  costUsd: z.number().optional(),
@@ -938,6 +941,9 @@ const CliUpdateStateSchema = z$1.object({
938
941
  const WebTerminalListItemSchema = z$1.object({
939
942
  id: z$1.string(),
940
943
  title: z$1.string().optional(),
944
+ /** New daemons always send an array (including []); absence identifies an
945
+ * old daemon that cannot persist terminal tags. */
946
+ tags: z$1.array(z$1.string()).optional(),
941
947
  cwd: z$1.string().optional(),
942
948
  createdAt: z$1.number().optional(),
943
949
  activityAt: z$1.number().optional(),
@@ -2444,12 +2450,14 @@ function closeTurn(state, status, envelopes, meta) {
2444
2450
  envelopes.push(createEnvelope("agent", {
2445
2451
  t: "turn-end",
2446
2452
  status,
2453
+ ...meta?.error ? { error: meta.error } : {},
2447
2454
  ...typeof meta?.costUsd === "number" ? { costUsd: meta.costUsd } : {},
2448
2455
  ...typeof meta?.durationMs === "number" ? { durationMs: meta.durationMs } : {},
2449
2456
  ...typeof meta?.numTurns === "number" ? { numTurns: meta.numTurns } : {},
2450
2457
  ...meta?.usage ? { usage: meta.usage } : {}
2451
2458
  }, { turn: state.currentTurnId }));
2452
2459
  state.currentTurnId = null;
2460
+ state.pendingAssistantError = void 0;
2453
2461
  clearSubagentTracking(state);
2454
2462
  }
2455
2463
  function toolTitle(name, input) {
@@ -2470,9 +2478,9 @@ function toToolArgs(input) {
2470
2478
  }
2471
2479
  return { input };
2472
2480
  }
2473
- function closeClaudeTurnWithStatus(state, status) {
2481
+ function closeClaudeTurnWithStatus(state, status, meta) {
2474
2482
  const envelopes = [];
2475
- closeTurn(state, status, envelopes);
2483
+ closeTurn(state, status, envelopes, meta);
2476
2484
  return {
2477
2485
  currentTurnId: state.currentTurnId,
2478
2486
  envelopes
@@ -2513,6 +2521,10 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2513
2521
  };
2514
2522
  }
2515
2523
  if (message.type === "assistant") {
2524
+ const assistantError = message.error;
2525
+ if (!message.isSidechain && typeof assistantError === "string" && assistantError.trim()) {
2526
+ state.pendingAssistantError = assistantError.trim();
2527
+ }
2516
2528
  const turnId = ensureTurn(state, envelopes);
2517
2529
  maybeEmitSubagentStart(state, turnId, subagent, envelopes);
2518
2530
  const usage = pickAssistantUsage(message);
@@ -2634,6 +2646,10 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2634
2646
  if (message.type === "result") {
2635
2647
  const raw = message;
2636
2648
  const status = raw.is_error === true ? "failed" : "completed";
2649
+ const error = status === "failed" ? raw.errors?.filter((item) => typeof item === "string" && item.trim()).join("\n") || state.pendingAssistantError || raw.subtype || "Claude turn failed" : void 0;
2650
+ if (status === "failed" && !state.currentTurnId) {
2651
+ ensureTurn(state, envelopes);
2652
+ }
2637
2653
  const usage = raw.usage && typeof raw.usage.input_tokens === "number" && typeof raw.usage.output_tokens === "number" ? {
2638
2654
  input_tokens: raw.usage.input_tokens,
2639
2655
  output_tokens: raw.usage.output_tokens,
@@ -2641,6 +2657,7 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2641
2657
  ...typeof raw.usage.cache_read_input_tokens === "number" ? { cache_read_input_tokens: raw.usage.cache_read_input_tokens } : {}
2642
2658
  } : void 0;
2643
2659
  closeTurn(state, status, envelopes, {
2660
+ error,
2644
2661
  costUsd: typeof raw.total_cost_usd === "number" ? raw.total_cost_usd : void 0,
2645
2662
  durationMs: typeof raw.duration_ms === "number" ? raw.duration_ms : void 0,
2646
2663
  numTurns: typeof raw.num_turns === "number" ? raw.num_turns : void 0,
@@ -3376,9 +3393,9 @@ class ApiSessionClient extends EventEmitter {
3376
3393
  }));
3377
3394
  }
3378
3395
  }
3379
- closeClaudeSessionTurn(status = "completed") {
3396
+ closeClaudeSessionTurn(status = "completed", meta) {
3380
3397
  this.emit("turn-ended", status);
3381
- const mapped = closeClaudeTurnWithStatus(this.claudeSessionProtocolState, status);
3398
+ const mapped = closeClaudeTurnWithStatus(this.claudeSessionProtocolState, status, meta);
3382
3399
  this.claudeSessionProtocolState.currentTurnId = mapped.currentTurnId;
3383
3400
  for (const envelope of mapped.envelopes) {
3384
3401
  this.sendSessionProtocolMessage(envelope);
@@ -9786,6 +9803,7 @@ function terminalListSignature(items) {
9786
9803
  const canon = [...items].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((t) => [
9787
9804
  t.id,
9788
9805
  t.title ?? "",
9806
+ t.tags ?? [],
9789
9807
  t.cwd ?? "",
9790
9808
  t.createdAt ?? 0,
9791
9809
  Math.floor((t.activityAt ?? 0) / ACTIVITY_SIGNATURE_BUCKET_MS),
@@ -9819,6 +9837,7 @@ const LIST_SESSIONS_FORMAT = [
9819
9837
  "#{pane_current_path}",
9820
9838
  "#{@vh_title}",
9821
9839
  "#{@vh_title_manual}",
9840
+ "#{@vh_tags}",
9822
9841
  // B-121: the control-mode client has no `pty.process`, so the agent-state
9823
9842
  // fast path lost its live `#{pane_current_command}` equivalent. Carry it in
9824
9843
  // the ONE list-sessions call the tracker already makes — the value goes
@@ -9833,8 +9852,8 @@ const LIST_SESSIONS_FORMAT = [
9833
9852
  function parseSessionListLine(line) {
9834
9853
  if (!line) return void 0;
9835
9854
  const parts = line.split(LIST_FIELD_SEP);
9836
- if (parts.length < 8) return void 0;
9837
- const [name, created, activity, cwd, vhTitle, manual, paneCommand] = parts;
9855
+ if (parts.length < 9) return void 0;
9856
+ const [name, created, activity, cwd, vhTitle, manual, vhTags, paneCommand] = parts;
9838
9857
  if (!name) return void 0;
9839
9858
  return {
9840
9859
  name,
@@ -9843,12 +9862,40 @@ function parseSessionListLine(line) {
9843
9862
  cwd: cwd || void 0,
9844
9863
  vhTitle: vhTitle.trim() || void 0,
9845
9864
  manual: manual.trim().length > 0,
9865
+ tags: parseTerminalTags(vhTags),
9846
9866
  paneCurrentCommand: paneCommand.trim() || void 0,
9847
9867
  // pane_title is last, so anything after field 8 is title content that
9848
9868
  // contained the separator — rejoin it rather than dropping it.
9849
- paneTitle: parts.slice(7).join(LIST_FIELD_SEP) || void 0
9869
+ paneTitle: parts.slice(8).join(LIST_FIELD_SEP) || void 0
9850
9870
  };
9851
9871
  }
9872
+ const TERMINAL_TAG_MAX_COUNT = 64;
9873
+ const TERMINAL_TAG_MAX_LENGTH = 24;
9874
+ const TERMINAL_TAGS_MAX_BYTES = 4096;
9875
+ function validateTerminalTags(value) {
9876
+ if (!Array.isArray(value) || value.length > TERMINAL_TAG_MAX_COUNT) return void 0;
9877
+ const seen = /* @__PURE__ */ new Set();
9878
+ const out = [];
9879
+ for (const valueTag of value) {
9880
+ if (typeof valueTag !== "string") return void 0;
9881
+ const tag = valueTag.trim().replace(/^#+/, "").trim().replace(/\s+/g, "-").slice(0, TERMINAL_TAG_MAX_LENGTH);
9882
+ if (!tag || tag !== valueTag) return void 0;
9883
+ const key = tag.toLowerCase();
9884
+ if (seen.has(key)) return void 0;
9885
+ seen.add(key);
9886
+ out.push(tag);
9887
+ }
9888
+ if (Buffer.byteLength(JSON.stringify(out), "utf8") > TERMINAL_TAGS_MAX_BYTES) return void 0;
9889
+ return out;
9890
+ }
9891
+ function parseTerminalTags(raw) {
9892
+ if (typeof raw !== "string" || !raw.trim()) return [];
9893
+ try {
9894
+ return validateTerminalTags(JSON.parse(raw)) ?? [];
9895
+ } catch {
9896
+ return [];
9897
+ }
9898
+ }
9852
9899
  function classifyPane(currentCommand, tail) {
9853
9900
  const cmd = (currentCommand || "").trim().replace(/^-/, "").toLowerCase();
9854
9901
  const isShell = SHELL_COMMANDS.has(cmd);
@@ -11281,6 +11328,8 @@ class WebTerminalManager {
11281
11328
  out.push({
11282
11329
  id,
11283
11330
  title,
11331
+ // Always include [] so Web can feature-detect tag support.
11332
+ tags: s.tags,
11284
11333
  cwd: s.cwd,
11285
11334
  createdAt: s.created,
11286
11335
  // tmux last-activity (epoch s) → ms; optional so old daemons
@@ -11458,6 +11507,25 @@ class WebTerminalManager {
11458
11507
  return false;
11459
11508
  }
11460
11509
  }
11510
+ /** Persist terminal tags in tmux, parallel to @vh_title. */
11511
+ setTags(terminalId, tags) {
11512
+ if (!isTmuxAvailable()) return false;
11513
+ if (!/^[a-zA-Z0-9_-]{1,64}$/.test(terminalId)) return false;
11514
+ const valid = validateTerminalTags(tags);
11515
+ if (!valid) return false;
11516
+ const name = `vh-${terminalId}`;
11517
+ try {
11518
+ const r = spawnSync("tmux", ["set-option", "-t", name, "@vh_tags", JSON.stringify(valid)], {
11519
+ stdio: "ignore",
11520
+ env: ptyEnv()
11521
+ });
11522
+ if (r.status !== 0) return false;
11523
+ this.kickListRefresh();
11524
+ return true;
11525
+ } catch {
11526
+ return false;
11527
+ }
11528
+ }
11461
11529
  }
11462
11530
 
11463
11531
  const TMUX_TIMEOUT_MS = 3e3;
@@ -13406,6 +13474,16 @@ class ApiMachineClient {
13406
13474
  }
13407
13475
  return { type: "success" };
13408
13476
  });
13477
+ this.rpcHandlerManager.registerHandler("set-terminal-tags", async (params) => {
13478
+ const { terminalId, tags } = params || {};
13479
+ if (!terminalId || !Array.isArray(tags)) {
13480
+ throw new Error("terminalId and tags are required");
13481
+ }
13482
+ if (!this.webTerminal.setTags(terminalId, tags)) {
13483
+ throw new Error("Failed to set terminal tags (invalid tags, tmux unavailable, or session gone)");
13484
+ }
13485
+ return { type: "success" };
13486
+ });
13409
13487
  this.rpcHandlerManager.registerHandler("stop-session", (params) => {
13410
13488
  const { sessionId } = params || {};
13411
13489
  if (!sessionId) {