very-happy-cli 0.2.108 → 0.2.110

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 (33) hide show
  1. package/dist/{AcpBackend-D4vV6SVl.cjs → AcpBackend-D0_wbTs6.cjs} +1 -1
  2. package/dist/{AcpBackend-DwR8rrkS.mjs → AcpBackend-DzZR7TEv.mjs} +1 -1
  3. package/dist/{AcpSessionManager-CPHDZ-nz.cjs → AcpSessionManager-Dcj_iHw8.cjs} +1 -1
  4. package/dist/{AcpSessionManager-C057dkLF.mjs → AcpSessionManager-tWuFWeIF.mjs} +1 -1
  5. package/dist/{config-BvVwprR0.cjs → config-CTCGilz_.cjs} +2 -2
  6. package/dist/{config-DKMwLc1k.mjs → config-CTyyVXHj.mjs} +2 -2
  7. package/dist/{index-CzSv4d_S.mjs → index-BW9kDx5m.mjs} +237 -91
  8. package/dist/{index-BXnHT-Wu.mjs → index-Brhvj7p0.mjs} +5 -5
  9. package/dist/{index-DM0Ku1Rv.cjs → index-CvnMiYgh.cjs} +247 -94
  10. package/dist/{index-Db5m34eh.cjs → index-D-adqB7g.cjs} +4 -4
  11. package/dist/{index-C7N-ONU_.cjs → index-DW78HyvK.cjs} +2 -2
  12. package/dist/{index-DYY1DUOD.mjs → index-jYltd-ZW.mjs} +2 -2
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-Cs49gg_E.mjs → installTerminalHooks-CJyoR9jp.mjs} +2 -2
  16. package/dist/{installTerminalHooks-BpvWmkZ9.cjs → installTerminalHooks-DhQ6k7YD.cjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-DY17lhMH.mjs → mcp-BO9-sxMr.mjs} +2 -2
  20. package/dist/{mcp-rTSmj-tn.cjs → mcp-Dr1PegOw.cjs} +2 -2
  21. package/dist/{runGemini-Bqs52hw_.mjs → runGemini-CuzqF6Ys.mjs} +4 -4
  22. package/dist/{runGemini-B3Mme1zr.cjs → runGemini-D6Wwfk_0.cjs} +4 -4
  23. package/dist/{runOpenClaw-CpbsQ7NZ.mjs → runOpenClaw-B5pkmSTb.mjs} +3 -3
  24. package/dist/{runOpenClaw-B2V9j9Le.cjs → runOpenClaw-BYmlS-4Q.cjs} +3 -3
  25. package/dist/{send-uHgpvIqm.mjs → send-Btz1IZ9U.mjs} +2 -2
  26. package/dist/{send-CLdMBQ8e.cjs → send-CuajooZC.cjs} +2 -2
  27. package/dist/sessions-CVUBS3LG.cjs +204 -0
  28. package/dist/sessions-DDwCphTQ.mjs +200 -0
  29. package/dist/{spawn-BXbVzbMs.cjs → spawn-CwS8CPbr.cjs} +16 -3
  30. package/dist/{spawn-Dt4ujmHH.mjs → spawn-DcA80_VN.mjs} +16 -3
  31. package/dist/{types-D4bwyM-D.cjs → types--t4zLibT.cjs} +18 -10
  32. package/dist/{types-D0-nhvuW.mjs → types-BdO0jg7G.mjs} +18 -10
  33. package/package.json +7 -7
@@ -3,7 +3,7 @@
3
3
  var node_child_process = require('node:child_process');
4
4
  var sdk = require('@agentclientprotocol/sdk');
5
5
  var node_crypto = require('node:crypto');
6
- var persistence = require('./types-D4bwyM-D.cjs');
6
+ var persistence = require('./types--t4zLibT.cjs');
7
7
 
