very-happy-cli 0.2.149 → 0.2.151
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-Baqb8dNm.mjs → AcpBackend-BjTA5gUB.mjs} +2 -2
- package/dist/{AcpBackend-CnZpEfGd.cjs → AcpBackend-blE9IEod.cjs} +2 -2
- package/dist/{AcpSessionManager-CC6mdFXs.mjs → AcpSessionManager-D-9mrp40.mjs} +1 -1
- package/dist/{AcpSessionManager-e2vxmIbj.cjs → AcpSessionManager-bF8PYu_B.cjs} +1 -1
- package/dist/{agentGuidance--QIi_rZ2.cjs → agentGuidance-BXa9ikrZ.cjs} +1 -1
- package/dist/{agentGuidance-oMVJvUl7.mjs → agentGuidance-Dvo3cZMW.mjs} +1 -1
- package/dist/{agentHome-CKSPsS8d.mjs → agentHome-CeFDNzvq.mjs} +2 -2
- package/dist/{agentHome-3kRCtzOL.cjs → agentHome-D6MSfwnp.cjs} +2 -2
- package/dist/codex/happyMcpStdioBridge.cjs +2 -2
- package/dist/codex/happyMcpStdioBridge.mjs +2 -2
- package/dist/{config-C8jpk6IA.mjs → config-C-A1XF62.mjs} +4 -4
- package/dist/{config-CzJZcvTO.cjs → config-Cadw1sAk.cjs} +4 -4
- package/dist/{index-DgK4KX-Z.cjs → index-BKGspSWk.cjs} +7 -7
- package/dist/{index-Dfkyar7S.mjs → index-CIyjJy8D.mjs} +338 -76
- package/dist/{index-CyfXsCXl.cjs → index-CqtfRzug.cjs} +338 -73
- package/dist/{index-DrVq0CqK.mjs → index-DEp6CmmS.mjs} +8 -8
- package/dist/{index-BPCXUrEe.mjs → index-DYQgcgL2.mjs} +4 -4
- package/dist/{index-DBN2Zpqc.cjs → index-vyRqLjDv.cjs} +4 -4
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{installTerminalHooks-kdAoTfNT.cjs → installTerminalHooks-Bf_adbl3.cjs} +4 -4
- package/dist/{installTerminalHooks-COqkLz6f.mjs → installTerminalHooks-Ch_yTtEA.mjs} +4 -4
- package/dist/lib.cjs +2 -2
- package/dist/lib.mjs +2 -2
- package/dist/{limits-BXI1Gzvd.mjs → limits-DS-bnYYp.mjs} +7 -7
- package/dist/{limits-FZWhlsBu.cjs → limits-DwcY3kyi.cjs} +7 -7
- package/dist/{mcp-DoLZbbZp.cjs → mcp-CMxEZ6-0.cjs} +4 -4
- package/dist/{mcp-Dfnoirpi.mjs → mcp-zu8n8TkR.mjs} +4 -4
- package/dist/{piRuntime-BK6YZm2X.mjs → piRuntime-Cu6cXZ6N.mjs} +2 -2
- package/dist/{piRuntime-DmE4ysfG.cjs → piRuntime-D3ny0mVg.cjs} +2 -2
- package/dist/{piTerminal-Bczubq2n.mjs → piTerminal-M677UP7y.mjs} +6 -6
- package/dist/{piTerminal-qQAzQwIU.cjs → piTerminal-kDSd_r9X.cjs} +6 -6
- package/dist/{runGemini-ComTu0EB.cjs → runGemini-DuwdVV1O.cjs} +6 -6
- package/dist/{runGemini-Cce_pjTs.mjs → runGemini-DvJwQVGp.mjs} +6 -6
- package/dist/{runOpenClaw-DXP5_chb.mjs → runOpenClaw-BG82bF18.mjs} +5 -5
- package/dist/{runOpenClaw-M3YlDt8z.cjs → runOpenClaw-BsvtCE_H.cjs} +5 -5
- package/dist/{send-0LrSwGgr.cjs → send-aUkjhey7.cjs} +20 -6
- package/dist/{send-DDD57Cr_.mjs → send-o6WM-1G8.mjs} +20 -6
- package/dist/{sessions-2Bkau9T4.cjs → sessions-B3vP18kz.cjs} +56 -10
- package/dist/{sessions-ClBPEtz2.mjs → sessions-DlU3foY_.mjs} +56 -10
- package/dist/{spawn-Cm3Da8Ge.cjs → spawn-DH6_p4-f.cjs} +166 -27
- package/dist/spawn-DxHIIqhi.mjs +397 -0
- package/dist/{teams-DKKeIG-u.cjs → teams-BW8D1n3o.cjs} +4 -4
- package/dist/{teams-DyBjdXI3.mjs → teams-DoAA_CcN.mjs} +4 -4
- package/dist/{todo-8RgCTDxH.cjs → todo-Bcc2XxGh.cjs} +3 -3
- package/dist/{todo-jXHGGH89.mjs → todo-BhJbG5eq.mjs} +3 -3
- package/dist/{types-ipzMqciq.mjs → types-BN3eiipi.mjs} +46 -3
- package/dist/{types-t1gxALVE.cjs → types-CPubg05Z.cjs} +52 -2
- package/package.json +7 -7
- package/dist/spawn-7lLymdT1.mjs +0 -258
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
|
|
2
2
|
import os, { homedir, userInfo, tmpdir } from 'node:os';
|
|
3
3
|
import { randomUUID, randomInt, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
|
|
4
|
-
import {
|
|
5
|
-
import { l as logger, F as ensurePrivateDirectory, G as hardenPrivateFile, c as configuration, p as packageJson, r as readSettings, H as readCredentials, x as credentialRelayProblem, o as readDaemonState, g as encodeBase64, I as readSettingsOutcome, u as updateSettings, J as encodeBase64Url, m as decodeBase64, K as writeCredentialsLegacy, L as writeCredentialsDataKey, e as readCredentialsForConfiguredRelay, j as encrypt, d as readPersistedSessions, w as writePrivateFileSync, M as decryptWithDataKey, N as decryptLegacy, O as persistSession, k as decrypt, h as ensurePrivateDirectorySync, P as acquireDaemonLock, Q as deletePersistedSessions, R as writeDaemonState, S as writeSettings, T as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, f as CLIPBOARD_MAX_BYTES, U as clearCredentials, V as clearMachineId, W as SandboxConfigSchema, X as getLatestDaemonLog } from './limits-
|
|
4
|
+
import { H as notifyDaemonSessionEvent, q as getProjectPath, z as BUILTIN_TODO_DISCOVERY, I as initializeSandbox, J as wrapCommand, E as contentLogMetadata, K as InvalidateSync, R as RawJSONLinesSchema, G as delay, L as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, N as notifyDaemonClaudeAuthFailed, O as commandOnPath, u as checkIfDaemonRunningAndCleanupStaleState, P as deriveLocalCliUpdateSummary, Q as AGENT_VERSION_SOURCES, T as isClaudeSessionId, U as getLocalHappyAgentCredentialPath, V as readLocalHappyAgentCredentials, W as parseExactVersion, X as compareExactVersions, Y as extractCompleteLines, Z as decideBackfill, _ as parseTerminalHookPayload, $ as tmuxArgs, a0 as scrubTmuxClientEnv, a1 as decideMirrorBinding, a2 as MIRROR_BACKFILL_LINES_DEFAULT, a3 as mirrorLineKey, a4 as mirrorLocalId, a5 as listDaemonSessions, w as stopDaemonSession, a6 as detectCLIAvailability, a7 as detectResumeSupport, a8 as refreshAgentHomes, d as describeAgentHome, a9 as isDaemonRunningCurrentlyInstalledHappyVersion, aa as stopDaemon, ab as TurnActivityTracker, ac as summarizeSpawnSessionForLog, ad as locateClaudeConversation, ae as agentHomeSpawnEnv, af as shellescape, A as ApiClient, ag as fetchCliUpdateState, ah as resolveCliUpdateCheckInterval, ai as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, aj as cleanupDaemonState, i as isValidSessionId, v as spawnDaemonSession, ak as listVhTerminals, al as isValidTerminalId, am as readVhTerminal, an as sendToVhTerminal, ao as ContextUsageSchema, ap as TITLE_PROMPT_PREFIX, aq as BOARD_ANALYZER_PROMPT_PREFIX, x as connectionState, ar as startOfflineReconnection, y as notifyDaemonSessionStarted, c as createEnvelope, C as CodexAppServerClient } from './types-BN3eiipi.mjs';
|
|
5
|
+
import { l as logger, F as ensurePrivateDirectory, G as hardenPrivateFile, c as configuration, p as packageJson, r as readSettings, H as readCredentials, x as credentialRelayProblem, o as readDaemonState, g as encodeBase64, I as readSettingsOutcome, u as updateSettings, J as encodeBase64Url, m as decodeBase64, K as writeCredentialsLegacy, L as writeCredentialsDataKey, e as readCredentialsForConfiguredRelay, j as encrypt, d as readPersistedSessions, w as writePrivateFileSync, M as decryptWithDataKey, N as decryptLegacy, O as persistSession, k as decrypt, h as ensurePrivateDirectorySync, P as acquireDaemonLock, Q as deletePersistedSessions, R as writeDaemonState, S as writeSettings, T as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, f as CLIPBOARD_MAX_BYTES, U as clearCredentials, V as clearMachineId, W as SandboxConfigSchema, X as getLatestDaemonLog } from './limits-DS-bnYYp.mjs';
|
|
6
6
|
import spawn$1, { spawn } from 'cross-spawn';
|
|
7
|
-
import path, { join, resolve, basename,
|
|
7
|
+
import path, { join, resolve, basename, delimiter, isAbsolute, dirname, sep, normalize } from 'node:path';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
9
|
-
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, unlinkSync, renameSync, writeFileSync, accessSync, constants, openSync, fsyncSync, closeSync, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, realpathSync, unlinkSync, renameSync, writeFileSync, accessSync, constants as constants$1, openSync, fsyncSync, closeSync, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
|
|
10
10
|
import { p as projectPath } from './projectPath-CSpqWdxp.mjs';
|
|
11
|
-
import { f as trimIdent, A as AGENT_GUIDANCE, t as teamScopePath, T as TeamConnectionPreflightError, e as createTeamsClient, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, g as TEAM_TOOL_NAMES, r as registerTeamsTools, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, h as REPORT_PROGRESS_TOOL_TITLE, i as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-
|
|
12
|
-
import { readFile, writeFile, rename, unlink, mkdir, symlink, rm, readlink, lstat, stat, open as open$1, appendFile } from 'node:fs/promises';
|
|
11
|
+
import { f as trimIdent, A as AGENT_GUIDANCE, t as teamScopePath, T as TeamConnectionPreflightError, e as createTeamsClient, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, g as TEAM_TOOL_NAMES, r as registerTeamsTools, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, h as REPORT_PROGRESS_TOOL_TITLE, i as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-Dvo3cZMW.mjs';
|
|
12
|
+
import { readFile, writeFile, rename, unlink, mkdir, access as access$1, symlink, rm, readlink, lstat, constants, stat, open as open$1, appendFile } from 'node:fs/promises';
|
|
13
13
|
import fs, { watch, access } from 'fs/promises';
|
|
14
14
|
import { useStdout, useInput, Box, Text, render } from 'ink';
|
|
15
15
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
@@ -456,17 +456,23 @@ function readClaudeSettings() {
|
|
|
456
456
|
}
|
|
457
457
|
function shouldIncludeCoAuthoredBy() {
|
|
458
458
|
const settings = readClaudeSettings();
|
|
459
|
-
if (!settings
|
|
459
|
+
if (!settings) {
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
if (settings.attribution?.commit === "") {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
if (settings.includeCoAuthoredBy === void 0) {
|
|
460
466
|
return true;
|
|
461
467
|
}
|
|
462
468
|
return settings.includeCoAuthoredBy;
|
|
463
469
|
}
|
|
464
470
|
|
|
465
471
|
const BASE_SYSTEM_PROMPT = (() => trimIdent(`
|
|
466
|
-
|
|
472
|
+
This chat is titled automatically from the user's first message. Call "mcp__happy__change_title" only when the topic has clearly moved on or the current title is too generic to find this chat later. Do not call it as a ritual at the start of a chat, and not for a quick question you can just answer.
|
|
467
473
|
`))() + "\n\n" + AGENT_GUIDANCE + "\n\n" + BUILTIN_TODO_DISCOVERY;
|
|
468
474
|
const CO_AUTHORED_CREDITS = (() => trimIdent(`
|
|
469
|
-
When
|
|
475
|
+
When you yourself create a git commit, instead of just giving co-credit to Claude, also give credit to Happy like so:
|
|
470
476
|
|
|
471
477
|
<main commit message>
|
|
472
478
|
|
|
@@ -474,6 +480,8 @@ const CO_AUTHORED_CREDITS = (() => trimIdent(`
|
|
|
474
480
|
via [Very Happy](https://github.com/Mereithhh/very-happy)
|
|
475
481
|
|
|
476
482
|
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
483
|
+
|
|
484
|
+
Do not add these lines to commit message text you only draft for the user.
|
|
477
485
|
`))();
|
|
478
486
|
const systemPrompt = (() => {
|
|
479
487
|
const includeCoAuthored = shouldIncludeCoAuthoredBy();
|
|
@@ -870,7 +878,7 @@ async function createSessionScanner(opts) {
|
|
|
870
878
|
let processedMessageKeys = /* @__PURE__ */ new Set();
|
|
871
879
|
let deadSessions = /* @__PURE__ */ new Set();
|
|
872
880
|
if (opts.sessionId) {
|
|
873
|
-
let messages = await readSessionLog(projectDir, opts.sessionId);
|
|
881
|
+
let messages = await readSessionLog$1(projectDir, opts.sessionId);
|
|
874
882
|
logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
|
|
875
883
|
for (let m of messages) {
|
|
876
884
|
processedMessageKeys.add(messageKey(m));
|
|
@@ -893,7 +901,7 @@ async function createSessionScanner(opts) {
|
|
|
893
901
|
}
|
|
894
902
|
}
|
|
895
903
|
for (let session of sessions) {
|
|
896
|
-
const sessionMessages = await readSessionLog(projectDir, session);
|
|
904
|
+
const sessionMessages = await readSessionLog$1(projectDir, session);
|
|
897
905
|
let skipped = 0;
|
|
898
906
|
let sent = 0;
|
|
899
907
|
for (let file of sessionMessages) {
|
|
@@ -970,7 +978,7 @@ async function createSessionScanner(opts) {
|
|
|
970
978
|
return;
|
|
971
979
|
}
|
|
972
980
|
if (options?.treatExistingAsProcessed) {
|
|
973
|
-
const existing = await readSessionLog(projectDir, sessionId);
|
|
981
|
+
const existing = await readSessionLog$1(projectDir, sessionId);
|
|
974
982
|
logger.debug(`[SESSION_SCANNER] Pre-marking ${existing.length} existing messages as processed for new session ${sessionId}`);
|
|
975
983
|
for (const m of existing) {
|
|
976
984
|
processedMessageKeys.add(messageKey(m));
|
|
@@ -998,7 +1006,7 @@ function messageKey(message) {
|
|
|
998
1006
|
throw Error();
|
|
999
1007
|
}
|
|
1000
1008
|
}
|
|
1001
|
-
async function readSessionLog(projectDir, sessionId) {
|
|
1009
|
+
async function readSessionLog$1(projectDir, sessionId) {
|
|
1002
1010
|
const expectedSessionFile = join(projectDir, `${sessionId}.jsonl`);
|
|
1003
1011
|
logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
|
|
1004
1012
|
let file;
|
|
@@ -5104,6 +5112,61 @@ function shareSafeEnvironmentInfo(env = process.env, runtime = process) {
|
|
|
5104
5112
|
};
|
|
5105
5113
|
}
|
|
5106
5114
|
|
|
5115
|
+
const NPM_SAFE_ARG = /^[A-Za-z0-9@=.,+/_-]+$/;
|
|
5116
|
+
function globalPrefixForPackage(packageDir) {
|
|
5117
|
+
if (!isAbsolute(packageDir)) return null;
|
|
5118
|
+
const dir = resolve(packageDir);
|
|
5119
|
+
const nodeModules = dirname(dir);
|
|
5120
|
+
const lib = dirname(nodeModules);
|
|
5121
|
+
const prefix = dirname(lib);
|
|
5122
|
+
if (basename(dir) !== "very-happy-cli" || basename(nodeModules) !== "node_modules" || basename(lib) !== "lib") return null;
|
|
5123
|
+
if (prefix === lib || resolve(prefix) === "/" || !NPM_SAFE_ARG.test(prefix)) return null;
|
|
5124
|
+
return prefix;
|
|
5125
|
+
}
|
|
5126
|
+
function packageDirForBin(realBin) {
|
|
5127
|
+
const packageDir = dirname(dirname(realBin));
|
|
5128
|
+
return basename(dirname(realBin)) === "bin" && basename(packageDir) === "very-happy-cli" ? packageDir : null;
|
|
5129
|
+
}
|
|
5130
|
+
function installLocationWarnings(facts, version) {
|
|
5131
|
+
const warnings = [];
|
|
5132
|
+
const running = resolve(facts.runningPackageDir);
|
|
5133
|
+
const packages = [...new Set(facts.onPath.map(packageDirForBin).filter((dir) => Boolean(dir)).map((dir) => resolve(dir)))];
|
|
5134
|
+
if (packages.length > 1) {
|
|
5135
|
+
warnings.push(`Found ${packages.length} very-happy installs on PATH (first one wins): ${packages.join(", ")}. Updating one does not update the others; remove the ones you do not use.`);
|
|
5136
|
+
}
|
|
5137
|
+
if (packages[0] && packages[0] !== running) {
|
|
5138
|
+
warnings.push(`The very-happy command runs ${packages[0]}, but this CLI runs from ${running}.`);
|
|
5139
|
+
}
|
|
5140
|
+
const prefix = globalPrefixForPackage(running);
|
|
5141
|
+
if (prefix && facts.npmGlobalPrefix && resolve(facts.npmGlobalPrefix) !== resolve(prefix)) {
|
|
5142
|
+
const target = version ? `very-happy-cli@${version}` : "very-happy-cli@latest";
|
|
5143
|
+
warnings.push(`npm install -g writes to ${facts.npmGlobalPrefix}, but this CLI runs from ${prefix}; a plain update will not take effect. Update with: npm install -g --prefix ${prefix} --allow-scripts=very-happy-cli,node-pty ${target} && very-happy daemon start`);
|
|
5144
|
+
}
|
|
5145
|
+
return warnings;
|
|
5146
|
+
}
|
|
5147
|
+
function gatherInstallLocationFacts(runningPackageDir, env = process.env) {
|
|
5148
|
+
if (process.platform === "win32") return null;
|
|
5149
|
+
const onPath = [];
|
|
5150
|
+
for (const dir of (env.PATH ?? "").split(delimiter)) {
|
|
5151
|
+
if (!dir) continue;
|
|
5152
|
+
try {
|
|
5153
|
+
const candidate = join(dir, "very-happy");
|
|
5154
|
+
if (!statSync(candidate).isFile()) continue;
|
|
5155
|
+
const real = realpathSync(candidate);
|
|
5156
|
+
if (!onPath.includes(real)) onPath.push(real);
|
|
5157
|
+
} catch {
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
let npmGlobalPrefix = null;
|
|
5161
|
+
try {
|
|
5162
|
+
const result = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8", timeout: 5e3, env });
|
|
5163
|
+
const out = result.status === 0 ? result.stdout.trim() : "";
|
|
5164
|
+
npmGlobalPrefix = out && isAbsolute(out) && !out.includes("\n") ? out : null;
|
|
5165
|
+
} catch {
|
|
5166
|
+
}
|
|
5167
|
+
return { runningPackageDir, onPath, npmGlobalPrefix };
|
|
5168
|
+
}
|
|
5169
|
+
|
|
5107
5170
|
function printCliUpdateStatus(state) {
|
|
5108
5171
|
const installed = configuration.currentCliVersion;
|
|
5109
5172
|
const running = state?.startedWithCliVersion ?? null;
|
|
@@ -5121,6 +5184,10 @@ function printCliUpdateStatus(state) {
|
|
|
5121
5184
|
console.log(chalk.yellow(" \u25B3 A newer CLI is available."));
|
|
5122
5185
|
}
|
|
5123
5186
|
if (summary.installCommand) console.log(` Update: ${summary.installCommand}`);
|
|
5187
|
+
const facts = gatherInstallLocationFacts(projectPath());
|
|
5188
|
+
for (const warning of facts ? installLocationWarnings(facts, state?.cliUpdate?.recommendedVersion ?? void 0) : []) {
|
|
5189
|
+
console.log(chalk.yellow(` \u26A0 ${warning}`));
|
|
5190
|
+
}
|
|
5124
5191
|
}
|
|
5125
5192
|
function getEnvironmentInfo() {
|
|
5126
5193
|
return shareSafeEnvironmentInfo();
|
|
@@ -5313,8 +5380,8 @@ ${typeLabels[type] || type}:`));
|
|
|
5313
5380
|
}
|
|
5314
5381
|
console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
|
|
5315
5382
|
try {
|
|
5316
|
-
const { resolveAgentHomesForCli } = await import('./agentHome-
|
|
5317
|
-
const { describeAgentHome } = await import('./types-
|
|
5383
|
+
const { resolveAgentHomesForCli } = await import('./agentHome-CeFDNzvq.mjs');
|
|
5384
|
+
const { describeAgentHome } = await import('./types-BN3eiipi.mjs').then(function (n) { return n.as; });
|
|
5318
5385
|
const homes = await resolveAgentHomesForCli();
|
|
5319
5386
|
console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
|
|
5320
5387
|
console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
|
|
@@ -6025,7 +6092,9 @@ async function sendUserMessage(sessionId, persisted, text, client, options = {})
|
|
|
6025
6092
|
},
|
|
6026
6093
|
meta: {
|
|
6027
6094
|
sentFrom: options.sentFrom ?? "cli",
|
|
6028
|
-
...options.model ? { model: options.model } : {}
|
|
6095
|
+
...options.model !== void 0 ? { model: options.model } : {},
|
|
6096
|
+
...options.permissionMode !== void 0 ? { permissionMode: options.permissionMode } : {},
|
|
6097
|
+
...options.effort !== void 0 ? { effort: options.effort } : {}
|
|
6029
6098
|
}
|
|
6030
6099
|
};
|
|
6031
6100
|
const encrypted = encodeBase64(encrypt(
|
|
@@ -6336,13 +6405,22 @@ function startDaemonControlServer({
|
|
|
6336
6405
|
permissionMode: z.string().optional(),
|
|
6337
6406
|
// B-069: spawn-origin tag ('assistant' = session_spawn). Recorded on
|
|
6338
6407
|
// the TrackedSession; old clients never send it.
|
|
6339
|
-
spawnedBy: z.string().optional()
|
|
6408
|
+
spawnedBy: z.string().optional(),
|
|
6409
|
+
// B-492: `very-happy spawn --fork` — same fields the web's fork flow
|
|
6410
|
+
// sends over the machine RPC. Old CLIs never send them.
|
|
6411
|
+
resumeClaudeSessionId: z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i).optional(),
|
|
6412
|
+
resumeCodexThreadId: z.string().min(1).max(200).optional(),
|
|
6413
|
+
parentSessionId: z.string().min(1).max(200).optional()
|
|
6340
6414
|
}),
|
|
6341
6415
|
response: {
|
|
6342
6416
|
200: z.object({
|
|
6343
6417
|
success: z.boolean(),
|
|
6344
6418
|
sessionId: z.string().optional(),
|
|
6345
|
-
approvedNewDirectoryCreation: z.boolean().optional()
|
|
6419
|
+
approvedNewDirectoryCreation: z.boolean().optional(),
|
|
6420
|
+
// B-492: true when resumeClaudeSessionId / resumeCodexThreadId was
|
|
6421
|
+
// honoured. A caller that asked for a fork and does not get this
|
|
6422
|
+
// back is talking to an older daemon that spawned a fresh session.
|
|
6423
|
+
resumed: z.boolean().optional()
|
|
6346
6424
|
}),
|
|
6347
6425
|
409: z.object({
|
|
6348
6426
|
success: z.boolean(),
|
|
@@ -6357,13 +6435,14 @@ function startDaemonControlServer({
|
|
|
6357
6435
|
}
|
|
6358
6436
|
}
|
|
6359
6437
|
}, async (request, reply) => {
|
|
6360
|
-
const { directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy } = request.body;
|
|
6438
|
+
const { directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId } = request.body;
|
|
6361
6439
|
if (!directory && variant !== "assistant") {
|
|
6362
6440
|
reply.code(500);
|
|
6363
6441
|
return { success: false, error: "directory is required" };
|
|
6364
6442
|
}
|
|
6365
6443
|
logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}, agent=${agent || "default"}, variant=${variant || "none"}, forceNew=${forceNew === true}, spawnedBy=${spawnedBy || "unset"}`);
|
|
6366
|
-
const result = await spawnSession({ directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy });
|
|
6444
|
+
const result = await spawnSession({ directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId });
|
|
6445
|
+
const resumed = Boolean(resumeClaudeSessionId || resumeCodexThreadId);
|
|
6367
6446
|
switch (result.type) {
|
|
6368
6447
|
case "success":
|
|
6369
6448
|
if (!result.sessionId) {
|
|
@@ -6376,7 +6455,8 @@ function startDaemonControlServer({
|
|
|
6376
6455
|
return {
|
|
6377
6456
|
success: true,
|
|
6378
6457
|
sessionId: result.sessionId,
|
|
6379
|
-
approvedNewDirectoryCreation: true
|
|
6458
|
+
approvedNewDirectoryCreation: true,
|
|
6459
|
+
...resumed ? { resumed: true } : {}
|
|
6380
6460
|
};
|
|
6381
6461
|
case "requestToApproveDirectoryCreation":
|
|
6382
6462
|
reply.code(409);
|
|
@@ -7617,8 +7697,8 @@ function serialTask(task) {
|
|
|
7617
7697
|
};
|
|
7618
7698
|
}
|
|
7619
7699
|
|
|
7620
|
-
function autoUpdateInstallArgs(version) {
|
|
7621
|
-
return ["i", "-g", "--allow-scripts=very-happy-cli,node-pty", `very-happy-cli@${version}`];
|
|
7700
|
+
function autoUpdateInstallArgs(version, prefix) {
|
|
7701
|
+
return ["i", "-g", ...prefix ? [`--prefix=${prefix}`] : [], "--allow-scripts=very-happy-cli,node-pty", `very-happy-cli@${version}`];
|
|
7622
7702
|
}
|
|
7623
7703
|
|
|
7624
7704
|
const processDefaults = {
|
|
@@ -7678,7 +7758,34 @@ const runNpmProcess = (args, deps = processDefaults) => new Promise((resolveResu
|
|
|
7678
7758
|
child.on("close", (code) => done({ code: timedOut ? null : code, stdout, stderr, terminationConfirmed: !timedOut || treeKilled }));
|
|
7679
7759
|
});
|
|
7680
7760
|
const runNpm = (args) => runNpmProcess(args);
|
|
7681
|
-
const defaults = {
|
|
7761
|
+
const defaults = {
|
|
7762
|
+
npm: runNpm,
|
|
7763
|
+
fs: { lstat, readlink, unlink, rm, readFile, symlink, access: access$1 },
|
|
7764
|
+
platform: process.platform,
|
|
7765
|
+
runningPackageDir: projectPath()
|
|
7766
|
+
};
|
|
7767
|
+
async function runningVersion(deps) {
|
|
7768
|
+
if (!deps.runningPackageDir) return null;
|
|
7769
|
+
try {
|
|
7770
|
+
const manifest = JSON.parse(await deps.fs.readFile(join(deps.runningPackageDir, "package.json"), "utf8"));
|
|
7771
|
+
return manifest?.name === "very-happy-cli" && typeof manifest.version === "string" ? manifest.version : null;
|
|
7772
|
+
} catch {
|
|
7773
|
+
return null;
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7776
|
+
async function verifyRunningCopy(code, version, deps) {
|
|
7777
|
+
if (code !== 0 || !deps.runningPackageDir) return code;
|
|
7778
|
+
return await runningVersion(deps) === version ? 0 : { failed: "installed_elsewhere" };
|
|
7779
|
+
}
|
|
7780
|
+
async function writable(path, deps) {
|
|
7781
|
+
if (!deps.fs.access) return true;
|
|
7782
|
+
try {
|
|
7783
|
+
await deps.fs.access(path, constants.W_OK);
|
|
7784
|
+
return true;
|
|
7785
|
+
} catch (error) {
|
|
7786
|
+
return error.code === "ENOENT";
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7682
7789
|
function ownedCliLink(link, target, packageDir) {
|
|
7683
7790
|
const resolved = resolve(dirname(link), target);
|
|
7684
7791
|
return resolved.startsWith(join(packageDir, "bin") + sep);
|
|
@@ -7686,14 +7793,27 @@ function ownedCliLink(link, target, packageDir) {
|
|
|
7686
7793
|
async function installCliSafely(version, deps = defaults) {
|
|
7687
7794
|
const parsed = parseExactVersion(version);
|
|
7688
7795
|
if (!parsed) throw new Error("Invalid exact CLI version");
|
|
7689
|
-
const args = autoUpdateInstallArgs(parsed.exact);
|
|
7690
7796
|
if (deps.platform === "win32") {
|
|
7691
|
-
const result2 = await deps.npm(
|
|
7692
|
-
return result2.terminationConfirmed === false ? "blocked" : result2.code;
|
|
7797
|
+
const result2 = await deps.npm(autoUpdateInstallArgs(parsed.exact));
|
|
7798
|
+
return result2.terminationConfirmed === false ? "blocked" : verifyRunningCopy(result2.code, parsed.exact, deps);
|
|
7693
7799
|
}
|
|
7694
|
-
|
|
7800
|
+
let pinnedPrefix;
|
|
7801
|
+
if (deps.runningPackageDir) {
|
|
7802
|
+
const running = globalPrefixForPackage(deps.runningPackageDir);
|
|
7803
|
+
const runningStat = running ? await deps.fs.lstat(deps.runningPackageDir).catch(() => null) : null;
|
|
7804
|
+
if (!running || !runningStat?.isDirectory() || runningStat.isSymbolicLink() || !await runningVersion(deps)) {
|
|
7805
|
+
return { manual: "install_location_unverified" };
|
|
7806
|
+
}
|
|
7807
|
+
for (const path of [dirname(deps.runningPackageDir), deps.runningPackageDir, join(running, "bin")]) {
|
|
7808
|
+
if (!await writable(path, deps)) return { manual: "install_location_not_writable" };
|
|
7809
|
+
}
|
|
7810
|
+
pinnedPrefix = running;
|
|
7811
|
+
}
|
|
7812
|
+
const prefixArgs = pinnedPrefix ? [`--prefix=${pinnedPrefix}`] : [];
|
|
7813
|
+
const args = autoUpdateInstallArgs(parsed.exact, pinnedPrefix);
|
|
7814
|
+
const rootResult = await deps.npm(["root", "-g", ...prefixArgs]);
|
|
7695
7815
|
if (rootResult.terminationConfirmed === false) return "blocked";
|
|
7696
|
-
const prefixResult = await deps.npm(["prefix", "-g"]);
|
|
7816
|
+
const prefixResult = await deps.npm(["prefix", "-g", ...prefixArgs]);
|
|
7697
7817
|
if (prefixResult.terminationConfirmed === false) return "blocked";
|
|
7698
7818
|
const root = rootResult.stdout.trim();
|
|
7699
7819
|
const prefix = prefixResult.stdout.trim();
|
|
@@ -7702,6 +7822,9 @@ async function installCliSafely(version, deps = defaults) {
|
|
|
7702
7822
|
}
|
|
7703
7823
|
if (resolve(root) !== resolve(prefix, "lib/node_modules")) throw new Error("Unexpected npm root/prefix relationship");
|
|
7704
7824
|
const packageDir = join(root, "very-happy-cli");
|
|
7825
|
+
if (deps.runningPackageDir && resolve(packageDir) !== resolve(deps.runningPackageDir)) {
|
|
7826
|
+
return { manual: "install_location_unverified" };
|
|
7827
|
+
}
|
|
7705
7828
|
const packageStat = await deps.fs.lstat(packageDir).catch((error) => {
|
|
7706
7829
|
if (error.code === "ENOENT") return null;
|
|
7707
7830
|
throw error;
|
|
@@ -7736,7 +7859,7 @@ async function installCliSafely(version, deps = defaults) {
|
|
|
7736
7859
|
return "blocked";
|
|
7737
7860
|
}
|
|
7738
7861
|
result = first.code;
|
|
7739
|
-
if (first.code === 0 || first.code === null) return first.code;
|
|
7862
|
+
if (first.code === 0 || first.code === null) return await verifyRunningCopy(first.code, parsed.exact, deps);
|
|
7740
7863
|
if (!/\b(?:EEXIST|ENOTEMPTY)\b/.test(first.stderr ?? "")) return first.code;
|
|
7741
7864
|
const failedStat = await deps.fs.lstat(packageDir).catch((error) => {
|
|
7742
7865
|
if (error.code === "ENOENT") return null;
|
|
@@ -7752,7 +7875,7 @@ async function installCliSafely(version, deps = defaults) {
|
|
|
7752
7875
|
safeToRestore = false;
|
|
7753
7876
|
return "blocked";
|
|
7754
7877
|
}
|
|
7755
|
-
return result;
|
|
7878
|
+
return await verifyRunningCopy(result, parsed.exact, deps);
|
|
7756
7879
|
} finally {
|
|
7757
7880
|
if (result !== 0 && safeToRestore) {
|
|
7758
7881
|
for (const { link, target } of ownLinks) {
|
|
@@ -7788,7 +7911,7 @@ function createUpdateController(deps) {
|
|
|
7788
7911
|
async function evaluate() {
|
|
7789
7912
|
if (!policy || running) return;
|
|
7790
7913
|
const target = manualVersion ?? policy.autoUpdateVersion ?? null;
|
|
7791
|
-
if (outcome?.version === target && ["failed", "installed"].includes(outcome.state)) {
|
|
7914
|
+
if (outcome?.version === target && ["failed", "installed", "manual_required"].includes(outcome.state)) {
|
|
7792
7915
|
const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail, manualVersion]);
|
|
7793
7916
|
if (fingerprint !== lastPublished) {
|
|
7794
7917
|
lastPublished = fingerprint;
|
|
@@ -7826,6 +7949,11 @@ function createUpdateController(deps) {
|
|
|
7826
7949
|
publish("installing", target);
|
|
7827
7950
|
try {
|
|
7828
7951
|
const code = await deps.install(target);
|
|
7952
|
+
if (code && typeof code === "object") {
|
|
7953
|
+
if ("manual" in code) publish("manual_required", target, code.manual);
|
|
7954
|
+
else publish("failed", target, code.failed);
|
|
7955
|
+
return;
|
|
7956
|
+
}
|
|
7829
7957
|
blocked = code === "blocked";
|
|
7830
7958
|
publish(
|
|
7831
7959
|
blocked ? "manual_required" : code === 0 ? "installed" : "failed",
|
|
@@ -8219,7 +8347,7 @@ class ClaudeAuthService {
|
|
|
8219
8347
|
const bin = join(dir, "security");
|
|
8220
8348
|
try {
|
|
8221
8349
|
if (!statSync(bin).isFile()) return null;
|
|
8222
|
-
accessSync(bin, constants.X_OK);
|
|
8350
|
+
accessSync(bin, constants$1.X_OK);
|
|
8223
8351
|
return dir;
|
|
8224
8352
|
} catch {
|
|
8225
8353
|
return null;
|
|
@@ -9260,6 +9388,8 @@ ${goal}`;
|
|
|
9260
9388
|
|
|
9261
9389
|
const MAX_LINE_CHARS = 500;
|
|
9262
9390
|
const MAX_ARGS_CHARS = 160;
|
|
9391
|
+
const DEFAULT_LIMITS = { line: MAX_LINE_CHARS, args: MAX_ARGS_CHARS };
|
|
9392
|
+
const NO_LIMITS = { line: Number.POSITIVE_INFINITY, args: Number.POSITIVE_INFINITY };
|
|
9263
9393
|
function truncateText(text, max = MAX_LINE_CHARS) {
|
|
9264
9394
|
const chars = Array.from(text);
|
|
9265
9395
|
if (chars.length <= max) return text;
|
|
@@ -9275,12 +9405,12 @@ function safeJson(value, max) {
|
|
|
9275
9405
|
return "[unserializable]";
|
|
9276
9406
|
}
|
|
9277
9407
|
}
|
|
9278
|
-
function formatMessageBody(body) {
|
|
9408
|
+
function formatMessageBody(body, limits = DEFAULT_LIMITS) {
|
|
9279
9409
|
if (!body || typeof body !== "object") return null;
|
|
9280
9410
|
const b = body;
|
|
9281
9411
|
if (b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string") {
|
|
9282
9412
|
const text = squash(b.content.text);
|
|
9283
|
-
return text.length > 0 ? `user: ${truncateText(text)}` : null;
|
|
9413
|
+
return text.length > 0 ? `user: ${truncateText(text, limits.line)}` : null;
|
|
9284
9414
|
}
|
|
9285
9415
|
if (b.role === "session" && b.content && typeof b.content === "object") {
|
|
9286
9416
|
const env = b.content;
|
|
@@ -9292,18 +9422,18 @@ function formatMessageBody(body) {
|
|
|
9292
9422
|
const text = squash(String(ev.text ?? ""));
|
|
9293
9423
|
if (text.length === 0) return null;
|
|
9294
9424
|
const role = env.role === "user" ? "user" : "assistant";
|
|
9295
|
-
return `${role}: ${truncateText(text)}`;
|
|
9425
|
+
return `${role}: ${truncateText(text, limits.line)}`;
|
|
9296
9426
|
}
|
|
9297
9427
|
case "tool-call-start": {
|
|
9298
9428
|
const name = String(ev.name ?? "tool");
|
|
9299
9429
|
const title = squash(String(ev.title ?? ""));
|
|
9300
|
-
const args = ev.args ? safeJson(ev.args,
|
|
9430
|
+
const args = ev.args ? safeJson(ev.args, limits.args) : "";
|
|
9301
9431
|
const detail = title || args;
|
|
9302
|
-
return `[tool] ${name}${detail ? `: ${truncateText(detail,
|
|
9432
|
+
return `[tool] ${name}${detail ? `: ${truncateText(detail, limits.args)}` : ""}`;
|
|
9303
9433
|
}
|
|
9304
9434
|
case "service": {
|
|
9305
9435
|
const text = squash(String(ev.text ?? ""));
|
|
9306
|
-
return text.length > 0 ? `[service] ${truncateText(text,
|
|
9436
|
+
return text.length > 0 ? `[service] ${truncateText(text, limits.args)}` : null;
|
|
9307
9437
|
}
|
|
9308
9438
|
case "turn-end": {
|
|
9309
9439
|
if (ev.status && ev.status !== "completed") return `[turn ${ev.status}]`;
|
|
@@ -9318,12 +9448,12 @@ function formatMessageBody(body) {
|
|
|
9318
9448
|
switch (d.type) {
|
|
9319
9449
|
case "message": {
|
|
9320
9450
|
const text = squash(String(d.message ?? ""));
|
|
9321
|
-
return text.length > 0 ? `assistant: ${truncateText(text)}` : null;
|
|
9451
|
+
return text.length > 0 ? `assistant: ${truncateText(text, limits.line)}` : null;
|
|
9322
9452
|
}
|
|
9323
9453
|
case "tool-call":
|
|
9324
|
-
return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input,
|
|
9454
|
+
return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input, limits.args)}`;
|
|
9325
9455
|
case "permission-request":
|
|
9326
|
-
return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")),
|
|
9456
|
+
return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")), limits.args)}`;
|
|
9327
9457
|
default:
|
|
9328
9458
|
return null;
|
|
9329
9459
|
}
|
|
@@ -9334,22 +9464,111 @@ function formatMessageBody(body) {
|
|
|
9334
9464
|
const texts = msg.content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => squash(c.text)).filter((t) => t.length > 0);
|
|
9335
9465
|
if (texts.length > 0) {
|
|
9336
9466
|
const role = msg.role === "user" ? "user" : "assistant";
|
|
9337
|
-
return `${role}: ${truncateText(texts.join(" "))}`;
|
|
9467
|
+
return `${role}: ${truncateText(texts.join(" "), limits.line)}`;
|
|
9338
9468
|
}
|
|
9339
9469
|
}
|
|
9340
9470
|
return null;
|
|
9341
9471
|
}
|
|
9342
9472
|
return null;
|
|
9343
9473
|
}
|
|
9344
|
-
function formatTranscript(bodies) {
|
|
9474
|
+
function formatTranscript(bodies, options = {}) {
|
|
9475
|
+
const limits = options.full ? NO_LIMITS : DEFAULT_LIMITS;
|
|
9345
9476
|
const lines = [];
|
|
9346
9477
|
for (const body of bodies) {
|
|
9347
|
-
const line = formatMessageBody(body);
|
|
9478
|
+
const line = formatMessageBody(body, limits);
|
|
9348
9479
|
if (line !== null) lines.push(line);
|
|
9349
9480
|
}
|
|
9350
9481
|
return lines.join("\n");
|
|
9351
9482
|
}
|
|
9352
9483
|
|
|
9484
|
+
function asRecord(value) {
|
|
9485
|
+
return value && typeof value === "object" ? value : null;
|
|
9486
|
+
}
|
|
9487
|
+
function sessionEnvelope(body) {
|
|
9488
|
+
const b = asRecord(body);
|
|
9489
|
+
if (!b || b.role !== "session") return null;
|
|
9490
|
+
const env = asRecord(b.content);
|
|
9491
|
+
if (!env) return null;
|
|
9492
|
+
const ev = asRecord(env.ev);
|
|
9493
|
+
return ev && typeof ev.t === "string" ? { role: env.role, ev } : null;
|
|
9494
|
+
}
|
|
9495
|
+
function isUserPrompt(body) {
|
|
9496
|
+
const b = asRecord(body);
|
|
9497
|
+
return !!b && b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string";
|
|
9498
|
+
}
|
|
9499
|
+
function agentPiece(body) {
|
|
9500
|
+
const env = sessionEnvelope(body);
|
|
9501
|
+
if (env && env.role !== "user") {
|
|
9502
|
+
if (env.ev.t === "text" && !env.ev.thinking) {
|
|
9503
|
+
const text = String(env.ev.text ?? "");
|
|
9504
|
+
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9505
|
+
}
|
|
9506
|
+
if (env.ev.t === "tool-call-start") return { kind: "tool" };
|
|
9507
|
+
return null;
|
|
9508
|
+
}
|
|
9509
|
+
const b = asRecord(body);
|
|
9510
|
+
if (b?.role === "agent" && b.content?.type === "acp") {
|
|
9511
|
+
const d = asRecord(b.content.data);
|
|
9512
|
+
if (d?.type === "message") {
|
|
9513
|
+
const text = String(d.message ?? "");
|
|
9514
|
+
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9515
|
+
}
|
|
9516
|
+
if (d?.type === "tool-call") return { kind: "tool" };
|
|
9517
|
+
}
|
|
9518
|
+
return null;
|
|
9519
|
+
}
|
|
9520
|
+
function analyzeLatestTurn(entries) {
|
|
9521
|
+
const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
|
|
9522
|
+
let userIndex = -1;
|
|
9523
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
9524
|
+
if (isUserPrompt(entries[i].body)) {
|
|
9525
|
+
userIndex = i;
|
|
9526
|
+
break;
|
|
9527
|
+
}
|
|
9528
|
+
}
|
|
9529
|
+
let ended = false;
|
|
9530
|
+
let status = null;
|
|
9531
|
+
let error = null;
|
|
9532
|
+
let texts = [];
|
|
9533
|
+
let lastMarker = null;
|
|
9534
|
+
let startedAfterPrompt = false;
|
|
9535
|
+
const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
|
|
9536
|
+
for (let i = userIndex + 1; i < entries.length; i++) {
|
|
9537
|
+
const body = entries[i].body;
|
|
9538
|
+
const env = sessionEnvelope(body);
|
|
9539
|
+
if (env?.ev?.t === "turn-start") {
|
|
9540
|
+
lastMarker = "start";
|
|
9541
|
+
startedAfterPrompt = true;
|
|
9542
|
+
ended = false;
|
|
9543
|
+
status = null;
|
|
9544
|
+
error = null;
|
|
9545
|
+
texts = [];
|
|
9546
|
+
continue;
|
|
9547
|
+
}
|
|
9548
|
+
if (env?.ev?.t === "turn-end") {
|
|
9549
|
+
lastMarker = "end";
|
|
9550
|
+
if (userIndex >= 0 && emitsStarts && !startedAfterPrompt) continue;
|
|
9551
|
+
ended = true;
|
|
9552
|
+
status = typeof env.ev.status === "string" ? env.ev.status : null;
|
|
9553
|
+
error = typeof env.ev.error === "string" ? env.ev.error : null;
|
|
9554
|
+
continue;
|
|
9555
|
+
}
|
|
9556
|
+
const piece = agentPiece(body);
|
|
9557
|
+
if (!piece) continue;
|
|
9558
|
+
if (piece.kind === "tool") texts = [];
|
|
9559
|
+
else texts.push(piece.text);
|
|
9560
|
+
}
|
|
9561
|
+
if (userIndex < 0) ended = lastMarker === "end";
|
|
9562
|
+
return {
|
|
9563
|
+
userSeq: userIndex >= 0 ? entries[userIndex].seq : null,
|
|
9564
|
+
ended,
|
|
9565
|
+
status: ended ? status : null,
|
|
9566
|
+
error: ended ? error : null,
|
|
9567
|
+
answer: texts.join("\n\n").trim(),
|
|
9568
|
+
lastSeq
|
|
9569
|
+
};
|
|
9570
|
+
}
|
|
9571
|
+
|
|
9353
9572
|
const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
|
|
9354
9573
|
async function openUserScopedSocket(token, options = {}) {
|
|
9355
9574
|
const socket = io(configuration.serverUrl, {
|
|
@@ -9695,19 +9914,21 @@ async function bearerToken() {
|
|
|
9695
9914
|
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9696
9915
|
return credentials.token;
|
|
9697
9916
|
}
|
|
9698
|
-
|
|
9917
|
+
function requirePersisted(sessionId) {
|
|
9699
9918
|
const persisted = readPersistedSessions()[sessionId];
|
|
9700
9919
|
if (!persisted) {
|
|
9701
9920
|
throw new Error(
|
|
9702
9921
|
`No local key for session ${sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`
|
|
9703
9922
|
);
|
|
9704
9923
|
}
|
|
9705
|
-
|
|
9924
|
+
return persisted;
|
|
9925
|
+
}
|
|
9926
|
+
async function readSessionLog(sessionId, persisted, limit) {
|
|
9706
9927
|
const token = await bearerToken();
|
|
9707
9928
|
const response = await axios.get(
|
|
9708
9929
|
`${configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
9709
9930
|
{
|
|
9710
|
-
params: { before_seq: 2147483647, limit
|
|
9931
|
+
params: { before_seq: 2147483647, limit },
|
|
9711
9932
|
headers: {
|
|
9712
9933
|
"Authorization": `Bearer ${token}`,
|
|
9713
9934
|
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
@@ -9718,21 +9939,62 @@ async function readSessionTranscript(sessionId, limit) {
|
|
|
9718
9939
|
const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
|
|
9719
9940
|
messages.reverse();
|
|
9720
9941
|
const key = decodeBase64(persisted.encryptionKey);
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
if (message.content?.t !== "encrypted") return null;
|
|
9942
|
+
return messages.map((message) => {
|
|
9943
|
+
if (message.content?.t !== "encrypted") return { seq: message.seq, body: null };
|
|
9724
9944
|
try {
|
|
9725
|
-
return decrypt(key, persisted.encryptionVariant, decodeBase64(message.content.c));
|
|
9945
|
+
return { seq: message.seq, body: decrypt(key, persisted.encryptionVariant, decodeBase64(message.content.c)) };
|
|
9726
9946
|
} catch {
|
|
9727
|
-
return null;
|
|
9947
|
+
return { seq: message.seq, body: null };
|
|
9728
9948
|
}
|
|
9729
9949
|
});
|
|
9950
|
+
}
|
|
9951
|
+
const TURN_WINDOW = 500;
|
|
9952
|
+
async function readSessionTranscript(sessionId, limit, options = {}) {
|
|
9953
|
+
const persisted = requirePersisted(sessionId);
|
|
9954
|
+
const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
|
|
9955
|
+
const entries = await readSessionLog(sessionId, persisted, Math.max(bounded, TURN_WINDOW));
|
|
9956
|
+
const shown = entries.slice(-bounded);
|
|
9957
|
+
const live = await listDaemonSessions();
|
|
9730
9958
|
return {
|
|
9731
9959
|
summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
|
|
9732
|
-
messageCount:
|
|
9733
|
-
transcript: formatTranscript(
|
|
9960
|
+
messageCount: shown.length,
|
|
9961
|
+
transcript: formatTranscript(shown.map((entry) => entry.body), { full: options.full }),
|
|
9962
|
+
turn: analyzeLatestTurn(entries)
|
|
9734
9963
|
};
|
|
9735
9964
|
}
|
|
9965
|
+
async function readSessionMetadata(sessionId, persisted) {
|
|
9966
|
+
const token = await bearerToken();
|
|
9967
|
+
const response = await axios.get(`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
9968
|
+
headers: {
|
|
9969
|
+
"Authorization": `Bearer ${token}`,
|
|
9970
|
+
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9971
|
+
},
|
|
9972
|
+
timeout: 15e3
|
|
9973
|
+
});
|
|
9974
|
+
const ciphertext = response.data?.session?.metadata;
|
|
9975
|
+
if (typeof ciphertext !== "string" || ciphertext.length === 0) return null;
|
|
9976
|
+
return decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(ciphertext));
|
|
9977
|
+
}
|
|
9978
|
+
class TurnWaitTimeoutError extends Error {
|
|
9979
|
+
constructor(sessionId, timeoutMs, turn) {
|
|
9980
|
+
super(`Session ${sessionId}: the latest turn did not end within ${Math.round(timeoutMs / 1e3)}s`);
|
|
9981
|
+
this.sessionId = sessionId;
|
|
9982
|
+
this.timeoutMs = timeoutMs;
|
|
9983
|
+
this.turn = turn;
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
async function waitForTurnEnd(sessionId, options) {
|
|
9987
|
+
const persisted = requirePersisted(sessionId);
|
|
9988
|
+
const pollMs = options.pollMs ?? 3e3;
|
|
9989
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
9990
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
9991
|
+
while (true) {
|
|
9992
|
+
const turn = analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW));
|
|
9993
|
+
if (turn.ended) return turn;
|
|
9994
|
+
if (Date.now() + pollMs > deadline) throw new TurnWaitTimeoutError(sessionId, options.timeoutMs, turn);
|
|
9995
|
+
await sleep(pollMs);
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
9736
9998
|
async function stopSession(sessionId) {
|
|
9737
9999
|
return await stopDaemonSession(sessionId);
|
|
9738
10000
|
}
|
|
@@ -11433,7 +11695,7 @@ async function retainIntent(path, input) {
|
|
|
11433
11695
|
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
11434
11696
|
let file;
|
|
11435
11697
|
try {
|
|
11436
|
-
file = await open$1(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
|
|
11698
|
+
file = await open$1(path, constants$1.O_WRONLY | constants$1.O_CREAT | constants$1.O_EXCL, 384);
|
|
11437
11699
|
} catch (error) {
|
|
11438
11700
|
if (error.code !== "EEXIST") throw new Error("Cannot retain team connection request");
|
|
11439
11701
|
}
|
|
@@ -11446,7 +11708,7 @@ async function retainIntent(path, input) {
|
|
|
11446
11708
|
}
|
|
11447
11709
|
return { fresh: true };
|
|
11448
11710
|
}
|
|
11449
|
-
const existing = await open$1(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
11711
|
+
const existing = await open$1(path, constants$1.O_RDONLY | (constants$1.O_NOFOLLOW ?? 0));
|
|
11450
11712
|
try {
|
|
11451
11713
|
const stat = await existing.stat();
|
|
11452
11714
|
if (!stat.isFile() || process.platform !== "win32" && (stat.mode & 63) !== 0 || typeof process.getuid === "function" && stat.uid !== process.getuid()) throw new Error("Unsafe team connection receipt");
|
|
@@ -12470,7 +12732,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
12470
12732
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
12471
12733
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
12472
12734
|
const fork = deps.run ?? (async (input) => {
|
|
12473
|
-
const { query } = await import('./index-
|
|
12735
|
+
const { query } = await import('./index-DYQgcgL2.mjs');
|
|
12474
12736
|
return runSideQuestion(query, input);
|
|
12475
12737
|
});
|
|
12476
12738
|
const run = async (input, live) => {
|
|
@@ -17758,7 +18020,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17758
18020
|
return;
|
|
17759
18021
|
} else if (subcommand === "install-terminal-hooks") {
|
|
17760
18022
|
try {
|
|
17761
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-
|
|
18023
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-Ch_yTtEA.mjs');
|
|
17762
18024
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
17763
18025
|
if (command.action === "help") {
|
|
17764
18026
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -17775,7 +18037,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17775
18037
|
}
|
|
17776
18038
|
} else if (subcommand === "agent-home") {
|
|
17777
18039
|
try {
|
|
17778
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-
|
|
18040
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-CeFDNzvq.mjs');
|
|
17779
18041
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
17780
18042
|
process.exit(0);
|
|
17781
18043
|
} catch (error) {
|
|
@@ -17784,7 +18046,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17784
18046
|
}
|
|
17785
18047
|
} else if (subcommand === "spawn") {
|
|
17786
18048
|
try {
|
|
17787
|
-
const { handleSpawnCommand } = await import('./spawn-
|
|
18049
|
+
const { handleSpawnCommand } = await import('./spawn-DxHIIqhi.mjs');
|
|
17788
18050
|
await handleSpawnCommand(args.slice(1));
|
|
17789
18051
|
} catch (error) {
|
|
17790
18052
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -17796,7 +18058,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17796
18058
|
return;
|
|
17797
18059
|
} else if (subcommand === "sessions") {
|
|
17798
18060
|
try {
|
|
17799
|
-
const { handleSessionsCommand } = await import('./sessions-
|
|
18061
|
+
const { handleSessionsCommand } = await import('./sessions-DlU3foY_.mjs');
|
|
17800
18062
|
await handleSessionsCommand(args.slice(1));
|
|
17801
18063
|
} catch (error) {
|
|
17802
18064
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -17808,7 +18070,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17808
18070
|
return;
|
|
17809
18071
|
} else if (subcommand === "send") {
|
|
17810
18072
|
try {
|
|
17811
|
-
const { handleSendCommand } = await import('./send-
|
|
18073
|
+
const { handleSendCommand } = await import('./send-o6WM-1G8.mjs');
|
|
17812
18074
|
await handleSendCommand(args.slice(1));
|
|
17813
18075
|
} catch (error) {
|
|
17814
18076
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -17914,9 +18176,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17914
18176
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
17915
18177
|
const projectId = args[3];
|
|
17916
18178
|
try {
|
|
17917
|
-
const { saveGoogleCloudProjectToConfig } = await import('./config-
|
|
17918
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-
|
|
17919
|
-
const { ApiClient: ApiClient2 } = await import('./types-
|
|
18179
|
+
const { saveGoogleCloudProjectToConfig } = await import('./config-C-A1XF62.mjs');
|
|
18180
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-DS-bnYYp.mjs').then(function (n) { return n.Y; });
|
|
18181
|
+
const { ApiClient: ApiClient2 } = await import('./types-BN3eiipi.mjs').then(function (n) { return n.at; });
|
|
17920
18182
|
let userEmail = void 0;
|
|
17921
18183
|
try {
|
|
17922
18184
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -17947,7 +18209,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17947
18209
|
}
|
|
17948
18210
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
17949
18211
|
try {
|
|
17950
|
-
const { readGeminiLocalConfig } = await import('./config-
|
|
18212
|
+
const { readGeminiLocalConfig } = await import('./config-C-A1XF62.mjs');
|
|
17951
18213
|
const config = readGeminiLocalConfig();
|
|
17952
18214
|
if (config.googleCloudProject) {
|
|
17953
18215
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -17987,7 +18249,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
17987
18249
|
process.exit(0);
|
|
17988
18250
|
}
|
|
17989
18251
|
try {
|
|
17990
|
-
const { runGemini } = await import('./runGemini-
|
|
18252
|
+
const { runGemini } = await import('./runGemini-DvJwQVGp.mjs');
|
|
17991
18253
|
let startedBy = void 0;
|
|
17992
18254
|
for (let i = 1; i < args.length; i++) {
|
|
17993
18255
|
if (args[i] === "--started-by") {
|
|
@@ -18010,7 +18272,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18010
18272
|
} else if (subcommand === "pi") {
|
|
18011
18273
|
if (args[1] === "--terminal") {
|
|
18012
18274
|
try {
|
|
18013
|
-
const { runPiTerminal } = await import('./piTerminal-
|
|
18275
|
+
const { runPiTerminal } = await import('./piTerminal-M677UP7y.mjs');
|
|
18014
18276
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
18015
18277
|
} catch (error) {
|
|
18016
18278
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18027,7 +18289,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18027
18289
|
}
|
|
18028
18290
|
};
|
|
18029
18291
|
try {
|
|
18030
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
18292
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-DEp6CmmS.mjs');
|
|
18031
18293
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
18032
18294
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
18033
18295
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -18055,7 +18317,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18055
18317
|
return;
|
|
18056
18318
|
} else if (subcommand === "acp") {
|
|
18057
18319
|
try {
|
|
18058
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
18320
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-DEp6CmmS.mjs');
|
|
18059
18321
|
let startedBy = void 0;
|
|
18060
18322
|
let verbose = false;
|
|
18061
18323
|
const acpArgs = [];
|
|
@@ -18095,7 +18357,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18095
18357
|
return;
|
|
18096
18358
|
} else if (subcommand === "openclaw") {
|
|
18097
18359
|
try {
|
|
18098
|
-
const { runOpenClaw } = await import('./runOpenClaw-
|
|
18360
|
+
const { runOpenClaw } = await import('./runOpenClaw-BG82bF18.mjs');
|
|
18099
18361
|
let startedBy = void 0;
|
|
18100
18362
|
let verbose = false;
|
|
18101
18363
|
let gatewayUrl;
|
|
@@ -18146,7 +18408,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18146
18408
|
return;
|
|
18147
18409
|
} else if (subcommand === "todo") {
|
|
18148
18410
|
try {
|
|
18149
|
-
const { handleTodoCommand } = await import('./todo-
|
|
18411
|
+
const { handleTodoCommand } = await import('./todo-BhJbG5eq.mjs');
|
|
18150
18412
|
await handleTodoCommand(args.slice(1));
|
|
18151
18413
|
} catch (error) {
|
|
18152
18414
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -18155,7 +18417,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18155
18417
|
return;
|
|
18156
18418
|
} else if (subcommand === "teams") {
|
|
18157
18419
|
try {
|
|
18158
|
-
const { handleTeamsCommand } = await import('./teams-
|
|
18420
|
+
const { handleTeamsCommand } = await import('./teams-DoAA_CcN.mjs');
|
|
18159
18421
|
await handleTeamsCommand(args.slice(1));
|
|
18160
18422
|
} catch (error) {
|
|
18161
18423
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -18164,7 +18426,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18164
18426
|
return;
|
|
18165
18427
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
18166
18428
|
try {
|
|
18167
|
-
const { handleMcpCommand } = await import('./mcp-
|
|
18429
|
+
const { handleMcpCommand } = await import('./mcp-zu8n8TkR.mjs');
|
|
18168
18430
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
18169
18431
|
} catch (error) {
|
|
18170
18432
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -18532,4 +18794,4 @@ ${chalk.bold("Examples:")}
|
|
|
18532
18794
|
}
|
|
18533
18795
|
}
|
|
18534
18796
|
|
|
18535
|
-
export { ALLOWED_SPAWN_PERMISSION_MODES as A,
|
|
18797
|
+
export { ALLOWED_SPAWN_PERMISSION_MODES as A, TitleGenerator as B, registerKillSessionHandler as C, DEFAULT_RECENT_LIMIT as D, CLAUDE_ATTACHMENT_KINDS as E, stageClaudeAttachments as F, GEMINI_MODEL_ENV as G, appendStagedAttachmentsToPrompt as H, BasePermissionHandler as I, writeSessionModeFile as J, normalizeAcpPermissionMode as K, removeSessionModeFile as L, MAX_READ_LIMIT as M, TEAM_SKILL as N, GEMINI_API_KEY_ENV as O, PI_TEAMS_EXTENSION as P, GOOGLE_API_KEY_ENV as Q, BaseReasoningProcessor as R, SPAWN_AGENTS as S, TurnWaitTimeoutError as T, MessageBuffer as U, CHANGE_TITLE_INSTRUCTION as V, listSessions as a, resolvePermissionRequest as b, archiveSession as c, sendUserMessage as d, sessionWebUrl as e, DEFAULT_GEMINI_MODEL as f, checkPreviewPath as g, registerAssistantSessionTools as h, readSessionMetadata as i, isValidSpawnOrigin as j, sanitizeSpawnPermissionMode as k, listAccountSessions as l, waitForSessionKey as m, initialMachineMetadata as n, createSessionMetadata as o, setupOfflineReconnection as p, query as q, readSessionTranscript as r, stopSession as s, tmuxSupportsSessionEnv as t, StreamRelay as u, MessageQueue2 as v, waitForTurnEnd as w, hashObject as x, startHappyServer as y, registerAgentAttachmentDownloads as z };
|