very-happy-cli 0.2.154 → 0.2.155
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-Ckyadhf4.mjs → AcpBackend-DVB0nzgR.mjs} +2 -2
- package/dist/{AcpBackend-B14xNj5L.cjs → AcpBackend-DmFcRh54.cjs} +3 -3
- package/dist/{AcpSessionManager-CCHmPdrR.cjs → AcpSessionManager-CWaSYTnT.cjs} +1 -1
- package/dist/{AcpSessionManager-H8t4P-IW.mjs → AcpSessionManager-dGDgNb-p.mjs} +1 -1
- package/dist/agentGuidance-BWxFC6UL.mjs +1613 -0
- package/dist/agentGuidance-un7YPryB.cjs +1665 -0
- package/dist/{agentHome-B20RWdgE.mjs → agentHome-ByLb2HFg.mjs} +5 -5
- package/dist/{agentHome-BUsLTZnz.cjs → agentHome-aucT8BHw.cjs} +5 -5
- package/dist/{auto-BQnncDcs.mjs → auto-BQnD6gVx.mjs} +21 -21
- package/dist/{auto-IeldARBS.cjs → auto-P9RoJaY0.cjs} +24 -24
- package/dist/codex/happyMcpStdioBridge.cjs +6 -3
- package/dist/codex/happyMcpStdioBridge.mjs +6 -3
- package/dist/{config-B-ocQh5V.mjs → config-BPZ2Y6o8.mjs} +8 -8
- package/dist/{config-DM_xI9M6.cjs → config-BoePh9PX.cjs} +8 -8
- package/dist/{index-okLyUzeD.cjs → index--KCpVs1c.cjs} +7 -7
- package/dist/{index-BELV05ko.mjs → index-B4oXnbYw.mjs} +463 -889
- package/dist/{index-ZKuBjvHi.mjs → index-BjzdXb9x.mjs} +21 -13
- package/dist/{index-BUxBxSxJ.cjs → index-DRrLaljt.cjs} +21 -13
- package/dist/{index-CIx3248o.mjs → index-DVVRqaVG.mjs} +7 -7
- package/dist/{index-eCjkm8Y2.cjs → index-D_fYUxVg.cjs} +521 -960
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/dist/{install-BZOdxfPd.cjs → install-CGigiocs.cjs} +1 -1
- package/dist/{install-CoLN3whR.mjs → install-DP5aPNEX.mjs} +1 -1
- package/dist/{installTerminalHooks-CA4OcAS7.cjs → installTerminalHooks-XrcPvfBs.cjs} +5 -5
- package/dist/{installTerminalHooks-Dde_8t36.mjs → installTerminalHooks-tIg7mANc.mjs} +5 -5
- package/dist/lib.cjs +2 -2
- package/dist/lib.mjs +2 -2
- package/dist/{limits-Dt7_unH4.mjs → limits-C_DW0M0q.mjs} +309 -13
- package/dist/{limits-CmzISka2.cjs → limits-DkfHJ3nS.cjs} +329 -12
- package/dist/{mcp-7iDHlyCI.cjs → mcp-BqlXl10F.cjs} +23 -23
- package/dist/{mcp-9Ui4nQZL.mjs → mcp-o7QglAQM.mjs} +17 -17
- package/dist/{piRuntime-BesGGPME.cjs → piRuntime-BxP4B_zY.cjs} +2 -2
- package/dist/{piRuntime-C4E4pgye.mjs → piRuntime-MjiJI-nV.mjs} +2 -2
- package/dist/{piTerminal-fUogwxYr.cjs → piTerminal-DYzRcwgb.cjs} +6 -6
- package/dist/{piTerminal-CCEGCQOY.mjs → piTerminal-Dpq6yqKU.mjs} +6 -6
- package/dist/{runGemini-CNPGSsSK.cjs → runGemini-8Pxh94Fm.cjs} +7 -7
- package/dist/{runGemini-BW-pEklU.mjs → runGemini-CDlcQ-OA.mjs} +6 -6
- package/dist/{runOpenClaw-Bsodkg_s.cjs → runOpenClaw-B5FSZIbK.cjs} +6 -6
- package/dist/{runOpenClaw-NRB6-_jU.mjs → runOpenClaw-YkhsgPs0.mjs} +5 -5
- package/dist/{send-Bd00QcHC.cjs → send-CwrYXTPr.cjs} +8 -42
- package/dist/{send-DYeSFg8s.mjs → send-DJStmLBv.mjs} +5 -39
- package/dist/{sessions-DlPOYKLU.mjs → sessions-JaVXlSqj.mjs} +88 -45
- package/dist/{sessions-RIFUoy_g.cjs → sessions-MX21NJXd.cjs} +99 -56
- package/dist/{spawn-D1bhyHbH.mjs → spawn-BaPnjClT.mjs} +22 -22
- package/dist/{spawn-DxrfHTF4.cjs → spawn-cIoglKVf.cjs} +30 -30
- package/dist/{teams-pfKHpkgL.mjs → teams-DXV5Pwwt.mjs} +8 -8
- package/dist/{teams-Cfy0VMCo.cjs → teams-Dqk7F3D-.cjs} +8 -8
- package/dist/{todo-B2CjDg_V.mjs → todo-2Z1REhfx.mjs} +8 -6
- package/dist/{todo-B4cBFzHB.cjs → todo-Buuqn9gm.cjs} +8 -6
- package/dist/{types-D5RWyBE5.mjs → types-CkbfdRzq.mjs} +4 -282
- package/dist/{types-Bd_TGr0F.cjs → types-a2xsH2UI.cjs} +16 -311
- package/package.json +7 -7
- package/dist/agentGuidance-DUBXppv-.cjs +0 -554
- package/dist/agentGuidance-TLYQxsm2.mjs +0 -530
|
@@ -1,14 +1,14 @@
|
|
|
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,
|
|
4
|
+
import { g as getProjectPath, k as initializeSandbox, w as wrapCommand, j as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, l as AsyncLock, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, m as commandOnPath, n as deriveLocalCliUpdateSummary, o as isClaudeSessionId, q as getLocalHappyAgentCredentialPath, s as readLocalHappyAgentCredentials, t as parseExactVersion, u as compareExactVersions, v as extractCompleteLines, x as decideBackfill, y as parseTerminalHookPayload, z as tmuxArgs, B as scrubTmuxClientEnv, D as decideMirrorBinding, E as MIRROR_BACKFILL_LINES_DEFAULT, G as mirrorLineKey, H as mirrorLocalId, J as detectCLIAvailability, K as detectResumeSupport, L as refreshAgentHomes, d as describeAgentHome, T as TurnActivityTracker, N as summarizeSpawnSessionForLog, O as locateClaudeConversation, P as agentHomeSpawnEnv, Q as shellescape, A as ApiClient, S as fetchCliUpdateState, U as resolveCliUpdateCheckInterval, V as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, W as listVhTerminals, X as readVhTerminal, Y as sendToVhTerminal, Z as TITLE_PROMPT_PREFIX, _ as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, $ as startOfflineReconnection, C as CodexAppServerClient } from './types-CkbfdRzq.mjs';
|
|
5
|
+
import { l as logger, a7 as notifyDaemonSessionEvent, x as BUILTIN_TODO_DISCOVERY, g as delay, a8 as STREAM_DELTA_MAX_CHARS, a9 as ensurePrivateDirectory, aa as hardenPrivateFile, c as configuration, ab as notifyDaemonClaudeAuthFailed, F as packageJson, r as readSettings, ac as readCredentials, Z as credentialRelayProblem, k as checkIfDaemonRunningAndCleanupStaleState, ad as readDaemonState, ae as AGENT_VERSION_SOURCES, w as encodeBase64, af as readSettingsOutcome, u as updateSettings, ag as encodeBase64Url, M as decodeBase64, ah as writeCredentialsLegacy, ai as writeCredentialsDataKey, d as readPersistedSessions, E as writePrivateFileSync, aj as decryptWithDataKey, ak as decryptLegacy, al as persistSession, z as ensurePrivateDirectorySync, am as getAgentCodeDefaults, an as normalizeAgentKey, ao as isClaudeOpus55Model, ap as supportsClaudeOpus55, aq as AUTOMATION_SUMMARY_MAX_CHARS, ar as AUTOMATION_CLAIM_MAX_RUNS, as as AUTOMATION_DEFAULT_LEASE_MS, f as isAutomationRunTerminal, at as isDaemonRunningCurrentlyInstalledHappyVersion, au as stopDaemon, av as acquireDaemonLock, aw as deletePersistedSessions, N as decrypt, ax as writeDaemonState, ay as writeSettings, az as cleanupDaemonState, aA as releaseDaemonLock, i as isValidSessionId, m as spawnDaemonSession, a1 as isValidTerminalId, aB as ContextUsageSchema, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, o as CLIPBOARD_MAX_BYTES, v as notifyDaemonSessionStarted, y as reportSessionEditToDaemon, aC as clearCredentials, aD as clearMachineId, h as readCredentialsForConfiguredRelay, aE as SandboxConfigSchema, e as createEnvelope, O as listDaemonSessions, n as stopDaemonSession, aF as getLatestDaemonLog } from './limits-C_DW0M0q.mjs';
|
|
6
6
|
import spawn$1, { spawn } from 'cross-spawn';
|
|
7
7
|
import path, { join, resolve, basename, delimiter, isAbsolute, dirname, sep, normalize } from 'node:path';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
9
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 './terminalTitleSuggest-DKgX3osW.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { E as trimIdent, F as AGENT_GUIDANCE, G as formatEditConflictNotice, z as sendUserMessage, S as SESSION_PEER_SENT_FROM, x as readSessionMetadata, y as waitForSessionKey, k as archiveSession, H as createAutomationsClient, t as isAutomationsUnavailable, I as AutomationsRequestError, J as analyzeLatestTurn, K as readSessionLog$1, L as TURN_WINDOW, N as userPromptText, v as teamScopePath, O as TeamConnectionPreflightError, C as createTeamsClient, e as listSessions, h as readSessionTranscript, n as deliverToSession, o as sessionWebUrl, i as stopSession, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, Q as TEAM_TOOL_NAMES, U as AUTOMATION_TOOL_NAMES, V as SESSION_PEER_TOOL_NAMES, r as registerTeamsTools, c as registerAutomationTools, d as registerSessionPeerTools, W as createSessionPeerToolExecutor, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, X as REPORT_PROGRESS_TOOL_TITLE, Y as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-BWxFC6UL.mjs';
|
|
12
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';
|
|
@@ -16,7 +16,7 @@ import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
|
16
16
|
import { query as query$1, AbortError } from '@anthropic-ai/claude-agent-sdk';
|
|
17
17
|
import axios, { AxiosError } from 'axios';
|
|
18
18
|
import 'node:events';
|
|
19
|
-
import
|
|
19
|
+
import 'socket.io-client';
|
|
20
20
|
import tweetnacl from 'tweetnacl';
|
|
21
21
|
import os$1, { homedir as homedir$1 } from 'os';
|
|
22
22
|
import { spawn as spawn$2, execSync as execSync$1, exec as exec$1 } from 'child_process';
|
|
@@ -878,7 +878,7 @@ async function createSessionScanner(opts) {
|
|
|
878
878
|
let processedMessageKeys = /* @__PURE__ */ new Set();
|
|
879
879
|
let deadSessions = /* @__PURE__ */ new Set();
|
|
880
880
|
if (opts.sessionId) {
|
|
881
|
-
let messages = await readSessionLog
|
|
881
|
+
let messages = await readSessionLog(projectDir, opts.sessionId);
|
|
882
882
|
logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
|
|
883
883
|
for (let m of messages) {
|
|
884
884
|
processedMessageKeys.add(messageKey(m));
|
|
@@ -901,7 +901,7 @@ async function createSessionScanner(opts) {
|
|
|
901
901
|
}
|
|
902
902
|
}
|
|
903
903
|
for (let session of sessions) {
|
|
904
|
-
const sessionMessages = await readSessionLog
|
|
904
|
+
const sessionMessages = await readSessionLog(projectDir, session);
|
|
905
905
|
let skipped = 0;
|
|
906
906
|
let sent = 0;
|
|
907
907
|
for (let file of sessionMessages) {
|
|
@@ -978,7 +978,7 @@ async function createSessionScanner(opts) {
|
|
|
978
978
|
return;
|
|
979
979
|
}
|
|
980
980
|
if (options?.treatExistingAsProcessed) {
|
|
981
|
-
const existing = await readSessionLog
|
|
981
|
+
const existing = await readSessionLog(projectDir, sessionId);
|
|
982
982
|
logger.debug(`[SESSION_SCANNER] Pre-marking ${existing.length} existing messages as processed for new session ${sessionId}`);
|
|
983
983
|
for (const m of existing) {
|
|
984
984
|
processedMessageKeys.add(messageKey(m));
|
|
@@ -1006,7 +1006,7 @@ function messageKey(message) {
|
|
|
1006
1006
|
throw Error();
|
|
1007
1007
|
}
|
|
1008
1008
|
}
|
|
1009
|
-
async function readSessionLog
|
|
1009
|
+
async function readSessionLog(projectDir, sessionId) {
|
|
1010
1010
|
const expectedSessionFile = join(projectDir, `${sessionId}.jsonl`);
|
|
1011
1011
|
logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
|
|
1012
1012
|
let file;
|
|
@@ -5380,8 +5380,8 @@ ${typeLabels[type] || type}:`));
|
|
|
5380
5380
|
}
|
|
5381
5381
|
console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
|
|
5382
5382
|
try {
|
|
5383
|
-
const { resolveAgentHomesForCli } = await import('./agentHome-
|
|
5384
|
-
const { describeAgentHome } = await import('./types-
|
|
5383
|
+
const { resolveAgentHomesForCli } = await import('./agentHome-ByLb2HFg.mjs');
|
|
5384
|
+
const { describeAgentHome } = await import('./types-CkbfdRzq.mjs').then(function (n) { return n.a0; });
|
|
5385
5385
|
const homes = await resolveAgentHomesForCli();
|
|
5386
5386
|
console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
|
|
5387
5387
|
console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
|
|
@@ -6063,59 +6063,6 @@ function formatAssistantReportMessage(title, sessionId, event) {
|
|
|
6063
6063
|
return `[\u7CFB\u7EDF\u901A\u62A5] \u4F1A\u8BDD\u300C${title}\u300D${state}\uFF08${sessionId}\uFF09\u3002\u8BF7\u7528 session_read \u6838\u5B9E\u7ED3\u679C\uFF0C\u5E76\u5411\u7528\u6237\u53E3\u5934\u6C47\u62A5\u4E00\u53E5\u7ED3\u8BBA\uFF1B\u591A\u6761\u901A\u62A5\u63A5\u8FD1\u65F6\u5408\u5E76\u6C47\u62A5\u3002`;
|
|
6064
6064
|
}
|
|
6065
6065
|
|
|
6066
|
-
function sessionWebUrl(sessionId) {
|
|
6067
|
-
return `${configuration.webappUrl.replace(/\/+$/, "")}/session/${sessionId}`;
|
|
6068
|
-
}
|
|
6069
|
-
async function waitForSessionKey(sessionId, timeoutMs) {
|
|
6070
|
-
const deadline = Date.now() + timeoutMs;
|
|
6071
|
-
while (true) {
|
|
6072
|
-
const entry = readPersistedSessions()[sessionId];
|
|
6073
|
-
if (entry) return entry;
|
|
6074
|
-
if (Date.now() >= deadline) {
|
|
6075
|
-
throw new Error(
|
|
6076
|
-
`Session ${sessionId} has no encryption key in ${configuration.sessionsFile}. Either it was not spawned by this machine's daemon, or the daemon is too old to persist session keys.`
|
|
6077
|
-
);
|
|
6078
|
-
}
|
|
6079
|
-
await delay(200);
|
|
6080
|
-
}
|
|
6081
|
-
}
|
|
6082
|
-
async function sendUserMessage(sessionId, persisted, text, client, options = {}) {
|
|
6083
|
-
const credentials = await readCredentialsForConfiguredRelay();
|
|
6084
|
-
if (!credentials) {
|
|
6085
|
-
throw new Error("Not authenticated. Run `very-happy auth login` first.");
|
|
6086
|
-
}
|
|
6087
|
-
const envelope = {
|
|
6088
|
-
role: "user",
|
|
6089
|
-
content: {
|
|
6090
|
-
type: "text",
|
|
6091
|
-
text
|
|
6092
|
-
},
|
|
6093
|
-
meta: {
|
|
6094
|
-
sentFrom: options.sentFrom ?? "cli",
|
|
6095
|
-
...options.model !== void 0 ? { model: options.model } : {},
|
|
6096
|
-
...options.permissionMode !== void 0 ? { permissionMode: options.permissionMode } : {},
|
|
6097
|
-
...options.effort !== void 0 ? { effort: options.effort } : {}
|
|
6098
|
-
}
|
|
6099
|
-
};
|
|
6100
|
-
const encrypted = encodeBase64(encrypt(
|
|
6101
|
-
decodeBase64(persisted.encryptionKey),
|
|
6102
|
-
persisted.encryptionVariant,
|
|
6103
|
-
envelope
|
|
6104
|
-
));
|
|
6105
|
-
await axios.post(
|
|
6106
|
-
`${configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
6107
|
-
{ messages: [{ content: encrypted, localId: options.localId ?? randomUUID() }] },
|
|
6108
|
-
{
|
|
6109
|
-
headers: {
|
|
6110
|
-
"Authorization": `Bearer ${credentials.token}`,
|
|
6111
|
-
"Content-Type": "application/json",
|
|
6112
|
-
"X-Happy-Client": `${client}/${configuration.currentCliVersion}`
|
|
6113
|
-
},
|
|
6114
|
-
timeout: 3e4
|
|
6115
|
-
}
|
|
6116
|
-
);
|
|
6117
|
-
}
|
|
6118
|
-
|
|
6119
6066
|
const ALLOWED_SPAWN_PERMISSION_MODES = [
|
|
6120
6067
|
"default",
|
|
6121
6068
|
"acceptEdits",
|
|
@@ -6271,7 +6218,9 @@ function startDaemonControlServer({
|
|
|
6271
6218
|
pushClipboard,
|
|
6272
6219
|
pushFilePreview,
|
|
6273
6220
|
onTerminalHook,
|
|
6274
|
-
setTerminalTitle
|
|
6221
|
+
setTerminalTitle,
|
|
6222
|
+
onSessionEdit,
|
|
6223
|
+
listPeers
|
|
6275
6224
|
}) {
|
|
6276
6225
|
return new Promise((resolve) => {
|
|
6277
6226
|
const app = fastify({
|
|
@@ -6594,6 +6543,45 @@ function startDaemonControlServer({
|
|
|
6594
6543
|
}
|
|
6595
6544
|
return { status: "ok" };
|
|
6596
6545
|
});
|
|
6546
|
+
typed.post("/session-edit", {
|
|
6547
|
+
schema: {
|
|
6548
|
+
body: z.object({
|
|
6549
|
+
sessionId: z.string().regex(/^[a-zA-Z0-9_-]{1,128}$/),
|
|
6550
|
+
path: z.string().min(1).max(4096),
|
|
6551
|
+
tool: z.string().min(1).max(64),
|
|
6552
|
+
cwd: z.string().max(4096).optional(),
|
|
6553
|
+
/** When the runner saw the call (ms). Clamped to now by the daemon;
|
|
6554
|
+
* older than the conflict window = ignored (replayed history). */
|
|
6555
|
+
at: z.number().int().nonnegative().optional()
|
|
6556
|
+
}),
|
|
6557
|
+
response: { 200: z.object({ status: z.literal("ok") }) }
|
|
6558
|
+
}
|
|
6559
|
+
}, async (request) => {
|
|
6560
|
+
try {
|
|
6561
|
+
onSessionEdit?.(request.body);
|
|
6562
|
+
} catch (error) {
|
|
6563
|
+
logger.debug("[CONTROL SERVER] session-edit handler failed:", error);
|
|
6564
|
+
}
|
|
6565
|
+
return { status: "ok" };
|
|
6566
|
+
});
|
|
6567
|
+
typed.post("/peers", {
|
|
6568
|
+
schema: {
|
|
6569
|
+
response: {
|
|
6570
|
+
200: z.object({
|
|
6571
|
+
sessions: z.array(z.object({
|
|
6572
|
+
sessionId: z.string(),
|
|
6573
|
+
kind: z.enum(["managed", "mirror"]),
|
|
6574
|
+
pid: z.number().optional(),
|
|
6575
|
+
cwd: z.string().optional(),
|
|
6576
|
+
flavor: z.string().optional(),
|
|
6577
|
+
title: z.string().optional(),
|
|
6578
|
+
variant: z.string().optional(),
|
|
6579
|
+
edits: z.array(z.object({ path: z.string(), tool: z.string(), at: z.number() }))
|
|
6580
|
+
}))
|
|
6581
|
+
})
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6584
|
+
}, async () => ({ sessions: listPeers ? listPeers() : [] }));
|
|
6597
6585
|
typed.post("/stop", {
|
|
6598
6586
|
schema: {
|
|
6599
6587
|
response: {
|
|
@@ -6629,6 +6617,273 @@ function startDaemonControlServer({
|
|
|
6629
6617
|
});
|
|
6630
6618
|
}
|
|
6631
6619
|
|
|
6620
|
+
const EDIT_CONFLICT_WINDOW_MS = 30 * 6e4;
|
|
6621
|
+
function pairKey$1(a, b, path) {
|
|
6622
|
+
return a < b ? `${a}\0${b}\0${path}` : `${b}\0${a}\0${path}`;
|
|
6623
|
+
}
|
|
6624
|
+
class EditConflictTracker {
|
|
6625
|
+
windowMs;
|
|
6626
|
+
maxPaths;
|
|
6627
|
+
/** real path → sessionId → latest edit */
|
|
6628
|
+
byPath = /* @__PURE__ */ new Map();
|
|
6629
|
+
/** (sorted pair, path) → when the notice went out */
|
|
6630
|
+
notified = /* @__PURE__ */ new Map();
|
|
6631
|
+
constructor(options = {}) {
|
|
6632
|
+
this.windowMs = options.windowMs ?? EDIT_CONFLICT_WINDOW_MS;
|
|
6633
|
+
this.maxPaths = options.maxPaths ?? 5e3;
|
|
6634
|
+
}
|
|
6635
|
+
/**
|
|
6636
|
+
* Remember an edit and return the peers to notify about it. `isLive`
|
|
6637
|
+
* decides whether another session still counts (dead ones are dropped
|
|
6638
|
+
* from the table on the spot).
|
|
6639
|
+
*/
|
|
6640
|
+
record(edit, isLive) {
|
|
6641
|
+
this.prune(edit.at);
|
|
6642
|
+
let sessions = this.byPath.get(edit.path);
|
|
6643
|
+
if (!sessions) {
|
|
6644
|
+
sessions = /* @__PURE__ */ new Map();
|
|
6645
|
+
this.byPath.set(edit.path, sessions);
|
|
6646
|
+
}
|
|
6647
|
+
const conflicts = [];
|
|
6648
|
+
for (const [sessionId, peer] of sessions) {
|
|
6649
|
+
if (sessionId === edit.sessionId) continue;
|
|
6650
|
+
if (!isLive(sessionId)) {
|
|
6651
|
+
sessions.delete(sessionId);
|
|
6652
|
+
continue;
|
|
6653
|
+
}
|
|
6654
|
+
if (edit.at - peer.at > this.windowMs) continue;
|
|
6655
|
+
const key = pairKey$1(edit.sessionId, sessionId, edit.path);
|
|
6656
|
+
const notifiedAt = this.notified.get(key);
|
|
6657
|
+
if (notifiedAt !== void 0 && edit.at - notifiedAt <= this.windowMs) continue;
|
|
6658
|
+
this.notified.set(key, edit.at);
|
|
6659
|
+
conflicts.push({ editor: edit, peer });
|
|
6660
|
+
}
|
|
6661
|
+
sessions.set(edit.sessionId, edit);
|
|
6662
|
+
return conflicts;
|
|
6663
|
+
}
|
|
6664
|
+
/** Paths this session edited inside the window, newest first. */
|
|
6665
|
+
editsOf(sessionId, now, limit = 20) {
|
|
6666
|
+
const out = [];
|
|
6667
|
+
for (const [path, sessions] of this.byPath) {
|
|
6668
|
+
const edit = sessions.get(sessionId);
|
|
6669
|
+
if (edit && now - edit.at <= this.windowMs) out.push({ path, tool: edit.tool, at: edit.at });
|
|
6670
|
+
}
|
|
6671
|
+
return out.sort((a, b) => b.at - a.at).slice(0, limit);
|
|
6672
|
+
}
|
|
6673
|
+
/** Session ended: nothing it did counts any more. */
|
|
6674
|
+
forget(sessionId) {
|
|
6675
|
+
for (const [path, sessions] of this.byPath) {
|
|
6676
|
+
sessions.delete(sessionId);
|
|
6677
|
+
if (sessions.size === 0) this.byPath.delete(path);
|
|
6678
|
+
}
|
|
6679
|
+
for (const key of this.notified.keys()) {
|
|
6680
|
+
const [a, b] = key.split("\0");
|
|
6681
|
+
if (a === sessionId || b === sessionId) this.notified.delete(key);
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
/** Drop everything older than the window (and cap the table size). */
|
|
6685
|
+
prune(now) {
|
|
6686
|
+
for (const [path, sessions] of this.byPath) {
|
|
6687
|
+
for (const [sessionId, edit] of sessions) if (now - edit.at > this.windowMs) sessions.delete(sessionId);
|
|
6688
|
+
if (sessions.size === 0) this.byPath.delete(path);
|
|
6689
|
+
}
|
|
6690
|
+
for (const [key, at] of this.notified) if (now - at > this.windowMs) this.notified.delete(key);
|
|
6691
|
+
if (this.byPath.size > this.maxPaths) {
|
|
6692
|
+
const oldest = [...this.byPath.entries()].map(([path, sessions]) => ({ path, at: Math.max(...[...sessions.values()].map((e) => e.at)) })).sort((a, b) => a.at - b.at);
|
|
6693
|
+
for (const entry of oldest.slice(0, this.byPath.size - this.maxPaths)) this.byPath.delete(entry.path);
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
/** Test / diagnostics helper. */
|
|
6697
|
+
size() {
|
|
6698
|
+
return this.byPath.size;
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
function normalizeEditPath(path, cwd, realpath = realpathSync.native ?? realpathSync) {
|
|
6702
|
+
const absolute = isAbsolute(path) ? resolve(path) : resolve(cwd, path);
|
|
6703
|
+
let existing = absolute;
|
|
6704
|
+
const tail = [];
|
|
6705
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
6706
|
+
try {
|
|
6707
|
+
const real = realpath(existing);
|
|
6708
|
+
return tail.length ? join(real, ...tail) : real;
|
|
6709
|
+
} catch {
|
|
6710
|
+
const parent = dirname(existing);
|
|
6711
|
+
if (parent === existing) break;
|
|
6712
|
+
tail.unshift(existing.slice(parent.length).replace(new RegExp(`^\\${sep}`), ""));
|
|
6713
|
+
existing = parent;
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6716
|
+
return absolute;
|
|
6717
|
+
}
|
|
6718
|
+
|
|
6719
|
+
const CONFLICT_COALESCE_MS = 1e4;
|
|
6720
|
+
const MAX_NOTICES_PER_PAIR = 5;
|
|
6721
|
+
function pairKey(a, b) {
|
|
6722
|
+
return a < b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
6723
|
+
}
|
|
6724
|
+
function senderOf(info, sessionId) {
|
|
6725
|
+
return { sessionId, title: info?.title, flavor: info?.flavor, cwd: info?.cwd };
|
|
6726
|
+
}
|
|
6727
|
+
function createPeerCoordinator(deps) {
|
|
6728
|
+
const now = deps.now ?? (() => Date.now());
|
|
6729
|
+
const windowMs = deps.windowMs ?? EDIT_CONFLICT_WINDOW_MS;
|
|
6730
|
+
const coalesceMs = deps.coalesceMs ?? CONFLICT_COALESCE_MS;
|
|
6731
|
+
const maxNoticesPerPair = deps.maxNoticesPerPair ?? MAX_NOTICES_PER_PAIR;
|
|
6732
|
+
const tracker = new EditConflictTracker({ windowMs });
|
|
6733
|
+
const readPersisted = deps.readPersisted ?? readPersistedSessions;
|
|
6734
|
+
const send = deps.send ?? sendUserMessage;
|
|
6735
|
+
const normalize = deps.normalize ?? normalizeEditPath;
|
|
6736
|
+
const liveTitle = deps.liveTitle ?? (async (sessionId, persisted) => (await readSessionMetadata(sessionId, persisted))?.summary?.text);
|
|
6737
|
+
const schedule = deps.schedule ?? ((fn, ms) => {
|
|
6738
|
+
const t = setTimeout(fn, ms);
|
|
6739
|
+
t.unref?.();
|
|
6740
|
+
return t;
|
|
6741
|
+
});
|
|
6742
|
+
let getMirrors = deps.getMirrors ?? (() => []);
|
|
6743
|
+
const pending = /* @__PURE__ */ new Map();
|
|
6744
|
+
const noticesSent = /* @__PURE__ */ new Map();
|
|
6745
|
+
let inflight = Promise.resolve();
|
|
6746
|
+
const snapshot = () => {
|
|
6747
|
+
const at = now();
|
|
6748
|
+
const out = /* @__PURE__ */ new Map();
|
|
6749
|
+
for (const child of deps.getChildren()) {
|
|
6750
|
+
const id = child.happySessionId;
|
|
6751
|
+
if (!id || out.has(id)) continue;
|
|
6752
|
+
const meta = child.happySessionMetadataFromLocalWebhook;
|
|
6753
|
+
out.set(id, {
|
|
6754
|
+
sessionId: id,
|
|
6755
|
+
kind: "managed",
|
|
6756
|
+
pid: child.pid,
|
|
6757
|
+
...meta?.path ? { cwd: meta.path } : {},
|
|
6758
|
+
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
6759
|
+
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
6760
|
+
...meta?.variant ?? child.variant ? { variant: meta?.variant ?? child.variant } : {},
|
|
6761
|
+
edits: tracker.editsOf(id, at)
|
|
6762
|
+
});
|
|
6763
|
+
}
|
|
6764
|
+
for (const mirror of getMirrors()) {
|
|
6765
|
+
if (out.has(mirror.sessionId)) continue;
|
|
6766
|
+
out.set(mirror.sessionId, {
|
|
6767
|
+
sessionId: mirror.sessionId,
|
|
6768
|
+
kind: "mirror",
|
|
6769
|
+
flavor: "terminal-mirror",
|
|
6770
|
+
...mirror.cwd ? { cwd: mirror.cwd } : {},
|
|
6771
|
+
...mirror.title ? { title: mirror.title } : {},
|
|
6772
|
+
edits: tracker.editsOf(mirror.sessionId, at)
|
|
6773
|
+
});
|
|
6774
|
+
}
|
|
6775
|
+
return out;
|
|
6776
|
+
};
|
|
6777
|
+
const deliver = async (recipient, peer, peerId, paths, peerEditedAgoMs, noticeId) => {
|
|
6778
|
+
if (recipient.kind === "mirror") return;
|
|
6779
|
+
const persisted = readPersisted()[recipient.sessionId];
|
|
6780
|
+
if (!persisted) {
|
|
6781
|
+
logger.debug(`[PEERS] no key for ${recipient.sessionId}; conflict notice ${noticeId} not delivered`);
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
const peerSender = senderOf(peer, peerId);
|
|
6785
|
+
if (!peerSender.title && peer?.kind === "managed") {
|
|
6786
|
+
const peerKey = readPersisted()[peer.sessionId];
|
|
6787
|
+
if (peerKey) {
|
|
6788
|
+
try {
|
|
6789
|
+
peerSender.title = await liveTitle(peer.sessionId, peerKey);
|
|
6790
|
+
} catch {
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
const text = formatEditConflictNotice({ id: noticeId, path: paths[0], paths, peer: peerSender, peerEditedAgoMs, windowMs });
|
|
6795
|
+
try {
|
|
6796
|
+
await send(recipient.sessionId, persisted, text, "edit-conflict", {
|
|
6797
|
+
sentFrom: SESSION_PEER_SENT_FROM,
|
|
6798
|
+
delivery: "steer",
|
|
6799
|
+
localId: `edit-conflict-${noticeId}-${recipient.sessionId}`
|
|
6800
|
+
});
|
|
6801
|
+
logger.debug(`[PEERS] conflict notice ${noticeId} \u2192 ${recipient.sessionId} (${paths.length} file(s), first ${paths[0]}, peer ${peerId})`);
|
|
6802
|
+
} catch (error) {
|
|
6803
|
+
logger.debug(`[PEERS] conflict notice ${noticeId} \u2192 ${recipient.sessionId} failed:`, error);
|
|
6804
|
+
}
|
|
6805
|
+
};
|
|
6806
|
+
const flushPair = async (key) => {
|
|
6807
|
+
const pair = pending.get(key);
|
|
6808
|
+
if (!pair) return;
|
|
6809
|
+
pending.delete(key);
|
|
6810
|
+
const at = now();
|
|
6811
|
+
const sent = (noticesSent.get(key) ?? []).filter((t) => at - t <= windowMs);
|
|
6812
|
+
if (sent.length >= maxNoticesPerPair) {
|
|
6813
|
+
noticesSent.set(key, sent);
|
|
6814
|
+
logger.debug(`[PEERS] pair ${pair.a}/${pair.b}: ${maxNoticesPerPair} notices already sent this window; ${pair.paths.size} conflicting path(s) not announced`);
|
|
6815
|
+
return;
|
|
6816
|
+
}
|
|
6817
|
+
sent.push(at);
|
|
6818
|
+
noticesSent.set(key, sent);
|
|
6819
|
+
const sessions = snapshot();
|
|
6820
|
+
const a = sessions.get(pair.a);
|
|
6821
|
+
const b = sessions.get(pair.b);
|
|
6822
|
+
const paths = [...pair.paths.keys()];
|
|
6823
|
+
const noticeId = randomUUID().slice(0, 8);
|
|
6824
|
+
const latestBy = (sessionId) => Math.max(...[...pair.paths.values()].map((c) => c.editor.sessionId === sessionId ? c.editor.at : c.peer.at));
|
|
6825
|
+
const agoFor = (recipientId, otherId) => Math.max(0, latestBy(recipientId) - latestBy(otherId));
|
|
6826
|
+
if (a) await deliver(a, b, pair.b, paths, agoFor(pair.a, pair.b), noticeId);
|
|
6827
|
+
if (b) await deliver(b, a, pair.a, paths, agoFor(pair.b, pair.a), noticeId);
|
|
6828
|
+
};
|
|
6829
|
+
const enqueue = (conflict) => {
|
|
6830
|
+
const key = pairKey(conflict.editor.sessionId, conflict.peer.sessionId);
|
|
6831
|
+
let pair = pending.get(key);
|
|
6832
|
+
if (!pair) {
|
|
6833
|
+
pair = { a: conflict.editor.sessionId, b: conflict.peer.sessionId, paths: /* @__PURE__ */ new Map(), timer: null };
|
|
6834
|
+
pair.timer = schedule(() => {
|
|
6835
|
+
inflight = inflight.then(() => flushPair(key)).catch(() => {
|
|
6836
|
+
});
|
|
6837
|
+
}, coalesceMs);
|
|
6838
|
+
pending.set(key, pair);
|
|
6839
|
+
}
|
|
6840
|
+
pair.paths.set(conflict.editor.path, conflict);
|
|
6841
|
+
};
|
|
6842
|
+
return {
|
|
6843
|
+
list: () => [...snapshot().values()],
|
|
6844
|
+
onEdit(edit) {
|
|
6845
|
+
const sessions = snapshot();
|
|
6846
|
+
const self = sessions.get(edit.sessionId);
|
|
6847
|
+
if (!self) {
|
|
6848
|
+
logger.debug(`[PEERS] edit from unknown/dead session ${edit.sessionId} ignored`);
|
|
6849
|
+
return [];
|
|
6850
|
+
}
|
|
6851
|
+
const current = now();
|
|
6852
|
+
const at = typeof edit.at === "number" && Number.isFinite(edit.at) ? Math.min(edit.at, current) : current;
|
|
6853
|
+
if (current - at > windowMs) {
|
|
6854
|
+
logger.debug(`[PEERS] edit from ${edit.sessionId} on ${edit.path} is ${Math.round((current - at) / 6e4)}m old (replayed history); ignored`);
|
|
6855
|
+
return [];
|
|
6856
|
+
}
|
|
6857
|
+
const cwd = edit.cwd ?? self.cwd ?? process.cwd();
|
|
6858
|
+
const path = normalize(edit.path, cwd);
|
|
6859
|
+
const conflicts = tracker.record({ sessionId: edit.sessionId, path, tool: edit.tool, at }, (id) => sessions.has(id));
|
|
6860
|
+
for (const conflict of conflicts) enqueue(conflict);
|
|
6861
|
+
return conflicts;
|
|
6862
|
+
},
|
|
6863
|
+
async flush() {
|
|
6864
|
+
for (const key of [...pending.keys()]) {
|
|
6865
|
+
const pair = pending.get(key);
|
|
6866
|
+
if (pair?.timer && typeof pair.timer === "object") clearTimeout(pair.timer);
|
|
6867
|
+
inflight = inflight.then(() => flushPair(key)).catch(() => {
|
|
6868
|
+
});
|
|
6869
|
+
}
|
|
6870
|
+
await inflight;
|
|
6871
|
+
},
|
|
6872
|
+
forget(sessionId) {
|
|
6873
|
+
tracker.forget(sessionId);
|
|
6874
|
+
for (const [key, pair] of pending) {
|
|
6875
|
+
if (pair.a === sessionId || pair.b === sessionId) {
|
|
6876
|
+
if (pair.timer && typeof pair.timer === "object") clearTimeout(pair.timer);
|
|
6877
|
+
pending.delete(key);
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6880
|
+
},
|
|
6881
|
+
setMirrorSource(source) {
|
|
6882
|
+
getMirrors = source;
|
|
6883
|
+
}
|
|
6884
|
+
};
|
|
6885
|
+
}
|
|
6886
|
+
|
|
6632
6887
|
const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
|
|
6633
6888
|
|
|
6634
6889
|
\u4F60\u662F very-happy \u7684\u8C03\u5EA6\u4E2D\u5FC3\uFF0C\u8DD1\u5728\u7528\u6237\u81EA\u5DF1\u7684\u673A\u5668\u4E0A\u3002\u7528\u6237\u4E3B\u8981\u901A\u8FC7\u8BED\u97F3\u8DDF\u4F60\u8BF4\u8BDD\uFF0C
|
|
@@ -8548,6 +8803,73 @@ class ClaudeAuthService {
|
|
|
8548
8803
|
}
|
|
8549
8804
|
}
|
|
8550
8805
|
|
|
8806
|
+
const CLAUDE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
8807
|
+
function pathOf(input) {
|
|
8808
|
+
if (!input || typeof input !== "object") return null;
|
|
8809
|
+
const record = input;
|
|
8810
|
+
const candidate = record.file_path ?? record.notebook_path;
|
|
8811
|
+
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
8812
|
+
}
|
|
8813
|
+
function extractClaudeEditPaths(body) {
|
|
8814
|
+
if (!body || typeof body !== "object") return [];
|
|
8815
|
+
const line = body;
|
|
8816
|
+
if (line.type !== "assistant") return [];
|
|
8817
|
+
const content = line.message?.content;
|
|
8818
|
+
if (!Array.isArray(content)) return [];
|
|
8819
|
+
const stamp = line.timestamp;
|
|
8820
|
+
const at = typeof stamp === "string" ? Date.parse(stamp) : typeof stamp === "number" ? stamp : NaN;
|
|
8821
|
+
const edits = [];
|
|
8822
|
+
for (const block of content) {
|
|
8823
|
+
if (!block || typeof block !== "object") continue;
|
|
8824
|
+
const b = block;
|
|
8825
|
+
if (b.type !== "tool_use" || typeof b.name !== "string" || !CLAUDE_EDIT_TOOLS.has(b.name)) continue;
|
|
8826
|
+
const path = pathOf(b.input);
|
|
8827
|
+
if (path) edits.push({ path, tool: b.name, ...Number.isFinite(at) ? { at } : {} });
|
|
8828
|
+
}
|
|
8829
|
+
return edits;
|
|
8830
|
+
}
|
|
8831
|
+
function extractCodexPatchPaths(changes) {
|
|
8832
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return [];
|
|
8833
|
+
return Object.keys(changes).filter((path) => path.trim() !== "").map((path) => ({ path, tool: "CodexPatch" }));
|
|
8834
|
+
}
|
|
8835
|
+
function extractAcpEditPaths(toolKind, args) {
|
|
8836
|
+
if (!args || typeof args !== "object") return [];
|
|
8837
|
+
const a = args;
|
|
8838
|
+
if (a.piTool === "write" || a.piTool === "edit") {
|
|
8839
|
+
const raw = a.rawInput;
|
|
8840
|
+
const path = raw && typeof raw.path === "string" && raw.path.trim() !== "" ? raw.path : null;
|
|
8841
|
+
return path ? [{ path, tool: a.piTool }] : [];
|
|
8842
|
+
}
|
|
8843
|
+
if (typeof a.piTool === "string") return [];
|
|
8844
|
+
if (toolKind !== "edit" || !Array.isArray(a.locations)) return [];
|
|
8845
|
+
const edits = [];
|
|
8846
|
+
for (const location of a.locations) {
|
|
8847
|
+
const path = location && typeof location === "object" ? location.path : void 0;
|
|
8848
|
+
if (typeof path === "string" && path.trim() !== "" && !edits.some((e) => e.path === path)) edits.push({ path, tool: "edit" });
|
|
8849
|
+
}
|
|
8850
|
+
return edits;
|
|
8851
|
+
}
|
|
8852
|
+
class EditReportThrottle {
|
|
8853
|
+
constructor(intervalMs = 5e3) {
|
|
8854
|
+
this.intervalMs = intervalMs;
|
|
8855
|
+
}
|
|
8856
|
+
lastReportedAt = /* @__PURE__ */ new Map();
|
|
8857
|
+
/** Returns the edits that should be reported now and remembers them. */
|
|
8858
|
+
take(edits, now = Date.now()) {
|
|
8859
|
+
const due = [];
|
|
8860
|
+
for (const edit of edits) {
|
|
8861
|
+
const last = this.lastReportedAt.get(edit.path);
|
|
8862
|
+
if (last !== void 0 && now - last < this.intervalMs) continue;
|
|
8863
|
+
this.lastReportedAt.set(edit.path, now);
|
|
8864
|
+
due.push(edit);
|
|
8865
|
+
}
|
|
8866
|
+
if (this.lastReportedAt.size > 512) {
|
|
8867
|
+
for (const [path, at] of this.lastReportedAt) if (now - at > this.intervalMs * 10) this.lastReportedAt.delete(path);
|
|
8868
|
+
}
|
|
8869
|
+
return due;
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
8872
|
+
|
|
8551
8873
|
const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set(["file-history-snapshot", "change", "queue-operation"]);
|
|
8552
8874
|
function parseLines(lines) {
|
|
8553
8875
|
const out = [];
|
|
@@ -8584,12 +8906,15 @@ function createMirrorScanner(opts) {
|
|
|
8584
8906
|
}
|
|
8585
8907
|
tail.offset = 0;
|
|
8586
8908
|
}
|
|
8909
|
+
let replaced = false;
|
|
8587
8910
|
if (tail.offset > size) {
|
|
8588
8911
|
logger.debug(`[MIRROR SCANNER] ${tail.path}: offset ${tail.offset} > size ${size} \u2014 file replaced, resetting (localId idempotency absorbs replays)`);
|
|
8589
8912
|
tail.offset = 0;
|
|
8913
|
+
replaced = true;
|
|
8590
8914
|
}
|
|
8591
8915
|
if (tail.offset === size) return;
|
|
8592
8916
|
const isFirstBackfillRead = tail.mode === "backfill-tail" && tail.offset === 0;
|
|
8917
|
+
const replay = isFirstBackfillRead || replaced;
|
|
8593
8918
|
const handle = await open$1(tail.path, "r");
|
|
8594
8919
|
let buf;
|
|
8595
8920
|
try {
|
|
@@ -8609,7 +8934,7 @@ function createMirrorScanner(opts) {
|
|
|
8609
8934
|
if (decision.truncated) opts.events.onBackfillTruncated?.();
|
|
8610
8935
|
tail.mode = "from-eof";
|
|
8611
8936
|
}
|
|
8612
|
-
if (messages.length > 0) opts.events.onMessages(messages);
|
|
8937
|
+
if (messages.length > 0) opts.events.onMessages(messages, { replay });
|
|
8613
8938
|
};
|
|
8614
8939
|
const sync = new InvalidateSync(async () => {
|
|
8615
8940
|
for (const tail of files.values()) {
|
|
@@ -8732,8 +9057,17 @@ function createMirrorManager(deps) {
|
|
|
8732
9057
|
binding.client.updateMetadata((current) => ({ ...current, ...patch }));
|
|
8733
9058
|
persistBinding(binding);
|
|
8734
9059
|
};
|
|
8735
|
-
const sendMessages = (binding, messages) => {
|
|
9060
|
+
const sendMessages = (binding, messages, context = { replay: false }) => {
|
|
8736
9061
|
for (const message of messages) {
|
|
9062
|
+
if (deps.onEdit && binding.status === "active" && !context.replay) {
|
|
9063
|
+
for (const edit of extractClaudeEditPaths(message)) {
|
|
9064
|
+
try {
|
|
9065
|
+
deps.onEdit({ sessionId: binding.happySessionId, path: edit.path, tool: edit.tool, cwd: binding.metadata.path, ...edit.at !== void 0 ? { at: edit.at } : {} });
|
|
9066
|
+
} catch (error) {
|
|
9067
|
+
logger.debug(`[MIRROR] edit tap failed for terminal ${binding.terminalId}:`, error);
|
|
9068
|
+
}
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
8737
9071
|
const key = mirrorLineKey(message);
|
|
8738
9072
|
binding.client.sendClaudeSessionMessage(message, key ? { localIdFor: (envelopeIndex) => mirrorLocalId(key, envelopeIndex) } : void 0);
|
|
8739
9073
|
}
|
|
@@ -8742,7 +9076,7 @@ function createMirrorManager(deps) {
|
|
|
8742
9076
|
const scanner = createMirrorScanner({
|
|
8743
9077
|
backfillLines,
|
|
8744
9078
|
events: {
|
|
8745
|
-
onMessages: (messages) => sendMessages(binding, messages),
|
|
9079
|
+
onMessages: (messages, context) => sendMessages(binding, messages, context),
|
|
8746
9080
|
onBackfillTruncated: opts.withTruncationNotice ? () => binding.client.sendSessionEvent({
|
|
8747
9081
|
type: "message",
|
|
8748
9082
|
message: `\u53EA\u8BFB\u955C\u50CF\u53EA\u56DE\u704C\u4E86\u6700\u8FD1 ${backfillLines} \u6761\uFF0C\u66F4\u65E9\u5185\u5BB9\u8BF7\u5728\u7EC8\u7AEF\u91CC\u56DE\u770B`
|
|
@@ -8765,6 +9099,10 @@ function createMirrorManager(deps) {
|
|
|
8765
9099
|
await binding.scanner?.cleanup();
|
|
8766
9100
|
binding.scanner = null;
|
|
8767
9101
|
binding.client.closeClaudeSessionTurn("completed");
|
|
9102
|
+
try {
|
|
9103
|
+
deps.onSessionEnded?.(binding.happySessionId);
|
|
9104
|
+
} catch {
|
|
9105
|
+
}
|
|
8768
9106
|
updateBindingMetadata(binding, {
|
|
8769
9107
|
lifecycleState: "archived",
|
|
8770
9108
|
lifecycleStateSince: Date.now(),
|
|
@@ -8977,6 +9315,13 @@ function createMirrorManager(deps) {
|
|
|
8977
9315
|
}
|
|
8978
9316
|
};
|
|
8979
9317
|
return {
|
|
9318
|
+
listActiveMirrors() {
|
|
9319
|
+
return [...bindings.values()].filter((binding) => binding.status === "active").map((binding) => ({
|
|
9320
|
+
sessionId: binding.happySessionId,
|
|
9321
|
+
...binding.metadata.path ? { cwd: binding.metadata.path } : {},
|
|
9322
|
+
...binding.metadata.summary?.text ? { title: binding.metadata.summary.text } : {}
|
|
9323
|
+
}));
|
|
9324
|
+
},
|
|
8980
9325
|
handleHookPayload(body) {
|
|
8981
9326
|
const event = parseTerminalHookPayload(body);
|
|
8982
9327
|
if (!event) {
|
|
@@ -9443,683 +9788,6 @@ Delegate from this working directory unless a different repository was explicitl
|
|
|
9443
9788
|
${goal}`;
|
|
9444
9789
|
}
|
|
9445
9790
|
|
|
9446
|
-
const MAX_LINE_CHARS = 500;
|
|
9447
|
-
const MAX_ARGS_CHARS = 160;
|
|
9448
|
-
const DEFAULT_LIMITS = { line: MAX_LINE_CHARS, args: MAX_ARGS_CHARS };
|
|
9449
|
-
const NO_LIMITS = { line: Number.POSITIVE_INFINITY, args: Number.POSITIVE_INFINITY };
|
|
9450
|
-
function truncateText(text, max = MAX_LINE_CHARS) {
|
|
9451
|
-
const chars = Array.from(text);
|
|
9452
|
-
if (chars.length <= max) return text;
|
|
9453
|
-
return chars.slice(0, max).join("") + ` \u2026[+${chars.length - max} chars]`;
|
|
9454
|
-
}
|
|
9455
|
-
function squash(text) {
|
|
9456
|
-
return text.replace(/\s+/g, " ").trim();
|
|
9457
|
-
}
|
|
9458
|
-
function safeJson(value, max) {
|
|
9459
|
-
try {
|
|
9460
|
-
return truncateText(JSON.stringify(value) ?? "", max);
|
|
9461
|
-
} catch {
|
|
9462
|
-
return "[unserializable]";
|
|
9463
|
-
}
|
|
9464
|
-
}
|
|
9465
|
-
function formatMessageBody(body, limits = DEFAULT_LIMITS) {
|
|
9466
|
-
if (!body || typeof body !== "object") return null;
|
|
9467
|
-
const b = body;
|
|
9468
|
-
if (b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string") {
|
|
9469
|
-
const text = squash(b.content.text);
|
|
9470
|
-
return text.length > 0 ? `user: ${truncateText(text, limits.line)}` : null;
|
|
9471
|
-
}
|
|
9472
|
-
if (b.role === "session" && b.content && typeof b.content === "object") {
|
|
9473
|
-
const env = b.content;
|
|
9474
|
-
const ev = env.ev;
|
|
9475
|
-
if (!ev || typeof ev.t !== "string") return null;
|
|
9476
|
-
switch (ev.t) {
|
|
9477
|
-
case "text": {
|
|
9478
|
-
if (ev.thinking) return null;
|
|
9479
|
-
const text = squash(String(ev.text ?? ""));
|
|
9480
|
-
if (text.length === 0) return null;
|
|
9481
|
-
const role = env.role === "user" ? "user" : "assistant";
|
|
9482
|
-
return `${role}: ${truncateText(text, limits.line)}`;
|
|
9483
|
-
}
|
|
9484
|
-
case "tool-call-start": {
|
|
9485
|
-
const name = String(ev.name ?? "tool");
|
|
9486
|
-
const title = squash(String(ev.title ?? ""));
|
|
9487
|
-
const args = ev.args ? safeJson(ev.args, limits.args) : "";
|
|
9488
|
-
const detail = title || args;
|
|
9489
|
-
return `[tool] ${name}${detail ? `: ${truncateText(detail, limits.args)}` : ""}`;
|
|
9490
|
-
}
|
|
9491
|
-
case "service": {
|
|
9492
|
-
const text = squash(String(ev.text ?? ""));
|
|
9493
|
-
return text.length > 0 ? `[service] ${truncateText(text, limits.args)}` : null;
|
|
9494
|
-
}
|
|
9495
|
-
case "turn-end": {
|
|
9496
|
-
if (ev.status && ev.status !== "completed") return `[turn ${ev.status}]`;
|
|
9497
|
-
return null;
|
|
9498
|
-
}
|
|
9499
|
-
default:
|
|
9500
|
-
return null;
|
|
9501
|
-
}
|
|
9502
|
-
}
|
|
9503
|
-
if (b.role === "agent" && b.content?.type === "acp" && b.content.data && typeof b.content.data === "object") {
|
|
9504
|
-
const d = b.content.data;
|
|
9505
|
-
switch (d.type) {
|
|
9506
|
-
case "message": {
|
|
9507
|
-
const text = squash(String(d.message ?? ""));
|
|
9508
|
-
return text.length > 0 ? `assistant: ${truncateText(text, limits.line)}` : null;
|
|
9509
|
-
}
|
|
9510
|
-
case "tool-call":
|
|
9511
|
-
return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input, limits.args)}`;
|
|
9512
|
-
case "permission-request":
|
|
9513
|
-
return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")), limits.args)}`;
|
|
9514
|
-
default:
|
|
9515
|
-
return null;
|
|
9516
|
-
}
|
|
9517
|
-
}
|
|
9518
|
-
if (b.role === "agent" && b.content?.type === "output") {
|
|
9519
|
-
const msg = b.content.data?.message;
|
|
9520
|
-
if (msg && Array.isArray(msg.content)) {
|
|
9521
|
-
const texts = msg.content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => squash(c.text)).filter((t) => t.length > 0);
|
|
9522
|
-
if (texts.length > 0) {
|
|
9523
|
-
const role = msg.role === "user" ? "user" : "assistant";
|
|
9524
|
-
return `${role}: ${truncateText(texts.join(" "), limits.line)}`;
|
|
9525
|
-
}
|
|
9526
|
-
}
|
|
9527
|
-
return null;
|
|
9528
|
-
}
|
|
9529
|
-
return null;
|
|
9530
|
-
}
|
|
9531
|
-
function formatTranscript(bodies, options = {}) {
|
|
9532
|
-
const limits = options.full ? NO_LIMITS : DEFAULT_LIMITS;
|
|
9533
|
-
const lines = [];
|
|
9534
|
-
for (const body of bodies) {
|
|
9535
|
-
const line = formatMessageBody(body, limits);
|
|
9536
|
-
if (line !== null) lines.push(line);
|
|
9537
|
-
}
|
|
9538
|
-
return lines.join("\n");
|
|
9539
|
-
}
|
|
9540
|
-
|
|
9541
|
-
function asRecord(value) {
|
|
9542
|
-
return value && typeof value === "object" ? value : null;
|
|
9543
|
-
}
|
|
9544
|
-
function sessionEnvelope(body) {
|
|
9545
|
-
const b = asRecord(body);
|
|
9546
|
-
if (!b || b.role !== "session") return null;
|
|
9547
|
-
const env = asRecord(b.content);
|
|
9548
|
-
if (!env) return null;
|
|
9549
|
-
const ev = asRecord(env.ev);
|
|
9550
|
-
return ev && typeof ev.t === "string" ? { role: env.role, ev } : null;
|
|
9551
|
-
}
|
|
9552
|
-
function isUserPrompt(body) {
|
|
9553
|
-
const b = asRecord(body);
|
|
9554
|
-
return !!b && b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string";
|
|
9555
|
-
}
|
|
9556
|
-
function agentPiece(body) {
|
|
9557
|
-
const env = sessionEnvelope(body);
|
|
9558
|
-
if (env && env.role !== "user") {
|
|
9559
|
-
if (env.ev.t === "text" && !env.ev.thinking) {
|
|
9560
|
-
const text = String(env.ev.text ?? "");
|
|
9561
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9562
|
-
}
|
|
9563
|
-
if (env.ev.t === "tool-call-start") return { kind: "tool" };
|
|
9564
|
-
return null;
|
|
9565
|
-
}
|
|
9566
|
-
const b = asRecord(body);
|
|
9567
|
-
if (b?.role === "agent" && b.content?.type === "acp") {
|
|
9568
|
-
const d = asRecord(b.content.data);
|
|
9569
|
-
if (d?.type === "message") {
|
|
9570
|
-
const text = String(d.message ?? "");
|
|
9571
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9572
|
-
}
|
|
9573
|
-
if (d?.type === "tool-call") return { kind: "tool" };
|
|
9574
|
-
}
|
|
9575
|
-
return null;
|
|
9576
|
-
}
|
|
9577
|
-
function userPromptText(body) {
|
|
9578
|
-
return isUserPrompt(body) ? body.content.text : null;
|
|
9579
|
-
}
|
|
9580
|
-
function analyzeLatestTurn(entries, options = {}) {
|
|
9581
|
-
const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
|
|
9582
|
-
let userIndex = -1;
|
|
9583
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
9584
|
-
if (isUserPrompt(entries[i].body) && (!options.anchor || options.anchor(entries[i].body))) {
|
|
9585
|
-
userIndex = i;
|
|
9586
|
-
break;
|
|
9587
|
-
}
|
|
9588
|
-
}
|
|
9589
|
-
let ended = false;
|
|
9590
|
-
let status = null;
|
|
9591
|
-
let error = null;
|
|
9592
|
-
let texts = [];
|
|
9593
|
-
let lastMarker = null;
|
|
9594
|
-
let startedAfterPrompt = false;
|
|
9595
|
-
const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
|
|
9596
|
-
for (let i = userIndex + 1; i < entries.length; i++) {
|
|
9597
|
-
const body = entries[i].body;
|
|
9598
|
-
if (options.anchor && isUserPrompt(body)) {
|
|
9599
|
-
if (ended) break;
|
|
9600
|
-
continue;
|
|
9601
|
-
}
|
|
9602
|
-
const env = sessionEnvelope(body);
|
|
9603
|
-
if (env?.ev?.t === "turn-start") {
|
|
9604
|
-
lastMarker = "start";
|
|
9605
|
-
startedAfterPrompt = true;
|
|
9606
|
-
ended = false;
|
|
9607
|
-
status = null;
|
|
9608
|
-
error = null;
|
|
9609
|
-
texts = [];
|
|
9610
|
-
continue;
|
|
9611
|
-
}
|
|
9612
|
-
if (env?.ev?.t === "turn-end") {
|
|
9613
|
-
lastMarker = "end";
|
|
9614
|
-
if (userIndex >= 0 && emitsStarts && !startedAfterPrompt) continue;
|
|
9615
|
-
ended = true;
|
|
9616
|
-
status = typeof env.ev.status === "string" ? env.ev.status : null;
|
|
9617
|
-
error = typeof env.ev.error === "string" ? env.ev.error : null;
|
|
9618
|
-
continue;
|
|
9619
|
-
}
|
|
9620
|
-
const piece = agentPiece(body);
|
|
9621
|
-
if (!piece) continue;
|
|
9622
|
-
if (piece.kind === "tool") texts = [];
|
|
9623
|
-
else texts.push(piece.text);
|
|
9624
|
-
}
|
|
9625
|
-
if (userIndex < 0) ended = lastMarker === "end";
|
|
9626
|
-
return {
|
|
9627
|
-
userSeq: userIndex >= 0 ? entries[userIndex].seq : null,
|
|
9628
|
-
ended,
|
|
9629
|
-
status: ended ? status : null,
|
|
9630
|
-
error: ended ? error : null,
|
|
9631
|
-
answer: texts.join("\n\n").trim(),
|
|
9632
|
-
lastSeq
|
|
9633
|
-
};
|
|
9634
|
-
}
|
|
9635
|
-
|
|
9636
|
-
const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
|
|
9637
|
-
async function openUserScopedSocket(token, options = {}) {
|
|
9638
|
-
const socket = io(configuration.serverUrl, {
|
|
9639
|
-
auth: {
|
|
9640
|
-
token,
|
|
9641
|
-
clientType: "user-scoped",
|
|
9642
|
-
happyClient: `cli-session-ops/${configuration.currentCliVersion}`
|
|
9643
|
-
},
|
|
9644
|
-
path: "/v1/updates",
|
|
9645
|
-
transports: ["websocket"],
|
|
9646
|
-
reconnection: false,
|
|
9647
|
-
autoConnect: false
|
|
9648
|
-
});
|
|
9649
|
-
await new Promise((resolve, reject) => {
|
|
9650
|
-
const timer = setTimeout(() => {
|
|
9651
|
-
socket.close();
|
|
9652
|
-
reject(new Error(`Timed out connecting to ${configuration.serverUrl}`));
|
|
9653
|
-
}, options.connectTimeoutMs ?? USER_SOCKET_CONNECT_TIMEOUT_MS);
|
|
9654
|
-
socket.once("connect", () => {
|
|
9655
|
-
clearTimeout(timer);
|
|
9656
|
-
resolve();
|
|
9657
|
-
});
|
|
9658
|
-
socket.once("connect_error", (error) => {
|
|
9659
|
-
clearTimeout(timer);
|
|
9660
|
-
socket.close();
|
|
9661
|
-
reject(new Error(`Could not connect to ${configuration.serverUrl}: ${error.message}`));
|
|
9662
|
-
});
|
|
9663
|
-
socket.connect();
|
|
9664
|
-
});
|
|
9665
|
-
return {
|
|
9666
|
-
async rpcCall(payload, timeoutMs) {
|
|
9667
|
-
const ack = await socket.timeout(timeoutMs).emitWithAck("rpc-call", payload);
|
|
9668
|
-
return ack;
|
|
9669
|
-
},
|
|
9670
|
-
close() {
|
|
9671
|
-
socket.close();
|
|
9672
|
-
}
|
|
9673
|
-
};
|
|
9674
|
-
}
|
|
9675
|
-
|
|
9676
|
-
const RPC_RATE_LIMIT_ERROR = "RPC rate limit reached";
|
|
9677
|
-
const RPC_ACCOUNT_RATE_LIMIT_ERROR = "RPC account rate limit reached";
|
|
9678
|
-
const RPC_RATE_RETRY_MIN_DELAY_MS = 2e3;
|
|
9679
|
-
const RPC_RATE_RETRY_MAX_DELAY_MS = 6e4;
|
|
9680
|
-
const RPC_RATE_MAX_RETRIES = 2;
|
|
9681
|
-
function isRpcRateLimitAck(ack) {
|
|
9682
|
-
if (!ack || typeof ack !== "object") return false;
|
|
9683
|
-
const a = ack;
|
|
9684
|
-
if (a.ok !== false) return false;
|
|
9685
|
-
if (a.code === "rpc_rate_limited" || a.code === "rpc_account_rate_limited") return true;
|
|
9686
|
-
return a.error === RPC_RATE_LIMIT_ERROR || a.error === RPC_ACCOUNT_RATE_LIMIT_ERROR;
|
|
9687
|
-
}
|
|
9688
|
-
function rpcRateRetryDelayMs(attempt, retryAfterMs, random = Math.random()) {
|
|
9689
|
-
if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
|
|
9690
|
-
return Math.min(RPC_RATE_RETRY_MAX_DELAY_MS, Math.round(retryAfterMs * (1 + random * 0.5)));
|
|
9691
|
-
}
|
|
9692
|
-
const exponent = Math.max(0, Math.min(attempt, 32) - 1);
|
|
9693
|
-
const ceiling = Math.min(RPC_RATE_RETRY_MIN_DELAY_MS * 2 ** exponent, RPC_RATE_RETRY_MAX_DELAY_MS);
|
|
9694
|
-
return Math.round(RPC_RATE_RETRY_MIN_DELAY_MS + random * (ceiling - RPC_RATE_RETRY_MIN_DELAY_MS));
|
|
9695
|
-
}
|
|
9696
|
-
|
|
9697
|
-
const PERMISSION_OPS_CLIENT = "session-ops";
|
|
9698
|
-
const PERMISSION_RPC_TIMEOUT_MS = 3e4;
|
|
9699
|
-
const SETTLE_TIMEOUT_MS = 5e3;
|
|
9700
|
-
const SETTLE_POLL_MS = 500;
|
|
9701
|
-
function buildPermissionRpcPayload(requestId, verdict) {
|
|
9702
|
-
if (verdict.kind === "approve") {
|
|
9703
|
-
return {
|
|
9704
|
-
id: requestId,
|
|
9705
|
-
approved: true,
|
|
9706
|
-
decision: verdict.forSession ? "approved_for_session" : "approved"
|
|
9707
|
-
};
|
|
9708
|
-
}
|
|
9709
|
-
return {
|
|
9710
|
-
id: requestId,
|
|
9711
|
-
approved: false,
|
|
9712
|
-
decision: "denied",
|
|
9713
|
-
...verdict.reason ? { reason: verdict.reason } : {}
|
|
9714
|
-
};
|
|
9715
|
-
}
|
|
9716
|
-
function interpretPermissionAck(ack, decryptResult) {
|
|
9717
|
-
if (!ack.ok) {
|
|
9718
|
-
const message = ack.error ?? "RPC call failed";
|
|
9719
|
-
if (message === "RPC method not available") {
|
|
9720
|
-
return { status: "offline", message: "no running wrapper has registered the permission RPC for this session" };
|
|
9721
|
-
}
|
|
9722
|
-
if (message === "RPC target disconnected") {
|
|
9723
|
-
return { status: "offline", message: "the wrapper disconnected while the permission RPC was in flight" };
|
|
9724
|
-
}
|
|
9725
|
-
if (/timed? ?out/i.test(message)) {
|
|
9726
|
-
return { status: "timeout", message };
|
|
9727
|
-
}
|
|
9728
|
-
if (isRpcRateLimitAck(ack)) {
|
|
9729
|
-
return { status: "rate-limited", message, retryAfterMs: rpcRateRetryDelayMs(1, ack.retryAfterMs, 0) };
|
|
9730
|
-
}
|
|
9731
|
-
return { status: "rejected", message };
|
|
9732
|
-
}
|
|
9733
|
-
let body = null;
|
|
9734
|
-
if (typeof ack.result === "string" && ack.result.length > 0) {
|
|
9735
|
-
try {
|
|
9736
|
-
body = decryptResult(ack.result);
|
|
9737
|
-
} catch {
|
|
9738
|
-
body = null;
|
|
9739
|
-
}
|
|
9740
|
-
}
|
|
9741
|
-
if (body && typeof body === "object" && typeof body.error === "string") {
|
|
9742
|
-
return { status: "handler-error", message: body.error };
|
|
9743
|
-
}
|
|
9744
|
-
return { status: "acknowledged" };
|
|
9745
|
-
}
|
|
9746
|
-
function pendingRequestsOf(agentState, now) {
|
|
9747
|
-
const requests = agentState?.requests;
|
|
9748
|
-
if (!requests || typeof requests !== "object") return [];
|
|
9749
|
-
return Object.entries(requests).map(([id, request]) => ({
|
|
9750
|
-
id,
|
|
9751
|
-
tool: typeof request?.tool === "string" ? request.tool : "unknown",
|
|
9752
|
-
...request?.kind ? { kind: request.kind } : {},
|
|
9753
|
-
...typeof request?.createdAt === "number" ? { createdAt: request.createdAt, waitingMs: Math.max(0, now - request.createdAt) } : {}
|
|
9754
|
-
})).sort((a, b) => (a.createdAt ?? Number.MAX_SAFE_INTEGER) - (b.createdAt ?? Number.MAX_SAFE_INTEGER));
|
|
9755
|
-
}
|
|
9756
|
-
const RPC_DEADLINE_GRACE_MS = 2e3;
|
|
9757
|
-
function withDeadline(promise, ms, message) {
|
|
9758
|
-
return new Promise((resolve, reject) => {
|
|
9759
|
-
const timer = setTimeout(() => reject(new Error(`${message} (timed out)`)), ms);
|
|
9760
|
-
promise.then(
|
|
9761
|
-
(value) => {
|
|
9762
|
-
clearTimeout(timer);
|
|
9763
|
-
resolve(value);
|
|
9764
|
-
},
|
|
9765
|
-
(error) => {
|
|
9766
|
-
clearTimeout(timer);
|
|
9767
|
-
reject(error);
|
|
9768
|
-
}
|
|
9769
|
-
);
|
|
9770
|
-
});
|
|
9771
|
-
}
|
|
9772
|
-
async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}) {
|
|
9773
|
-
const now = deps.now ?? Date.now;
|
|
9774
|
-
const persisted = (deps.readPersisted ?? readPersistedSessions)()[sessionId];
|
|
9775
|
-
if (!persisted) {
|
|
9776
|
-
throw new Error(
|
|
9777
|
-
`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.`
|
|
9778
|
-
);
|
|
9779
|
-
}
|
|
9780
|
-
const token = await (deps.bearerToken ?? bearerToken$1)();
|
|
9781
|
-
const fetchState = deps.fetchAgentState ?? fetchDecryptedAgentState;
|
|
9782
|
-
const before = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9783
|
-
if (!before.some((request) => request.id === requestId)) {
|
|
9784
|
-
const hint = before.length > 0 ? `Pending request ids: ${before.map((request) => `${request.id} (${request.tool})`).join(", ")}` : "The session has no pending requests.";
|
|
9785
|
-
throw new Error(`Request ${requestId} is not pending on session ${sessionId}. ${hint}`);
|
|
9786
|
-
}
|
|
9787
|
-
const payload = buildPermissionRpcPayload(requestId, verdict);
|
|
9788
|
-
const key = decodeBase64(persisted.encryptionKey);
|
|
9789
|
-
const params = encodeBase64(encrypt(key, persisted.encryptionVariant, payload));
|
|
9790
|
-
const transport = await (deps.openTransport ?? openUserScopedSocket)(token);
|
|
9791
|
-
const rpcTimeoutMs = deps.rpcTimeoutMs ?? PERMISSION_RPC_TIMEOUT_MS;
|
|
9792
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
9793
|
-
let ack;
|
|
9794
|
-
try {
|
|
9795
|
-
for (let attempt = 1; ; attempt++) {
|
|
9796
|
-
try {
|
|
9797
|
-
ack = await withDeadline(
|
|
9798
|
-
transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
|
|
9799
|
-
rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
|
|
9800
|
-
`permission RPC did not settle within ${rpcTimeoutMs}ms`
|
|
9801
|
-
);
|
|
9802
|
-
} catch (error) {
|
|
9803
|
-
ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
|
|
9804
|
-
}
|
|
9805
|
-
if (!isRpcRateLimitAck(ack) || attempt > RPC_RATE_MAX_RETRIES) break;
|
|
9806
|
-
const waitMs = rpcRateRetryDelayMs(attempt, ack.retryAfterMs, (deps.random ?? Math.random)());
|
|
9807
|
-
deps.onRateLimited?.({ attempt, waitMs, serverError: ack.error ?? "RPC rate limit reached" });
|
|
9808
|
-
await sleep(waitMs);
|
|
9809
|
-
}
|
|
9810
|
-
} finally {
|
|
9811
|
-
transport.close();
|
|
9812
|
-
}
|
|
9813
|
-
const outcome = interpretPermissionAck(ack, (encrypted) => decrypt(key, persisted.encryptionVariant, decodeBase64(encrypted)));
|
|
9814
|
-
if (outcome.status !== "acknowledged") {
|
|
9815
|
-
return { sessionId, requestId, payload, outcome };
|
|
9816
|
-
}
|
|
9817
|
-
const deadline = now() + (deps.settleTimeoutMs ?? SETTLE_TIMEOUT_MS);
|
|
9818
|
-
let settled = false;
|
|
9819
|
-
for (; ; ) {
|
|
9820
|
-
const pending = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9821
|
-
if (!pending.some((request) => request.id === requestId)) {
|
|
9822
|
-
settled = true;
|
|
9823
|
-
break;
|
|
9824
|
-
}
|
|
9825
|
-
if (now() >= deadline) break;
|
|
9826
|
-
await sleep(SETTLE_POLL_MS);
|
|
9827
|
-
}
|
|
9828
|
-
return { sessionId, requestId, payload, outcome, settled };
|
|
9829
|
-
}
|
|
9830
|
-
async function bearerToken$1() {
|
|
9831
|
-
const credentials = await readCredentialsForConfiguredRelay();
|
|
9832
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9833
|
-
return credentials.token;
|
|
9834
|
-
}
|
|
9835
|
-
async function fetchDecryptedAgentState(sessionId, persisted, token) {
|
|
9836
|
-
const response = await axios.get(
|
|
9837
|
-
`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
|
|
9838
|
-
{
|
|
9839
|
-
headers: {
|
|
9840
|
-
"Authorization": `Bearer ${token}`,
|
|
9841
|
-
"X-Happy-Client": `${PERMISSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9842
|
-
},
|
|
9843
|
-
timeout: 15e3,
|
|
9844
|
-
// 404 is a real answer ("not yours / gone"), not a transport fault.
|
|
9845
|
-
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
9846
|
-
}
|
|
9847
|
-
);
|
|
9848
|
-
if (response.status === 404) {
|
|
9849
|
-
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).`);
|
|
9850
|
-
}
|
|
9851
|
-
const raw = response.data?.session?.agentState;
|
|
9852
|
-
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
9853
|
-
return decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(raw));
|
|
9854
|
-
}
|
|
9855
|
-
|
|
9856
|
-
const SESSION_OPS_CLIENT = "session-ops";
|
|
9857
|
-
const DEFAULT_RECENT_LIMIT = 15;
|
|
9858
|
-
const MAX_READ_LIMIT = 100;
|
|
9859
|
-
function mergeSessionSummaries(live, persisted, options = {}) {
|
|
9860
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9861
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9862
|
-
const summaries = [];
|
|
9863
|
-
for (const child of live) {
|
|
9864
|
-
const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
|
|
9865
|
-
if (!id || seen.has(id)) continue;
|
|
9866
|
-
seen.add(id);
|
|
9867
|
-
summaries.push(toSummary(id, persisted[id], livenessOf(child)));
|
|
9868
|
-
}
|
|
9869
|
-
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);
|
|
9870
|
-
for (const [id, entry] of rest) {
|
|
9871
|
-
summaries.push(toSummary(id, entry, { live: false }));
|
|
9872
|
-
}
|
|
9873
|
-
if (!options.tag) return summaries;
|
|
9874
|
-
const wanted = options.tag;
|
|
9875
|
-
return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9876
|
-
}
|
|
9877
|
-
function livenessOf(child) {
|
|
9878
|
-
return { live: true, pid: typeof child.pid === "number" ? child.pid : void 0 };
|
|
9879
|
-
}
|
|
9880
|
-
function sessionLiveness(live, sessionId) {
|
|
9881
|
-
const child = live.find((entry) => entry.happySessionId === sessionId);
|
|
9882
|
-
return child ? livenessOf(child) : { live: false };
|
|
9883
|
-
}
|
|
9884
|
-
function toSummary(id, persisted, extra) {
|
|
9885
|
-
const meta = persisted?.metadata;
|
|
9886
|
-
return {
|
|
9887
|
-
id,
|
|
9888
|
-
live: extra.live,
|
|
9889
|
-
...extra.pid !== void 0 ? { pid: extra.pid } : {},
|
|
9890
|
-
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
9891
|
-
...meta?.path ? { cwd: meta.path } : {},
|
|
9892
|
-
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
9893
|
-
...meta?.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9894
|
-
...meta?.variant ? { variant: meta.variant } : {},
|
|
9895
|
-
url: sessionWebUrl(id),
|
|
9896
|
-
...persisted?.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {}
|
|
9897
|
-
};
|
|
9898
|
-
}
|
|
9899
|
-
async function listSessions(options = {}) {
|
|
9900
|
-
const live = await listDaemonSessions();
|
|
9901
|
-
return mergeSessionSummaries(live, readPersistedSessions(), options);
|
|
9902
|
-
}
|
|
9903
|
-
function summarizeAccountSession(row, persisted, liveIds, now) {
|
|
9904
|
-
const base = {
|
|
9905
|
-
id: row.id,
|
|
9906
|
-
live: liveIds.has(row.id),
|
|
9907
|
-
url: sessionWebUrl(row.id),
|
|
9908
|
-
decryptable: false,
|
|
9909
|
-
active: row.active === true,
|
|
9910
|
-
archived: typeof row.archivedAt === "number",
|
|
9911
|
-
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
9912
|
-
...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {},
|
|
9913
|
-
attention: false
|
|
9914
|
-
};
|
|
9915
|
-
if (!persisted) return base;
|
|
9916
|
-
const key = decodeBase64(persisted.encryptionKey);
|
|
9917
|
-
let metadata = null;
|
|
9918
|
-
let agentState = null;
|
|
9919
|
-
try {
|
|
9920
|
-
metadata = row.metadata ? decrypt(key, persisted.encryptionVariant, decodeBase64(row.metadata)) : null;
|
|
9921
|
-
agentState = row.agentState ? decrypt(key, persisted.encryptionVariant, decodeBase64(row.agentState)) : null;
|
|
9922
|
-
} catch {
|
|
9923
|
-
return base;
|
|
9924
|
-
}
|
|
9925
|
-
if (!metadata) return base;
|
|
9926
|
-
const meta = metadata;
|
|
9927
|
-
const pending = pendingRequestsOf(agentState, now);
|
|
9928
|
-
return {
|
|
9929
|
-
...base,
|
|
9930
|
-
decryptable: true,
|
|
9931
|
-
...meta.summary?.text ? { title: meta.summary.text } : {},
|
|
9932
|
-
...meta.path ? { cwd: meta.path } : {},
|
|
9933
|
-
...meta.flavor ? { flavor: meta.flavor } : {},
|
|
9934
|
-
...meta.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9935
|
-
...meta.variant ? { variant: meta.variant } : {},
|
|
9936
|
-
...meta.machineId ? { machineId: meta.machineId } : {},
|
|
9937
|
-
...persisted.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
|
|
9938
|
-
pending,
|
|
9939
|
-
attention: pending.length > 0
|
|
9940
|
-
};
|
|
9941
|
-
}
|
|
9942
|
-
function orderAccountSessions(summaries, options = {}) {
|
|
9943
|
-
const oldestWait = (summary) => Math.max(0, ...(summary.pending ?? []).map((request) => request.waitingMs ?? 0));
|
|
9944
|
-
const rank = (summary) => summary.attention ? 0 : summary.live ? 1 : 2;
|
|
9945
|
-
let rows = summaries.filter((summary) => summary.flavor !== "terminal-mirror").slice().sort((a, b) => {
|
|
9946
|
-
const byRank = rank(a) - rank(b);
|
|
9947
|
-
if (byRank !== 0) return byRank;
|
|
9948
|
-
if (a.attention && b.attention) return oldestWait(b) - oldestWait(a);
|
|
9949
|
-
return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
|
|
9950
|
-
});
|
|
9951
|
-
if (options.tag) {
|
|
9952
|
-
const wanted = options.tag;
|
|
9953
|
-
rows = rows.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9954
|
-
}
|
|
9955
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9956
|
-
let idleKept = 0;
|
|
9957
|
-
return rows.filter((summary) => rank(summary) < 2 || idleKept++ < recentLimit);
|
|
9958
|
-
}
|
|
9959
|
-
async function listAccountSessions(options = {}) {
|
|
9960
|
-
const token = await bearerToken();
|
|
9961
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/sessions`, {
|
|
9962
|
-
headers: {
|
|
9963
|
-
"Authorization": `Bearer ${token}`,
|
|
9964
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9965
|
-
},
|
|
9966
|
-
timeout: 15e3
|
|
9967
|
-
});
|
|
9968
|
-
const rows = Array.isArray(response.data?.sessions) ? response.data.sessions : [];
|
|
9969
|
-
const persisted = readPersistedSessions();
|
|
9970
|
-
const live = await listDaemonSessions();
|
|
9971
|
-
const liveIds = new Set(live.map((child) => child.happySessionId).filter((id) => typeof id === "string"));
|
|
9972
|
-
const now = Date.now();
|
|
9973
|
-
const summaries = rows.filter((row) => typeof row?.id === "string").map((row) => summarizeAccountSession(row, persisted[row.id], liveIds, now)).filter((summary) => options.includeArchived || !summary.archived);
|
|
9974
|
-
return orderAccountSessions(summaries, options);
|
|
9975
|
-
}
|
|
9976
|
-
async function bearerToken() {
|
|
9977
|
-
const credentials = await readCredentialsForConfiguredRelay();
|
|
9978
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9979
|
-
return credentials.token;
|
|
9980
|
-
}
|
|
9981
|
-
function requirePersisted(sessionId) {
|
|
9982
|
-
const persisted = readPersistedSessions()[sessionId];
|
|
9983
|
-
if (!persisted) {
|
|
9984
|
-
throw new Error(
|
|
9985
|
-
`No local key for session ${sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`
|
|
9986
|
-
);
|
|
9987
|
-
}
|
|
9988
|
-
return persisted;
|
|
9989
|
-
}
|
|
9990
|
-
async function readSessionLog(sessionId, persisted, limit) {
|
|
9991
|
-
const token = await bearerToken();
|
|
9992
|
-
const response = await axios.get(
|
|
9993
|
-
`${configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
9994
|
-
{
|
|
9995
|
-
params: { before_seq: 2147483647, limit },
|
|
9996
|
-
headers: {
|
|
9997
|
-
"Authorization": `Bearer ${token}`,
|
|
9998
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9999
|
-
},
|
|
10000
|
-
timeout: 15e3
|
|
10001
|
-
}
|
|
10002
|
-
);
|
|
10003
|
-
const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
|
|
10004
|
-
messages.reverse();
|
|
10005
|
-
const key = decodeBase64(persisted.encryptionKey);
|
|
10006
|
-
return messages.map((message) => {
|
|
10007
|
-
if (message.content?.t !== "encrypted") return { seq: message.seq, body: null };
|
|
10008
|
-
try {
|
|
10009
|
-
return { seq: message.seq, body: decrypt(key, persisted.encryptionVariant, decodeBase64(message.content.c)) };
|
|
10010
|
-
} catch {
|
|
10011
|
-
return { seq: message.seq, body: null };
|
|
10012
|
-
}
|
|
10013
|
-
});
|
|
10014
|
-
}
|
|
10015
|
-
const TURN_WINDOW = 500;
|
|
10016
|
-
async function readSessionTranscript(sessionId, limit, options = {}) {
|
|
10017
|
-
const persisted = requirePersisted(sessionId);
|
|
10018
|
-
const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
|
|
10019
|
-
const entries = await readSessionLog(sessionId, persisted, Math.max(bounded, TURN_WINDOW));
|
|
10020
|
-
const shown = entries.slice(-bounded);
|
|
10021
|
-
const live = await listDaemonSessions();
|
|
10022
|
-
return {
|
|
10023
|
-
summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
|
|
10024
|
-
messageCount: shown.length,
|
|
10025
|
-
transcript: formatTranscript(shown.map((entry) => entry.body), { full: options.full }),
|
|
10026
|
-
turn: analyzeLatestTurn(entries)
|
|
10027
|
-
};
|
|
10028
|
-
}
|
|
10029
|
-
async function readSessionMetadata(sessionId, persisted) {
|
|
10030
|
-
const token = await bearerToken();
|
|
10031
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
10032
|
-
headers: {
|
|
10033
|
-
"Authorization": `Bearer ${token}`,
|
|
10034
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
10035
|
-
},
|
|
10036
|
-
timeout: 15e3
|
|
10037
|
-
});
|
|
10038
|
-
const ciphertext = response.data?.session?.metadata;
|
|
10039
|
-
if (typeof ciphertext !== "string" || ciphertext.length === 0) return null;
|
|
10040
|
-
return decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(ciphertext));
|
|
10041
|
-
}
|
|
10042
|
-
async function readSessionState(sessionId, persisted) {
|
|
10043
|
-
const token = await bearerToken();
|
|
10044
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
10045
|
-
headers: {
|
|
10046
|
-
"Authorization": `Bearer ${token}`,
|
|
10047
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
10048
|
-
},
|
|
10049
|
-
timeout: 15e3,
|
|
10050
|
-
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
10051
|
-
});
|
|
10052
|
-
if (response.status === 404) return { found: false, active: false };
|
|
10053
|
-
const row = response.data?.session;
|
|
10054
|
-
if (!row || typeof row.id !== "string") throw new Error(`Server returned no session row for ${sessionId}`);
|
|
10055
|
-
const state = {
|
|
10056
|
-
found: true,
|
|
10057
|
-
active: row.active === true,
|
|
10058
|
-
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
10059
|
-
archivedAt: typeof row.archivedAt === "number" ? row.archivedAt : null,
|
|
10060
|
-
...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {}
|
|
10061
|
-
};
|
|
10062
|
-
if (persisted && typeof row.metadata === "string" && row.metadata.length > 0) {
|
|
10063
|
-
try {
|
|
10064
|
-
const metadata = decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(row.metadata));
|
|
10065
|
-
if (metadata?.machineId) state.machineId = metadata.machineId;
|
|
10066
|
-
} catch {
|
|
10067
|
-
}
|
|
10068
|
-
}
|
|
10069
|
-
return state;
|
|
10070
|
-
}
|
|
10071
|
-
class TurnWaitTimeoutError extends Error {
|
|
10072
|
-
constructor(sessionId, timeoutMs, turn) {
|
|
10073
|
-
super(`Session ${sessionId}: the latest turn did not end within ${Math.round(timeoutMs / 1e3)}s`);
|
|
10074
|
-
this.sessionId = sessionId;
|
|
10075
|
-
this.timeoutMs = timeoutMs;
|
|
10076
|
-
this.turn = turn;
|
|
10077
|
-
}
|
|
10078
|
-
}
|
|
10079
|
-
async function waitForTurnEnd(sessionId, options) {
|
|
10080
|
-
const persisted = requirePersisted(sessionId);
|
|
10081
|
-
const pollMs = options.pollMs ?? 3e3;
|
|
10082
|
-
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
10083
|
-
const deadline = Date.now() + options.timeoutMs;
|
|
10084
|
-
while (true) {
|
|
10085
|
-
const turn = analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW));
|
|
10086
|
-
if (turn.ended) return turn;
|
|
10087
|
-
if (Date.now() + pollMs > deadline) throw new TurnWaitTimeoutError(sessionId, options.timeoutMs, turn);
|
|
10088
|
-
await sleep(pollMs);
|
|
10089
|
-
}
|
|
10090
|
-
}
|
|
10091
|
-
async function stopSession(sessionId) {
|
|
10092
|
-
return await stopDaemonSession(sessionId);
|
|
10093
|
-
}
|
|
10094
|
-
async function archiveSession(sessionId) {
|
|
10095
|
-
const token = await bearerToken();
|
|
10096
|
-
await axios.post(
|
|
10097
|
-
`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}/archive`,
|
|
10098
|
-
{},
|
|
10099
|
-
{
|
|
10100
|
-
headers: {
|
|
10101
|
-
"Authorization": `Bearer ${token}`,
|
|
10102
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
10103
|
-
},
|
|
10104
|
-
timeout: 1e4
|
|
10105
|
-
}
|
|
10106
|
-
);
|
|
10107
|
-
}
|
|
10108
|
-
async function unarchiveSession(sessionId) {
|
|
10109
|
-
const token = await bearerToken();
|
|
10110
|
-
await axios.post(
|
|
10111
|
-
`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}/unarchive`,
|
|
10112
|
-
{},
|
|
10113
|
-
{
|
|
10114
|
-
headers: {
|
|
10115
|
-
"Authorization": `Bearer ${token}`,
|
|
10116
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
10117
|
-
},
|
|
10118
|
-
timeout: 1e4
|
|
10119
|
-
}
|
|
10120
|
-
);
|
|
10121
|
-
}
|
|
10122
|
-
|
|
10123
9791
|
function receiptPath(home, operationId) {
|
|
10124
9792
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
|
|
10125
9793
|
return join(home, "teams", "effects", `${operationId}.json`);
|
|
@@ -10709,7 +10377,7 @@ function automationRunAnchor(runId) {
|
|
|
10709
10377
|
async function defaultReadTurn(sessionId, runId) {
|
|
10710
10378
|
const persisted = readPersistedSessions()[sessionId];
|
|
10711
10379
|
if (!persisted) throw new Error(`No local key for session ${sessionId}`);
|
|
10712
|
-
return analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10380
|
+
return analyzeLatestTurn(await readSessionLog$1(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10713
10381
|
}
|
|
10714
10382
|
async function defaultSend(sessionId, text, meta) {
|
|
10715
10383
|
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
@@ -11208,6 +10876,7 @@ async function startDaemon() {
|
|
|
11208
10876
|
logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
11209
10877
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
11210
10878
|
const turnActivity = new TurnActivityTracker();
|
|
10879
|
+
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
11211
10880
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
11212
10881
|
let teamWorker;
|
|
11213
10882
|
let automationRunner;
|
|
@@ -12067,6 +11736,7 @@ async function startDaemon() {
|
|
|
12067
11736
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
12068
11737
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
12069
11738
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11739
|
+
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
12070
11740
|
if (session?.happySessionId && session.encryption) {
|
|
12071
11741
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
12072
11742
|
logger.debug(`[DAEMON RUN] Process PID ${pid} exited, preserved session ${session.happySessionId} for resume`);
|
|
@@ -12152,6 +11822,10 @@ async function startDaemon() {
|
|
|
12152
11822
|
onSessionStateEvent,
|
|
12153
11823
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
12154
11824
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
11825
|
+
onSessionEdit: (edit) => {
|
|
11826
|
+
peerCoordinator.onEdit(edit);
|
|
11827
|
+
},
|
|
11828
|
+
listPeers: () => peerCoordinator.list(),
|
|
12155
11829
|
pushClipboard: (text, terminalId) => {
|
|
12156
11830
|
if (!apiMachineRef) {
|
|
12157
11831
|
return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
|
|
@@ -12217,9 +11891,12 @@ async function startDaemon() {
|
|
|
12217
11891
|
const mirrorManager = createMirrorManager({
|
|
12218
11892
|
api,
|
|
12219
11893
|
machineId,
|
|
12220
|
-
onBindingsChanged: () => apiMachine.requestTerminalListRefresh()
|
|
11894
|
+
onBindingsChanged: () => apiMachine.requestTerminalListRefresh(),
|
|
11895
|
+
onEdit: (edit) => peerCoordinator.onEdit(edit),
|
|
11896
|
+
onSessionEnded: (sessionId) => peerCoordinator.forget(sessionId)
|
|
12221
11897
|
});
|
|
12222
11898
|
mirrorManagerRef = mirrorManager;
|
|
11899
|
+
peerCoordinator.setMirrorSource(() => mirrorManager.listActiveMirrors());
|
|
12223
11900
|
apiMachine.setMirrorIntegration({
|
|
12224
11901
|
resolveMirrorSessionId: (terminalId) => mirrorManager.resolveMirrorSessionId(terminalId),
|
|
12225
11902
|
onTerminalClosed: (terminalId) => mirrorManager.onTerminalClosed(terminalId),
|
|
@@ -12551,127 +12228,6 @@ This session is now connected as the team lead. Call team_inspect to read the au
|
|
|
12551
12228
|
};
|
|
12552
12229
|
}
|
|
12553
12230
|
|
|
12554
|
-
const ACTIVE_FRESH_WINDOW_MS = 15 * 60 * 1e3;
|
|
12555
|
-
const DEFAULT_RESUME_WAIT_MS = 3e4;
|
|
12556
|
-
const DEFAULT_RESUME_POLL_MS = 1e3;
|
|
12557
|
-
function classifySessionState(input) {
|
|
12558
|
-
const { liveHere, server, now } = input;
|
|
12559
|
-
if (!server.found) return "not_found";
|
|
12560
|
-
if (typeof server.archivedAt === "number") return "archived";
|
|
12561
|
-
if (liveHere) return "live";
|
|
12562
|
-
if (server.active && typeof server.activeAt === "number" && now - server.activeAt <= ACTIVE_FRESH_WINDOW_MS) return "live";
|
|
12563
|
-
return "offline";
|
|
12564
|
-
}
|
|
12565
|
-
function explainNotLive(sessionId, status, resumeHint) {
|
|
12566
|
-
switch (status) {
|
|
12567
|
-
case "archived":
|
|
12568
|
-
return `Session ${sessionId} is archived \u2014 no live wrapper will receive this message. ${resumeHint}`;
|
|
12569
|
-
case "offline":
|
|
12570
|
-
return `Session ${sessionId} is offline (its wrapper exited or timed out) \u2014 no live wrapper will receive this message. ${resumeHint}`;
|
|
12571
|
-
case "not_found":
|
|
12572
|
-
return `Session ${sessionId} was not found on this account \u2014 it cannot be messaged or resumed. Spawn a new session instead.`;
|
|
12573
|
-
}
|
|
12574
|
-
}
|
|
12575
|
-
const CLI_RESUME_HINT = "Use --resume to bring it back on this machine, or spawn a new session.";
|
|
12576
|
-
const defaultDeps = {
|
|
12577
|
-
listLive: () => listDaemonSessions(),
|
|
12578
|
-
readState: (sessionId, persisted) => readSessionState(sessionId, persisted),
|
|
12579
|
-
send: (sessionId, persisted, text, client, options) => sendUserMessage(sessionId, persisted, text, client, options),
|
|
12580
|
-
unarchive: unarchiveSession,
|
|
12581
|
-
archive: archiveSession,
|
|
12582
|
-
resume: resumeDaemonSession,
|
|
12583
|
-
sleep: (ms) => delay(ms).then(() => void 0),
|
|
12584
|
-
now: () => Date.now()
|
|
12585
|
-
};
|
|
12586
|
-
async function deliverToSession(sessionId, persisted, text, client, options = {}, deps = {}) {
|
|
12587
|
-
const d = { ...defaultDeps, ...deps };
|
|
12588
|
-
const resumeHint = options.resumeHint ?? CLI_RESUME_HINT;
|
|
12589
|
-
const classify = async () => {
|
|
12590
|
-
const [live, server] = await Promise.all([d.listLive(), d.readState(sessionId, persisted)]);
|
|
12591
|
-
return classifySessionState({ liveHere: sessionLiveness(live, sessionId).live, server, now: d.now() });
|
|
12592
|
-
};
|
|
12593
|
-
let status = await classify();
|
|
12594
|
-
let resumed = false;
|
|
12595
|
-
let resumeOutcome;
|
|
12596
|
-
if (status !== "live") {
|
|
12597
|
-
if (!options.resume || status === "not_found") {
|
|
12598
|
-
return { sessionId, status, delivered: false, resumed: false, stored: false, error: explainNotLive(sessionId, status, resumeHint) };
|
|
12599
|
-
}
|
|
12600
|
-
const wasArchived = status === "archived";
|
|
12601
|
-
if (wasArchived) {
|
|
12602
|
-
try {
|
|
12603
|
-
await d.unarchive(sessionId);
|
|
12604
|
-
} catch (error) {
|
|
12605
|
-
const message = `unarchive failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
12606
|
-
return { sessionId, status, delivered: false, resumed: false, stored: false, error: message, resume: { ok: false, error: message } };
|
|
12607
|
-
}
|
|
12608
|
-
}
|
|
12609
|
-
const outcome = await d.resume(sessionId, {
|
|
12610
|
-
...typeof options.model === "string" ? { model: options.model } : {},
|
|
12611
|
-
...options.permissionMode !== void 0 ? { permissionMode: options.permissionMode } : {}
|
|
12612
|
-
});
|
|
12613
|
-
if (!outcome.success) {
|
|
12614
|
-
let message = outcome.error ?? "resume failed";
|
|
12615
|
-
if (wasArchived) {
|
|
12616
|
-
try {
|
|
12617
|
-
await d.archive(sessionId);
|
|
12618
|
-
} catch (error) {
|
|
12619
|
-
message += `; failed to restore archive state: ${error instanceof Error ? error.message : String(error)}`;
|
|
12620
|
-
}
|
|
12621
|
-
}
|
|
12622
|
-
return { sessionId, status, delivered: false, resumed: false, stored: false, error: message, resume: { ok: false, error: message } };
|
|
12623
|
-
}
|
|
12624
|
-
resumed = true;
|
|
12625
|
-
resumeOutcome = { ok: true };
|
|
12626
|
-
status = await waitUntilLive(classify, d, options);
|
|
12627
|
-
if (status !== "live") {
|
|
12628
|
-
const waited = Math.round((options.waitMs ?? DEFAULT_RESUME_WAIT_MS) / 1e3);
|
|
12629
|
-
return {
|
|
12630
|
-
sessionId,
|
|
12631
|
-
status,
|
|
12632
|
-
delivered: false,
|
|
12633
|
-
resumed,
|
|
12634
|
-
stored: false,
|
|
12635
|
-
resume: resumeOutcome,
|
|
12636
|
-
error: `Session ${sessionId} was resumed but no live wrapper showed up within ${waited}s (state: ${status}); message not sent`
|
|
12637
|
-
};
|
|
12638
|
-
}
|
|
12639
|
-
}
|
|
12640
|
-
await d.send(sessionId, persisted, text, client, {
|
|
12641
|
-
...options.model !== void 0 ? { model: options.model } : {},
|
|
12642
|
-
...options.sentFrom !== void 0 ? { sentFrom: options.sentFrom } : {}
|
|
12643
|
-
});
|
|
12644
|
-
let after = "live";
|
|
12645
|
-
try {
|
|
12646
|
-
after = await classify();
|
|
12647
|
-
} catch {
|
|
12648
|
-
after = "live";
|
|
12649
|
-
}
|
|
12650
|
-
if (after !== "live") {
|
|
12651
|
-
return {
|
|
12652
|
-
sessionId,
|
|
12653
|
-
status: after,
|
|
12654
|
-
delivered: false,
|
|
12655
|
-
resumed,
|
|
12656
|
-
stored: true,
|
|
12657
|
-
...resumeOutcome ? { resume: resumeOutcome } : {},
|
|
12658
|
-
error: `Session ${sessionId} went ${after} while sending: the message is stored server-side but no wrapper is attached to read it. ${after === "not_found" ? "Spawn a new session." : resumeHint}`
|
|
12659
|
-
};
|
|
12660
|
-
}
|
|
12661
|
-
return { sessionId, status: "live", delivered: true, resumed, stored: true, ...resumeOutcome ? { resume: resumeOutcome } : {} };
|
|
12662
|
-
}
|
|
12663
|
-
async function waitUntilLive(classify, d, options) {
|
|
12664
|
-
const waitMs = options.waitMs ?? DEFAULT_RESUME_WAIT_MS;
|
|
12665
|
-
const pollMs = options.pollMs ?? DEFAULT_RESUME_POLL_MS;
|
|
12666
|
-
const deadline = d.now() + waitMs;
|
|
12667
|
-
let status = await classify();
|
|
12668
|
-
while (status !== "live" && d.now() < deadline) {
|
|
12669
|
-
await d.sleep(pollMs);
|
|
12670
|
-
status = await classify();
|
|
12671
|
-
}
|
|
12672
|
-
return status;
|
|
12673
|
-
}
|
|
12674
|
-
|
|
12675
12231
|
const PERSONAL_MEMORY_SOFT_LIMIT_CHARS = 2e3;
|
|
12676
12232
|
function headingTitle(line) {
|
|
12677
12233
|
const m = /^##(?!#)\s*(.*?)\s*$/.exec(line);
|
|
@@ -13038,13 +12594,14 @@ function normalizeProgress(value) {
|
|
|
13038
12594
|
return flat.length > PROGRESS_MAX_CHARS$1 ? flat.slice(0, PROGRESS_MAX_CHARS$1) : flat;
|
|
13039
12595
|
}
|
|
13040
12596
|
|
|
13041
|
-
function createMcpServer(handlers, options, sessionId) {
|
|
12597
|
+
function createMcpServer(handlers, options, sessionId, client) {
|
|
13042
12598
|
const mcp = new McpServer({
|
|
13043
12599
|
name: "Very Happy Tools",
|
|
13044
12600
|
version: "1.0.0"
|
|
13045
12601
|
});
|
|
13046
12602
|
registerTeamsTools(mcp, sessionId);
|
|
13047
12603
|
registerAutomationTools(mcp);
|
|
12604
|
+
if (client) registerSessionPeerTools(mcp, createSessionPeerToolExecutor(client));
|
|
13048
12605
|
mcp.registerTool("change_title", {
|
|
13049
12606
|
description: "Change the title of the current chat session",
|
|
13050
12607
|
title: "Change Chat Title",
|
|
@@ -13247,7 +12804,7 @@ async function startHappyServer(client, options) {
|
|
|
13247
12804
|
}
|
|
13248
12805
|
return;
|
|
13249
12806
|
}
|
|
13250
|
-
const mcp = createMcpServer(handlers, options, client.sessionId);
|
|
12807
|
+
const mcp = createMcpServer(handlers, options, client.sessionId, client);
|
|
13251
12808
|
try {
|
|
13252
12809
|
const transport = new StreamableHTTPServerTransport({
|
|
13253
12810
|
sessionIdGenerator: void 0
|
|
@@ -13288,6 +12845,7 @@ async function startHappyServer(client, options) {
|
|
|
13288
12845
|
REPORT_PROGRESS_TOOL_NAME,
|
|
13289
12846
|
...TEAM_TOOL_NAMES,
|
|
13290
12847
|
...AUTOMATION_TOOL_NAMES,
|
|
12848
|
+
...SESSION_PEER_TOOL_NAMES,
|
|
13291
12849
|
...options?.assistant && !process.env.VH_TEAM_SCOPE_FILE ? ASSISTANT_TOOL_NAMES : []
|
|
13292
12850
|
],
|
|
13293
12851
|
stop: () => {
|
|
@@ -13624,7 +13182,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13624
13182
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13625
13183
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13626
13184
|
const fork = deps.run ?? (async (input) => {
|
|
13627
|
-
const { query } = await import('./index-
|
|
13185
|
+
const { query } = await import('./index-DVVRqaVG.mjs');
|
|
13628
13186
|
return runSideQuestion(query, input);
|
|
13629
13187
|
});
|
|
13630
13188
|
const run = async (input, live) => {
|
|
@@ -14411,6 +13969,17 @@ async function runClaude(credentials, options = {}) {
|
|
|
14411
13969
|
});
|
|
14412
13970
|
session.on("claude-session-message", (body) => boardAnalyzer.noteClaudeMessage(body));
|
|
14413
13971
|
session.on("turn-ended", () => boardAnalyzer.onTurnEnd());
|
|
13972
|
+
const editThrottle = new EditReportThrottle();
|
|
13973
|
+
session.on("claude-session-message", (body) => {
|
|
13974
|
+
try {
|
|
13975
|
+
for (const edit of editThrottle.take(extractClaudeEditPaths(body))) {
|
|
13976
|
+
reportSessionEditToDaemon({ sessionId: response.id, path: edit.path, tool: edit.tool, cwd: workingDirectory, ...edit.at !== void 0 ? { at: edit.at } : {} });
|
|
13977
|
+
}
|
|
13978
|
+
} catch (error) {
|
|
13979
|
+
logger.debug("[START] edit report failed:", error);
|
|
13980
|
+
}
|
|
13981
|
+
});
|
|
13982
|
+
options = { ...options, claudeEnvVars: { ...options.claudeEnvVars, VH_PEER_SESSION_ID: response.id } };
|
|
14414
13983
|
const initialScannerSessionId = forkClaudeSessionId ?? (metadata.claudeSessionId ?? null);
|
|
14415
13984
|
const remoteScanner = await createSessionScanner({
|
|
14416
13985
|
sessionId: initialScannerSessionId,
|
|
@@ -18397,6 +17966,7 @@ async function runCodex(opts) {
|
|
|
18397
17966
|
return "denied";
|
|
18398
17967
|
}
|
|
18399
17968
|
});
|
|
17969
|
+
const editThrottle = new EditReportThrottle();
|
|
18400
17970
|
client.setEventHandler((msg) => {
|
|
18401
17971
|
if (msg.type === "token_count") {
|
|
18402
17972
|
session.sendAgentUsageSnapshot("codex", msg);
|
|
@@ -18460,6 +18030,9 @@ async function runCodex(opts) {
|
|
|
18460
18030
|
}
|
|
18461
18031
|
if (msg.type === "patch_apply_begin") {
|
|
18462
18032
|
const { changes } = msg;
|
|
18033
|
+
for (const edit of editThrottle.take(extractCodexPatchPaths(changes))) {
|
|
18034
|
+
reportSessionEditToDaemon({ sessionId: session.sessionId, path: edit.path, tool: edit.tool, cwd: process.cwd() });
|
|
18035
|
+
}
|
|
18463
18036
|
const changeCount = Object.keys(changes).length;
|
|
18464
18037
|
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
18465
18038
|
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
@@ -18505,6 +18078,7 @@ async function runCodex(opts) {
|
|
|
18505
18078
|
};
|
|
18506
18079
|
let first = true;
|
|
18507
18080
|
let appendSystemPromptInjected = false;
|
|
18081
|
+
process.env.VH_PEER_SESSION_ID = session.sessionId;
|
|
18508
18082
|
try {
|
|
18509
18083
|
logger.debug("[codex]: client.connect begin");
|
|
18510
18084
|
await client.connect();
|
|
@@ -18918,7 +18492,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18918
18492
|
return;
|
|
18919
18493
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18920
18494
|
try {
|
|
18921
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-
|
|
18495
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-tIg7mANc.mjs');
|
|
18922
18496
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18923
18497
|
if (command.action === "help") {
|
|
18924
18498
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18935,7 +18509,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18935
18509
|
}
|
|
18936
18510
|
} else if (subcommand === "agent-home") {
|
|
18937
18511
|
try {
|
|
18938
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-
|
|
18512
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-ByLb2HFg.mjs');
|
|
18939
18513
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18940
18514
|
process.exit(0);
|
|
18941
18515
|
} catch (error) {
|
|
@@ -18944,7 +18518,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18944
18518
|
}
|
|
18945
18519
|
} else if (subcommand === "spawn") {
|
|
18946
18520
|
try {
|
|
18947
|
-
const { handleSpawnCommand } = await import('./spawn-
|
|
18521
|
+
const { handleSpawnCommand } = await import('./spawn-BaPnjClT.mjs');
|
|
18948
18522
|
await handleSpawnCommand(args.slice(1));
|
|
18949
18523
|
} catch (error) {
|
|
18950
18524
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18956,7 +18530,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18956
18530
|
return;
|
|
18957
18531
|
} else if (subcommand === "sessions") {
|
|
18958
18532
|
try {
|
|
18959
|
-
const { handleSessionsCommand } = await import('./sessions-
|
|
18533
|
+
const { handleSessionsCommand } = await import('./sessions-JaVXlSqj.mjs');
|
|
18960
18534
|
await handleSessionsCommand(args.slice(1));
|
|
18961
18535
|
} catch (error) {
|
|
18962
18536
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18968,7 +18542,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18968
18542
|
return;
|
|
18969
18543
|
} else if (subcommand === "send") {
|
|
18970
18544
|
try {
|
|
18971
|
-
const { handleSendCommand } = await import('./send-
|
|
18545
|
+
const { handleSendCommand } = await import('./send-DJStmLBv.mjs');
|
|
18972
18546
|
await handleSendCommand(args.slice(1));
|
|
18973
18547
|
} catch (error) {
|
|
18974
18548
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -19074,9 +18648,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19074
18648
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
19075
18649
|
const projectId = args[3];
|
|
19076
18650
|
try {
|
|
19077
|
-
const { saveGoogleCloudProjectToConfig } = await import('./config-
|
|
19078
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-
|
|
19079
|
-
const { ApiClient: ApiClient2 } = await import('./types-
|
|
18651
|
+
const { saveGoogleCloudProjectToConfig } = await import('./config-BPZ2Y6o8.mjs');
|
|
18652
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-C_DW0M0q.mjs').then(function (n) { return n.aG; });
|
|
18653
|
+
const { ApiClient: ApiClient2 } = await import('./types-CkbfdRzq.mjs').then(function (n) { return n.a1; });
|
|
19080
18654
|
let userEmail = void 0;
|
|
19081
18655
|
try {
|
|
19082
18656
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -19107,7 +18681,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19107
18681
|
}
|
|
19108
18682
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
19109
18683
|
try {
|
|
19110
|
-
const { readGeminiLocalConfig } = await import('./config-
|
|
18684
|
+
const { readGeminiLocalConfig } = await import('./config-BPZ2Y6o8.mjs');
|
|
19111
18685
|
const config = readGeminiLocalConfig();
|
|
19112
18686
|
if (config.googleCloudProject) {
|
|
19113
18687
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -19147,7 +18721,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19147
18721
|
process.exit(0);
|
|
19148
18722
|
}
|
|
19149
18723
|
try {
|
|
19150
|
-
const { runGemini } = await import('./runGemini-
|
|
18724
|
+
const { runGemini } = await import('./runGemini-CDlcQ-OA.mjs');
|
|
19151
18725
|
let startedBy = void 0;
|
|
19152
18726
|
for (let i = 1; i < args.length; i++) {
|
|
19153
18727
|
if (args[i] === "--started-by") {
|
|
@@ -19170,7 +18744,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19170
18744
|
} else if (subcommand === "pi") {
|
|
19171
18745
|
if (args[1] === "--terminal") {
|
|
19172
18746
|
try {
|
|
19173
|
-
const { runPiTerminal } = await import('./piTerminal-
|
|
18747
|
+
const { runPiTerminal } = await import('./piTerminal-Dpq6yqKU.mjs');
|
|
19174
18748
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
19175
18749
|
} catch (error) {
|
|
19176
18750
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -19187,7 +18761,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19187
18761
|
}
|
|
19188
18762
|
};
|
|
19189
18763
|
try {
|
|
19190
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
18764
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BjzdXb9x.mjs');
|
|
19191
18765
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
19192
18766
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
19193
18767
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -19215,7 +18789,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19215
18789
|
return;
|
|
19216
18790
|
} else if (subcommand === "acp") {
|
|
19217
18791
|
try {
|
|
19218
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
18792
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BjzdXb9x.mjs');
|
|
19219
18793
|
let startedBy = void 0;
|
|
19220
18794
|
let verbose = false;
|
|
19221
18795
|
const acpArgs = [];
|
|
@@ -19255,7 +18829,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19255
18829
|
return;
|
|
19256
18830
|
} else if (subcommand === "openclaw") {
|
|
19257
18831
|
try {
|
|
19258
|
-
const { runOpenClaw } = await import('./runOpenClaw-
|
|
18832
|
+
const { runOpenClaw } = await import('./runOpenClaw-YkhsgPs0.mjs');
|
|
19259
18833
|
let startedBy = void 0;
|
|
19260
18834
|
let verbose = false;
|
|
19261
18835
|
let gatewayUrl;
|
|
@@ -19306,7 +18880,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19306
18880
|
return;
|
|
19307
18881
|
} else if (subcommand === "todo") {
|
|
19308
18882
|
try {
|
|
19309
|
-
const { handleTodoCommand } = await import('./todo-
|
|
18883
|
+
const { handleTodoCommand } = await import('./todo-2Z1REhfx.mjs');
|
|
19310
18884
|
await handleTodoCommand(args.slice(1));
|
|
19311
18885
|
} catch (error) {
|
|
19312
18886
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -19315,7 +18889,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19315
18889
|
return;
|
|
19316
18890
|
} else if (subcommand === "teams") {
|
|
19317
18891
|
try {
|
|
19318
|
-
const { handleTeamsCommand } = await import('./teams-
|
|
18892
|
+
const { handleTeamsCommand } = await import('./teams-DXV5Pwwt.mjs');
|
|
19319
18893
|
await handleTeamsCommand(args.slice(1));
|
|
19320
18894
|
} catch (error) {
|
|
19321
18895
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -19324,7 +18898,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19324
18898
|
return;
|
|
19325
18899
|
} else if (subcommand === "auto") {
|
|
19326
18900
|
try {
|
|
19327
|
-
const { handleAutoCommand } = await import('./auto-
|
|
18901
|
+
const { handleAutoCommand } = await import('./auto-BQnD6gVx.mjs');
|
|
19328
18902
|
await handleAutoCommand(args.slice(1));
|
|
19329
18903
|
} catch (error) {
|
|
19330
18904
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -19336,7 +18910,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19336
18910
|
return;
|
|
19337
18911
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
19338
18912
|
try {
|
|
19339
|
-
const { handleMcpCommand } = await import('./mcp-
|
|
18913
|
+
const { handleMcpCommand } = await import('./mcp-o7QglAQM.mjs');
|
|
19340
18914
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
19341
18915
|
} catch (error) {
|
|
19342
18916
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -19706,4 +19280,4 @@ ${chalk.bold("Examples:")}
|
|
|
19706
19280
|
}
|
|
19707
19281
|
}
|
|
19708
19282
|
|
|
19709
|
-
export {
|
|
19283
|
+
export { ALLOWED_SPAWN_PERMISSION_MODES as A, BasePermissionHandler as B, CLAUDE_ATTACHMENT_KINDS as C, DEFAULT_GEMINI_MODEL as D, EditReportThrottle as E, normalizeAcpPermissionMode as F, GEMINI_MODEL_ENV as G, removeSessionModeFile as H, GEMINI_API_KEY_ENV as I, GOOGLE_API_KEY_ENV as J, BaseReasoningProcessor as K, MessageBuffer as L, MessageQueue2 as M, CHANGE_TITLE_INSTRUCTION as N, PI_TEAMS_EXTENSION as P, SPAWN_AGENTS as S, TEAM_SKILL as T, describeAction as a, tmuxSupportsSessionEnv as b, resolveFirstMessageMeta as c, describeTrigger as d, checkPreviewPath as e, registerAssistantSessionTools as f, suggestTitleForPrompt as g, initialMachineMetadata as h, isValidSpawnOrigin as i, createSessionMetadata as j, setupOfflineReconnection as k, StreamRelay as l, hashObject as m, startHappyServer as n, registerAgentAttachmentDownloads as o, parseDuration as p, query as q, resolveSpawnPermissionMode as r, sanitizeSpawnPermissionMode as s, triggerFromFlags as t, TitleGenerator as u, registerKillSessionHandler as v, stageClaudeAttachments as w, appendStagedAttachmentsToPrompt as x, extractAcpEditPaths as y, writeSessionModeFile as z };
|