8
8
  const DEFAULT_TIMEOUTS = {
9
9
  /** Default initialization timeout: 60 seconds */
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { l as logger, d as contentLogMetadata, e as errorLogMetadata, f as packageJson, g as delay } from './types-D0-nhvuW.mjs';
4
+ import { l as logger, d as contentLogMetadata, e as errorLogMetadata, f as packageJson, g as delay } from './types-BdO0jg7G.mjs';
5
5
 
6
6
  const DEFAULT_TIMEOUTS = {
7
7
  /** Default initialization timeout: 60 seconds */
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var cuid2 = require('@paralleldrive/cuid2');
4
- var persistence = require('./types-D4bwyM-D.cjs');
4
+ var persistence = require('./types--t4zLibT.cjs');
5
5
 
6
6
  function turnOptions(turnId, time) {
7
7
  return turnId ? { turn: turnId, time } : { time };
@@ -1,5 +1,5 @@
1
1
  import { createId } from '@paralleldrive/cuid2';
2
- import { b as createEnvelope } from './types-D0-nhvuW.mjs';
2
+ import { b as createEnvelope } from './types-BdO0jg7G.mjs';
3
3
 
4
4
  function turnOptions(turnId, time) {
5
5
  return turnId ? { turn: turnId, time } : { time };
@@ -4,8 +4,8 @@ var fs = require('fs');
4
4
  var path = require('path');
5
5
  var os = require('os');
6
6
  var child_process = require('child_process');
7
- var persistence = require('./types-D4bwyM-D.cjs');
8
- var index = require('./index-DM0Ku1Rv.cjs');
7
+ var persistence = require('./types--t4zLibT.cjs');
8
+ var index = require('./index-CvnMiYgh.cjs');
9
9
  require('axios');
10
10
  require('chalk');
11
11
  require('node:util');
@@ -2,8 +2,8 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { execSync } from 'child_process';
5
- import { l as logger } from './types-D0-nhvuW.mjs';
6
- import { D as DEFAULT_GEMINI_MODEL, G as GEMINI_MODEL_ENV } from './index-CzSv4d_S.mjs';
5
+ import { l as logger } from './types-BdO0jg7G.mjs';
6
+ import { f as DEFAULT_GEMINI_MODEL, G as GEMINI_MODEL_ENV } from './index-BW9kDx5m.mjs';
7
7
  import 'axios';
8
8
  import 'chalk';
9
9
  import 'node:util';
@@ -1,7 +1,7 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
2
2
  import os, { homedir, userInfo } from 'node:os';
3
3
  import { randomUUID, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
4
- import { n as readDaemonState, l as logger, c as configuration, o as clearDaemonState, q as getProjectPath, p as projectPath, s as initializeSandbox, t as wrapCommand, d as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, g as delay, u as AsyncLock, v as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, x as hardenPrivateFile, f as packageJson, i as readSettings, y as readCredentials, z as credentialRelayProblem, B as deriveLocalCliUpdateSummary, j as encodeBase64, C as updateSettings, D as encodeBase64Url, E as decodeBase64, F as writeCredentialsLegacy, G as writeCredentialsDataKey, H as readCredentialsForConfiguredRelay, J as encrypt, r as readPersistedSessions, K as isClaudeSessionId, L as getLocalHappyAgentCredentialPath, N as readLocalHappyAgentCredentials, O as decryptWithDataKey, P as decryptLegacy, Q as extractCompleteLines, S as decideBackfill, T as parseTerminalHookPayload, U as tmuxArgs, V as scrubTmuxClientEnv, W as decideMirrorBinding, X as MIRROR_BACKFILL_LINES_DEFAULT, Y as persistSession, Z as mirrorLineKey, _ as mirrorLocalId, w as writePrivateFileSync, $ as detectResumeSupport, a0 as detectCLIAvailability, a1 as acquireDaemonLock, a2 as summarizeSpawnSessionForLog, a3 as deletePersistedSessions, a4 as shellescape, a5 as decrypt, a6 as writeDaemonState, A as ApiClient, a7 as writeSettings, a8 as fetchCliUpdateState, a9 as resolveCliUpdateCheckInterval, aa as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, ab as releaseDaemonLock, ac as isValidSessionId, ad as listVhTerminals, ae as isValidTerminalId, af as readVhTerminal, ag as sendToVhTerminal, ah as TITLE_PROMPT_PREFIX, ai as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, aj as startOfflineReconnection, ak as clearCredentials, al as clearMachineId, k as ensurePrivateDirectorySync, am as SandboxConfigSchema, b as createEnvelope, an as CodexAppServerClient, ao as getLatestDaemonLog } from './types-D0-nhvuW.mjs';
4
+ import { o as readDaemonState, l as logger, c as configuration, q as clearDaemonState, s as getProjectPath, p as projectPath, t as initializeSandbox, u as wrapCommand, d as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, g as delay, v as AsyncLock, x as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, y as hardenPrivateFile, f as packageJson, j as readSettings, z as readCredentials, B as credentialRelayProblem, C as deriveLocalCliUpdateSummary, k as encodeBase64, D as updateSettings, E as encodeBase64Url, F as decodeBase64, G as writeCredentialsLegacy, H as writeCredentialsDataKey, J as readCredentialsForConfiguredRelay, K as encrypt, r as readPersistedSessions, L as isClaudeSessionId, N as getLocalHappyAgentCredentialPath, O as readLocalHappyAgentCredentials, P as decryptWithDataKey, Q as decryptLegacy, S as extractCompleteLines, T as decideBackfill, U as parseTerminalHookPayload, V as tmuxArgs, W as scrubTmuxClientEnv, X as decideMirrorBinding, Y as MIRROR_BACKFILL_LINES_DEFAULT, Z as persistSession, _ as mirrorLineKey, $ as mirrorLocalId, w as writePrivateFileSync, a0 as detectResumeSupport, a1 as detectCLIAvailability, a2 as acquireDaemonLock, a3 as summarizeSpawnSessionForLog, a4 as deletePersistedSessions, a5 as shellescape, a6 as decrypt, a7 as writeDaemonState, A as ApiClient, a8 as writeSettings, a9 as fetchCliUpdateState, aa as resolveCliUpdateCheckInterval, ab as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, ac as releaseDaemonLock, i as isValidSessionId, ad as listVhTerminals, ae as isValidTerminalId, af as readVhTerminal, ag as sendToVhTerminal, ah as TITLE_PROMPT_PREFIX, ai as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, aj as startOfflineReconnection, ak as clearCredentials, al as clearMachineId, m as ensurePrivateDirectorySync, am as SandboxConfigSchema, b as createEnvelope, an as CodexAppServerClient, ao as getLatestDaemonLog } from './types-BdO0jg7G.mjs';
5
5
  import spawn$1, { spawn } from 'cross-spawn';
6
6
  import path, { join, resolve, basename, delimiter, isAbsolute, normalize, sep, dirname } from 'node:path';
7
7
  import { createInterface } from 'node:readline';
@@ -4422,16 +4422,23 @@ function claudeModeHash(mode) {
4422
4422
  });
4423
4423
  }
4424
4424
 
4425
- function assistantSpawnTags(env = process.env) {
4426
- if (env.HAPPY_SPAWNED_BY !== "assistant") return void 0;
4425
+ const SPAWN_ORIGIN_TAG_MAX = 24;
4426
+ function spawnOriginTags(env = process.env) {
4427
4427
  if (env.HAPPY_SESSION_VARIANT === "assistant") return void 0;
4428
- return ["assistant"];
4428
+ const origin = (env.HAPPY_SPAWNED_BY ?? "").trim();
4429
+ if (!origin) return void 0;
4430
+ if (origin.length > SPAWN_ORIGIN_TAG_MAX) return void 0;
4431
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(origin)) return void 0;
4432
+ return [origin];
4433
+ }
4434
+ function isValidSpawnOrigin(value) {
4435
+ return spawnOriginTags({ HAPPY_SPAWNED_BY: value }) !== void 0;
4429
4436
  }
4430
4437
  function createSessionMetadata(opts) {
4431
4438
  const state = {
4432
4439
  controlledByUser: false
4433
4440
  };
4434
- const assistantTags = assistantSpawnTags();
4441
+ const originTags = spawnOriginTags();
4435
4442
  const metadata = {
4436
4443
  path: process.cwd(),
4437
4444
  host: os.hostname(),
@@ -4452,10 +4459,10 @@ function createSessionMetadata(opts) {
4452
4459
  dangerouslySkipPermissions: opts.dangerouslySkipPermissions ?? null,
4453
4460
  ...opts.parentSessionId ? { parentSessionId: opts.parentSessionId } : {},
4454
4461
  ...opts.forkedFromMessageId ? { forkedFromMessageId: opts.forkedFromMessageId } : {},
4455
- // B-091: assistant-dispatched sessions carry the 'assistant' tag from
4456
- // birth (see assistantSpawnTags) — applies to every flavor the daemon
4462
+ // B-091/B-303: dispatched sessions carry their spawn origin as a tag
4463
+ // from birth (see spawnOriginTags) — applies to every flavor the daemon
4457
4464
  // can spawn, since HAPPY_SPAWNED_BY is exported flavor-independently.
4458
- ...assistantTags ? { tags: assistantTags } : {}
4465
+ ...originTags ? { tags: originTags } : {}
4459
4466
  };
4460
4467
  return { state, metadata };
4461
4468
  }
@@ -7416,12 +7423,38 @@ function createMirrorScanner(opts) {
7416
7423
  };
7417
7424
  }
7418
7425
 
7426
+ const PENDING_CREATE_BACKOFF_MS = [1e4, 3e4, 6e4, 12e4, 3e5];
7427
+ const PENDING_CREATE_MAX_AGE_MS = 30 * 6e4;
7428
+ function backoffForAttempt(attempts) {
7429
+ const index = Math.min(Math.max(attempts, 1), PENDING_CREATE_BACKOFF_MS.length) - 1;
7430
+ return PENDING_CREATE_BACKOFF_MS[index];
7431
+ }
7432
+ function pendingCreateAfterFailure(previous, event, now) {
7433
+ const sameConversation = previous?.event.claudeSessionId === event.claudeSessionId;
7434
+ const attempts = sameConversation ? previous.attempts + 1 : 1;
7435
+ return {
7436
+ event,
7437
+ attempts,
7438
+ firstFailedAt: sameConversation ? previous.firstFailedAt : now,
7439
+ nextAttemptAt: now + backoffForAttempt(attempts)
7440
+ };
7441
+ }
7442
+ function planPendingCreate(pending, now) {
7443
+ if (now - pending.firstFailedAt >= PENDING_CREATE_MAX_AGE_MS) return "drop";
7444
+ return now >= pending.nextAttemptAt ? "retry" : "wait";
7445
+ }
7446
+ function pendingCreateSupersededBy(pending, event) {
7447
+ if (event.event === "SessionStart") return true;
7448
+ return pending.event.claudeSessionId === event.claudeSessionId;
7449
+ }
7450
+
7419
7451
  const TERMINAL_MIRROR_FLAVOR = "terminal-mirror";
7420
7452
  const RECONCILE_HYSTERESIS_MS = 2e4;
7421
7453
  function createMirrorManager(deps) {
7422
7454
  const backfillLines = deps.backfillLines ?? MIRROR_BACKFILL_LINES_DEFAULT;
7423
7455
  const bindings = /* @__PURE__ */ new Map();
7424
7456
  const lastEndedAt = /* @__PURE__ */ new Map();
7457
+ const pendingCreates = /* @__PURE__ */ new Map();
7425
7458
  let chain = Promise.resolve();
7426
7459
  const reactivate = async (binding) => {
7427
7460
  try {
@@ -7581,6 +7614,14 @@ function createMirrorManager(deps) {
7581
7614
  logger.debug(`[MIRROR] reconcile reactivated (in place) terminal ${binding.terminalId}`);
7582
7615
  deps.onBindingsChanged?.();
7583
7616
  };
7617
+ const parkFailedCreate = (event, reason) => {
7618
+ const pending = pendingCreateAfterFailure(pendingCreates.get(event.terminalId), event, Date.now());
7619
+ pendingCreates.set(event.terminalId, pending);
7620
+ logger.debug(
7621
+ `[MIRROR] mirror bind failed for terminal ${event.terminalId} (attempt ${pending.attempts}, retrying):`,
7622
+ reason
7623
+ );
7624
+ };
7584
7625
  const createBinding = async (event, replaces) => {
7585
7626
  if (replaces) {
7586
7627
  await teardownBinding(replaces, "superseded by a fresh conversation");
@@ -7602,11 +7643,18 @@ function createMirrorManager(deps) {
7602
7643
  terminalId: event.terminalId,
7603
7644
  claudeSessionId: event.claudeSessionId
7604
7645
  };
7605
- const response = await deps.api.getOrCreateSession({ tag: randomUUID(), metadata, state: {} });
7646
+ let response = null;
7647
+ try {
7648
+ response = await deps.api.getOrCreateSession({ tag: randomUUID(), metadata, state: {} });
7649
+ } catch (error) {
7650
+ parkFailedCreate(event, error);
7651
+ return;
7652
+ }
7606
7653
  if (!response) {
7607
- logger.debug(`[MIRROR] server unreachable \u2014 dropping mirror bind for terminal ${event.terminalId}`);
7654
+ parkFailedCreate(event, "server unreachable");
7608
7655
  return;
7609
7656
  }
7657
+ pendingCreates.delete(event.terminalId);
7610
7658
  const client = deps.api.sessionSyncClient(response);
7611
7659
  client.skipExistingMessages();
7612
7660
  const binding = {
@@ -7662,6 +7710,8 @@ function createMirrorManager(deps) {
7662
7710
  logger.debug(`[MIRROR] terminal ${binding.terminalId} mirror continues as claude ${event.claudeSessionId}`);
7663
7711
  };
7664
7712
  const handleEvent = async (event) => {
7713
+ const parked = pendingCreates.get(event.terminalId);
7714
+ if (parked && pendingCreateSupersededBy(parked, event)) pendingCreates.delete(event.terminalId);
7665
7715
  const binding = bindings.get(event.terminalId) ?? null;
7666
7716
  const decision = decideMirrorBinding(
7667
7717
  event,
@@ -7694,6 +7744,7 @@ function createMirrorManager(deps) {
7694
7744
  });
7695
7745
  },
7696
7746
  onTerminalClosed(terminalId) {
7747
+ pendingCreates.delete(terminalId);
7697
7748
  const binding = bindings.get(terminalId);
7698
7749
  if (!binding) return;
7699
7750
  chain = chain.then(() => teardownBinding(binding, "terminal closed")).catch((error) => {
@@ -7717,6 +7768,7 @@ function createMirrorManager(deps) {
7717
7768
  for (const item of list) {
7718
7769
  const binding = bindings.get(item.id) ?? null;
7719
7770
  if (item.agentState === "shell") {
7771
+ pendingCreates.delete(item.id);
7720
7772
  if (binding && binding.status === "active") {
7721
7773
  chain = chain.then(() => {
7722
7774
  const b = bindings.get(item.id);
@@ -7740,6 +7792,21 @@ function createMirrorManager(deps) {
7740
7792
  return b && b.status === "ended" ? reactivateInPlace(b) : void 0;
7741
7793
  }).catch((e) => logger.debug(`[MIRROR] reconcile in-place reactivate failed for ${item.id}:`, e));
7742
7794
  } else if (!binding) {
7795
+ const pending = pendingCreates.get(item.id);
7796
+ if (pending) {
7797
+ const decision = planPendingCreate(pending, now);
7798
+ if (decision === "drop") {
7799
+ pendingCreates.delete(item.id);
7800
+ logger.debug(`[MIRROR] giving up on the parked mirror bind for terminal ${item.id}`);
7801
+ } else if (decision === "retry") {
7802
+ chain = chain.then(() => {
7803
+ const still = pendingCreates.get(item.id);
7804
+ if (!still || still !== pending || bindings.has(item.id)) return void 0;
7805
+ return createBinding(still.event);
7806
+ }).catch((e) => logger.debug(`[MIRROR] reconcile create retry failed for ${item.id}:`, e));
7807
+ }
7808
+ continue;
7809
+ }
7743
7810
  persisted ??= readPersistedSessions();
7744
7811
  const p = persisted;
7745
7812
  chain = chain.then(() => {
@@ -9297,6 +9364,109 @@ function formatTranscript(bodies) {
9297
9364
  return lines.join("\n");
9298
9365
  }
9299
9366
 
9367
+ const SESSION_OPS_CLIENT = "session-ops";
9368
+ const DEFAULT_RECENT_LIMIT = 15;
9369
+ const MAX_READ_LIMIT = 100;
9370
+ function mergeSessionSummaries(live, persisted, options = {}) {
9371
+ const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
9372
+ const seen = /* @__PURE__ */ new Set();
9373
+ const summaries = [];
9374
+ for (const child of live) {
9375
+ const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
9376
+ if (!id || seen.has(id)) continue;
9377
+ seen.add(id);
9378
+ summaries.push(toSummary(id, persisted[id], {
9379
+ live: true,
9380
+ pid: typeof child.pid === "number" ? child.pid : void 0
9381
+ }));
9382
+ }
9383
+ const rest = Object.entries(persisted).filter(([id]) => !seen.has(id)).filter(([, entry]) => entry.metadata?.flavor !== "terminal-mirror").sort((a, b) => b[1].savedAt - a[1].savedAt).slice(0, recentLimit);
9384
+ for (const [id, entry] of rest) {
9385
+ summaries.push(toSummary(id, entry, { live: false }));
9386
+ }
9387
+ if (!options.tag) return summaries;
9388
+ const wanted = options.tag;
9389
+ return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
9390
+ }
9391
+ function toSummary(id, persisted, extra) {
9392
+ const meta = persisted?.metadata;
9393
+ return {
9394
+ id,
9395
+ live: extra.live,
9396
+ ...extra.pid !== void 0 ? { pid: extra.pid } : {},
9397
+ ...meta?.summary?.text ? { title: meta.summary.text } : {},
9398
+ ...meta?.path ? { cwd: meta.path } : {},
9399
+ ...meta?.flavor ? { flavor: meta.flavor } : {},
9400
+ ...meta?.tags?.length ? { tags: [...meta.tags] } : {},
9401
+ ...meta?.variant ? { variant: meta.variant } : {},
9402
+ url: sessionWebUrl(id),
9403
+ ...persisted?.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {}
9404
+ };
9405
+ }
9406
+ async function listSessions(options = {}) {
9407
+ const live = await listDaemonSessions();
9408
+ return mergeSessionSummaries(live, readPersistedSessions(), options);
9409
+ }
9410
+ async function bearerToken() {
9411
+ const credentials = await readCredentialsForConfiguredRelay();
9412
+ if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
9413
+ return credentials.token;
9414
+ }
9415
+ async function readSessionTranscript(sessionId, limit) {
9416
+ const persisted = readPersistedSessions()[sessionId];
9417
+ if (!persisted) {
9418
+ throw new Error(
9419
+ `No local key for session ${sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`
9420
+ );
9421
+ }
9422
+ const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
9423
+ const token = await bearerToken();
9424
+ const response = await axios.get(
9425
+ `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
9426
+ {
9427
+ params: { before_seq: 2147483647, limit: bounded },
9428
+ headers: {
9429
+ "Authorization": `Bearer ${token}`,
9430
+ "X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
9431
+ },
9432
+ timeout: 15e3
9433
+ }
9434
+ );
9435
+ const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
9436
+ messages.reverse();
9437
+ const key = decodeBase64(persisted.encryptionKey);
9438
+ const bodies = messages.map((message) => {
9439
+ if (message.content?.t !== "encrypted") return null;
9440
+ try {
9441
+ return decrypt(key, persisted.encryptionVariant, decodeBase64(message.content.c));
9442
+ } catch {
9443
+ return null;
9444
+ }
9445
+ });
9446
+ return {
9447
+ summary: toSummary(sessionId, persisted, { live: false }),
9448
+ messageCount: messages.length,
9449
+ transcript: formatTranscript(bodies)
9450
+ };
9451
+ }
9452
+ async function stopSession(sessionId) {
9453
+ return await stopDaemonSession(sessionId);
9454
+ }
9455
+ async function archiveSession(sessionId) {
9456
+ const token = await bearerToken();
9457
+ await axios.post(
9458
+ `${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}/archive`,
9459
+ {},
9460
+ {
9461
+ headers: {
9462
+ "Authorization": `Bearer ${token}`,
9463
+ "X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
9464
+ },
9465
+ timeout: 1e4
9466
+ }
9467
+ );
9468
+ }
9469
+
9300
9470
  const PERSONAL_MEMORY_SOFT_LIMIT_CHARS = 2e3;
9301
9471
  function headingTitle(line) {
9302
9472
  const m = /^##(?!#)\s*(.*?)\s*$/.exec(line);
@@ -9393,19 +9563,24 @@ function fail(text) {
9393
9563
  function describeSession(id, persisted, extra) {
9394
9564
  const meta = persisted?.metadata;
9395
9565
  const parts = [id];
9396
- if (extra?.live !== void 0) parts.push(extra.live ? "[running]" : "[not running]");
9397
9566
  if (meta?.summary?.text) parts.push(`title="${meta.summary.text}"`);
9398
9567
  if (meta?.path) parts.push(`cwd=${meta.path}`);
9399
9568
  if (meta?.flavor) parts.push(`agent=${meta.flavor}`);
9569
+ if (meta?.tags?.length) parts.push(`tags=${meta.tags.join(",")}`);
9400
9570
  if (meta?.variant === "assistant") parts.push("(assistant)");
9401
- if (extra?.pid) parts.push(`pid=${extra.pid}`);
9402
9571
  parts.push(sessionWebUrl(id));
9403
9572
  return parts.join(" ");
9404
9573
  }
9405
- async function bearerToken() {
9406
- const credentials = await readCredentialsForConfiguredRelay();
9407
- if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
9408
- return credentials.token;
9574
+ function describeSummary(summary) {
9575
+ const parts = [summary.id, summary.live ? "[running]" : "[not running]"];
9576
+ if (summary.title) parts.push(`title="${summary.title}"`);
9577
+ if (summary.cwd) parts.push(`cwd=${summary.cwd}`);
9578
+ if (summary.flavor) parts.push(`agent=${summary.flavor}`);
9579
+ if (summary.tags?.length) parts.push(`tags=${summary.tags.join(",")}`);
9580
+ if (summary.variant === "assistant") parts.push("(assistant)");
9581
+ if (summary.pid !== void 0) parts.push(`pid=${summary.pid}`);
9582
+ parts.push(summary.url);
9583
+ return parts.join(" ");
9409
9584
  }
9410
9585
  function registerAssistantTools(mcp) {
9411
9586
  mcp.registerTool("sessions_list", {
@@ -9414,22 +9589,10 @@ function registerAssistantTools(mcp) {
9414
9589
  inputSchema: {}
9415
9590
  }, async () => {
9416
9591
  try {
9417
- const live = await listDaemonSessions();
9418
- const persisted = readPersistedSessions();
9419
- const lines = [];
9420
- const seen = /* @__PURE__ */ new Set();
9421
- for (const child of live) {
9422
- const id = child.happySessionId;
9423
- seen.add(id);
9424
- lines.push(describeSession(id, persisted[id], { pid: child.pid, live: true }));
9425
- }
9426
- const rest = Object.entries(persisted).filter(([id]) => !seen.has(id)).filter(([, entry]) => entry.metadata?.flavor !== "terminal-mirror").sort((a, b) => b[1].savedAt - a[1].savedAt).slice(0, 15);
9427
- for (const [id, entry] of rest) {
9428
- lines.push(describeSession(id, entry, { live: false }));
9429
- }
9430
- if (lines.length === 0) return ok("No sessions found on this machine.");
9431
- return ok(`${lines.length} session(s):
9432
- ${lines.join("\n")}`);
9592
+ const sessions = await listSessions();
9593
+ if (sessions.length === 0) return ok("No sessions found on this machine.");
9594
+ return ok(`${sessions.length} session(s):
9595
+ ${sessions.map(describeSummary).join("\n")}`);
9433
9596
  } catch (error) {
9434
9597
  return fail(`Failed to list sessions: ${error instanceof Error ? error.message : String(error)}`);
9435
9598
  }
@@ -9443,37 +9606,12 @@ ${lines.join("\n")}`);
9443
9606
  }
9444
9607
  }, async (args) => {
9445
9608
  if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
9446
- const limit = Math.max(1, Math.min(100, Math.floor(args.limit ?? 20)));
9447
9609
  try {
9448
- const persisted = readPersistedSessions()[args.sessionId];
9449
- if (!persisted) {
9450
- return fail(`No local key for session ${args.sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`);
9451
- }
9452
- const token = await bearerToken();
9453
- const response = await axios.get(
9454
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(args.sessionId)}/messages`,
9455
- {
9456
- params: { before_seq: 2147483647, limit },
9457
- headers: { "Authorization": `Bearer ${token}`, "X-Happy-Client": `${MCP_CLIENT_TAG}/${configuration.currentCliVersion}` },
9458
- timeout: 15e3
9459
- }
9460
- );
9461
- const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
9462
- messages.reverse();
9463
- const key = decodeBase64(persisted.encryptionKey);
9464
- const bodies = messages.map((m) => {
9465
- if (m.content?.t !== "encrypted") return null;
9466
- try {
9467
- return decrypt(key, persisted.encryptionVariant, decodeBase64(m.content.c));
9468
- } catch {
9469
- return null;
9470
- }
9471
- });
9472
- const transcript = formatTranscript(bodies);
9473
- const header = describeSession(args.sessionId, persisted);
9474
- return ok(`${header}
9475
- --- last ${messages.length} message(s) ---
9476
- ${transcript.length > 0 ? transcript : "(no readable conversation content in this range)"}`);
9610
+ const result = await readSessionTranscript(args.sessionId, args.limit ?? 20);
9611
+ const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
9612
+ return ok(`${describeSummary(result.summary)}
9613
+ --- last ${result.messageCount} message(s) ---
9614
+ ${body}`);
9477
9615
  } catch (error) {
9478
9616
  return fail(`Failed to read session: ${error instanceof Error ? error.message : String(error)}`);
9479
9617
  }
@@ -9538,7 +9676,7 @@ ${transcript.length > 0 ? transcript : "(no readable conversation content in thi
9538
9676
  if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
9539
9677
  try {
9540
9678
  const persisted = readPersistedSessions()[args.sessionId];
9541
- const success = await stopDaemonSession(args.sessionId);
9679
+ const success = await stopSession(args.sessionId);
9542
9680
  if (!success) return fail(`Session ${args.sessionId} was not found among the daemon's running sessions.`);
9543
9681
  return ok(`Stopped ${describeSession(args.sessionId, persisted)}`);
9544
9682
  } catch (error) {
@@ -9554,15 +9692,7 @@ ${transcript.length > 0 ? transcript : "(no readable conversation content in thi
9554
9692
  }, async (args) => {
9555
9693
  if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
9556
9694
  try {
9557
- const token = await bearerToken();
9558
- await axios.post(
9559
- `${configuration.serverUrl}/v1/sessions/${encodeURIComponent(args.sessionId)}/archive`,
9560
- {},
9561
- {
9562
- headers: { "Authorization": `Bearer ${token}`, "X-Happy-Client": `${MCP_CLIENT_TAG}/${configuration.currentCliVersion}` },
9563
- timeout: 1e4
9564
- }
9565
- );
9695
+ await archiveSession(args.sessionId);
9566
9696
  const persisted = readPersistedSessions()[args.sessionId];
9567
9697
  return ok(`Archived ${describeSession(args.sessionId, persisted)}`);
9568
9698
  } catch (error) {
@@ -10238,7 +10368,7 @@ function registerSideQuestionHandler(rpc, deps) {
10238
10368
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
10239
10369
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
10240
10370
  const run = deps.run ?? (async (input) => {
10241
- const { query } = await import('./index-DYY1DUOD.mjs');
10371
+ const { query } = await import('./index-jYltd-ZW.mjs');
10242
10372
  return runSideQuestion(query, input);
10243
10373
  });
10244
10374
  const slots = /* @__PURE__ */ new Map();
@@ -10778,6 +10908,7 @@ async function runClaude(credentials, options = {}) {
10778
10908
  const forkedFromMessageId = process.env.HAPPY_FORKED_FROM_MESSAGE_ID;
10779
10909
  const importedFromClaudeSessionId = process.env.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID;
10780
10910
  const importTitle = process.env.HAPPY_IMPORT_TITLE;
10911
+ const originTags = spawnOriginTags();
10781
10912
  let metadata = {
10782
10913
  path: workingDirectory,
10783
10914
  host: os.hostname(),
@@ -10810,12 +10941,13 @@ async function runClaude(credentials, options = {}) {
10810
10941
  ...importedFromClaudeSessionId ? { importedFromClaudeSessionId } : {},
10811
10942
  ...importTitle ? { summary: { text: importTitle, updatedAt: Date.now() } } : {},
10812
10943
  ...isAssistantVariant ? { variant: "assistant" } : {},
10813
- // B-091: sessions the assistant dispatched (daemon spawn RPC exports
10814
- // HAPPY_SPAWNED_BY, B-069) are born with the 'assistant' tag so every
10815
- // list shows their origin. NOT on the meta-agent itself — that one is
10816
- // the variant above and never joins the lists at all. (Shared helper:
10817
- // the other flavors get the same tag via createSessionMetadata.)
10818
- ...assistantSpawnTags() ? { tags: assistantSpawnTags() } : {}
10944
+ // B-091/B-303: dispatched sessions (daemon spawn RPC exports
10945
+ // HAPPY_SPAWNED_BY, B-069) are born tagged with their spawn origin so
10946
+ // every list shows where the work came from. NOT on the meta-agent
10947
+ // itself — that one is the variant above and never joins the lists at
10948
+ // all. (Shared helper: the other flavors get the same tag via
10949
+ // createSessionMetadata.)
10950
+ ...originTags ? { tags: originTags } : {}
10819
10951
  };
10820
10952
  const reconnectSessionId = process.env.HAPPY_RECONNECT_SESSION_ID;
10821
10953
  const reconnectKeyBase64 = process.env.HAPPY_RECONNECT_ENCRYPTION_KEY;
@@ -15166,7 +15298,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15166
15298
  process.exit(0);
15167
15299
  } else if (subcommand === "install-terminal-hooks") {
15168
15300
  try {
15169
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-Cs49gg_E.mjs');
15301
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-CJyoR9jp.mjs');
15170
15302
  const command = parseTerminalHooksArgs(args.slice(1));
15171
15303
  if (command.action === "help") {
15172
15304
  console.log(TERMINAL_HOOKS_HELP);
@@ -15183,7 +15315,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15183
15315
  }
15184
15316
  } else if (subcommand === "spawn") {
15185
15317
  try {
15186
- const { handleSpawnCommand } = await import('./spawn-Dt4ujmHH.mjs');
15318
+ const { handleSpawnCommand } = await import('./spawn-DcA80_VN.mjs');
15187
15319
  await handleSpawnCommand(args.slice(1));
15188
15320
  } catch (error) {
15189
15321
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -15193,9 +15325,21 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15193
15325
  process.exit(1);
15194
15326
  }
15195
15327
  return;
15328
+ } else if (subcommand === "sessions") {
15329
+ try {
15330
+ const { handleSessionsCommand } = await import('./sessions-DDwCphTQ.mjs');
15331
+ await handleSessionsCommand(args.slice(1));
15332
+ } catch (error) {
15333
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
15334
+ if (process.env.DEBUG) {
15335
+ console.error(error);
15336
+ }
15337
+ process.exit(1);
15338
+ }
15339
+ return;
15196
15340
  } else if (subcommand === "send") {
15197
15341
  try {
15198
- const { handleSendCommand } = await import('./send-uHgpvIqm.mjs');
15342
+ const { handleSendCommand } = await import('./send-Btz1IZ9U.mjs');
15199
15343
  await handleSendCommand(args.slice(1));
15200
15344
  } catch (error) {
15201
15345
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -15301,9 +15445,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15301
15445
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
15302
15446
  const projectId = args[3];
15303
15447
  try {
15304
- const { saveGoogleCloudProjectToConfig } = await import('./config-DKMwLc1k.mjs');
15305
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-D0-nhvuW.mjs').then(function (n) { return n.ap; });
15306
- const { ApiClient: ApiClient2 } = await import('./types-D0-nhvuW.mjs').then(function (n) { return n.aq; });
15448
+ const { saveGoogleCloudProjectToConfig } = await import('./config-CTyyVXHj.mjs');
15449
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-BdO0jg7G.mjs').then(function (n) { return n.ap; });
15450
+ const { ApiClient: ApiClient2 } = await import('./types-BdO0jg7G.mjs').then(function (n) { return n.aq; });
15307
15451
  let userEmail = void 0;
15308
15452
  try {
15309
15453
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -15334,7 +15478,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15334
15478
  }
15335
15479
  if (geminiSubcommand === "project" && args[2] === "get") {
15336
15480
  try {
15337
- const { readGeminiLocalConfig } = await import('./config-DKMwLc1k.mjs');
15481
+ const { readGeminiLocalConfig } = await import('./config-CTyyVXHj.mjs');
15338
15482
  const config = readGeminiLocalConfig();
15339
15483
  if (config.googleCloudProject) {
15340
15484
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -15374,7 +15518,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15374
15518
  process.exit(0);
15375
15519
  }
15376
15520
  try {
15377
- const { runGemini } = await import('./runGemini-Bqs52hw_.mjs');
15521
+ const { runGemini } = await import('./runGemini-CuzqF6Ys.mjs');
15378
15522
  let startedBy = void 0;
15379
15523
  for (let i = 1; i < args.length; i++) {
15380
15524
  if (args[i] === "--started-by") {
@@ -15396,7 +15540,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15396
15540
  return;
15397
15541
  } else if (subcommand === "acp") {
15398
15542
  try {
15399
- const { runAcp, resolveAcpAgentConfig } = await import('./index-BXnHT-Wu.mjs');
15543
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-Brhvj7p0.mjs');
15400
15544
  let startedBy = void 0;
15401
15545
  let verbose = false;
15402
15546
  const acpArgs = [];
@@ -15436,7 +15580,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15436
15580
  return;
15437
15581
  } else if (subcommand === "openclaw") {
15438
15582
  try {
15439
- const { runOpenClaw } = await import('./runOpenClaw-CpbsQ7NZ.mjs');
15583
+ const { runOpenClaw } = await import('./runOpenClaw-B5pkmSTb.mjs');
15440
15584
  let startedBy = void 0;
15441
15585
  let verbose = false;
15442
15586
  let gatewayUrl;
@@ -15487,7 +15631,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15487
15631
  return;
15488
15632
  } else if (subcommand === "mcp" && args.length === 1) {
15489
15633
  try {
15490
- const { handleMcpCommand } = await import('./mcp-DY17lhMH.mjs');
15634
+ const { handleMcpCommand } = await import('./mcp-BO9-sxMr.mjs');
15491
15635
  await handleMcpCommand();
15492
15636
  } catch (error) {
15493
15637
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -15702,6 +15846,8 @@ ${chalk.bold("Usage:")}
15702
15846
  print its web URL (for automation; see spawn --help)
15703
15847
  very-happy send Send a message into an existing session
15704
15848
  (for automation; see send --help)
15849
+ very-happy sessions List, read, stop or archive sessions on this
15850
+ machine (for automation; see sessions --help)
15705
15851
  very-happy codex Start Codex mode
15706
15852
  very-happy gemini Start Gemini mode (ACP)
15707
15853
  very-happy acp Start a generic ACP-compatible agent
@@ -15843,4 +15989,4 @@ ${chalk.bold("Examples:")}
15843
15989
  }
15844
15990
  }
15845
15991
 
15846
- export { BasePermissionHandler as B, CHANGE_TITLE_INSTRUCTION as C, DEFAULT_GEMINI_MODEL as D, GEMINI_MODEL_ENV as G, MessageQueue2 as M, sessionWebUrl as a, sendUserMessage as b, checkIfDaemonRunningAndCleanupStaleState as c, createSessionMetadata as d, setupOfflineReconnection as e, startHappyServer as f, GEMINI_API_KEY_ENV as g, hashObject as h, initialMachineMetadata as i, GOOGLE_API_KEY_ENV as j, BaseReasoningProcessor as k, MessageBuffer as l, notifyDaemonSessionStarted as n, pushClipboardViaDaemon as p, query as q, registerKillSessionHandler as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, waitForSessionKey as w };
15992
+ export { BasePermissionHandler as B, CHANGE_TITLE_INSTRUCTION as C, DEFAULT_RECENT_LIMIT as D, GEMINI_MODEL_ENV as G, MAX_READ_LIMIT as M, sessionWebUrl as a, sendUserMessage as b, checkIfDaemonRunningAndCleanupStaleState as c, stopSession as d, archiveSession as e, DEFAULT_GEMINI_MODEL as f, initialMachineMetadata as g, createSessionMetadata as h, isValidSpawnOrigin as i, setupOfflineReconnection as j, MessageQueue2 as k, listSessions as l, hashObject as m, notifyDaemonSessionStarted as n, startHappyServer as o, pushClipboardViaDaemon as p, query as q, readSessionTranscript as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, registerKillSessionHandler as u, GEMINI_API_KEY_ENV as v, waitForSessionKey as w, GOOGLE_API_KEY_ENV as x, BaseReasoningProcessor as y, MessageBuffer as z };
@@ -1,10 +1,10 @@
1
- import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-DwR8rrkS.mjs';
2
- export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-DwR8rrkS.mjs';
3
- import { h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, p as projectPath } from './types-D0-nhvuW.mjs';
4
- import { A as AcpSessionManager } from './AcpSessionManager-C057dkLF.mjs';
1
+ import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-DzZR7TEv.mjs';
2
+ export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-DzZR7TEv.mjs';
3
+ import { h as connectionState, A as ApiClient, j as readSettings, k as encodeBase64, l as logger, p as projectPath } from './types-BdO0jg7G.mjs';
4
+ import { A as AcpSessionManager } from './AcpSessionManager-tWuFWeIF.mjs';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { join } from 'node:path';
7
- import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, f as startHappyServer, r as registerKillSessionHandler, B as BasePermissionHandler } from './index-CzSv4d_S.mjs';
7
+ import { g as initialMachineMetadata, h as createSessionMetadata, j as setupOfflineReconnection, n as notifyDaemonSessionStarted, k as MessageQueue2, m as hashObject, o as startHappyServer, u as registerKillSessionHandler, B as BasePermissionHandler } from './index-BW9kDx5m.mjs';
8
8
  import 'node:child_process';
9
9
  import '@agentclientprotocol/sdk';
10
10
  import 'axios';