very-happy-cli 0.2.116 → 0.2.118

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 (37) hide show
  1. package/dist/{AcpBackend-CfKPvoTU.mjs → AcpBackend-Bk9b823B.mjs} +116 -12
  2. package/dist/{AcpBackend-CauAQpC1.cjs → AcpBackend-p_KNJkHc.cjs} +116 -12
  3. package/dist/{AcpSessionManager-CA6xeET_.cjs → AcpSessionManager-B9FTbpGQ.cjs} +15 -2
  4. package/dist/{AcpSessionManager-BQ6pBRW4.mjs → AcpSessionManager-CDvqVHKf.mjs} +15 -2
  5. package/dist/{config-BpR_CwZu.mjs → config-CIpgmGLR.mjs} +2 -2
  6. package/dist/{config-BoteFn-v.cjs → config-CkUn1dI4.cjs} +2 -2
  7. package/dist/{index-C45xN8s0.mjs → index-B6cpyG_G.mjs} +78 -8
  8. package/dist/{index-D7azxfcs.mjs → index-BUVQf9PI.mjs} +2 -2
  9. package/dist/{index-4jiUeiUz.cjs → index-BXOXRblP.cjs} +513 -47
  10. package/dist/{index-DWmhmji9.cjs → index-BzgtwTxC.cjs} +77 -7
  11. package/dist/{index-lX1UliOs.cjs → index-CxjX8DGz.cjs} +2 -2
  12. package/dist/{index-Dq-eFp89.mjs → index-DIXjhnV1.mjs} +502 -45
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-k3TrBHD0.mjs → installTerminalHooks-BAa6LaQQ.mjs} +2 -2
  16. package/dist/{installTerminalHooks-DnaFkrTF.cjs → installTerminalHooks-lXQa6bQU.cjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +43 -1
  19. package/dist/lib.d.mts +43 -1
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-DSpEpM_2.cjs → mcp-DMD7FxBI.cjs} +62 -16
  22. package/dist/{mcp-DRePTOap.mjs → mcp-Dwc299mU.mjs} +62 -17
  23. package/dist/{runGemini-_9-a7duU.mjs → runGemini-BaxGXE9w.mjs} +4 -4
  24. package/dist/{runGemini-IWHyBq0A.cjs → runGemini-Bj4Egq7j.cjs} +4 -4
  25. package/dist/{runOpenClaw-0myYIz1U.cjs → runOpenClaw-BJhSvXYy.cjs} +3 -3
  26. package/dist/{runOpenClaw-Boq6LQ9a.mjs → runOpenClaw-BwJhbIKU.mjs} +3 -3
  27. package/dist/{send-CwWvFfay.cjs → send-BWGUkWhi.cjs} +2 -2
  28. package/dist/{send-Cyo6mgMl.mjs → send-DX13svrS.mjs} +2 -2
  29. package/dist/sessions-CGhpBNqn.cjs +322 -0
  30. package/dist/sessions-L7O3ypda.mjs +317 -0
  31. package/dist/{spawn-DQ6FTUK9.cjs → spawn-CbY233Eg.cjs} +6 -7
  32. package/dist/{spawn-CU0DMauA.mjs → spawn-DPDWZdoW.mjs} +3 -4
  33. package/dist/{types-Ds0SiQ1D.mjs → types-BtuOM9lw.mjs} +52 -19
  34. package/dist/{types-C5kEpkcZ.cjs → types-ClvvHves.cjs} +54 -19
  35. package/package.json +7 -7
  36. package/dist/sessions-CbzqYiAy.cjs +0 -204
  37. package/dist/sessions-lvfTH_ql.mjs +0 -200
@@ -3,7 +3,7 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var persistence = require('./types-C5kEpkcZ.cjs');
6
+ var persistence = require('./types-ClvvHves.cjs');
7
7
  var spawn = require('cross-spawn');
8
8
  var path = require('node:path');
9
9
  var node_readline = require('node:readline');
@@ -16,7 +16,7 @@ var React = require('react');
16
16
  var claudeAgentSdk = require('@anthropic-ai/claude-agent-sdk');
17
17
  var axios = require('axios');
18
18
  require('node:events');
19
- require('socket.io-client');
19
+ var socket_ioClient = require('socket.io-client');
20
20
  var tweetnacl = require('tweetnacl');
21
21
  var os$1 = require('os');
22
22
  var child_process = require('child_process');
@@ -150,7 +150,9 @@ async function daemonPost(path, body) {
150
150
  signal: AbortSignal.timeout(timeout)
151
151
  });
