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