very-happy-cli 0.2.115 → 0.2.117
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.
- package/dist/{AcpBackend-DEZTWZZu.mjs → AcpBackend-C4RMHt0I.mjs} +1 -1
- package/dist/{AcpBackend-BrZN8KM-.cjs → AcpBackend-CrbXvcgQ.cjs} +1 -1
- package/dist/{AcpSessionManager-DjulpKY0.cjs → AcpSessionManager-CjAVO4RX.cjs} +1 -1
- package/dist/{AcpSessionManager-BzP3eiOG.mjs → AcpSessionManager-Cnc8Jatl.mjs} +1 -1
- package/dist/{config-CMgRBQMS.mjs → config-CnA3JIUn.mjs} +2 -2
- package/dist/{config-BNAsx9Wg.cjs → config-O9quMfxg.cjs} +2 -2
- package/dist/{index-B2KfhNku.mjs → index-B0o3H6_x.mjs} +2 -2
- package/dist/{index-BHNEb4bc.mjs → index-BYZKuQoy.mjs} +590 -41
- package/dist/{index-tPXwlc_t.cjs → index-DcESl4cI.cjs} +601 -43
- package/dist/{index-CpGR7rIw.mjs → index-DiRN0Rv8.mjs} +78 -8
- package/dist/{index-B3UkbonP.cjs → index-DqmVH6R5.cjs} +2 -2
- package/dist/{index-ClR0jhjm.cjs → index-W-O4xR_O.cjs} +77 -7
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-DD6QuHfT.mjs → installTerminalHooks-DeYqyjCB.mjs} +2 -2
- package/dist/{installTerminalHooks-CWfCF68p.cjs → installTerminalHooks-zfsWSotO.cjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +66 -1
- package/dist/lib.d.mts +66 -1
- package/dist/lib.mjs +1 -1
- package/dist/{mcp-CiNrjYox.mjs → mcp-D_5aOK0I.mjs} +62 -17
- package/dist/{mcp-CqjiDRnG.cjs → mcp-nYqC4FYm.cjs} +62 -16
- package/dist/{runGemini-DdJqpywr.mjs → runGemini-CEURRmM4.mjs} +4 -4
- package/dist/{runGemini-UN5IznJp.cjs → runGemini-DYf3ntib.cjs} +4 -4
- package/dist/{runOpenClaw-CM5UrVBy.mjs → runOpenClaw-BozfKWng.mjs} +3 -3
- package/dist/{runOpenClaw-9hhfGllZ.cjs → runOpenClaw-CNKjAw-H.cjs} +3 -3
- package/dist/{send-CnVtXOj0.cjs → send-BZLhRZb-.cjs} +2 -2
- package/dist/{send-C6G2jsQp.mjs → send-CYznZGrp.mjs} +2 -2
- package/dist/sessions-DYPB3Peg.mjs +317 -0
- package/dist/sessions-YWt9YEN8.cjs +322 -0
- package/dist/{spawn-qb344Y5E.mjs → spawn-CsrHZd3y.mjs} +3 -4
- package/dist/{spawn-CfYddkS_.cjs → spawn-tSvo3stN.cjs} +6 -7
- package/dist/{types-CpnzfHHS.cjs → types-DD3AK6Nb.cjs} +189 -26
- package/dist/{types-B3qx8Ga1.mjs → types-jSfU_0o1.mjs} +187 -26
- package/package.json +7 -7
- package/dist/sessions-CGdcF05T.cjs +0 -204
- package/dist/sessions-CmabJzYS.mjs +0 -200
|
@@ -1,11 +1,11 @@
|
|
|
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, tmpdir } from 'node:os';
|
|
3
3
|
import { randomUUID, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
|
|
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, S as STREAM_DELTA_MAX_CHARS, x as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, y as hardenPrivateFile, f as packageJson, j as readSettings,
|
|
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, S as STREAM_DELTA_MAX_CHARS, x as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, y as hardenPrivateFile, f as packageJson, z as commandOnPath, j as readSettings, B as readCredentials, C as credentialRelayProblem, D as deriveLocalCliUpdateSummary, k as encodeBase64, E as updateSettings, F as encodeBase64Url, G as decodeBase64, H as writeCredentialsLegacy, J as writeCredentialsDataKey, K as readCredentialsForConfiguredRelay, L as encrypt, r as readPersistedSessions, N as isClaudeSessionId, w as writePrivateFileSync, O as getLocalHappyAgentCredentialPath, P as readLocalHappyAgentCredentials, Q as decryptWithDataKey, T as decryptLegacy, U as compareExactVersions, V as extractCompleteLines, W as decideBackfill, X as parseTerminalHookPayload, Y as tmuxArgs, Z as scrubTmuxClientEnv, _ as decideMirrorBinding, $ as MIRROR_BACKFILL_LINES_DEFAULT, a0 as persistSession, a1 as mirrorLineKey, a2 as mirrorLocalId, a3 as detectCLIAvailability, a4 as detectResumeSupport, a5 as acquireDaemonLock, a6 as summarizeSpawnSessionForLog, a7 as deletePersistedSessions, a8 as shellescape, a9 as decrypt, aa as writeDaemonState, A as ApiClient, ab as writeSettings, ac as fetchCliUpdateState, ad as resolveCliUpdateCheckInterval, ae as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, af as releaseDaemonLock, i as isValidSessionId, ag as listVhTerminals, ah as isValidTerminalId, ai as readVhTerminal, aj as sendToVhTerminal, ak as TITLE_PROMPT_PREFIX, al as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, am as startOfflineReconnection, an as clearCredentials, ao as clearMachineId, m as ensurePrivateDirectorySync, ap as SandboxConfigSchema, b as createEnvelope, aq as CodexAppServerClient, ar as getLatestDaemonLog } from './types-jSfU_0o1.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';
|
|
8
|
-
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, unlinkSync, writeFileSync, accessSync, constants,
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, unlinkSync, renameSync, writeFileSync, accessSync, constants, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
|
|
9
9
|
import { t as trimIdent, A as AGENT_GUIDANCE, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, c as REPORT_PROGRESS_TOOL_TITLE, d as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-C2_pkgRI.mjs';
|
|
10
10
|
import { readFile, writeFile, rename, unlink, mkdir, stat, open as open$1, appendFile } from 'node:fs/promises';
|
|
11
11
|
import fs, { watch, access } from 'fs/promises';
|
|
@@ -14,7 +14,7 @@ import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
|
14
14
|
import { query as query$1, AbortError } from '@anthropic-ai/claude-agent-sdk';
|
|
15
15
|
import axios, { AxiosError } from 'axios';
|
|
16
16
|
import 'node:events';
|
|
17
|
-
import 'socket.io-client';
|
|
17
|
+
import { io } from 'socket.io-client';
|
|
18
18
|
import tweetnacl from 'tweetnacl';
|
|
19
19
|
import os$1, { homedir as homedir$1 } from 'os';
|
|
20
20
|
import { spawn as spawn$2, execSync as execSync$1, exec } from 'child_process';
|
|
@@ -128,7 +128,9 @@ async function daemonPost(path, body) {
|
|
|
128
128
|
signal: AbortSignal.timeout(timeout)
|
|
129
129
|
});
|
|
130
130
|
if (!response.ok) {
|
|
131
|
-
const
|
|
131
|
+
const failure = await response.json().catch(() => null);
|
|
132
|
+
const reason = typeof failure?.error === "string" && failure.error ? failure.error : `HTTP ${response.status}`;
|
|
133
|
+
const errorMessage = `Request failed: ${path}, ${reason}`;
|
|
132
134
|
logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
133
135
|
return {
|
|
134
136
|
error: errorMessage
|
|
@@ -195,6 +197,13 @@ async function pushClipboardViaDaemon(text) {
|
|
|
195
197
|
}
|
|
196
198
|
return result;
|
|
197
199
|
}
|
|
200
|
+
async function setTerminalTitleViaDaemon(terminalId, title, ifAbsent = false) {
|
|
201
|
+
const result = await daemonPost("/terminal-title", { terminalId, title, ifAbsent });
|
|
202
|
+
if (result?.error) {
|
|
203
|
+
return { ok: false, error: result.error };
|
|
204
|
+
}
|
|
205
|
+
return { ok: result?.status === "ok", error: result?.status === "ok" ? void 0 : "unexpected daemon response" };
|
|
206
|
+
}
|
|
198
207
|
async function checkIfDaemonRunningAndCleanupStaleState() {
|
|
199
208
|
const state = await readDaemonState();
|
|
200
209
|
if (!state) {
|
|
@@ -4651,8 +4660,8 @@ function claudeModeHash(mode) {
|
|
|
4651
4660
|
}
|
|
4652
4661
|
|
|
4653
4662
|
const SPAWN_ORIGIN_TAG_MAX = 24;
|
|
4654
|
-
function spawnOriginTags(env = process.env) {
|
|
4655
|
-
if (env.HAPPY_SESSION_VARIANT === "assistant") return void 0;
|
|
4663
|
+
function spawnOriginTags(env = process.env, flavor = void 0) {
|
|
4664
|
+
if (env.HAPPY_SESSION_VARIANT === "assistant" && flavor === "claude") return void 0;
|
|
4656
4665
|
const origin = (env.HAPPY_SPAWNED_BY ?? "").trim();
|
|
4657
4666
|
if (!origin) return void 0;
|
|
4658
4667
|
if (origin.length > SPAWN_ORIGIN_TAG_MAX) return void 0;
|
|
@@ -4666,7 +4675,7 @@ function createSessionMetadata(opts) {
|
|
|
4666
4675
|
const state = {
|
|
4667
4676
|
controlledByUser: false
|
|
4668
4677
|
};
|
|
4669
|
-
const originTags = spawnOriginTags();
|
|
4678
|
+
const originTags = spawnOriginTags(process.env, opts.flavor);
|
|
4670
4679
|
const metadata = {
|
|
4671
4680
|
path: process.cwd(),
|
|
4672
4681
|
host: os.hostname(),
|
|
@@ -4885,7 +4894,7 @@ function daemonReadiness(authenticated, running, hasState) {
|
|
|
4885
4894
|
message: "\u26A0\uFE0F Daemon is not running \u2014 run `very-happy daemon start`"
|
|
4886
4895
|
};
|
|
4887
4896
|
}
|
|
4888
|
-
function collectRuntimeReadiness(run = spawnSync, currentNodeVersion = process.version) {
|
|
4897
|
+
function collectRuntimeReadiness(run = spawnSync, currentNodeVersion = process.version, onPath = commandOnPath) {
|
|
4889
4898
|
const tmux = probe("tmux", run);
|
|
4890
4899
|
return {
|
|
4891
4900
|
node: {
|
|
@@ -4896,7 +4905,8 @@ function collectRuntimeReadiness(run = spawnSync, currentNodeVersion = process.v
|
|
|
4896
4905
|
...tmux,
|
|
4897
4906
|
supportsSessionEnv: tmux.available && tmuxSupportsSessionEnv(tmux.version)
|
|
4898
4907
|
},
|
|
4899
|
-
agents: ["claude", "codex", "gemini", "opencode", "openclaw"].map((command) => probe(command, run))
|
|
4908
|
+
agents: ["claude", "codex", "gemini", "opencode", "openclaw", "pi"].map((command) => probe(command, run)),
|
|
4909
|
+
piAdapter: { available: onPath("pi-acp") }
|
|
4900
4910
|
};
|
|
4901
4911
|
}
|
|
4902
4912
|
|
|
@@ -5145,6 +5155,11 @@ ${typeLabels[type] || type}:`));
|
|
|
5145
5155
|
} else {
|
|
5146
5156
|
console.log(chalk.green(`\u2713 External agent command${availableAgents.length === 1 ? "" : "s"}: ${availableAgents.map(toolProbeLabel).join(", ")}`));
|
|
5147
5157
|
}
|
|
5158
|
+
const piProbe = readiness.agents.find((agent) => agent.command === "pi");
|
|
5159
|
+
if (piProbe?.available && !readiness.piAdapter.available) {
|
|
5160
|
+
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"));
|
|
5161
|
+
console.log(chalk.gray(" Install it for the daemon user: npm install -g pi-acp@0.0.33"));
|
|
5162
|
+
}
|
|
5148
5163
|
console.log(chalk.bold("\n\u{1F510} Authentication"));
|
|
5149
5164
|
let authenticated = false;
|
|
5150
5165
|
try {
|
|
@@ -5195,6 +5210,11 @@ ${typeLabels[type] || type}:`));
|
|
|
5195
5210
|
console.log(chalk.green("\n\u2705 Doctor diagnosis complete!\n"));
|
|
5196
5211
|
}
|
|
5197
5212
|
|
|
5213
|
+
const SPAWN_AGENTS = ["claude", "codex", "gemini", "openclaw", "pi"];
|
|
5214
|
+
function isSpawnAgent(value) {
|
|
5215
|
+
return typeof value === "string" && SPAWN_AGENTS.includes(value);
|
|
5216
|
+
}
|
|
5217
|
+
|
|
5198
5218
|
const DEFAULT_LOG_PRUNE_POLICY = {
|
|
5199
5219
|
maxAgeMs: 14 * 24 * 60 * 6e4,
|
|
5200
5220
|
maxTotalBytes: 200 * 1024 * 1024,
|
|
@@ -5596,6 +5616,10 @@ function spawnHappyCLI(args, options = {}) {
|
|
|
5596
5616
|
});
|
|
5597
5617
|
}
|
|
5598
5618
|
|
|
5619
|
+
function assistantSpawnMode(options) {
|
|
5620
|
+
if (options.variant !== "assistant") return "none";
|
|
5621
|
+
return options.agent === void 0 || options.agent === "claude" ? "claude-singleton" : "env-only";
|
|
5622
|
+
}
|
|
5599
5623
|
function isAssistantTracked(tracked) {
|
|
5600
5624
|
return tracked.variant === "assistant" || tracked.happySessionMetadataFromLocalWebhook?.variant === "assistant";
|
|
5601
5625
|
}
|
|
@@ -5820,7 +5844,8 @@ function startDaemonControlServer({
|
|
|
5820
5844
|
onSessionStateEvent,
|
|
5821
5845
|
onClaudeAuthFailed,
|
|
5822
5846
|
pushClipboard,
|
|
5823
|
-
onTerminalHook
|
|
5847
|
+
onTerminalHook,
|
|
5848
|
+
setTerminalTitle
|
|
5824
5849
|
}) {
|
|
5825
5850
|
return new Promise((resolve) => {
|
|
5826
5851
|
const app = fastify({
|
|
@@ -5940,7 +5965,7 @@ function startDaemonControlServer({
|
|
|
5940
5965
|
// ignores it and supplies its own home daemon-side.
|
|
5941
5966
|
directory: z.string().default(""),
|
|
5942
5967
|
sessionId: z.string().optional(),
|
|
5943
|
-
agent: z.enum(
|
|
5968
|
+
agent: z.enum(SPAWN_AGENTS).optional(),
|
|
5944
5969
|
environmentVariables: z.record(z.string(), z.string()).optional(),
|
|
5945
5970
|
variant: z.enum(["assistant"]).optional(),
|
|
5946
5971
|
// B-051: assistant only — stop the live assistant, purge its
|
|
@@ -6045,6 +6070,30 @@ function startDaemonControlServer({
|
|
|
6045
6070
|
logger.debug(`[CONTROL SERVER] Clipboard push request (${text.length} chars)`);
|
|
6046
6071
|
return pushClipboard(text);
|
|
6047
6072
|
});
|
|
6073
|
+
typed.post("/terminal-title", {
|
|
6074
|
+
schema: {
|
|
6075
|
+
body: z.object({
|
|
6076
|
+
terminalId: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/),
|
|
6077
|
+
title: z.string().min(1).max(200),
|
|
6078
|
+
ifAbsent: z.boolean().optional()
|
|
6079
|
+
}),
|
|
6080
|
+
response: {
|
|
6081
|
+
200: z.object({ status: z.literal("ok") }),
|
|
6082
|
+
409: z.object({ error: z.string() }),
|
|
6083
|
+
503: z.object({ error: z.string() })
|
|
6084
|
+
}
|
|
6085
|
+
}
|
|
6086
|
+
}, async (request, reply) => {
|
|
6087
|
+
const { terminalId, title, ifAbsent } = request.body;
|
|
6088
|
+
const landed = setTerminalTitle ? setTerminalTitle(terminalId, title, !!ifAbsent) : "starting";
|
|
6089
|
+
if (landed === "starting") {
|
|
6090
|
+
return reply.code(503).send({ error: "daemon is still starting up" });
|
|
6091
|
+
}
|
|
6092
|
+
if (!landed) {
|
|
6093
|
+
return reply.code(409).send({ error: "Failed to set terminal title (tmux unavailable or terminal gone)" });
|
|
6094
|
+
}
|
|
6095
|
+
return { status: "ok" };
|
|
6096
|
+
});
|
|
6048
6097
|
typed.post("/stop", {
|
|
6049
6098
|
schema: {
|
|
6050
6099
|
response: {
|
|
@@ -6850,6 +6899,76 @@ function expandEnvironmentVariables(envVars, sourceEnv = process.env) {
|
|
|
6850
6899
|
return expanded;
|
|
6851
6900
|
}
|
|
6852
6901
|
|
|
6902
|
+
const SESSION_MODE_DIR = "session-modes";
|
|
6903
|
+
const ACP_PERMISSION_MODES = ["default", "acceptEdits", "plan", "bypassPermissions"];
|
|
6904
|
+
function normalizeAcpPermissionMode(value) {
|
|
6905
|
+
if (typeof value !== "string") return null;
|
|
6906
|
+
if (value === "yolo") return "bypassPermissions";
|
|
6907
|
+
return ACP_PERMISSION_MODES.includes(value) ? value : null;
|
|
6908
|
+
}
|
|
6909
|
+
function sessionModeFilePath(happySessionId, dir = join(configuration.happyHomeDir, SESSION_MODE_DIR)) {
|
|
6910
|
+
return join(dir, `${happySessionId}.json`);
|
|
6911
|
+
}
|
|
6912
|
+
function sessionModeFilePayload(permissionMode, now = Date.now()) {
|
|
6913
|
+
return { permissionMode, updatedAt: now };
|
|
6914
|
+
}
|
|
6915
|
+
function writeSessionModeFile(happySessionId, permissionMode, opts = {}) {
|
|
6916
|
+
const path = sessionModeFilePath(happySessionId, opts.dir);
|
|
6917
|
+
mkdirSync(join(path, ".."), { recursive: true, mode: 448 });
|
|
6918
|
+
const payload = sessionModeFilePayload(permissionMode, opts.now);
|
|
6919
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
6920
|
+
writePrivateFileSync(tmp, JSON.stringify(payload));
|
|
6921
|
+
renameSync(tmp, path);
|
|
6922
|
+
return payload;
|
|
6923
|
+
}
|
|
6924
|
+
function removeSessionModeFile(happySessionId, dir) {
|
|
6925
|
+
const path = sessionModeFilePath(happySessionId, dir);
|
|
6926
|
+
if (existsSync(path)) unlinkSync(path);
|
|
6927
|
+
}
|
|
6928
|
+
|
|
6929
|
+
const IGNORED_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["--happy-starting-mode"]);
|
|
6930
|
+
const PI_ADAPTER_INSTALL_HINT = "very-happy pi needs the pi-acp adapter on PATH: npm install -g pi-acp@0.0.33";
|
|
6931
|
+
function piAdapterMissingHint(detail) {
|
|
6932
|
+
return detail && /ENOENT/.test(detail) ? PI_ADAPTER_INSTALL_HINT : null;
|
|
6933
|
+
}
|
|
6934
|
+
function parsePiRunnerArgs(args) {
|
|
6935
|
+
const parsed = { verbose: false, passthrough: [] };
|
|
6936
|
+
for (let i = 0; i < args.length; i++) {
|
|
6937
|
+
const arg = args[i];
|
|
6938
|
+
if (arg === "--") {
|
|
6939
|
+
parsed.passthrough.push(...args.slice(i + 1));
|
|
6940
|
+
break;
|
|
6941
|
+
}
|
|
6942
|
+
if (arg === "--started-by") {
|
|
6943
|
+
const value = args[++i];
|
|
6944
|
+
if (value === "daemon" || value === "terminal") parsed.startedBy = value;
|
|
6945
|
+
continue;
|
|
6946
|
+
}
|
|
6947
|
+
if (arg === "--verbose") {
|
|
6948
|
+
parsed.verbose = true;
|
|
6949
|
+
continue;
|
|
6950
|
+
}
|
|
6951
|
+
if (arg === "--permission-mode") {
|
|
6952
|
+
const mode = normalizeAcpPermissionMode(args[++i]);
|
|
6953
|
+
if (mode) parsed.permissionMode = mode;
|
|
6954
|
+
continue;
|
|
6955
|
+
}
|
|
6956
|
+
if (IGNORED_FLAGS_WITH_VALUE.has(arg)) {
|
|
6957
|
+
i++;
|
|
6958
|
+
continue;
|
|
6959
|
+
}
|
|
6960
|
+
throw new Error(`Unknown option for very-happy pi: ${arg} (use -- to pass args to pi-acp)`);
|
|
6961
|
+
}
|
|
6962
|
+
return parsed;
|
|
6963
|
+
}
|
|
6964
|
+
|
|
6965
|
+
function spawnAgentUnavailableError(agent, availability) {
|
|
6966
|
+
if (agent === "pi" && !availability.pi) {
|
|
6967
|
+
return { type: "error", errorMessage: PI_ADAPTER_INSTALL_HINT };
|
|
6968
|
+
}
|
|
6969
|
+
return null;
|
|
6970
|
+
}
|
|
6971
|
+
|
|
6853
6972
|
const ResumableMetadataSchema = z.object({
|
|
6854
6973
|
path: z.string().min(1),
|
|
6855
6974
|
flavor: z.string().optional(),
|
|
@@ -7097,6 +7216,24 @@ function decideHandover(run) {
|
|
|
7097
7216
|
return { action: "handover" };
|
|
7098
7217
|
}
|
|
7099
7218
|
|
|
7219
|
+
function decideAutoUpdate(context) {
|
|
7220
|
+
if (!context.enabled) return { action: "skip", reason: "auto-update disabled" };
|
|
7221
|
+
const target = context.recommendedVersion;
|
|
7222
|
+
if (!target) return { action: "skip", reason: "no recommended version published" };
|
|
7223
|
+
const ordering = compareExactVersions(context.currentVersion, target);
|
|
7224
|
+
if (ordering === null) return { action: "skip", reason: "version numbers not comparable" };
|
|
7225
|
+
if (ordering === 0) return { action: "skip", reason: "already current" };
|
|
7226
|
+
if (ordering > 0) return { action: "skip", reason: `already ahead of the recommended ${target}` };
|
|
7227
|
+
if (context.failedVersion === target) {
|
|
7228
|
+
return { action: "skip", reason: `already failed to install ${target} once` };
|
|
7229
|
+
}
|
|
7230
|
+
if (!context.idle) return { action: "skip", reason: "machine is busy" };
|
|
7231
|
+
return { action: "install", version: target };
|
|
7232
|
+
}
|
|
7233
|
+
function autoUpdateInstallArgs(version) {
|
|
7234
|
+
return ["i", "-g", "--allow-scripts=very-happy-cli,node-pty", `very-happy-cli@${version}`];
|
|
7235
|
+
}
|
|
7236
|
+
|
|
7100
7237
|
const CLAUDE_KEYCHAIN_SERVICE_PREFIX = "Claude Code";
|
|
7101
7238
|
function keychainIdentity(env = process.env, deps = {}) {
|
|
7102
7239
|
const home = deps.home ?? homedir();
|
|
@@ -8315,6 +8452,7 @@ function sanitizeImportTitle(value) {
|
|
|
8315
8452
|
return collapsed.length > 200 ? `${collapsed.slice(0, 199)}\u2026` : collapsed;
|
|
8316
8453
|
}
|
|
8317
8454
|
const hostSuffix = process.env.HAPPY_VARIANT === "dev" ? "-dev" : "";
|
|
8455
|
+
const startupCliAvailability = detectCLIAvailability();
|
|
8318
8456
|
const initialMachineMetadata = {
|
|
8319
8457
|
host: os$1.hostname() + hostSuffix,
|
|
8320
8458
|
platform: os$1.platform(),
|
|
@@ -8322,7 +8460,7 @@ const initialMachineMetadata = {
|
|
|
8322
8460
|
homeDir: os$1.homedir(),
|
|
8323
8461
|
happyHomeDir: configuration.happyHomeDir,
|
|
8324
8462
|
happyLibDir: projectPath(),
|
|
8325
|
-
cliAvailability:
|
|
8463
|
+
cliAvailability: startupCliAvailability,
|
|
8326
8464
|
resumeSupport: { ...detectResumeSupport(), rpcAvailable: true }
|
|
8327
8465
|
};
|
|
8328
8466
|
async function preflightNewBundle(bundlePath) {
|
|
@@ -8377,6 +8515,40 @@ function withHandoverHold(state, reason) {
|
|
|
8377
8515
|
return reason ? { ...state, handoverHold: { reason, at: Date.now() } } : { ...state, handoverHold: null };
|
|
8378
8516
|
}
|
|
8379
8517
|
const HANDOVER_PREFLIGHT_TIMEOUT_MS = 3e4;
|
|
8518
|
+
function runNpmInstall(args) {
|
|
8519
|
+
return new Promise((resolve) => {
|
|
8520
|
+
let settled = false;
|
|
8521
|
+
const done = (code) => {
|
|
8522
|
+
if (!settled) {
|
|
8523
|
+
settled = true;
|
|
8524
|
+
resolve(code);
|
|
8525
|
+
}
|
|
8526
|
+
};
|
|
8527
|
+
let child;
|
|
8528
|
+
try {
|
|
8529
|
+
child = spawn$3("npm", args, { stdio: "ignore", env: process.env });
|
|
8530
|
+
} catch {
|
|
8531
|
+
done(null);
|
|
8532
|
+
return;
|
|
8533
|
+
}
|
|
8534
|
+
const timer = setTimeout(() => {
|
|
8535
|
+
try {
|
|
8536
|
+
child.kill("SIGKILL");
|
|
8537
|
+
} catch {
|
|
8538
|
+
}
|
|
8539
|
+
done(null);
|
|
8540
|
+
}, AUTO_UPDATE_INSTALL_TIMEOUT_MS);
|
|
8541
|
+
child.on("error", () => {
|
|
8542
|
+
clearTimeout(timer);
|
|
8543
|
+
done(null);
|
|
8544
|
+
});
|
|
8545
|
+
child.on("close", (code) => {
|
|
8546
|
+
clearTimeout(timer);
|
|
8547
|
+
done(code);
|
|
8548
|
+
});
|
|
8549
|
+
});
|
|
8550
|
+
}
|
|
8551
|
+
const AUTO_UPDATE_INSTALL_TIMEOUT_MS = 10 * 6e4;
|
|
8380
8552
|
async function startDaemon() {
|
|
8381
8553
|
let claudeAuthServiceRef = null;
|
|
8382
8554
|
let cliUpdateStateRef = null;
|
|
@@ -8546,18 +8718,26 @@ async function startDaemon() {
|
|
|
8546
8718
|
};
|
|
8547
8719
|
const assistantSpawnGate = createSpawnGate();
|
|
8548
8720
|
const spawnSession = (options) => {
|
|
8549
|
-
if (options
|
|
8721
|
+
if (assistantSpawnMode(options) !== "claude-singleton") {
|
|
8550
8722
|
return spawnSessionImpl(options);
|
|
8551
8723
|
}
|
|
8552
8724
|
return options.forceNew ? assistantSpawnGate.replace(() => spawnSessionImpl(options)) : assistantSpawnGate.join(() => spawnSessionImpl(options));
|
|
8553
8725
|
};
|
|
8726
|
+
const currentCliAvailability = () => apiMachineRef?.getCLIAvailability() ?? startupCliAvailability;
|
|
8554
8727
|
const spawnSessionImpl = async (options) => {
|
|
8555
8728
|
logger.debugLargeJson("[DAEMON RUN] Spawning session", summarizeSpawnSessionForLog(options));
|
|
8556
8729
|
const spawnPermissionMode = sanitizeSpawnPermissionMode(options.permissionMode);
|
|
8557
8730
|
if (options.permissionMode !== void 0 && spawnPermissionMode === null) {
|
|
8558
8731
|
logger.warn("[DAEMON RUN] Ignoring invalid permissionMode in spawn request");
|
|
8559
8732
|
}
|
|
8560
|
-
|
|
8733
|
+
const unavailable = spawnAgentUnavailableError(options.agent, currentCliAvailability());
|
|
8734
|
+
if (unavailable) {
|
|
8735
|
+
logger.warn(`[DAEMON RUN] Refusing spawn of agent '${options.agent}': ${unavailable.errorMessage}`);
|
|
8736
|
+
return unavailable;
|
|
8737
|
+
}
|
|
8738
|
+
const assistantMode = assistantSpawnMode(options);
|
|
8739
|
+
const trackedVariant = assistantMode === "claude-singleton" ? options.variant : void 0;
|
|
8740
|
+
if (assistantMode === "claude-singleton") {
|
|
8561
8741
|
options = { ...options, directory: assistantHome() };
|
|
8562
8742
|
if (options.forceNew) {
|
|
8563
8743
|
for (const tracked of [...pidToTrackedSession.values()]) {
|
|
@@ -8760,7 +8940,13 @@ async function startDaemon() {
|
|
|
8760
8940
|
logger.debug(`[DAEMON RUN] Attempting to spawn session in tmux: ${sessionDesc}`);
|
|
8761
8941
|
const tmux = getTmuxUtilities(tmuxSessionName);
|
|
8762
8942
|
const cliPath = join$1(projectPath(), "dist", "index.mjs");
|
|
8763
|
-
const agent = options.agent
|
|
8943
|
+
const agent = options.agent ?? "claude";
|
|
8944
|
+
if (!isSpawnAgent(agent)) {
|
|
8945
|
+
return {
|
|
8946
|
+
type: "error",
|
|
8947
|
+
errorMessage: `Unsupported agent type: '${options.agent}'. Please update your CLI to the latest version.`
|
|
8948
|
+
};
|
|
8949
|
+
}
|
|
8764
8950
|
const resumeId = agent === "claude" ? options.resumeClaudeSessionId : agent === "codex" ? options.resumeCodexThreadId : void 0;
|
|
8765
8951
|
const resumeFragment = resumeId ? ` --resume ${shellescape(resumeId)}` : "";
|
|
8766
8952
|
const permissionModeFragment = spawnPermissionMode ? ` --permission-mode ${shellescape(spawnPermissionMode)}` : "";
|
|
@@ -8788,7 +8974,7 @@ async function startDaemon() {
|
|
|
8788
8974
|
pid: tmuxResult.pid,
|
|
8789
8975
|
// Real PID from tmux -P flag
|
|
8790
8976
|
tmuxSessionId: tmuxResult.sessionId,
|
|
8791
|
-
variant:
|
|
8977
|
+
variant: trackedVariant,
|
|
8792
8978
|
spawnedBy: options.spawnedBy,
|
|
8793
8979
|
directoryCreated,
|
|
8794
8980
|
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.`
|
|
@@ -8835,6 +9021,9 @@ async function startDaemon() {
|
|
|
8835
9021
|
case "openclaw":
|
|
8836
9022
|
agentCommand = "openclaw";
|
|
8837
9023
|
break;
|
|
9024
|
+
case "pi":
|
|
9025
|
+
agentCommand = "pi";
|
|
9026
|
+
break;
|
|
8838
9027
|
default:
|
|
8839
9028
|
return {
|
|
8840
9029
|
type: "error",
|
|
@@ -8866,7 +9055,7 @@ async function startDaemon() {
|
|
|
8866
9055
|
},
|
|
8867
9056
|
directoryCreated,
|
|
8868
9057
|
message: directoryCreated ? `The path '${directory}' did not exist. We created a new folder and spawned a new session there.` : void 0,
|
|
8869
|
-
variant:
|
|
9058
|
+
variant: trackedVariant,
|
|
8870
9059
|
spawnedBy: options.spawnedBy
|
|
8871
9060
|
});
|
|
8872
9061
|
}
|
|
@@ -9347,6 +9536,10 @@ async function startDaemon() {
|
|
|
9347
9536
|
},
|
|
9348
9537
|
onTerminalHook: (body) => {
|
|
9349
9538
|
mirrorManagerRef?.handleHookPayload(body);
|
|
9539
|
+
},
|
|
9540
|
+
setTerminalTitle: (terminalId, title, ifAbsent) => {
|
|
9541
|
+
if (!apiMachineRef) return "starting";
|
|
9542
|
+
return apiMachineRef.setTerminalTitle(terminalId, title, ifAbsent);
|
|
9350
9543
|
}
|
|
9351
9544
|
});
|
|
9352
9545
|
const daemonClaudeCredentials = resolveClaudeCredentialReadiness();
|
|
@@ -9443,6 +9636,48 @@ async function startDaemon() {
|
|
|
9443
9636
|
host: initialMachineMetadata.host
|
|
9444
9637
|
})).then(() => logger.debug(`[DAEMON RUN] claimed the machine record for host ${initialMachineMetadata.host}`)).catch((error) => logger.debug("[DAEMON RUN] could not refresh the recorded host:", error));
|
|
9445
9638
|
}
|
|
9639
|
+
let autoUpdateFailedVersion = null;
|
|
9640
|
+
let autoUpdateRunning = false;
|
|
9641
|
+
const maybeAutoUpdate = async (cliUpdate) => {
|
|
9642
|
+
if (autoUpdateRunning) return;
|
|
9643
|
+
const settings = await readSettings();
|
|
9644
|
+
const decision = decideAutoUpdate({
|
|
9645
|
+
enabled: (settings.cliAutoUpdate ?? "idle") !== "off",
|
|
9646
|
+
currentVersion: cliUpdate.currentVersion,
|
|
9647
|
+
recommendedVersion: cliUpdate.recommendedVersion,
|
|
9648
|
+
idle: pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals(),
|
|
9649
|
+
failedVersion: autoUpdateFailedVersion
|
|
9650
|
+
});
|
|
9651
|
+
if (decision.action === "skip") {
|
|
9652
|
+
logger.debug(`[DAEMON RUN] auto-update skipped: ${decision.reason}`);
|
|
9653
|
+
return;
|
|
9654
|
+
}
|
|
9655
|
+
autoUpdateRunning = true;
|
|
9656
|
+
const target = decision.version;
|
|
9657
|
+
const report = (state, detail) => {
|
|
9658
|
+
cliUpdateStateRef = { ...cliUpdate, autoUpdate: { state, version: target, detail, at: Date.now() } };
|
|
9659
|
+
void apiMachine.setCliUpdateState(cliUpdateStateRef);
|
|
9660
|
+
};
|
|
9661
|
+
logger.warn(`[DAEMON RUN] auto-updating CLI to ${target} (machine idle)`);
|
|
9662
|
+
report("installing");
|
|
9663
|
+
try {
|
|
9664
|
+
const code = await runNpmInstall(autoUpdateInstallArgs(target));
|
|
9665
|
+
if (code === 0) {
|
|
9666
|
+
logger.warn(`[DAEMON RUN] auto-update installed ${target}; handover follows once it verifies`);
|
|
9667
|
+
report("installed");
|
|
9668
|
+
} else {
|
|
9669
|
+
autoUpdateFailedVersion = target;
|
|
9670
|
+
logger.warn(`[DAEMON RUN] auto-update of ${target} failed with exit ${code}`);
|
|
9671
|
+
report("failed", `npm exited ${code}`);
|
|
9672
|
+
}
|
|
9673
|
+
} catch (error) {
|
|
9674
|
+
autoUpdateFailedVersion = target;
|
|
9675
|
+
logger.warn(`[DAEMON RUN] auto-update of ${target} could not run:`, error);
|
|
9676
|
+
report("failed", String(error).slice(0, 200));
|
|
9677
|
+
} finally {
|
|
9678
|
+
autoUpdateRunning = false;
|
|
9679
|
+
}
|
|
9680
|
+
};
|
|
9446
9681
|
let cliUpdateCheckRunning = false;
|
|
9447
9682
|
const refreshCliUpdate = async () => {
|
|
9448
9683
|
if (cliUpdateCheckRunning) return;
|
|
@@ -9455,6 +9690,7 @@ async function startDaemon() {
|
|
|
9455
9690
|
cliUpdateStateRef = cliUpdate;
|
|
9456
9691
|
apiMachine.setCliUpdateState(cliUpdate);
|
|
9457
9692
|
logger.debug(`[DAEMON RUN] CLI update policy checked: ${cliUpdate.status} (recommended=${cliUpdate.recommendedVersion ?? "none"}, minimum=${cliUpdate.minimumVersion ?? "none"})`);
|
|
9693
|
+
await maybeAutoUpdate(cliUpdate);
|
|
9458
9694
|
} catch (error) {
|
|
9459
9695
|
logger.debug("[DAEMON RUN] CLI update policy check failed:", error);
|
|
9460
9696
|
} finally {
|
|
@@ -9667,6 +9903,194 @@ function formatTranscript(bodies) {
|
|
|
9667
9903
|
return lines.join("\n");
|
|
9668
9904
|
}
|
|
9669
9905
|
|
|
9906
|
+
const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
|
|
9907
|
+
async function openUserScopedSocket(token, options = {}) {
|
|
9908
|
+
const socket = io(configuration.serverUrl, {
|
|
9909
|
+
auth: {
|
|
9910
|
+
token,
|
|
9911
|
+
clientType: "user-scoped",
|
|
9912
|
+
happyClient: `cli-session-ops/${configuration.currentCliVersion}`
|
|
9913
|
+
},
|
|
9914
|
+
path: "/v1/updates",
|
|
9915
|
+
transports: ["websocket"],
|
|
9916
|
+
reconnection: false,
|
|
9917
|
+
autoConnect: false
|
|
9918
|
+
});
|
|
9919
|
+
await new Promise((resolve, reject) => {
|
|
9920
|
+
const timer = setTimeout(() => {
|
|
9921
|
+
socket.close();
|
|
9922
|
+
reject(new Error(`Timed out connecting to ${configuration.serverUrl}`));
|
|
9923
|
+
}, options.connectTimeoutMs ?? USER_SOCKET_CONNECT_TIMEOUT_MS);
|
|
9924
|
+
socket.once("connect", () => {
|
|
9925
|
+
clearTimeout(timer);
|
|
9926
|
+
resolve();
|
|
9927
|
+
});
|
|
9928
|
+
socket.once("connect_error", (error) => {
|
|
9929
|
+
clearTimeout(timer);
|
|
9930
|
+
socket.close();
|
|
9931
|
+
reject(new Error(`Could not connect to ${configuration.serverUrl}: ${error.message}`));
|
|
9932
|
+
});
|
|
9933
|
+
socket.connect();
|
|
9934
|
+
});
|
|
9935
|
+
return {
|
|
9936
|
+
async rpcCall(payload, timeoutMs) {
|
|
9937
|
+
const ack = await socket.timeout(timeoutMs).emitWithAck("rpc-call", payload);
|
|
9938
|
+
return ack;
|
|
9939
|
+
},
|
|
9940
|
+
close() {
|
|
9941
|
+
socket.close();
|
|
9942
|
+
}
|
|
9943
|
+
};
|
|
9944
|
+
}
|
|
9945
|
+
|
|
9946
|
+
const PERMISSION_OPS_CLIENT = "session-ops";
|
|
9947
|
+
const PERMISSION_RPC_TIMEOUT_MS = 3e4;
|
|
9948
|
+
const SETTLE_TIMEOUT_MS = 5e3;
|
|
9949
|
+
const SETTLE_POLL_MS = 500;
|
|
9950
|
+
function buildPermissionRpcPayload(requestId, verdict) {
|
|
9951
|
+
if (verdict.kind === "approve") {
|
|
9952
|
+
return {
|
|
9953
|
+
id: requestId,
|
|
9954
|
+
approved: true,
|
|
9955
|
+
decision: verdict.forSession ? "approved_for_session" : "approved"
|
|
9956
|
+
};
|
|
9957
|
+
}
|
|
9958
|
+
return {
|
|
9959
|
+
id: requestId,
|
|
9960
|
+
approved: false,
|
|
9961
|
+
decision: "denied",
|
|
9962
|
+
...verdict.reason ? { reason: verdict.reason } : {}
|
|
9963
|
+
};
|
|
9964
|
+
}
|
|
9965
|
+
function interpretPermissionAck(ack, decryptResult) {
|
|
9966
|
+
if (!ack.ok) {
|
|
9967
|
+
const message = ack.error ?? "RPC call failed";
|
|
9968
|
+
if (message === "RPC method not available") {
|
|
9969
|
+
return { status: "offline", message: "no running wrapper has registered the permission RPC for this session" };
|
|
9970
|
+
}
|
|
9971
|
+
if (message === "RPC target disconnected") {
|
|
9972
|
+
return { status: "offline", message: "the wrapper disconnected while the permission RPC was in flight" };
|
|
9973
|
+
}
|
|
9974
|
+
if (/timed? ?out/i.test(message)) {
|
|
9975
|
+
return { status: "timeout", message };
|
|
9976
|
+
}
|
|
9977
|
+
return { status: "rejected", message };
|
|
9978
|
+
}
|
|
9979
|
+
let body = null;
|
|
9980
|
+
if (typeof ack.result === "string" && ack.result.length > 0) {
|
|
9981
|
+
try {
|
|
9982
|
+
body = decryptResult(ack.result);
|
|
9983
|
+
} catch {
|
|
9984
|
+
body = null;
|
|
9985
|
+
}
|
|
9986
|
+
}
|
|
9987
|
+
if (body && typeof body === "object" && typeof body.error === "string") {
|
|
9988
|
+
return { status: "handler-error", message: body.error };
|
|
9989
|
+
}
|
|
9990
|
+
return { status: "acknowledged" };
|
|
9991
|
+
}
|
|
9992
|
+
function pendingRequestsOf(agentState, now) {
|
|
9993
|
+
const requests = agentState?.requests;
|
|
9994
|
+
if (!requests || typeof requests !== "object") return [];
|
|
9995
|
+
return Object.entries(requests).map(([id, request]) => ({
|
|
9996
|
+
id,
|
|
9997
|
+
tool: typeof request?.tool === "string" ? request.tool : "unknown",
|
|
9998
|
+
...request?.kind ? { kind: request.kind } : {},
|
|
9999
|
+
...typeof request?.createdAt === "number" ? { createdAt: request.createdAt, waitingMs: Math.max(0, now - request.createdAt) } : {}
|
|
10000
|
+
})).sort((a, b) => (a.createdAt ?? Number.MAX_SAFE_INTEGER) - (b.createdAt ?? Number.MAX_SAFE_INTEGER));
|
|
10001
|
+
}
|
|
10002
|
+
const RPC_DEADLINE_GRACE_MS = 2e3;
|
|
10003
|
+
function withDeadline(promise, ms, message) {
|
|
10004
|
+
return new Promise((resolve, reject) => {
|
|
10005
|
+
const timer = setTimeout(() => reject(new Error(`${message} (timed out)`)), ms);
|
|
10006
|
+
promise.then(
|
|
10007
|
+
(value) => {
|
|
10008
|
+
clearTimeout(timer);
|
|
10009
|
+
resolve(value);
|
|
10010
|
+
},
|
|
10011
|
+
(error) => {
|
|
10012
|
+
clearTimeout(timer);
|
|
10013
|
+
reject(error);
|
|
10014
|
+
}
|
|
10015
|
+
);
|
|
10016
|
+
});
|
|
10017
|
+
}
|
|
10018
|
+
async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}) {
|
|
10019
|
+
const now = deps.now ?? Date.now;
|
|
10020
|
+
const persisted = (deps.readPersisted ?? readPersistedSessions)()[sessionId];
|
|
10021
|
+
if (!persisted) {
|
|
10022
|
+
throw new Error(
|
|
10023
|
+
`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.`
|
|
10024
|
+
);
|
|
10025
|
+
}
|
|
10026
|
+
const token = await (deps.bearerToken ?? bearerToken$1)();
|
|
10027
|
+
const fetchState = deps.fetchAgentState ?? fetchDecryptedAgentState;
|
|
10028
|
+
const before = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
10029
|
+
if (!before.some((request) => request.id === requestId)) {
|
|
10030
|
+
const hint = before.length > 0 ? `Pending request ids: ${before.map((request) => `${request.id} (${request.tool})`).join(", ")}` : "The session has no pending requests.";
|
|
10031
|
+
throw new Error(`Request ${requestId} is not pending on session ${sessionId}. ${hint}`);
|
|
10032
|
+
}
|
|
10033
|
+
const payload = buildPermissionRpcPayload(requestId, verdict);
|
|
10034
|
+
const key = decodeBase64(persisted.encryptionKey);
|
|
10035
|
+
const params = encodeBase64(encrypt(key, persisted.encryptionVariant, payload));
|
|
10036
|
+
const transport = await (deps.openTransport ?? openUserScopedSocket)(token);
|
|
10037
|
+
const rpcTimeoutMs = deps.rpcTimeoutMs ?? PERMISSION_RPC_TIMEOUT_MS;
|
|
10038
|
+
let ack;
|
|
10039
|
+
try {
|
|
10040
|
+
ack = await withDeadline(
|
|
10041
|
+
transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
|
|
10042
|
+
rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
|
|
10043
|
+
`permission RPC did not settle within ${rpcTimeoutMs}ms`
|
|
10044
|
+
);
|
|
10045
|
+
} catch (error) {
|
|
10046
|
+
ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
|
|
10047
|
+
} finally {
|
|
10048
|
+
transport.close();
|
|
10049
|
+
}
|
|
10050
|
+
const outcome = interpretPermissionAck(ack, (encrypted) => decrypt(key, persisted.encryptionVariant, decodeBase64(encrypted)));
|
|
10051
|
+
if (outcome.status !== "acknowledged") {
|
|
10052
|
+
return { sessionId, requestId, payload, outcome };
|
|
10053
|
+
}
|
|
10054
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
10055
|
+
const deadline = now() + (deps.settleTimeoutMs ?? SETTLE_TIMEOUT_MS);
|
|
10056
|
+
let settled = false;
|
|
10057
|
+
for (; ; ) {
|
|
10058
|
+
const pending = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
10059
|
+
if (!pending.some((request) => request.id === requestId)) {
|
|
10060
|
+
settled = true;
|
|
10061
|
+
break;
|
|
10062
|
+
}
|
|
10063
|
+
if (now() >= deadline) break;
|
|
10064
|
+
await sleep(SETTLE_POLL_MS);
|
|
10065
|
+
}
|
|
10066
|
+
return { sessionId, requestId, payload, outcome, settled };
|
|
10067
|
+
}
|
|
10068
|
+
async function bearerToken$1() {
|
|
10069
|
+
const credentials = await readCredentialsForConfiguredRelay();
|
|
10070
|
+
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
10071
|
+
return credentials.token;
|
|
10072
|
+
}
|
|
10073
|
+
async function fetchDecryptedAgentState(sessionId, persisted, token) {
|
|
10074
|
+
const response = await axios.get(
|
|
10075
|
+
`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
|
|
10076
|
+
{
|
|
10077
|
+
headers: {
|
|
10078
|
+
"Authorization": `Bearer ${token}`,
|
|
10079
|
+
"X-Happy-Client": `${PERMISSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
10080
|
+
},
|
|
10081
|
+
timeout: 15e3,
|
|
10082
|
+
// 404 is a real answer ("not yours / gone"), not a transport fault.
|
|
10083
|
+
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
10084
|
+
}
|
|
10085
|
+
);
|
|
10086
|
+
if (response.status === 404) {
|
|
10087
|
+
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).`);
|
|
10088
|
+
}
|
|
10089
|
+
const raw = response.data?.session?.agentState;
|
|
10090
|
+
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
10091
|
+
return decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(raw));
|
|
10092
|
+
}
|
|
10093
|
+
|
|
9670
10094
|
const SESSION_OPS_CLIENT = "session-ops";
|
|
9671
10095
|
const DEFAULT_RECENT_LIMIT = 15;
|
|
9672
10096
|
const MAX_READ_LIMIT = 100;
|
|
@@ -9678,10 +10102,7 @@ function mergeSessionSummaries(live, persisted, options = {}) {
|
|
|
9678
10102
|
const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
|
|
9679
10103
|
if (!id || seen.has(id)) continue;
|
|
9680
10104
|
seen.add(id);
|
|
9681
|
-
summaries.push(toSummary(id, persisted[id],
|
|
9682
|
-
live: true,
|
|
9683
|
-
pid: typeof child.pid === "number" ? child.pid : void 0
|
|
9684
|
-
}));
|
|
10105
|
+
summaries.push(toSummary(id, persisted[id], livenessOf(child)));
|
|
9685
10106
|
}
|
|
9686
10107
|
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);
|
|
9687
10108
|
for (const [id, entry] of rest) {
|
|
@@ -9691,6 +10112,13 @@ function mergeSessionSummaries(live, persisted, options = {}) {
|
|
|
9691
10112
|
const wanted = options.tag;
|
|
9692
10113
|
return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9693
10114
|
}
|
|
10115
|
+
function livenessOf(child) {
|
|
10116
|
+
return { live: true, pid: typeof child.pid === "number" ? child.pid : void 0 };
|
|
10117
|
+
}
|
|
10118
|
+
function sessionLiveness(live, sessionId) {
|
|
10119
|
+
const child = live.find((entry) => entry.happySessionId === sessionId);
|
|
10120
|
+
return child ? livenessOf(child) : { live: false };
|
|
10121
|
+
}
|
|
9694
10122
|
function toSummary(id, persisted, extra) {
|
|
9695
10123
|
const meta = persisted?.metadata;
|
|
9696
10124
|
return {
|
|
@@ -9710,6 +10138,79 @@ async function listSessions(options = {}) {
|
|
|
9710
10138
|
const live = await listDaemonSessions();
|
|
9711
10139
|
return mergeSessionSummaries(live, readPersistedSessions(), options);
|
|
9712
10140
|
}
|
|
10141
|
+
function summarizeAccountSession(row, persisted, liveIds, now) {
|
|
10142
|
+
const base = {
|
|
10143
|
+
id: row.id,
|
|
10144
|
+
live: liveIds.has(row.id),
|
|
10145
|
+
url: sessionWebUrl(row.id),
|
|
10146
|
+
decryptable: false,
|
|
10147
|
+
active: row.active === true,
|
|
10148
|
+
archived: typeof row.archivedAt === "number",
|
|
10149
|
+
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
10150
|
+
...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {},
|
|
10151
|
+
attention: false
|
|
10152
|
+
};
|
|
10153
|
+
if (!persisted) return base;
|
|
10154
|
+
const key = decodeBase64(persisted.encryptionKey);
|
|
10155
|
+
let metadata = null;
|
|
10156
|
+
let agentState = null;
|
|
10157
|
+
try {
|
|
10158
|
+
metadata = row.metadata ? decrypt(key, persisted.encryptionVariant, decodeBase64(row.metadata)) : null;
|
|
10159
|
+
agentState = row.agentState ? decrypt(key, persisted.encryptionVariant, decodeBase64(row.agentState)) : null;
|
|
10160
|
+
} catch {
|
|
10161
|
+
return base;
|
|
10162
|
+
}
|
|
10163
|
+
if (!metadata) return base;
|
|
10164
|
+
const meta = metadata;
|
|
10165
|
+
const pending = pendingRequestsOf(agentState, now);
|
|
10166
|
+
return {
|
|
10167
|
+
...base,
|
|
10168
|
+
decryptable: true,
|
|
10169
|
+
...meta.summary?.text ? { title: meta.summary.text } : {},
|
|
10170
|
+
...meta.path ? { cwd: meta.path } : {},
|
|
10171
|
+
...meta.flavor ? { flavor: meta.flavor } : {},
|
|
10172
|
+
...meta.tags?.length ? { tags: [...meta.tags] } : {},
|
|
10173
|
+
...meta.variant ? { variant: meta.variant } : {},
|
|
10174
|
+
...meta.machineId ? { machineId: meta.machineId } : {},
|
|
10175
|
+
...persisted.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
|
|
10176
|
+
pending,
|
|
10177
|
+
attention: pending.length > 0
|
|
10178
|
+
};
|
|
10179
|
+
}
|
|
10180
|
+
function orderAccountSessions(summaries, options = {}) {
|
|
10181
|
+
const oldestWait = (summary) => Math.max(0, ...(summary.pending ?? []).map((request) => request.waitingMs ?? 0));
|
|
10182
|
+
const rank = (summary) => summary.attention ? 0 : summary.live ? 1 : 2;
|
|
10183
|
+
let rows = summaries.filter((summary) => summary.flavor !== "terminal-mirror").slice().sort((a, b) => {
|
|
10184
|
+
const byRank = rank(a) - rank(b);
|
|
10185
|
+
if (byRank !== 0) return byRank;
|
|
10186
|
+
if (a.attention && b.attention) return oldestWait(b) - oldestWait(a);
|
|
10187
|
+
return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
|
|
10188
|
+
});
|
|
10189
|
+
if (options.tag) {
|
|
10190
|
+
const wanted = options.tag;
|
|
10191
|
+
rows = rows.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
10192
|
+
}
|
|
10193
|
+
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
10194
|
+
let idleKept = 0;
|
|
10195
|
+
return rows.filter((summary) => rank(summary) < 2 || idleKept++ < recentLimit);
|
|
10196
|
+
}
|
|
10197
|
+
async function listAccountSessions(options = {}) {
|
|
10198
|
+
const token = await bearerToken();
|
|
10199
|
+
const response = await axios.get(`${configuration.serverUrl}/v1/sessions`, {
|
|
10200
|
+
headers: {
|
|
10201
|
+
"Authorization": `Bearer ${token}`,
|
|
10202
|
+
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
10203
|
+
},
|
|
10204
|
+
timeout: 15e3
|
|
10205
|
+
});
|
|
10206
|
+
const rows = Array.isArray(response.data?.sessions) ? response.data.sessions : [];
|
|
10207
|
+
const persisted = readPersistedSessions();
|
|
10208
|
+
const live = await listDaemonSessions();
|
|
10209
|
+
const liveIds = new Set(live.map((child) => child.happySessionId).filter((id) => typeof id === "string"));
|
|
10210
|
+
const now = Date.now();
|
|
10211
|
+
const summaries = rows.filter((row) => typeof row?.id === "string").map((row) => summarizeAccountSession(row, persisted[row.id], liveIds, now)).filter((summary) => options.includeArchived || !summary.archived);
|
|
10212
|
+
return orderAccountSessions(summaries, options);
|
|
10213
|
+
}
|
|
9713
10214
|
async function bearerToken() {
|
|
9714
10215
|
const credentials = await readCredentialsForConfiguredRelay();
|
|
9715
10216
|
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
@@ -9738,6 +10239,7 @@ async function readSessionTranscript(sessionId, limit) {
|
|
|
9738
10239
|
const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
|
|
9739
10240
|
messages.reverse();
|
|
9740
10241
|
const key = decodeBase64(persisted.encryptionKey);
|
|
10242
|
+
const live = await listDaemonSessions();
|
|
9741
10243
|
const bodies = messages.map((message) => {
|
|
9742
10244
|
if (message.content?.t !== "encrypted") return null;
|
|
9743
10245
|
try {
|
|
@@ -9747,7 +10249,7 @@ async function readSessionTranscript(sessionId, limit) {
|
|
|
9747
10249
|
}
|
|
9748
10250
|
});
|
|
9749
10251
|
return {
|
|
9750
|
-
summary: toSummary(sessionId, persisted,
|
|
10252
|
+
summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
|
|
9751
10253
|
messageCount: messages.length,
|
|
9752
10254
|
transcript: formatTranscript(bodies)
|
|
9753
10255
|
};
|
|
@@ -9843,13 +10345,16 @@ function normalizeSpawnDirectory(input, homeDir) {
|
|
|
9843
10345
|
return { ok: true, directory: normalize(expanded) };
|
|
9844
10346
|
}
|
|
9845
10347
|
|
|
9846
|
-
const
|
|
10348
|
+
const ASSISTANT_SESSION_TOOL_NAMES = [
|
|
9847
10349
|
"sessions_list",
|
|
9848
10350
|
"session_read",
|
|
9849
10351
|
"session_send",
|
|
9850
10352
|
"session_spawn",
|
|
9851
10353
|
"session_kill",
|
|
9852
|
-
"session_archive"
|
|
10354
|
+
"session_archive"
|
|
10355
|
+
];
|
|
10356
|
+
const ASSISTANT_TOOL_NAMES = [
|
|
10357
|
+
...ASSISTANT_SESSION_TOOL_NAMES,
|
|
9853
10358
|
"terminals_list",
|
|
9854
10359
|
"terminal_read",
|
|
9855
10360
|
"terminal_send",
|
|
@@ -9886,6 +10391,10 @@ function describeSummary(summary) {
|
|
|
9886
10391
|
return parts.join(" ");
|
|
9887
10392
|
}
|
|
9888
10393
|
function registerAssistantTools(mcp) {
|
|
10394
|
+
registerAssistantSessionTools(mcp);
|
|
10395
|
+
registerAssistantMachineTools(mcp);
|
|
10396
|
+
}
|
|
10397
|
+
function registerAssistantSessionTools(mcp) {
|
|
9889
10398
|
mcp.registerTool("sessions_list", {
|
|
9890
10399
|
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.",
|
|
9891
10400
|
title: "List Sessions",
|
|
@@ -10002,6 +10511,8 @@ ${body}`);
|
|
|
10002
10511
|
return fail(`Failed to archive session: ${error instanceof Error ? error.message : String(error)}`);
|
|
10003
10512
|
}
|
|
10004
10513
|
});
|
|
10514
|
+
}
|
|
10515
|
+
function registerAssistantMachineTools(mcp) {
|
|
10005
10516
|
mcp.registerTool("terminals_list", {
|
|
10006
10517
|
description: "List the web terminals (tmux sessions) on this machine with their id, title and working directory.",
|
|
10007
10518
|
title: "List Terminals",
|
|
@@ -10671,7 +11182,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
10671
11182
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
10672
11183
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
10673
11184
|
const run = deps.run ?? (async (input) => {
|
|
10674
|
-
const { query } = await import('./index-
|
|
11185
|
+
const { query } = await import('./index-B0o3H6_x.mjs');
|
|
10675
11186
|
return runSideQuestion(query, input);
|
|
10676
11187
|
});
|
|
10677
11188
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -11211,7 +11722,7 @@ async function runClaude(credentials, options = {}) {
|
|
|
11211
11722
|
const forkedFromMessageId = process.env.HAPPY_FORKED_FROM_MESSAGE_ID;
|
|
11212
11723
|
const importedFromClaudeSessionId = process.env.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID;
|
|
11213
11724
|
const importTitle = process.env.HAPPY_IMPORT_TITLE;
|
|
11214
|
-
const originTags = spawnOriginTags();
|
|
11725
|
+
const originTags = spawnOriginTags(process.env, "claude");
|
|
11215
11726
|
let metadata = {
|
|
11216
11727
|
path: workingDirectory,
|
|
11217
11728
|
host: os.hostname(),
|
|
@@ -15601,7 +16112,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15601
16112
|
process.exit(0);
|
|
15602
16113
|
} else if (subcommand === "install-terminal-hooks") {
|
|
15603
16114
|
try {
|
|
15604
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-
|
|
16115
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-DeYqyjCB.mjs');
|
|
15605
16116
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
15606
16117
|
if (command.action === "help") {
|
|
15607
16118
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -15618,7 +16129,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15618
16129
|
}
|
|
15619
16130
|
} else if (subcommand === "spawn") {
|
|
15620
16131
|
try {
|
|
15621
|
-
const { handleSpawnCommand } = await import('./spawn-
|
|
16132
|
+
const { handleSpawnCommand } = await import('./spawn-CsrHZd3y.mjs');
|
|
15622
16133
|
await handleSpawnCommand(args.slice(1));
|
|
15623
16134
|
} catch (error) {
|
|
15624
16135
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -15630,7 +16141,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15630
16141
|
return;
|
|
15631
16142
|
} else if (subcommand === "sessions") {
|
|
15632
16143
|
try {
|
|
15633
|
-
const { handleSessionsCommand } = await import('./sessions-
|
|
16144
|
+
const { handleSessionsCommand } = await import('./sessions-DYPB3Peg.mjs');
|
|
15634
16145
|
await handleSessionsCommand(args.slice(1));
|
|
15635
16146
|
} catch (error) {
|
|
15636
16147
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -15642,7 +16153,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15642
16153
|
return;
|
|
15643
16154
|
} else if (subcommand === "send") {
|
|
15644
16155
|
try {
|
|
15645
|
-
const { handleSendCommand } = await import('./send-
|
|
16156
|
+
const { handleSendCommand } = await import('./send-CYznZGrp.mjs');
|
|
15646
16157
|
await handleSendCommand(args.slice(1));
|
|
15647
16158
|
} catch (error) {
|
|
15648
16159
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -15748,9 +16259,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15748
16259
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
15749
16260
|
const projectId = args[3];
|
|
15750
16261
|
try {
|
|
15751
|
-
const { saveGoogleCloudProjectToConfig } = await import('./config-
|
|
15752
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-
|
|
15753
|
-
const { ApiClient: ApiClient2 } = await import('./types-
|
|
16262
|
+
const { saveGoogleCloudProjectToConfig } = await import('./config-CnA3JIUn.mjs');
|
|
16263
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-jSfU_0o1.mjs').then(function (n) { return n.as; });
|
|
16264
|
+
const { ApiClient: ApiClient2 } = await import('./types-jSfU_0o1.mjs').then(function (n) { return n.at; });
|
|
15754
16265
|
let userEmail = void 0;
|
|
15755
16266
|
try {
|
|
15756
16267
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -15781,7 +16292,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15781
16292
|
}
|
|
15782
16293
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
15783
16294
|
try {
|
|
15784
|
-
const { readGeminiLocalConfig } = await import('./config-
|
|
16295
|
+
const { readGeminiLocalConfig } = await import('./config-CnA3JIUn.mjs');
|
|
15785
16296
|
const config = readGeminiLocalConfig();
|
|
15786
16297
|
if (config.googleCloudProject) {
|
|
15787
16298
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -15821,7 +16332,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15821
16332
|
process.exit(0);
|
|
15822
16333
|
}
|
|
15823
16334
|
try {
|
|
15824
|
-
const { runGemini } = await import('./runGemini-
|
|
16335
|
+
const { runGemini } = await import('./runGemini-CEURRmM4.mjs');
|
|
15825
16336
|
let startedBy = void 0;
|
|
15826
16337
|
for (let i = 1; i < args.length; i++) {
|
|
15827
16338
|
if (args[i] === "--started-by") {
|
|
@@ -15841,9 +16352,44 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15841
16352
|
process.exit(1);
|
|
15842
16353
|
}
|
|
15843
16354
|
return;
|
|
16355
|
+
} else if (subcommand === "pi") {
|
|
16356
|
+
let hintPrinted = false;
|
|
16357
|
+
const printHint = (detail) => {
|
|
16358
|
+
const hint = piAdapterMissingHint(detail);
|
|
16359
|
+
if (hint && !hintPrinted) {
|
|
16360
|
+
hintPrinted = true;
|
|
16361
|
+
console.error(chalk.gray(hint));
|
|
16362
|
+
}
|
|
16363
|
+
};
|
|
16364
|
+
try {
|
|
16365
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-DiRN0Rv8.mjs');
|
|
16366
|
+
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
16367
|
+
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
16368
|
+
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
16369
|
+
await ensureDaemonRunning();
|
|
16370
|
+
await runAcp({
|
|
16371
|
+
credentials,
|
|
16372
|
+
startedBy: parsed.startedBy,
|
|
16373
|
+
verbose: parsed.verbose,
|
|
16374
|
+
permissionMode: parsed.permissionMode,
|
|
16375
|
+
agentName: resolved.agentName,
|
|
16376
|
+
command: resolved.command,
|
|
16377
|
+
args: resolved.args,
|
|
16378
|
+
onBackendError: printHint
|
|
16379
|
+
});
|
|
16380
|
+
if (hintPrinted) process.exit(1);
|
|
16381
|
+
} catch (error) {
|
|
16382
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
16383
|
+
printHint(error instanceof Error ? error.message : void 0);
|
|
16384
|
+
if (process.env.DEBUG) {
|
|
16385
|
+
console.error(error);
|
|
16386
|
+
}
|
|
16387
|
+
process.exit(1);
|
|
16388
|
+
}
|
|
16389
|
+
return;
|
|
15844
16390
|
} else if (subcommand === "acp") {
|
|
15845
16391
|
try {
|
|
15846
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
16392
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-DiRN0Rv8.mjs');
|
|
15847
16393
|
let startedBy = void 0;
|
|
15848
16394
|
let verbose = false;
|
|
15849
16395
|
const acpArgs = [];
|
|
@@ -15883,7 +16429,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15883
16429
|
return;
|
|
15884
16430
|
} else if (subcommand === "openclaw") {
|
|
15885
16431
|
try {
|
|
15886
|
-
const { runOpenClaw } = await import('./runOpenClaw-
|
|
16432
|
+
const { runOpenClaw } = await import('./runOpenClaw-BozfKWng.mjs');
|
|
15887
16433
|
let startedBy = void 0;
|
|
15888
16434
|
let verbose = false;
|
|
15889
16435
|
let gatewayUrl;
|
|
@@ -15934,7 +16480,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15934
16480
|
return;
|
|
15935
16481
|
} else if (subcommand === "mcp" && args.length === 1) {
|
|
15936
16482
|
try {
|
|
15937
|
-
const { handleMcpCommand } = await import('./mcp-
|
|
16483
|
+
const { handleMcpCommand } = await import('./mcp-D_5aOK0I.mjs');
|
|
15938
16484
|
await handleMcpCommand();
|
|
15939
16485
|
} catch (error) {
|
|
15940
16486
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -16149,10 +16695,12 @@ ${chalk.bold("Usage:")}
|
|
|
16149
16695
|
print its web URL (for automation; see spawn --help)
|
|
16150
16696
|
very-happy send Send a message into an existing session
|
|
16151
16697
|
(for automation; see send --help)
|
|
16152
|
-
very-happy sessions List, read, stop
|
|
16153
|
-
|
|
16698
|
+
very-happy sessions List, read, stop, archive, approve or deny
|
|
16699
|
+
sessions and their permission requests (for
|
|
16700
|
+
automation; see sessions --help)
|
|
16154
16701
|
very-happy codex Start Codex mode
|
|
16155
16702
|
very-happy gemini Start Gemini mode (ACP)
|
|
16703
|
+
very-happy pi Start pi mode (ACP via the pi-acp adapter)
|
|
16156
16704
|
very-happy acp Start a generic ACP-compatible agent
|
|
16157
16705
|
very-happy openclaw Connect through a configured OpenClaw gateway
|
|
16158
16706
|
very-happy install-terminal-hooks
|
|
@@ -16178,6 +16726,7 @@ ${chalk.bold("Examples:")}
|
|
|
16178
16726
|
very-happy --js-runtime bun Use bun instead of node to spawn Claude Code
|
|
16179
16727
|
very-happy --claude-env ANTHROPIC_BASE_URL=http://127.0.0.1:3456
|
|
16180
16728
|
Use a custom API endpoint (e.g., claude-code-router)
|
|
16729
|
+
very-happy pi Start pi (needs pi-acp on PATH)
|
|
16181
16730
|
very-happy acp gemini Start Gemini via generic ACP runner
|
|
16182
16731
|
very-happy acp opencode Start OpenCode through its built-in ACP adapter
|
|
16183
16732
|
very-happy acp -- your-agent --acp
|
|
@@ -16292,4 +16841,4 @@ ${chalk.bold("Examples:")}
|
|
|
16292
16841
|
}
|
|
16293
16842
|
}
|
|
16294
16843
|
|
|
16295
|
-
export { ALLOWED_SPAWN_PERMISSION_MODES as A,
|
|
16844
|
+
export { ALLOWED_SPAWN_PERMISSION_MODES as A, registerKillSessionHandler as B, BasePermissionHandler as C, DEFAULT_RECENT_LIMIT as D, writeSessionModeFile as E, normalizeAcpPermissionMode as F, GEMINI_MODEL_ENV as G, removeSessionModeFile as H, GEMINI_API_KEY_ENV as I, GOOGLE_API_KEY_ENV as J, BaseReasoningProcessor as K, MessageBuffer as L, MAX_READ_LIMIT as M, CHANGE_TITLE_INSTRUCTION as N, SPAWN_AGENTS as S, TitleGenerator as T, sessionWebUrl as a, sanitizeSpawnPermissionMode as b, checkIfDaemonRunningAndCleanupStaleState as c, sendUserMessage as d, listSessions as e, stopSession as f, resolvePermissionRequest as g, archiveSession as h, isValidSpawnOrigin as i, DEFAULT_GEMINI_MODEL as j, setTerminalTitleViaDaemon as k, listAccountSessions as l, registerAssistantSessionTools as m, initialMachineMetadata as n, createSessionMetadata as o, pushClipboardViaDaemon as p, query as q, readSessionTranscript as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, setupOfflineReconnection as u, notifyDaemonSessionStarted as v, waitForSessionKey as w, MessageQueue2 as x, hashObject as y, startHappyServer as z };
|