152
152
  if (!response.ok) {
153
- const errorMessage = `Request failed: ${path}, HTTP ${response.status}`;
153
+ const failure = await response.json().catch(() => null);
154
+ const reason = typeof failure?.error === "string" && failure.error ? failure.error : `HTTP ${response.status}`;
155
+ const errorMessage = `Request failed: ${path}, ${reason}`;
154
156
  persistence.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
155
157
  return {
156
158
  error: errorMessage
@@ -217,6 +219,13 @@ async function pushClipboardViaDaemon(text) {
217
219
  }
218
220
  return result;
219
221
  }
222
+ async function setTerminalTitleViaDaemon(terminalId, title, ifAbsent = false) {
223
+ const result = await daemonPost("/terminal-title", { terminalId, title, ifAbsent });
224
+ if (result?.error) {
225
+ return { ok: false, error: result.error };
226
+ }
227
+ return { ok: result?.status === "ok", error: result?.status === "ok" ? void 0 : "unexpected daemon response" };
228
+ }
220
229
  async function checkIfDaemonRunningAndCleanupStaleState() {
221
230
  const state = await persistence.readDaemonState();
222
231
  if (!state) {
@@ -4673,8 +4682,8 @@ function claudeModeHash(mode) {
4673
4682
  }
4674
4683
 
4675
4684
  const SPAWN_ORIGIN_TAG_MAX = 24;
4676
- function spawnOriginTags(env = process.env) {
4677
- if (env.HAPPY_SESSION_VARIANT === "assistant") return void 0;
4685
+ function spawnOriginTags(env = process.env, flavor = void 0) {
4686
+ if (env.HAPPY_SESSION_VARIANT === "assistant" && flavor === "claude") return void 0;
4678
4687
  const origin = (env.HAPPY_SPAWNED_BY ?? "").trim();
4679
4688
  if (!origin) return void 0;
4680
4689
  if (origin.length > SPAWN_ORIGIN_TAG_MAX) return void 0;
@@ -4688,7 +4697,7 @@ function createSessionMetadata(opts) {
4688
4697
  const state = {
4689
4698
  controlledByUser: false
4690
4699
  };
4691
- const originTags = spawnOriginTags();
4700
+ const originTags = spawnOriginTags(process.env, opts.flavor);
4692
4701
  const metadata = {
4693
4702
  path: process.cwd(),
4694
4703
  host: os.hostname(),
@@ -4907,7 +4916,7 @@ function daemonReadiness(authenticated, running, hasState) {
4907
4916
  message: "\u26A0\uFE0F Daemon is not running \u2014 run `very-happy daemon start`"
4908
4917
  };
4909
4918
  }
4910
- function collectRuntimeReadiness(run = node_child_process.spawnSync, currentNodeVersion = process.version) {
4919
+ function collectRuntimeReadiness(run = node_child_process.spawnSync, currentNodeVersion = process.version, onPath = persistence.commandOnPath) {
4911
4920
  const tmux = probe("tmux", run);
4912
4921
  return {
4913
4922
  node: {
@@ -4918,7 +4927,8 @@ function collectRuntimeReadiness(run = node_child_process.spawnSync, currentNode
4918
4927
  ...tmux,
4919
4928
  supportsSessionEnv: tmux.available && tmuxSupportsSessionEnv(tmux.version)
4920
4929
  },
4921
- agents: ["claude", "codex", "gemini", "opencode", "openclaw"].map((command) => probe(command, run))
4930
+ agents: ["claude", "codex", "gemini", "opencode", "openclaw", "pi"].map((command) => probe(command, run)),
4931
+ piAdapter: { available: onPath("pi-acp") }
4922
4932
  };
4923
4933
  }
4924
4934
 
@@ -5167,6 +5177,11 @@ ${typeLabels[type] || type}:`));
5167
5177
  } else {
5168
5178
  console.log(chalk.green(`\u2713 External agent command${availableAgents.length === 1 ? "" : "s"}: ${availableAgents.map(toolProbeLabel).join(", ")}`));
5169
5179
  }
5180
+ const piProbe = readiness.agents.find((agent) => agent.command === "pi");
5181
+ if (piProbe?.available && !readiness.piAdapter.available) {
5182
+ console.log(chalk.yellow("\u25CB pi found, but the pi-acp adapter is not on PATH \u2014 `very-happy pi` and spawning pi from the Web need it"));
5183
+ console.log(chalk.gray(" Install it for the daemon user: npm install -g pi-acp@0.0.33"));
5184
+ }
5170
5185
  console.log(chalk.bold("\n\u{1F510} Authentication"));
5171
5186
  let authenticated = false;
5172
5187
  try {
@@ -5217,6 +5232,11 @@ ${typeLabels[type] || type}:`));
5217
5232
  console.log(chalk.green("\n\u2705 Doctor diagnosis complete!\n"));
5218
5233
  }
5219
5234
 
5235
+ const SPAWN_AGENTS = ["claude", "codex", "gemini", "openclaw", "pi"];
5236
+ function isSpawnAgent(value) {
5237
+ return typeof value === "string" && SPAWN_AGENTS.includes(value);
5238
+ }
5239
+
5220
5240
  const DEFAULT_LOG_PRUNE_POLICY = {
5221
5241
  maxAgeMs: 14 * 24 * 60 * 6e4,
5222
5242
  maxTotalBytes: 200 * 1024 * 1024,
@@ -5618,6 +5638,10 @@ function spawnHappyCLI(args, options = {}) {
5618
5638
  });
5619
5639
  }
5620
5640
 
5641
+ function assistantSpawnMode(options) {
5642
+ if (options.variant !== "assistant") return "none";
5643
+ return options.agent === void 0 || options.agent === "claude" ? "claude-singleton" : "env-only";
5644
+ }
5621
5645
  function isAssistantTracked(tracked) {
5622
5646
  return tracked.variant === "assistant" || tracked.happySessionMetadataFromLocalWebhook?.variant === "assistant";
5623
5647
  }
@@ -5842,7 +5866,8 @@ function startDaemonControlServer({
5842
5866
  onSessionStateEvent,
5843
5867
  onClaudeAuthFailed,
5844
5868
  pushClipboard,
5845
- onTerminalHook
5869
+ onTerminalHook,
5870
+ setTerminalTitle
5846
5871
  }) {
5847
5872
  return new Promise((resolve) => {
5848
5873
  const app = fastify({
@@ -5962,7 +5987,7 @@ function startDaemonControlServer({
5962
5987
  // ignores it and supplies its own home daemon-side.
5963
5988
  directory: z.z.string().default(""),
5964
5989
  sessionId: z.z.string().optional(),
5965
- agent: z.z.enum(["claude", "codex", "gemini", "openclaw"]).optional(),
5990
+ agent: z.z.enum(SPAWN_AGENTS).optional(),
5966
5991
  environmentVariables: z.z.record(z.z.string(), z.z.string()).optional(),
5967
5992
  variant: z.z.enum(["assistant"]).optional(),
5968
5993
  // B-051: assistant only — stop the live assistant, purge its
@@ -6067,6 +6092,30 @@ function startDaemonControlServer({
6067
6092
  persistence.logger.debug(`[CONTROL SERVER] Clipboard push request (${text.length} chars)`);
6068
6093
  return pushClipboard(text);
6069
6094
  });
6095
+ typed.post("/terminal-title", {
6096
+ schema: {
6097
+ body: z.z.object({
6098
+ terminalId: z.z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/),
6099
+ title: z.z.string().min(1).max(200),
6100
+ ifAbsent: z.z.boolean().optional()
6101
+ }),
6102
+ response: {
6103
+ 200: z.z.object({ status: z.z.literal("ok") }),
6104
+ 409: z.z.object({ error: z.z.string() }),
6105
+ 503: z.z.object({ error: z.z.string() })
6106
+ }
6107
+ }
6108
+ }, async (request, reply) => {
6109
+ const { terminalId, title, ifAbsent } = request.body;
6110
+ const landed = setTerminalTitle ? setTerminalTitle(terminalId, title, !!ifAbsent) : "starting";
6111
+ if (landed === "starting") {
6112
+ return reply.code(503).send({ error: "daemon is still starting up" });
6113
+ }
6114
+ if (!landed) {
6115
+ return reply.code(409).send({ error: "Failed to set terminal title (tmux unavailable or terminal gone)" });
6116
+ }
6117
+ return { status: "ok" };
6118
+ });
6070
6119
  typed.post("/stop", {
6071
6120
  schema: {
6072
6121
  response: {
@@ -6872,6 +6921,76 @@ function expandEnvironmentVariables(envVars, sourceEnv = process.env) {
6872
6921
  return expanded;
6873
6922
  }
6874
6923
 
6924
+ const SESSION_MODE_DIR = "session-modes";
6925
+ const ACP_PERMISSION_MODES = ["default", "acceptEdits", "plan", "bypassPermissions"];
6926
+ function normalizeAcpPermissionMode(value) {
6927
+ if (typeof value !== "string") return null;
6928
+ if (value === "yolo") return "bypassPermissions";
6929
+ return ACP_PERMISSION_MODES.includes(value) ? value : null;
6930
+ }
6931
+ function sessionModeFilePath(happySessionId, dir = path.join(persistence.configuration.happyHomeDir, SESSION_MODE_DIR)) {
6932
+ return path.join(dir, `${happySessionId}.json`);
6933
+ }
6934
+ function sessionModeFilePayload(permissionMode, now = Date.now()) {
6935
+ return { permissionMode, updatedAt: now };
6936
+ }
6937
+ function writeSessionModeFile(happySessionId, permissionMode, opts = {}) {
6938
+ const path$1 = sessionModeFilePath(happySessionId, opts.dir);
6939
+ node_fs.mkdirSync(path.join(path$1, ".."), { recursive: true, mode: 448 });
6940
+ const payload = sessionModeFilePayload(permissionMode, opts.now);
6941
+ const tmp = `${path$1}.${process.pid}.tmp`;
6942
+ persistence.writePrivateFileSync(tmp, JSON.stringify(payload));
6943
+ node_fs.renameSync(tmp, path$1);
6944
+ return payload;
6945
+ }
6946
+ function removeSessionModeFile(happySessionId, dir) {
6947
+ const path = sessionModeFilePath(happySessionId, dir);
6948
+ if (node_fs.existsSync(path)) node_fs.unlinkSync(path);
6949
+ }
6950
+
6951
+ const IGNORED_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["--happy-starting-mode"]);
6952
+ const PI_ADAPTER_INSTALL_HINT = "very-happy pi needs the pi-acp adapter on PATH: npm install -g pi-acp@0.0.33";
6953
+ function piAdapterMissingHint(detail) {
6954
+ return detail && /ENOENT/.test(detail) ? PI_ADAPTER_INSTALL_HINT : null;
6955
+ }
6956
+ function parsePiRunnerArgs(args) {
6957
+ const parsed = { verbose: false, passthrough: [] };
6958
+ for (let i = 0; i < args.length; i++) {
6959
+ const arg = args[i];
6960
+ if (arg === "--") {
6961
+ parsed.passthrough.push(...args.slice(i + 1));
6962
+ break;
6963
+ }
6964
+ if (arg === "--started-by") {
6965
+ const value = args[++i];
6966
+ if (value === "daemon" || value === "terminal") parsed.startedBy = value;
6967
+ continue;
6968
+ }
6969
+ if (arg === "--verbose") {
6970
+ parsed.verbose = true;
6971
+ continue;
6972
+ }
6973
+ if (arg === "--permission-mode") {
6974
+ const mode = normalizeAcpPermissionMode(args[++i]);
6975
+ if (mode) parsed.permissionMode = mode;
6976
+ continue;
6977
+ }
6978
+ if (IGNORED_FLAGS_WITH_VALUE.has(arg)) {
6979
+ i++;
6980
+ continue;
6981
+ }
6982
+ throw new Error(`Unknown option for very-happy pi: ${arg} (use -- to pass args to pi-acp)`);
6983
+ }
6984
+ return parsed;
6985
+ }
6986
+
6987
+ function spawnAgentUnavailableError(agent, availability) {
6988
+ if (agent === "pi" && !availability.pi) {
6989
+ return { type: "error", errorMessage: PI_ADAPTER_INSTALL_HINT };
6990
+ }
6991
+ return null;
6992
+ }
6993
+
6875
6994
  const ResumableMetadataSchema = z.z.object({
6876
6995
  path: z.z.string().min(1),
6877
6996
  flavor: z.z.string().optional(),
@@ -7121,9 +7240,12 @@ function decideHandover(run) {
7121
7240
 
7122
7241
  function decideAutoUpdate(context) {
7123
7242
  if (!context.enabled) return { action: "skip", reason: "auto-update disabled" };
7124
- const target = context.recommendedVersion;
7125
- if (!target) return { action: "skip", reason: "no recommended version published" };
7126
- if (target === context.currentVersion) return { action: "skip", reason: "already current" };
7243
+ const target = context.autoUpdateVersion;
7244
+ if (!target) return { action: "skip", reason: "no version approved for automatic install" };
7245
+ const ordering = persistence.compareExactVersions(context.currentVersion, target);
7246
+ if (ordering === null) return { action: "skip", reason: "version numbers not comparable" };
7247
+ if (ordering === 0) return { action: "skip", reason: "already current" };
7248
+ if (ordering > 0) return { action: "skip", reason: `already ahead of the approved ${target}` };
7127
7249
  if (context.failedVersion === target) {
7128
7250
  return { action: "skip", reason: `already failed to install ${target} once` };
7129
7251
  }
@@ -7197,7 +7319,7 @@ function parseJsonObject(text) {
7197
7319
  return null;
7198
7320
  }
7199
7321
  }
7200
- function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-4jiUeiUz.cjs', document.baseURI).href))).resolve(s)) {
7322
+ function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BXOXRblP.cjs', document.baseURI).href))).resolve(s)) {
7201
7323
  const platformArch = `${platform}-${arch}`;
7202
7324
  const candidates = [
7203
7325
  `@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
@@ -8352,6 +8474,7 @@ function sanitizeImportTitle(value) {
8352
8474
  return collapsed.length > 200 ? `${collapsed.slice(0, 199)}\u2026` : collapsed;
8353
8475
  }
8354
8476
  const hostSuffix = process.env.HAPPY_VARIANT === "dev" ? "-dev" : "";
8477
+ const startupCliAvailability = persistence.detectCLIAvailability();
8355
8478
  const initialMachineMetadata = {
8356
8479
  host: os$1.hostname() + hostSuffix,
8357
8480
  platform: os$1.platform(),
@@ -8359,7 +8482,7 @@ const initialMachineMetadata = {
8359
8482
  homeDir: os$1.homedir(),
8360
8483
  happyHomeDir: persistence.configuration.happyHomeDir,
8361
8484
  happyLibDir: persistence.projectPath(),
8362
- cliAvailability: persistence.detectCLIAvailability(),
8485
+ cliAvailability: startupCliAvailability,
8363
8486
  resumeSupport: { ...persistence.detectResumeSupport(), rpcAvailable: true }
8364
8487
  };
8365
8488
  async function preflightNewBundle(bundlePath) {
@@ -8617,18 +8740,26 @@ async function startDaemon() {
8617
8740
  };
8618
8741
  const assistantSpawnGate = createSpawnGate();
8619
8742
  const spawnSession = (options) => {
8620
- if (options.variant !== "assistant") {
8743
+ if (assistantSpawnMode(options) !== "claude-singleton") {
8621
8744
  return spawnSessionImpl(options);
8622
8745
  }
8623
8746
  return options.forceNew ? assistantSpawnGate.replace(() => spawnSessionImpl(options)) : assistantSpawnGate.join(() => spawnSessionImpl(options));
8624
8747
  };
8748
+ const currentCliAvailability = () => apiMachineRef?.getCLIAvailability() ?? startupCliAvailability;
8625
8749
  const spawnSessionImpl = async (options) => {
8626
8750
  persistence.logger.debugLargeJson("[DAEMON RUN] Spawning session", persistence.summarizeSpawnSessionForLog(options));
8627
8751
  const spawnPermissionMode = sanitizeSpawnPermissionMode(options.permissionMode);
8628
8752
  if (options.permissionMode !== void 0 && spawnPermissionMode === null) {
8629
8753
  persistence.logger.warn("[DAEMON RUN] Ignoring invalid permissionMode in spawn request");
8630
8754
  }
8631
- if (options.variant === "assistant") {
8755
+ const unavailable = spawnAgentUnavailableError(options.agent, currentCliAvailability());
8756
+ if (unavailable) {
8757
+ persistence.logger.warn(`[DAEMON RUN] Refusing spawn of agent '${options.agent}': ${unavailable.errorMessage}`);
8758
+ return unavailable;
8759
+ }
8760
+ const assistantMode = assistantSpawnMode(options);
8761
+ const trackedVariant = assistantMode === "claude-singleton" ? options.variant : void 0;
8762
+ if (assistantMode === "claude-singleton") {
8632
8763
  options = { ...options, directory: assistantHome() };
8633
8764
  if (options.forceNew) {
8634
8765
  for (const tracked of [...pidToTrackedSession.values()]) {
@@ -8831,7 +8962,13 @@ async function startDaemon() {
8831
8962
  persistence.logger.debug(`[DAEMON RUN] Attempting to spawn session in tmux: ${sessionDesc}`);
8832
8963
  const tmux = getTmuxUtilities(tmuxSessionName);
8833
8964
  const cliPath = path$1.join(persistence.projectPath(), "dist", "index.mjs");
8834
- const agent = options.agent === "gemini" ? "gemini" : options.agent === "codex" ? "codex" : options.agent === "openclaw" ? "openclaw" : "claude";
8965
+ const agent = options.agent ?? "claude";
8966
+ if (!isSpawnAgent(agent)) {
8967
+ return {
8968
+ type: "error",
8969
+ errorMessage: `Unsupported agent type: '${options.agent}'. Please update your CLI to the latest version.`
8970
+ };
8971
+ }
8835
8972
  const resumeId = agent === "claude" ? options.resumeClaudeSessionId : agent === "codex" ? options.resumeCodexThreadId : void 0;
8836
8973
  const resumeFragment = resumeId ? ` --resume ${persistence.shellescape(resumeId)}` : "";
8837
8974
  const permissionModeFragment = spawnPermissionMode ? ` --permission-mode ${persistence.shellescape(spawnPermissionMode)}` : "";
@@ -8859,7 +8996,7 @@ async function startDaemon() {
8859
8996
  pid: tmuxResult.pid,
8860
8997
  // Real PID from tmux -P flag
8861
8998
  tmuxSessionId: tmuxResult.sessionId,
8862
- variant: options.variant,
8999
+ variant: trackedVariant,
8863
9000
  spawnedBy: options.spawnedBy,
8864
9001
  directoryCreated,
8865
9002
  message: directoryCreated ? `The path '${directory}' did not exist. We created a new folder and spawned a new session in tmux session '${tmuxSessionName}'. Use 'tmux attach -t ${tmuxSessionName}' to view the session.` : `Spawned new session in tmux session '${tmuxSessionName}'. Use 'tmux attach -t ${tmuxSessionName}' to view the session.`
@@ -8906,6 +9043,9 @@ async function startDaemon() {
8906
9043
  case "openclaw":
8907
9044
  agentCommand = "openclaw";
8908
9045
  break;
9046
+ case "pi":
9047
+ agentCommand = "pi";
9048
+ break;
8909
9049
  default:
8910
9050
  return {
8911
9051
  type: "error",
@@ -8937,7 +9077,7 @@ async function startDaemon() {
8937
9077
  },
8938
9078
  directoryCreated,
8939
9079
  message: directoryCreated ? `The path '${directory}' did not exist. We created a new folder and spawned a new session there.` : void 0,
8940
- variant: options.variant,
9080
+ variant: trackedVariant,
8941
9081
  spawnedBy: options.spawnedBy
8942
9082
  });
8943
9083
  }
@@ -9418,6 +9558,10 @@ async function startDaemon() {
9418
9558
  },
9419
9559
  onTerminalHook: (body) => {
9420
9560
  mirrorManagerRef?.handleHookPayload(body);
9561
+ },
9562
+ setTerminalTitle: (terminalId, title, ifAbsent) => {
9563
+ if (!apiMachineRef) return "starting";
9564
+ return apiMachineRef.setTerminalTitle(terminalId, title, ifAbsent);
9421
9565
  }
9422
9566
  });
9423
9567
  const daemonClaudeCredentials = resolveClaudeCredentialReadiness();
@@ -9522,7 +9666,7 @@ async function startDaemon() {
9522
9666
  const decision = decideAutoUpdate({
9523
9667
  enabled: (settings.cliAutoUpdate ?? "idle") !== "off",
9524
9668
  currentVersion: cliUpdate.currentVersion,
9525
- recommendedVersion: cliUpdate.recommendedVersion,
9669
+ autoUpdateVersion: cliUpdate.autoUpdateVersion ?? null,
9526
9670
  idle: pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals(),
9527
9671
  failedVersion: autoUpdateFailedVersion
9528
9672
  });
@@ -9781,6 +9925,194 @@ function formatTranscript(bodies) {
9781
9925
  return lines.join("\n");
9782
9926
  }
9783
9927
 
9928
+ const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
9929
+ async function openUserScopedSocket(token, options = {}) {
9930
+ const socket = socket_ioClient.io(persistence.configuration.serverUrl, {
9931
+ auth: {
9932
+ token,
9933
+ clientType: "user-scoped",
9934
+ happyClient: `cli-session-ops/${persistence.configuration.currentCliVersion}`
9935
+ },
9936
+ path: "/v1/updates",
9937
+ transports: ["websocket"],
9938
+ reconnection: false,
9939
+ autoConnect: false
9940
+ });
9941
+ await new Promise((resolve, reject) => {
9942
+ const timer = setTimeout(() => {
9943
+ socket.close();
9944
+ reject(new Error(`Timed out connecting to ${persistence.configuration.serverUrl}`));
9945
+ }, options.connectTimeoutMs ?? USER_SOCKET_CONNECT_TIMEOUT_MS);
9946
+ socket.once("connect", () => {
9947
+ clearTimeout(timer);
9948
+ resolve();
9949
+ });
9950
+ socket.once("connect_error", (error) => {
9951
+ clearTimeout(timer);
9952
+ socket.close();
9953
+ reject(new Error(`Could not connect to ${persistence.configuration.serverUrl}: ${error.message}`));
9954
+ });
9955
+ socket.connect();
9956
+ });
9957
+ return {
9958
+ async rpcCall(payload, timeoutMs) {
9959
+ const ack = await socket.timeout(timeoutMs).emitWithAck("rpc-call", payload);
9960
+ return ack;
9961
+ },
9962
+ close() {
9963
+ socket.close();
9964
+ }
9965
+ };
9966
+ }
9967
+
9968
+ const PERMISSION_OPS_CLIENT = "session-ops";
9969
+ const PERMISSION_RPC_TIMEOUT_MS = 3e4;
9970
+ const SETTLE_TIMEOUT_MS = 5e3;
9971
+ const SETTLE_POLL_MS = 500;
9972
+ function buildPermissionRpcPayload(requestId, verdict) {
9973
+ if (verdict.kind === "approve") {
9974
+ return {
9975
+ id: requestId,
9976
+ approved: true,
9977
+ decision: verdict.forSession ? "approved_for_session" : "approved"
9978
+ };
9979
+ }
9980
+ return {
9981
+ id: requestId,
9982
+ approved: false,
9983
+ decision: "denied",
9984
+ ...verdict.reason ? { reason: verdict.reason } : {}
9985
+ };
9986
+ }
9987
+ function interpretPermissionAck(ack, decryptResult) {
9988
+ if (!ack.ok) {
9989
+ const message = ack.error ?? "RPC call failed";
9990
+ if (message === "RPC method not available") {
9991
+ return { status: "offline", message: "no running wrapper has registered the permission RPC for this session" };
9992
+ }
9993
+ if (message === "RPC target disconnected") {
9994
+ return { status: "offline", message: "the wrapper disconnected while the permission RPC was in flight" };
9995
+ }
9996
+ if (/timed? ?out/i.test(message)) {
9997
+ return { status: "timeout", message };
9998
+ }
9999
+ return { status: "rejected", message };
10000
+ }
10001
+ let body = null;
10002
+ if (typeof ack.result === "string" && ack.result.length > 0) {
10003
+ try {
10004
+ body = decryptResult(ack.result);
10005
+ } catch {
10006
+ body = null;
10007
+ }
10008
+ }
10009
+ if (body && typeof body === "object" && typeof body.error === "string") {
10010
+ return { status: "handler-error", message: body.error };
10011
+ }
10012
+ return { status: "acknowledged" };
10013
+ }
10014
+ function pendingRequestsOf(agentState, now) {
10015
+ const requests = agentState?.requests;
10016
+ if (!requests || typeof requests !== "object") return [];
10017
+ return Object.entries(requests).map(([id, request]) => ({
10018
+ id,
10019
+ tool: typeof request?.tool === "string" ? request.tool : "unknown",
10020
+ ...request?.kind ? { kind: request.kind } : {},
10021
+ ...typeof request?.createdAt === "number" ? { createdAt: request.createdAt, waitingMs: Math.max(0, now - request.createdAt) } : {}
10022
+ })).sort((a, b) => (a.createdAt ?? Number.MAX_SAFE_INTEGER) - (b.createdAt ?? Number.MAX_SAFE_INTEGER));
10023
+ }
10024
+ const RPC_DEADLINE_GRACE_MS = 2e3;
10025
+ function withDeadline(promise, ms, message) {
10026
+ return new Promise((resolve, reject) => {
10027
+ const timer = setTimeout(() => reject(new Error(`${message} (timed out)`)), ms);
10028
+ promise.then(
10029
+ (value) => {
10030
+ clearTimeout(timer);
10031
+ resolve(value);
10032
+ },
10033
+ (error) => {
10034
+ clearTimeout(timer);
10035
+ reject(error);
10036
+ }
10037
+ );
10038
+ });
10039
+ }
10040
+ async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}) {
10041
+ const now = deps.now ?? Date.now;
10042
+ const persisted = (deps.readPersisted ?? persistence.readPersistedSessions)()[sessionId];
10043
+ if (!persisted) {
10044
+ throw new Error(
10045
+ `No local key for session ${sessionId} \u2014 approve/deny needs the session key from ~/.happy/sessions.json (the RPC payload is encrypted with it), so only sessions this machine's daemon spawned within 14 days can be answered from here.`
10046
+ );
10047
+ }
10048
+ const token = await (deps.bearerToken ?? bearerToken$1)();
10049
+ const fetchState = deps.fetchAgentState ?? fetchDecryptedAgentState;
10050
+ const before = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
10051
+ if (!before.some((request) => request.id === requestId)) {
10052
+ const hint = before.length > 0 ? `Pending request ids: ${before.map((request) => `${request.id} (${request.tool})`).join(", ")}` : "The session has no pending requests.";
10053
+ throw new Error(`Request ${requestId} is not pending on session ${sessionId}. ${hint}`);
10054
+ }
10055
+ const payload = buildPermissionRpcPayload(requestId, verdict);
10056
+ const key = persistence.decodeBase64(persisted.encryptionKey);
10057
+ const params = persistence.encodeBase64(persistence.encrypt(key, persisted.encryptionVariant, payload));
10058
+ const transport = await (deps.openTransport ?? openUserScopedSocket)(token);
10059
+ const rpcTimeoutMs = deps.rpcTimeoutMs ?? PERMISSION_RPC_TIMEOUT_MS;
10060
+ let ack;
10061
+ try {
10062
+ ack = await withDeadline(
10063
+ transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
10064
+ rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
10065
+ `permission RPC did not settle within ${rpcTimeoutMs}ms`
10066
+ );
10067
+ } catch (error) {
10068
+ ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
10069
+ } finally {
10070
+ transport.close();
10071
+ }
10072
+ const outcome = interpretPermissionAck(ack, (encrypted) => persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(encrypted)));
10073
+ if (outcome.status !== "acknowledged") {
10074
+ return { sessionId, requestId, payload, outcome };
10075
+ }
10076
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
10077
+ const deadline = now() + (deps.settleTimeoutMs ?? SETTLE_TIMEOUT_MS);
10078
+ let settled = false;
10079
+ for (; ; ) {
10080
+ const pending = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
10081
+ if (!pending.some((request) => request.id === requestId)) {
10082
+ settled = true;
10083
+ break;
10084
+ }
10085
+ if (now() >= deadline) break;
10086
+ await sleep(SETTLE_POLL_MS);
10087
+ }
10088
+ return { sessionId, requestId, payload, outcome, settled };
10089
+ }
10090
+ async function bearerToken$1() {
10091
+ const credentials = await persistence.readCredentialsForConfiguredRelay();
10092
+ if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
10093
+ return credentials.token;
10094
+ }
10095
+ async function fetchDecryptedAgentState(sessionId, persisted, token) {
10096
+ const response = await axios.get(
10097
+ `${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
10098
+ {
10099
+ headers: {
10100
+ "Authorization": `Bearer ${token}`,
10101
+ "X-Happy-Client": `${PERMISSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
10102
+ },
10103
+ timeout: 15e3,
10104
+ // 404 is a real answer ("not yours / gone"), not a transport fault.
10105
+ validateStatus: (status) => status >= 200 && status < 300 || status === 404
10106
+ }
10107
+ );
10108
+ if (response.status === 404) {
10109
+ throw new Error(`Session ${sessionId} was not found on this account (it may have been deleted, or the local key belongs to a session on another relay).`);
10110
+ }
10111
+ const raw = response.data?.session?.agentState;
10112
+ if (typeof raw !== "string" || raw.length === 0) return null;
10113
+ return persistence.decrypt(persistence.decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, persistence.decodeBase64(raw));
10114
+ }
10115
+
9784
10116
  const SESSION_OPS_CLIENT = "session-ops";
9785
10117
  const DEFAULT_RECENT_LIMIT = 15;
9786
10118
  const MAX_READ_LIMIT = 100;
@@ -9792,10 +10124,7 @@ function mergeSessionSummaries(live, persisted, options = {}) {
9792
10124
  const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
9793
10125
  if (!id || seen.has(id)) continue;
9794
10126
  seen.add(id);
9795
- summaries.push(toSummary(id, persisted[id], {
9796
- live: true,
9797
- pid: typeof child.pid === "number" ? child.pid : void 0
9798
- }));
10127
+ summaries.push(toSummary(id, persisted[id], livenessOf(child)));
9799
10128
  }
9800
10129
  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);
9801
10130
  for (const [id, entry] of rest) {
@@ -9805,6 +10134,13 @@ function mergeSessionSummaries(live, persisted, options = {}) {
9805
10134
  const wanted = options.tag;
9806
10135
  return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
9807
10136
  }
10137
+ function livenessOf(child) {
10138
+ return { live: true, pid: typeof child.pid === "number" ? child.pid : void 0 };
10139
+ }
10140
+ function sessionLiveness(live, sessionId) {
10141
+ const child = live.find((entry) => entry.happySessionId === sessionId);
10142
+ return child ? livenessOf(child) : { live: false };
10143
+ }
9808
10144
  function toSummary(id, persisted, extra) {
9809
10145
  const meta = persisted?.metadata;
9810
10146
  return {
@@ -9824,6 +10160,79 @@ async function listSessions(options = {}) {
9824
10160
  const live = await listDaemonSessions();
9825
10161
  return mergeSessionSummaries(live, persistence.readPersistedSessions(), options);
9826
10162
  }
10163
+ function summarizeAccountSession(row, persisted, liveIds, now) {
10164
+ const base = {
10165
+ id: row.id,
10166
+ live: liveIds.has(row.id),
10167
+ url: sessionWebUrl(row.id),
10168
+ decryptable: false,
10169
+ active: row.active === true,
10170
+ archived: typeof row.archivedAt === "number",
10171
+ ...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
10172
+ ...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {},
10173
+ attention: false
10174
+ };
10175
+ if (!persisted) return base;
10176
+ const key = persistence.decodeBase64(persisted.encryptionKey);
10177
+ let metadata = null;
10178
+ let agentState = null;
10179
+ try {
10180
+ metadata = row.metadata ? persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(row.metadata)) : null;
10181
+ agentState = row.agentState ? persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(row.agentState)) : null;
10182
+ } catch {
10183
+ return base;
10184
+ }
10185
+ if (!metadata) return base;
10186
+ const meta = metadata;
10187
+ const pending = pendingRequestsOf(agentState, now);
10188
+ return {
10189
+ ...base,
10190
+ decryptable: true,
10191
+ ...meta.summary?.text ? { title: meta.summary.text } : {},
10192
+ ...meta.path ? { cwd: meta.path } : {},
10193
+ ...meta.flavor ? { flavor: meta.flavor } : {},
10194
+ ...meta.tags?.length ? { tags: [...meta.tags] } : {},
10195
+ ...meta.variant ? { variant: meta.variant } : {},
10196
+ ...meta.machineId ? { machineId: meta.machineId } : {},
10197
+ ...persisted.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
10198
+ pending,
10199
+ attention: pending.length > 0
10200
+ };
10201
+ }
10202
+ function orderAccountSessions(summaries, options = {}) {
10203
+ const oldestWait = (summary) => Math.max(0, ...(summary.pending ?? []).map((request) => request.waitingMs ?? 0));
10204
+ const rank = (summary) => summary.attention ? 0 : summary.live ? 1 : 2;
10205
+ let rows = summaries.filter((summary) => summary.flavor !== "terminal-mirror").slice().sort((a, b) => {
10206
+ const byRank = rank(a) - rank(b);
10207
+ if (byRank !== 0) return byRank;
10208
+ if (a.attention && b.attention) return oldestWait(b) - oldestWait(a);
10209
+ return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
10210
+ });
10211
+ if (options.tag) {
10212
+ const wanted = options.tag;
10213
+ rows = rows.filter((summary) => summary.tags?.includes(wanted) === true);
10214
+ }
10215
+ const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
10216
+ let idleKept = 0;
10217
+ return rows.filter((summary) => rank(summary) < 2 || idleKept++ < recentLimit);
10218
+ }
10219
+ async function listAccountSessions(options = {}) {
10220
+ const token = await bearerToken();
10221
+ const response = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions`, {
10222
+ headers: {
10223
+ "Authorization": `Bearer ${token}`,
10224
+ "X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
10225
+ },
10226
+ timeout: 15e3
10227
+ });
10228
+ const rows = Array.isArray(response.data?.sessions) ? response.data.sessions : [];
10229
+ const persisted = persistence.readPersistedSessions();
10230
+ const live = await listDaemonSessions();
10231
+ const liveIds = new Set(live.map((child) => child.happySessionId).filter((id) => typeof id === "string"));
10232
+ const now = Date.now();
10233
+ const summaries = rows.filter((row) => typeof row?.id === "string").map((row) => summarizeAccountSession(row, persisted[row.id], liveIds, now)).filter((summary) => options.includeArchived || !summary.archived);
10234
+ return orderAccountSessions(summaries, options);
10235
+ }
9827
10236
  async function bearerToken() {
9828
10237
  const credentials = await persistence.readCredentialsForConfiguredRelay();
9829
10238
  if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
@@ -9852,6 +10261,7 @@ async function readSessionTranscript(sessionId, limit) {
9852
10261
  const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
9853
10262
  messages.reverse();
9854
10263
  const key = persistence.decodeBase64(persisted.encryptionKey);
10264
+ const live = await listDaemonSessions();
9855
10265
  const bodies = messages.map((message) => {
9856
10266
  if (message.content?.t !== "encrypted") return null;
9857
10267
  try {
@@ -9861,7 +10271,7 @@ async function readSessionTranscript(sessionId, limit) {
9861
10271
  }
9862
10272
  });
9863
10273
  return {
9864
- summary: toSummary(sessionId, persisted, { live: false }),
10274
+ summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
9865
10275
  messageCount: messages.length,
9866
10276
  transcript: formatTranscript(bodies)
9867
10277
  };
@@ -9957,13 +10367,16 @@ function normalizeSpawnDirectory(input, homeDir) {
9957
10367
  return { ok: true, directory: path.normalize(expanded) };
9958
10368
  }
9959
10369
 
9960
- const ASSISTANT_TOOL_NAMES = [
10370
+ const ASSISTANT_SESSION_TOOL_NAMES = [
9961
10371
  "sessions_list",
9962
10372
  "session_read",
9963
10373
  "session_send",
9964
10374
  "session_spawn",
9965
10375
  "session_kill",
9966
- "session_archive",
10376
+ "session_archive"
10377
+ ];
10378
+ const ASSISTANT_TOOL_NAMES = [
10379
+ ...ASSISTANT_SESSION_TOOL_NAMES,
9967
10380
  "terminals_list",
9968
10381
  "terminal_read",
9969
10382
  "terminal_send",
@@ -10000,6 +10413,10 @@ function describeSummary(summary) {
10000
10413
  return parts.join(" ");
10001
10414
  }
10002
10415
  function registerAssistantTools(mcp) {
10416
+ registerAssistantSessionTools(mcp);
10417
+ registerAssistantMachineTools(mcp);
10418
+ }
10419
+ function registerAssistantSessionTools(mcp) {
10003
10420
  mcp.registerTool("sessions_list", {
10004
10421
  description: "List Claude Code sessions on this machine: sessions currently tracked by the local daemon (running) plus recently seen ones. Returns id, title, working directory, agent flavor and web URL for each.",
10005
10422
  title: "List Sessions",
@@ -10116,6 +10533,8 @@ ${body}`);
10116
10533
  return fail(`Failed to archive session: ${error instanceof Error ? error.message : String(error)}`);
10117
10534
  }
10118
10535
  });
10536
+ }
10537
+ function registerAssistantMachineTools(mcp) {
10119
10538
  mcp.registerTool("terminals_list", {
10120
10539
  description: "List the web terminals (tmux sessions) on this machine with their id, title and working directory.",
10121
10540
  title: "List Terminals",
@@ -10785,7 +11204,7 @@ function registerSideQuestionHandler(rpc, deps) {
10785
11204
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
10786
11205
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
10787
11206
  const run = deps.run ?? (async (input) => {
10788
- const { query } = await Promise.resolve().then(function () { return require('./index-lX1UliOs.cjs'); });
11207
+ const { query } = await Promise.resolve().then(function () { return require('./index-CxjX8DGz.cjs'); });
10789
11208
  return runSideQuestion(query, input);
10790
11209
  });
10791
11210
  const slots = /* @__PURE__ */ new Map();
@@ -10887,7 +11306,7 @@ const MAX_TITLE_CHARS = 60;
10887
11306
  const GENERATION_TIMEOUT_MS = 3e4;
10888
11307
  function resolveClaudeBinary() {
10889
11308
  try {
10890
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-4jiUeiUz.cjs', document.baseURI).href)));
11309
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BXOXRblP.cjs', document.baseURI).href)));
10891
11310
  const utilsPath = path.resolve(path.join(persistence.projectPath(), "scripts", "claude_version_utils.cjs"));
10892
11311
  const { getClaudeCliPath } = require$1(utilsPath);
10893
11312
  const path$1 = getClaudeCliPath();
@@ -11325,7 +11744,7 @@ async function runClaude(credentials, options = {}) {
11325
11744
  const forkedFromMessageId = process.env.HAPPY_FORKED_FROM_MESSAGE_ID;
11326
11745
  const importedFromClaudeSessionId = process.env.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID;
11327
11746
  const importTitle = process.env.HAPPY_IMPORT_TITLE;
11328
- const originTags = spawnOriginTags();
11747
+ const originTags = spawnOriginTags(process.env, "claude");
11329
11748
  let metadata = {
11330
11749
  path: workingDirectory,
11331
11750
  host: os.hostname(),
@@ -13089,9 +13508,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
13089
13508
  };
13090
13509
  }
13091
13510
 
13092
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-4jiUeiUz.cjs', document.baseURI).href)));
13511
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BXOXRblP.cjs', document.baseURI).href)));
13093
13512
  const __dirname$1 = path.dirname(__filename$1);
13094
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-4jiUeiUz.cjs', document.baseURI).href)));
13513
+ const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BXOXRblP.cjs', document.baseURI).href)));
13095
13514
  const PRISMA_QUERY_ENGINE_FILES = {
13096
13515
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
13097
13516
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -15715,7 +16134,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15715
16134
  process.exit(0);
15716
16135
  } else if (subcommand === "install-terminal-hooks") {
15717
16136
  try {
15718
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-DnaFkrTF.cjs'); });
16137
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-lXQa6bQU.cjs'); });
15719
16138
  const command = parseTerminalHooksArgs(args.slice(1));
15720
16139
  if (command.action === "help") {
15721
16140
  console.log(TERMINAL_HOOKS_HELP);
@@ -15732,7 +16151,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15732
16151
  }
15733
16152
  } else if (subcommand === "spawn") {
15734
16153
  try {
15735
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-DQ6FTUK9.cjs'); });
16154
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-CbY233Eg.cjs'); });
15736
16155
  await handleSpawnCommand(args.slice(1));
15737
16156
  } catch (error) {
15738
16157
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -15744,7 +16163,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15744
16163
  return;
15745
16164
  } else if (subcommand === "sessions") {
15746
16165
  try {
15747
- const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-CbzqYiAy.cjs'); });
16166
+ const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-CGhpBNqn.cjs'); });
15748
16167
  await handleSessionsCommand(args.slice(1));
15749
16168
  } catch (error) {
15750
16169
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -15756,7 +16175,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15756
16175
  return;
15757
16176
  } else if (subcommand === "send") {
15758
16177
  try {
15759
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-CwWvFfay.cjs'); });
16178
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-BWGUkWhi.cjs'); });
15760
16179
  await handleSendCommand(args.slice(1));
15761
16180
  } catch (error) {
15762
16181
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -15862,9 +16281,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15862
16281
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
15863
16282
  const projectId = args[3];
15864
16283
  try {
15865
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-BoteFn-v.cjs'); });
15866
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-C5kEpkcZ.cjs'); }).then(function (n) { return n.persistence; });
15867
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-C5kEpkcZ.cjs'); }).then(function (n) { return n.api; });
16284
+ const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-CkUn1dI4.cjs'); });
16285
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-ClvvHves.cjs'); }).then(function (n) { return n.persistence; });
16286
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-ClvvHves.cjs'); }).then(function (n) { return n.api; });
15868
16287
  let userEmail = void 0;
15869
16288
  try {
15870
16289
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -15895,7 +16314,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15895
16314
  }
15896
16315
  if (geminiSubcommand === "project" && args[2] === "get") {
15897
16316
  try {
15898
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-BoteFn-v.cjs'); });
16317
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-CkUn1dI4.cjs'); });
15899
16318
  const config = readGeminiLocalConfig();
15900
16319
  if (config.googleCloudProject) {
15901
16320
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -15935,7 +16354,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15935
16354
  process.exit(0);
15936
16355
  }
15937
16356
  try {
15938
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-IWHyBq0A.cjs'); });
16357
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-Bj4Egq7j.cjs'); });
15939
16358
  let startedBy = void 0;
15940
16359
  for (let i = 1; i < args.length; i++) {
15941
16360
  if (args[i] === "--started-by") {
@@ -15955,9 +16374,44 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15955
16374
  process.exit(1);
15956
16375
  }
15957
16376
  return;
16377
+ } else if (subcommand === "pi") {
16378
+ let hintPrinted = false;
16379
+ const printHint = (detail) => {
16380
+ const hint = piAdapterMissingHint(detail);
16381
+ if (hint && !hintPrinted) {
16382
+ hintPrinted = true;
16383
+ console.error(chalk.gray(hint));
16384
+ }
16385
+ };
16386
+ try {
16387
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BzgtwTxC.cjs'); });
16388
+ const parsed = parsePiRunnerArgs(args.slice(1));
16389
+ const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
16390
+ const { credentials } = await authAndSetupMachineIfNeeded();
16391
+ await ensureDaemonRunning();
16392
+ await runAcp({
16393
+ credentials,
16394
+ startedBy: parsed.startedBy,
16395
+ verbose: parsed.verbose,
16396
+ permissionMode: parsed.permissionMode,
16397
+ agentName: resolved.agentName,
16398
+ command: resolved.command,
16399
+ args: resolved.args,
16400
+ onBackendError: printHint
16401
+ });
16402
+ if (hintPrinted) process.exit(1);
16403
+ } catch (error) {
16404
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
16405
+ printHint(error instanceof Error ? error.message : void 0);
16406
+ if (process.env.DEBUG) {
16407
+ console.error(error);
16408
+ }
16409
+ process.exit(1);
16410
+ }
16411
+ return;
15958
16412
  } else if (subcommand === "acp") {
15959
16413
  try {
15960
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-DWmhmji9.cjs'); });
16414
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BzgtwTxC.cjs'); });
15961
16415
  let startedBy = void 0;
15962
16416
  let verbose = false;
15963
16417
  const acpArgs = [];
@@ -15997,7 +16451,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15997
16451
  return;
15998
16452
  } else if (subcommand === "openclaw") {
15999
16453
  try {
16000
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-0myYIz1U.cjs'); });
16454
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-BJhSvXYy.cjs'); });
16001
16455
  let startedBy = void 0;
16002
16456
  let verbose = false;
16003
16457
  let gatewayUrl;
@@ -16048,7 +16502,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16048
16502
  return;
16049
16503
  } else if (subcommand === "mcp" && args.length === 1) {
16050
16504
  try {
16051
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-DSpEpM_2.cjs'); });
16505
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-DMD7FxBI.cjs'); });
16052
16506
  await handleMcpCommand();
16053
16507
  } catch (error) {
16054
16508
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -16263,10 +16717,12 @@ ${chalk.bold("Usage:")}
16263
16717
  print its web URL (for automation; see spawn --help)
16264
16718
  very-happy send Send a message into an existing session
16265
16719
  (for automation; see send --help)
16266
- very-happy sessions List, read, stop or archive sessions on this
16267
- machine (for automation; see sessions --help)
16720
+ very-happy sessions List, read, stop, archive, approve or deny
16721
+ sessions and their permission requests (for
16722
+ automation; see sessions --help)
16268
16723
  very-happy codex Start Codex mode
16269
16724
  very-happy gemini Start Gemini mode (ACP)
16725
+ very-happy pi Start pi mode (ACP via the pi-acp adapter)
16270
16726
  very-happy acp Start a generic ACP-compatible agent
16271
16727
  very-happy openclaw Connect through a configured OpenClaw gateway
16272
16728
  very-happy install-terminal-hooks
@@ -16292,6 +16748,7 @@ ${chalk.bold("Examples:")}
16292
16748
  very-happy --js-runtime bun Use bun instead of node to spawn Claude Code
16293
16749
  very-happy --claude-env ANTHROPIC_BASE_URL=http://127.0.0.1:3456
16294
16750
  Use a custom API endpoint (e.g., claude-code-router)
16751
+ very-happy pi Start pi (needs pi-acp on PATH)
16295
16752
  very-happy acp gemini Start Gemini via generic ACP runner
16296
16753
  very-happy acp opencode Start OpenCode through its built-in ACP adapter
16297
16754
  very-happy acp -- your-agent --acp
@@ -16418,24 +16875,33 @@ exports.GOOGLE_API_KEY_ENV = GOOGLE_API_KEY_ENV;
16418
16875
  exports.MAX_READ_LIMIT = MAX_READ_LIMIT;
16419
16876
  exports.MessageBuffer = MessageBuffer;
16420
16877
  exports.MessageQueue2 = MessageQueue2;
16878
+ exports.SPAWN_AGENTS = SPAWN_AGENTS;
16879
+ exports.TitleGenerator = TitleGenerator;
16421
16880
  exports.archiveSession = archiveSession;
16422
16881
  exports.checkIfDaemonRunningAndCleanupStaleState = checkIfDaemonRunningAndCleanupStaleState;
16423
16882
  exports.createSessionMetadata = createSessionMetadata;
16424
16883
  exports.hashObject = hashObject;
16425
16884
  exports.initialMachineMetadata = initialMachineMetadata;
16426
16885
  exports.isValidSpawnOrigin = isValidSpawnOrigin;
16886
+ exports.listAccountSessions = listAccountSessions;
16427
16887
  exports.listSessions = listSessions;
16888
+ exports.normalizeAcpPermissionMode = normalizeAcpPermissionMode;
16428
16889
  exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
16429
16890
  exports.pushClipboardViaDaemon = pushClipboardViaDaemon;
16430
16891
  exports.query = query;
16431
16892
  exports.readSessionTranscript = readSessionTranscript;
16893
+ exports.registerAssistantSessionTools = registerAssistantSessionTools;
16432
16894
  exports.registerKillSessionHandler = registerKillSessionHandler;
16895
+ exports.removeSessionModeFile = removeSessionModeFile;
16896
+ exports.resolvePermissionRequest = resolvePermissionRequest;
16433
16897
  exports.sanitizeSpawnPermissionMode = sanitizeSpawnPermissionMode;
16434
16898
  exports.sendUserMessage = sendUserMessage;
16435
16899
  exports.sessionWebUrl = sessionWebUrl;
16900
+ exports.setTerminalTitleViaDaemon = setTerminalTitleViaDaemon;
16436
16901
  exports.setupOfflineReconnection = setupOfflineReconnection;
16437
16902
  exports.spawnDaemonSession = spawnDaemonSession;
16438
16903
  exports.startHappyServer = startHappyServer;
16439
16904
  exports.stopSession = stopSession;
16440
16905
  exports.tmuxSupportsSessionEnv = tmuxSupportsSessionEnv;
16441
16906
  exports.waitForSessionKey = waitForSessionKey;
16907
+ exports.writeSessionModeFile = writeSessionModeFile;