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
|
@@ -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-
|
|
6
|
+
var persistence = require('./types-DD3AK6Nb.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
|
|
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(
|
|
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(),
|
|
@@ -7119,6 +7238,24 @@ function decideHandover(run) {
|
|
|
7119
7238
|
return { action: "handover" };
|
|
7120
7239
|
}
|
|
7121
7240
|
|
|
7241
|
+
function decideAutoUpdate(context) {
|
|
7242
|
+
if (!context.enabled) return { action: "skip", reason: "auto-update disabled" };
|
|
7243
|
+
const target = context.recommendedVersion;
|
|
7244
|
+
if (!target) return { action: "skip", reason: "no recommended version published" };
|
|
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 recommended ${target}` };
|
|
7249
|
+
if (context.failedVersion === target) {
|
|
7250
|
+
return { action: "skip", reason: `already failed to install ${target} once` };
|
|
7251
|
+
}
|
|
7252
|
+
if (!context.idle) return { action: "skip", reason: "machine is busy" };
|
|
7253
|
+
return { action: "install", version: target };
|
|
7254
|
+
}
|
|
7255
|
+
function autoUpdateInstallArgs(version) {
|
|
7256
|
+
return ["i", "-g", "--allow-scripts=very-happy-cli,node-pty", `very-happy-cli@${version}`];
|
|
7257
|
+
}
|
|
7258
|
+
|
|
7122
7259
|
const CLAUDE_KEYCHAIN_SERVICE_PREFIX = "Claude Code";
|
|
7123
7260
|
function keychainIdentity(env = process.env, deps = {}) {
|
|
7124
7261
|
const home = deps.home ?? os.homedir();
|
|
@@ -7182,7 +7319,7 @@ function parseJsonObject(text) {
|
|
|
7182
7319
|
return null;
|
|
7183
7320
|
}
|
|
7184
7321
|
}
|
|
7185
|
-
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-
|
|
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-DcESl4cI.cjs', document.baseURI).href))).resolve(s)) {
|
|
7186
7323
|
const platformArch = `${platform}-${arch}`;
|
|
7187
7324
|
const candidates = [
|
|
7188
7325
|
`@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
|
|
@@ -8337,6 +8474,7 @@ function sanitizeImportTitle(value) {
|
|
|
8337
8474
|
return collapsed.length > 200 ? `${collapsed.slice(0, 199)}\u2026` : collapsed;
|
|
8338
8475
|
}
|
|
8339
8476
|
const hostSuffix = process.env.HAPPY_VARIANT === "dev" ? "-dev" : "";
|
|
8477
|
+
const startupCliAvailability = persistence.detectCLIAvailability();
|
|
8340
8478
|
const initialMachineMetadata = {
|
|
8341
8479
|
host: os$1.hostname() + hostSuffix,
|
|
8342
8480
|
platform: os$1.platform(),
|
|
@@ -8344,7 +8482,7 @@ const initialMachineMetadata = {
|
|
|
8344
8482
|
homeDir: os$1.homedir(),
|
|
8345
8483
|
happyHomeDir: persistence.configuration.happyHomeDir,
|
|
8346
8484
|
happyLibDir: persistence.projectPath(),
|
|
8347
|
-
cliAvailability:
|
|
8485
|
+
cliAvailability: startupCliAvailability,
|
|
8348
8486
|
resumeSupport: { ...persistence.detectResumeSupport(), rpcAvailable: true }
|
|
8349
8487
|
};
|
|
8350
8488
|
async function preflightNewBundle(bundlePath) {
|
|
@@ -8399,6 +8537,40 @@ function withHandoverHold(state, reason) {
|
|
|
8399
8537
|
return reason ? { ...state, handoverHold: { reason, at: Date.now() } } : { ...state, handoverHold: null };
|
|
8400
8538
|
}
|
|
8401
8539
|
const HANDOVER_PREFLIGHT_TIMEOUT_MS = 3e4;
|
|
8540
|
+
function runNpmInstall(args) {
|
|
8541
|
+
return new Promise((resolve) => {
|
|
8542
|
+
let settled = false;
|
|
8543
|
+
const done = (code) => {
|
|
8544
|
+
if (!settled) {
|
|
8545
|
+
settled = true;
|
|
8546
|
+
resolve(code);
|
|
8547
|
+
}
|
|
8548
|
+
};
|
|
8549
|
+
let child;
|
|
8550
|
+
try {
|
|
8551
|
+
child = node_child_process.spawn("npm", args, { stdio: "ignore", env: process.env });
|
|
8552
|
+
} catch {
|
|
8553
|
+
done(null);
|
|
8554
|
+
return;
|
|
8555
|
+
}
|
|
8556
|
+
const timer = setTimeout(() => {
|
|
8557
|
+
try {
|
|
8558
|
+
child.kill("SIGKILL");
|
|
8559
|
+
} catch {
|
|
8560
|
+
}
|
|
8561
|
+
done(null);
|
|
8562
|
+
}, AUTO_UPDATE_INSTALL_TIMEOUT_MS);
|
|
8563
|
+
child.on("error", () => {
|
|
8564
|
+
clearTimeout(timer);
|
|
8565
|
+
done(null);
|
|
8566
|
+
});
|
|
8567
|
+
child.on("close", (code) => {
|
|
8568
|
+
clearTimeout(timer);
|
|
8569
|
+
done(code);
|
|
8570
|
+
});
|
|
8571
|
+
});
|
|
8572
|
+
}
|
|
8573
|
+
const AUTO_UPDATE_INSTALL_TIMEOUT_MS = 10 * 6e4;
|
|
8402
8574
|
async function startDaemon() {
|
|
8403
8575
|
let claudeAuthServiceRef = null;
|
|
8404
8576
|
let cliUpdateStateRef = null;
|
|
@@ -8568,18 +8740,26 @@ async function startDaemon() {
|
|
|
8568
8740
|
};
|
|
8569
8741
|
const assistantSpawnGate = createSpawnGate();
|
|
8570
8742
|
const spawnSession = (options) => {
|
|
8571
|
-
if (options
|
|
8743
|
+
if (assistantSpawnMode(options) !== "claude-singleton") {
|
|
8572
8744
|
return spawnSessionImpl(options);
|
|
8573
8745
|
}
|
|
8574
8746
|
return options.forceNew ? assistantSpawnGate.replace(() => spawnSessionImpl(options)) : assistantSpawnGate.join(() => spawnSessionImpl(options));
|
|
8575
8747
|
};
|
|
8748
|
+
const currentCliAvailability = () => apiMachineRef?.getCLIAvailability() ?? startupCliAvailability;
|
|
8576
8749
|
const spawnSessionImpl = async (options) => {
|
|
8577
8750
|
persistence.logger.debugLargeJson("[DAEMON RUN] Spawning session", persistence.summarizeSpawnSessionForLog(options));
|
|
8578
8751
|
const spawnPermissionMode = sanitizeSpawnPermissionMode(options.permissionMode);
|
|
8579
8752
|
if (options.permissionMode !== void 0 && spawnPermissionMode === null) {
|
|
8580
8753
|
persistence.logger.warn("[DAEMON RUN] Ignoring invalid permissionMode in spawn request");
|
|
8581
8754
|
}
|
|
8582
|
-
|
|
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") {
|
|
8583
8763
|
options = { ...options, directory: assistantHome() };
|
|
8584
8764
|
if (options.forceNew) {
|
|
8585
8765
|
for (const tracked of [...pidToTrackedSession.values()]) {
|
|
@@ -8782,7 +8962,13 @@ async function startDaemon() {
|
|
|
8782
8962
|
persistence.logger.debug(`[DAEMON RUN] Attempting to spawn session in tmux: ${sessionDesc}`);
|
|
8783
8963
|
const tmux = getTmuxUtilities(tmuxSessionName);
|
|
8784
8964
|
const cliPath = path$1.join(persistence.projectPath(), "dist", "index.mjs");
|
|
8785
|
-
const agent = options.agent
|
|
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
|
+
}
|
|
8786
8972
|
const resumeId = agent === "claude" ? options.resumeClaudeSessionId : agent === "codex" ? options.resumeCodexThreadId : void 0;
|
|
8787
8973
|
const resumeFragment = resumeId ? ` --resume ${persistence.shellescape(resumeId)}` : "";
|
|
8788
8974
|
const permissionModeFragment = spawnPermissionMode ? ` --permission-mode ${persistence.shellescape(spawnPermissionMode)}` : "";
|
|
@@ -8810,7 +8996,7 @@ async function startDaemon() {
|
|
|
8810
8996
|
pid: tmuxResult.pid,
|
|
8811
8997
|
// Real PID from tmux -P flag
|
|
8812
8998
|
tmuxSessionId: tmuxResult.sessionId,
|
|
8813
|
-
variant:
|
|
8999
|
+
variant: trackedVariant,
|
|
8814
9000
|
spawnedBy: options.spawnedBy,
|
|
8815
9001
|
directoryCreated,
|
|
8816
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.`
|
|
@@ -8857,6 +9043,9 @@ async function startDaemon() {
|
|
|
8857
9043
|
case "openclaw":
|
|
8858
9044
|
agentCommand = "openclaw";
|
|
8859
9045
|
break;
|
|
9046
|
+
case "pi":
|
|
9047
|
+
agentCommand = "pi";
|
|
9048
|
+
break;
|
|
8860
9049
|
default:
|
|
8861
9050
|
return {
|
|
8862
9051
|
type: "error",
|
|
@@ -8888,7 +9077,7 @@ async function startDaemon() {
|
|
|
8888
9077
|
},
|
|
8889
9078
|
directoryCreated,
|
|
8890
9079
|
message: directoryCreated ? `The path '${directory}' did not exist. We created a new folder and spawned a new session there.` : void 0,
|
|
8891
|
-
variant:
|
|
9080
|
+
variant: trackedVariant,
|
|
8892
9081
|
spawnedBy: options.spawnedBy
|
|
8893
9082
|
});
|
|
8894
9083
|
}
|
|
@@ -9369,6 +9558,10 @@ async function startDaemon() {
|
|
|
9369
9558
|
},
|
|
9370
9559
|
onTerminalHook: (body) => {
|
|
9371
9560
|
mirrorManagerRef?.handleHookPayload(body);
|
|
9561
|
+
},
|
|
9562
|
+
setTerminalTitle: (terminalId, title, ifAbsent) => {
|
|
9563
|
+
if (!apiMachineRef) return "starting";
|
|
9564
|
+
return apiMachineRef.setTerminalTitle(terminalId, title, ifAbsent);
|
|
9372
9565
|
}
|
|
9373
9566
|
});
|
|
9374
9567
|
const daemonClaudeCredentials = resolveClaudeCredentialReadiness();
|
|
@@ -9465,6 +9658,48 @@ async function startDaemon() {
|
|
|
9465
9658
|
host: initialMachineMetadata.host
|
|
9466
9659
|
})).then(() => persistence.logger.debug(`[DAEMON RUN] claimed the machine record for host ${initialMachineMetadata.host}`)).catch((error) => persistence.logger.debug("[DAEMON RUN] could not refresh the recorded host:", error));
|
|
9467
9660
|
}
|
|
9661
|
+
let autoUpdateFailedVersion = null;
|
|
9662
|
+
let autoUpdateRunning = false;
|
|
9663
|
+
const maybeAutoUpdate = async (cliUpdate) => {
|
|
9664
|
+
if (autoUpdateRunning) return;
|
|
9665
|
+
const settings = await persistence.readSettings();
|
|
9666
|
+
const decision = decideAutoUpdate({
|
|
9667
|
+
enabled: (settings.cliAutoUpdate ?? "idle") !== "off",
|
|
9668
|
+
currentVersion: cliUpdate.currentVersion,
|
|
9669
|
+
recommendedVersion: cliUpdate.recommendedVersion,
|
|
9670
|
+
idle: pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals(),
|
|
9671
|
+
failedVersion: autoUpdateFailedVersion
|
|
9672
|
+
});
|
|
9673
|
+
if (decision.action === "skip") {
|
|
9674
|
+
persistence.logger.debug(`[DAEMON RUN] auto-update skipped: ${decision.reason}`);
|
|
9675
|
+
return;
|
|
9676
|
+
}
|
|
9677
|
+
autoUpdateRunning = true;
|
|
9678
|
+
const target = decision.version;
|
|
9679
|
+
const report = (state, detail) => {
|
|
9680
|
+
cliUpdateStateRef = { ...cliUpdate, autoUpdate: { state, version: target, detail, at: Date.now() } };
|
|
9681
|
+
void apiMachine.setCliUpdateState(cliUpdateStateRef);
|
|
9682
|
+
};
|
|
9683
|
+
persistence.logger.warn(`[DAEMON RUN] auto-updating CLI to ${target} (machine idle)`);
|
|
9684
|
+
report("installing");
|
|
9685
|
+
try {
|
|
9686
|
+
const code = await runNpmInstall(autoUpdateInstallArgs(target));
|
|
9687
|
+
if (code === 0) {
|
|
9688
|
+
persistence.logger.warn(`[DAEMON RUN] auto-update installed ${target}; handover follows once it verifies`);
|
|
9689
|
+
report("installed");
|
|
9690
|
+
} else {
|
|
9691
|
+
autoUpdateFailedVersion = target;
|
|
9692
|
+
persistence.logger.warn(`[DAEMON RUN] auto-update of ${target} failed with exit ${code}`);
|
|
9693
|
+
report("failed", `npm exited ${code}`);
|
|
9694
|
+
}
|
|
9695
|
+
} catch (error) {
|
|
9696
|
+
autoUpdateFailedVersion = target;
|
|
9697
|
+
persistence.logger.warn(`[DAEMON RUN] auto-update of ${target} could not run:`, error);
|
|
9698
|
+
report("failed", String(error).slice(0, 200));
|
|
9699
|
+
} finally {
|
|
9700
|
+
autoUpdateRunning = false;
|
|
9701
|
+
}
|
|
9702
|
+
};
|
|
9468
9703
|
let cliUpdateCheckRunning = false;
|
|
9469
9704
|
const refreshCliUpdate = async () => {
|
|
9470
9705
|
if (cliUpdateCheckRunning) return;
|
|
@@ -9477,6 +9712,7 @@ async function startDaemon() {
|
|
|
9477
9712
|
cliUpdateStateRef = cliUpdate;
|
|
9478
9713
|
apiMachine.setCliUpdateState(cliUpdate);
|
|
9479
9714
|
persistence.logger.debug(`[DAEMON RUN] CLI update policy checked: ${cliUpdate.status} (recommended=${cliUpdate.recommendedVersion ?? "none"}, minimum=${cliUpdate.minimumVersion ?? "none"})`);
|
|
9715
|
+
await maybeAutoUpdate(cliUpdate);
|
|
9480
9716
|
} catch (error) {
|
|
9481
9717
|
persistence.logger.debug("[DAEMON RUN] CLI update policy check failed:", error);
|
|
9482
9718
|
} finally {
|
|
@@ -9689,6 +9925,194 @@ function formatTranscript(bodies) {
|
|
|
9689
9925
|
return lines.join("\n");
|
|
9690
9926
|
}
|
|
9691
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
|
+
|
|
9692
10116
|
const SESSION_OPS_CLIENT = "session-ops";
|
|
9693
10117
|
const DEFAULT_RECENT_LIMIT = 15;
|
|
9694
10118
|
const MAX_READ_LIMIT = 100;
|
|
@@ -9700,10 +10124,7 @@ function mergeSessionSummaries(live, persisted, options = {}) {
|
|
|
9700
10124
|
const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
|
|
9701
10125
|
if (!id || seen.has(id)) continue;
|
|
9702
10126
|
seen.add(id);
|
|
9703
|
-
summaries.push(toSummary(id, persisted[id],
|
|
9704
|
-
live: true,
|
|
9705
|
-
pid: typeof child.pid === "number" ? child.pid : void 0
|
|
9706
|
-
}));
|
|
10127
|
+
summaries.push(toSummary(id, persisted[id], livenessOf(child)));
|
|
9707
10128
|
}
|
|
9708
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);
|
|
9709
10130
|
for (const [id, entry] of rest) {
|
|
@@ -9713,6 +10134,13 @@ function mergeSessionSummaries(live, persisted, options = {}) {
|
|
|
9713
10134
|
const wanted = options.tag;
|
|
9714
10135
|
return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9715
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
|
+
}
|
|
9716
10144
|
function toSummary(id, persisted, extra) {
|
|
9717
10145
|
const meta = persisted?.metadata;
|
|
9718
10146
|
return {
|
|
@@ -9732,6 +10160,79 @@ async function listSessions(options = {}) {
|
|
|
9732
10160
|
const live = await listDaemonSessions();
|
|
9733
10161
|
return mergeSessionSummaries(live, persistence.readPersistedSessions(), options);
|
|
9734
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
|
+
}
|
|
9735
10236
|
async function bearerToken() {
|
|
9736
10237
|
const credentials = await persistence.readCredentialsForConfiguredRelay();
|
|
9737
10238
|
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
@@ -9760,6 +10261,7 @@ async function readSessionTranscript(sessionId, limit) {
|
|
|
9760
10261
|
const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
|
|
9761
10262
|
messages.reverse();
|
|
9762
10263
|
const key = persistence.decodeBase64(persisted.encryptionKey);
|
|
10264
|
+
const live = await listDaemonSessions();
|
|
9763
10265
|
const bodies = messages.map((message) => {
|
|
9764
10266
|
if (message.content?.t !== "encrypted") return null;
|
|
9765
10267
|
try {
|
|
@@ -9769,7 +10271,7 @@ async function readSessionTranscript(sessionId, limit) {
|
|
|
9769
10271
|
}
|
|
9770
10272
|
});
|
|
9771
10273
|
return {
|
|
9772
|
-
summary: toSummary(sessionId, persisted,
|
|
10274
|
+
summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
|
|
9773
10275
|
messageCount: messages.length,
|
|
9774
10276
|
transcript: formatTranscript(bodies)
|
|
9775
10277
|
};
|
|
@@ -9865,13 +10367,16 @@ function normalizeSpawnDirectory(input, homeDir) {
|
|
|
9865
10367
|
return { ok: true, directory: path.normalize(expanded) };
|
|
9866
10368
|
}
|
|
9867
10369
|
|
|
9868
|
-
const
|
|
10370
|
+
const ASSISTANT_SESSION_TOOL_NAMES = [
|
|
9869
10371
|
"sessions_list",
|
|
9870
10372
|
"session_read",
|
|
9871
10373
|
"session_send",
|
|
9872
10374
|
"session_spawn",
|
|
9873
10375
|
"session_kill",
|
|
9874
|
-
"session_archive"
|
|
10376
|
+
"session_archive"
|
|
10377
|
+
];
|
|
10378
|
+
const ASSISTANT_TOOL_NAMES = [
|
|
10379
|
+
...ASSISTANT_SESSION_TOOL_NAMES,
|
|
9875
10380
|
"terminals_list",
|
|
9876
10381
|
"terminal_read",
|
|
9877
10382
|
"terminal_send",
|
|
@@ -9908,6 +10413,10 @@ function describeSummary(summary) {
|
|
|
9908
10413
|
return parts.join(" ");
|
|
9909
10414
|
}
|
|
9910
10415
|
function registerAssistantTools(mcp) {
|
|
10416
|
+
registerAssistantSessionTools(mcp);
|
|
10417
|
+
registerAssistantMachineTools(mcp);
|
|
10418
|
+
}
|
|
10419
|
+
function registerAssistantSessionTools(mcp) {
|
|
9911
10420
|
mcp.registerTool("sessions_list", {
|
|
9912
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.",
|
|
9913
10422
|
title: "List Sessions",
|
|
@@ -10024,6 +10533,8 @@ ${body}`);
|
|
|
10024
10533
|
return fail(`Failed to archive session: ${error instanceof Error ? error.message : String(error)}`);
|
|
10025
10534
|
}
|
|
10026
10535
|
});
|
|
10536
|
+
}
|
|
10537
|
+
function registerAssistantMachineTools(mcp) {
|
|
10027
10538
|
mcp.registerTool("terminals_list", {
|
|
10028
10539
|
description: "List the web terminals (tmux sessions) on this machine with their id, title and working directory.",
|
|
10029
10540
|
title: "List Terminals",
|
|
@@ -10693,7 +11204,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
10693
11204
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
10694
11205
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
10695
11206
|
const run = deps.run ?? (async (input) => {
|
|
10696
|
-
const { query } = await Promise.resolve().then(function () { return require('./index-
|
|
11207
|
+
const { query } = await Promise.resolve().then(function () { return require('./index-DqmVH6R5.cjs'); });
|
|
10697
11208
|
return runSideQuestion(query, input);
|
|
10698
11209
|
});
|
|
10699
11210
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -10795,7 +11306,7 @@ const MAX_TITLE_CHARS = 60;
|
|
|
10795
11306
|
const GENERATION_TIMEOUT_MS = 3e4;
|
|
10796
11307
|
function resolveClaudeBinary() {
|
|
10797
11308
|
try {
|
|
10798
|
-
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-
|
|
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-DcESl4cI.cjs', document.baseURI).href)));
|
|
10799
11310
|
const utilsPath = path.resolve(path.join(persistence.projectPath(), "scripts", "claude_version_utils.cjs"));
|
|
10800
11311
|
const { getClaudeCliPath } = require$1(utilsPath);
|
|
10801
11312
|
const path$1 = getClaudeCliPath();
|
|
@@ -11233,7 +11744,7 @@ async function runClaude(credentials, options = {}) {
|
|
|
11233
11744
|
const forkedFromMessageId = process.env.HAPPY_FORKED_FROM_MESSAGE_ID;
|
|
11234
11745
|
const importedFromClaudeSessionId = process.env.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID;
|
|
11235
11746
|
const importTitle = process.env.HAPPY_IMPORT_TITLE;
|
|
11236
|
-
const originTags = spawnOriginTags();
|
|
11747
|
+
const originTags = spawnOriginTags(process.env, "claude");
|
|
11237
11748
|
let metadata = {
|
|
11238
11749
|
path: workingDirectory,
|
|
11239
11750
|
host: os.hostname(),
|
|
@@ -12997,9 +13508,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
|
|
|
12997
13508
|
};
|
|
12998
13509
|
}
|
|
12999
13510
|
|
|
13000
|
-
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-
|
|
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-DcESl4cI.cjs', document.baseURI).href)));
|
|
13001
13512
|
const __dirname$1 = path.dirname(__filename$1);
|
|
13002
|
-
const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
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-DcESl4cI.cjs', document.baseURI).href)));
|
|
13003
13514
|
const PRISMA_QUERY_ENGINE_FILES = {
|
|
13004
13515
|
"arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
|
|
13005
13516
|
"x64-darwin": "libquery_engine-darwin.dylib.node",
|
|
@@ -15623,7 +16134,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15623
16134
|
process.exit(0);
|
|
15624
16135
|
} else if (subcommand === "install-terminal-hooks") {
|
|
15625
16136
|
try {
|
|
15626
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-
|
|
16137
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-zfsWSotO.cjs'); });
|
|
15627
16138
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
15628
16139
|
if (command.action === "help") {
|
|
15629
16140
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -15640,7 +16151,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15640
16151
|
}
|
|
15641
16152
|
} else if (subcommand === "spawn") {
|
|
15642
16153
|
try {
|
|
15643
|
-
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-
|
|
16154
|
+
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-tSvo3stN.cjs'); });
|
|
15644
16155
|
await handleSpawnCommand(args.slice(1));
|
|
15645
16156
|
} catch (error) {
|
|
15646
16157
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -15652,7 +16163,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15652
16163
|
return;
|
|
15653
16164
|
} else if (subcommand === "sessions") {
|
|
15654
16165
|
try {
|
|
15655
|
-
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-
|
|
16166
|
+
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-YWt9YEN8.cjs'); });
|
|
15656
16167
|
await handleSessionsCommand(args.slice(1));
|
|
15657
16168
|
} catch (error) {
|
|
15658
16169
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -15664,7 +16175,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15664
16175
|
return;
|
|
15665
16176
|
} else if (subcommand === "send") {
|
|
15666
16177
|
try {
|
|
15667
|
-
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-
|
|
16178
|
+
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-BZLhRZb-.cjs'); });
|
|
15668
16179
|
await handleSendCommand(args.slice(1));
|
|
15669
16180
|
} catch (error) {
|
|
15670
16181
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -15770,9 +16281,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15770
16281
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
15771
16282
|
const projectId = args[3];
|
|
15772
16283
|
try {
|
|
15773
|
-
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-
|
|
15774
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-
|
|
15775
|
-
const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-
|
|
16284
|
+
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-O9quMfxg.cjs'); });
|
|
16285
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-DD3AK6Nb.cjs'); }).then(function (n) { return n.persistence; });
|
|
16286
|
+
const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-DD3AK6Nb.cjs'); }).then(function (n) { return n.api; });
|
|
15776
16287
|
let userEmail = void 0;
|
|
15777
16288
|
try {
|
|
15778
16289
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -15803,7 +16314,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15803
16314
|
}
|
|
15804
16315
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
15805
16316
|
try {
|
|
15806
|
-
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-
|
|
16317
|
+
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-O9quMfxg.cjs'); });
|
|
15807
16318
|
const config = readGeminiLocalConfig();
|
|
15808
16319
|
if (config.googleCloudProject) {
|
|
15809
16320
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -15843,7 +16354,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15843
16354
|
process.exit(0);
|
|
15844
16355
|
}
|
|
15845
16356
|
try {
|
|
15846
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
16357
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DYf3ntib.cjs'); });
|
|
15847
16358
|
let startedBy = void 0;
|
|
15848
16359
|
for (let i = 1; i < args.length; i++) {
|
|
15849
16360
|
if (args[i] === "--started-by") {
|
|
@@ -15863,9 +16374,44 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15863
16374
|
process.exit(1);
|
|
15864
16375
|
}
|
|
15865
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-W-O4xR_O.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;
|
|
15866
16412
|
} else if (subcommand === "acp") {
|
|
15867
16413
|
try {
|
|
15868
|
-
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-
|
|
16414
|
+
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-W-O4xR_O.cjs'); });
|
|
15869
16415
|
let startedBy = void 0;
|
|
15870
16416
|
let verbose = false;
|
|
15871
16417
|
const acpArgs = [];
|
|
@@ -15905,7 +16451,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15905
16451
|
return;
|
|
15906
16452
|
} else if (subcommand === "openclaw") {
|
|
15907
16453
|
try {
|
|
15908
|
-
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-
|
|
16454
|
+
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-CNKjAw-H.cjs'); });
|
|
15909
16455
|
let startedBy = void 0;
|
|
15910
16456
|
let verbose = false;
|
|
15911
16457
|
let gatewayUrl;
|
|
@@ -15956,7 +16502,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
15956
16502
|
return;
|
|
15957
16503
|
} else if (subcommand === "mcp" && args.length === 1) {
|
|
15958
16504
|
try {
|
|
15959
|
-
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-
|
|
16505
|
+
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-nYqC4FYm.cjs'); });
|
|
15960
16506
|
await handleMcpCommand();
|
|
15961
16507
|
} catch (error) {
|
|
15962
16508
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -16171,10 +16717,12 @@ ${chalk.bold("Usage:")}
|
|
|
16171
16717
|
print its web URL (for automation; see spawn --help)
|
|
16172
16718
|
very-happy send Send a message into an existing session
|
|
16173
16719
|
(for automation; see send --help)
|
|
16174
|
-
very-happy sessions List, read, stop
|
|
16175
|
-
|
|
16720
|
+
very-happy sessions List, read, stop, archive, approve or deny
|
|
16721
|
+
sessions and their permission requests (for
|
|
16722
|
+
automation; see sessions --help)
|
|
16176
16723
|
very-happy codex Start Codex mode
|
|
16177
16724
|
very-happy gemini Start Gemini mode (ACP)
|
|
16725
|
+
very-happy pi Start pi mode (ACP via the pi-acp adapter)
|
|
16178
16726
|
very-happy acp Start a generic ACP-compatible agent
|
|
16179
16727
|
very-happy openclaw Connect through a configured OpenClaw gateway
|
|
16180
16728
|
very-happy install-terminal-hooks
|
|
@@ -16200,6 +16748,7 @@ ${chalk.bold("Examples:")}
|
|
|
16200
16748
|
very-happy --js-runtime bun Use bun instead of node to spawn Claude Code
|
|
16201
16749
|
very-happy --claude-env ANTHROPIC_BASE_URL=http://127.0.0.1:3456
|
|
16202
16750
|
Use a custom API endpoint (e.g., claude-code-router)
|
|
16751
|
+
very-happy pi Start pi (needs pi-acp on PATH)
|
|
16203
16752
|
very-happy acp gemini Start Gemini via generic ACP runner
|
|
16204
16753
|
very-happy acp opencode Start OpenCode through its built-in ACP adapter
|
|
16205
16754
|
very-happy acp -- your-agent --acp
|
|
@@ -16326,24 +16875,33 @@ exports.GOOGLE_API_KEY_ENV = GOOGLE_API_KEY_ENV;
|
|
|
16326
16875
|
exports.MAX_READ_LIMIT = MAX_READ_LIMIT;
|
|
16327
16876
|
exports.MessageBuffer = MessageBuffer;
|
|
16328
16877
|
exports.MessageQueue2 = MessageQueue2;
|
|
16878
|
+
exports.SPAWN_AGENTS = SPAWN_AGENTS;
|
|
16879
|
+
exports.TitleGenerator = TitleGenerator;
|
|
16329
16880
|
exports.archiveSession = archiveSession;
|
|
16330
16881
|
exports.checkIfDaemonRunningAndCleanupStaleState = checkIfDaemonRunningAndCleanupStaleState;
|
|
16331
16882
|
exports.createSessionMetadata = createSessionMetadata;
|
|
16332
16883
|
exports.hashObject = hashObject;
|
|
16333
16884
|
exports.initialMachineMetadata = initialMachineMetadata;
|
|
16334
16885
|
exports.isValidSpawnOrigin = isValidSpawnOrigin;
|
|
16886
|
+
exports.listAccountSessions = listAccountSessions;
|
|
16335
16887
|
exports.listSessions = listSessions;
|
|
16888
|
+
exports.normalizeAcpPermissionMode = normalizeAcpPermissionMode;
|
|
16336
16889
|
exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
|
|
16337
16890
|
exports.pushClipboardViaDaemon = pushClipboardViaDaemon;
|
|
16338
16891
|
exports.query = query;
|
|
16339
16892
|
exports.readSessionTranscript = readSessionTranscript;
|
|
16893
|
+
exports.registerAssistantSessionTools = registerAssistantSessionTools;
|
|
16340
16894
|
exports.registerKillSessionHandler = registerKillSessionHandler;
|
|
16895
|
+
exports.removeSessionModeFile = removeSessionModeFile;
|
|
16896
|
+
exports.resolvePermissionRequest = resolvePermissionRequest;
|
|
16341
16897
|
exports.sanitizeSpawnPermissionMode = sanitizeSpawnPermissionMode;
|
|
16342
16898
|
exports.sendUserMessage = sendUserMessage;
|
|
16343
16899
|
exports.sessionWebUrl = sessionWebUrl;
|
|
16900
|
+
exports.setTerminalTitleViaDaemon = setTerminalTitleViaDaemon;
|
|
16344
16901
|
exports.setupOfflineReconnection = setupOfflineReconnection;
|
|
16345
16902
|
exports.spawnDaemonSession = spawnDaemonSession;
|
|
16346
16903
|
exports.startHappyServer = startHappyServer;
|
|
16347
16904
|
exports.stopSession = stopSession;
|
|
16348
16905
|
exports.tmuxSupportsSessionEnv = tmuxSupportsSessionEnv;
|
|
16349
16906
|
exports.waitForSessionKey = waitForSessionKey;
|
|
16907
|
+
exports.writeSessionModeFile = writeSessionModeFile;
|