very-happy-cli 0.2.153 → 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-YDGv4E-g.mjs → AcpBackend-DVB0nzgR.mjs} +2 -2
- package/dist/{AcpBackend-DSt8nTYm.cjs → AcpBackend-DmFcRh54.cjs} +3 -3
- package/dist/{AcpSessionManager-uov6I9C8.cjs → AcpSessionManager-CWaSYTnT.cjs} +1 -1
- package/dist/{AcpSessionManager-Cxf5HQX1.mjs → AcpSessionManager-dGDgNb-p.mjs} +1 -1
- package/dist/agentGuidance-BWxFC6UL.mjs +1613 -0
- package/dist/agentGuidance-un7YPryB.cjs +1665 -0
- package/dist/{agentHome-D68Xh2ae.mjs → agentHome-ByLb2HFg.mjs} +5 -5
- package/dist/{agentHome-BJ7bde35.cjs → agentHome-aucT8BHw.cjs} +5 -5
- package/dist/{auto-CcXxfqkC.mjs → auto-BQnD6gVx.mjs} +21 -21
- package/dist/{auto-C-90ojIH.cjs → auto-P9RoJaY0.cjs} +24 -24
- package/dist/codex/happyMcpStdioBridge.cjs +6 -3
- package/dist/codex/happyMcpStdioBridge.mjs +6 -3
- package/dist/{config-CHJ189UJ.mjs → config-BPZ2Y6o8.mjs} +8 -8
- package/dist/{config-D23kF0_u.cjs → config-BoePh9PX.cjs} +8 -8
- package/dist/{index-D49WvbDT.cjs → index--KCpVs1c.cjs} +7 -7
- package/dist/{index-BacwoXzs.mjs → index-B4oXnbYw.mjs} +522 -729
- package/dist/{index-BgYZGODP.mjs → index-BjzdXb9x.mjs} +21 -13
- package/dist/{index-DEOVn46R.cjs → index-DRrLaljt.cjs} +21 -13
- package/dist/{index-BXA9fxbK.mjs → index-DVVRqaVG.mjs} +7 -7
- package/dist/{index-DHrwHxHf.cjs → index-D_fYUxVg.cjs} +579 -798
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/dist/{install-BiOXBnnS.cjs → install-CGigiocs.cjs} +1 -1
- package/dist/{install-Ca7_Z3bV.mjs → install-DP5aPNEX.mjs} +1 -1
- package/dist/{installTerminalHooks-BF0kL0ae.cjs → installTerminalHooks-XrcPvfBs.cjs} +5 -5
- package/dist/{installTerminalHooks-Bpl32K75.mjs → installTerminalHooks-tIg7mANc.mjs} +5 -5
- package/dist/lib.cjs +2 -2
- package/dist/lib.mjs +2 -2
- package/dist/{limits-BtAuKRl2.mjs → limits-C_DW0M0q.mjs} +309 -13
- package/dist/{limits-DY_sFA3J.cjs → limits-DkfHJ3nS.cjs} +329 -12
- package/dist/{mcp-JpqI2POW.cjs → mcp-BqlXl10F.cjs} +23 -23
- package/dist/{mcp-DQkmk4VV.mjs → mcp-o7QglAQM.mjs} +17 -17
- package/dist/{piRuntime-DRbbeQ72.cjs → piRuntime-BxP4B_zY.cjs} +2 -2
- package/dist/{piRuntime-DBpe12NN.mjs → piRuntime-MjiJI-nV.mjs} +2 -2
- package/dist/{piTerminal-qdJ9KZVL.cjs → piTerminal-DYzRcwgb.cjs} +6 -6
- package/dist/{piTerminal-BoHvPEZ_.mjs → piTerminal-Dpq6yqKU.mjs} +6 -6
- package/dist/{runGemini-CtOEFvhf.cjs → runGemini-8Pxh94Fm.cjs} +7 -7
- package/dist/{runGemini-YqH7a-7O.mjs → runGemini-CDlcQ-OA.mjs} +6 -6
- package/dist/{runOpenClaw-DoqZXBvL.cjs → runOpenClaw-B5FSZIbK.cjs} +6 -6
- package/dist/{runOpenClaw-DSSyFQBb.mjs → runOpenClaw-YkhsgPs0.mjs} +5 -5
- package/dist/{send-9075kHZP.cjs → send-CwrYXTPr.cjs} +58 -54
- package/dist/{send-Cs-RwqNf.mjs → send-DJStmLBv.mjs} +56 -53
- package/dist/{sessions-iBnYzVUK.mjs → sessions-JaVXlSqj.mjs} +88 -45
- package/dist/{sessions-qULzUzlI.cjs → sessions-MX21NJXd.cjs} +99 -56
- package/dist/{spawn-C6fRkedx.mjs → spawn-BaPnjClT.mjs} +22 -22
- package/dist/{spawn-od9V23cc.cjs → spawn-cIoglKVf.cjs} +30 -30
- package/dist/{teams-CkrWQ4uM.mjs → teams-DXV5Pwwt.mjs} +8 -8
- package/dist/{teams-CrCzU-AV.cjs → teams-Dqk7F3D-.cjs} +8 -8
- package/dist/{todo-aCZmSt27.mjs → todo-2Z1REhfx.mjs} +8 -6
- package/dist/{todo-BCT_ZPuV.cjs → todo-Buuqn9gm.cjs} +8 -6
- package/dist/{types-BvqOVUOW.mjs → types-CkbfdRzq.mjs} +7 -267
- package/dist/{types-CUCU7Vw8.cjs → types-a2xsH2UI.cjs} +19 -295
- package/package.json +7 -7
- package/dist/agentGuidance-BM4kMNyr.cjs +0 -553
- package/dist/agentGuidance-C8unGWp7.mjs +0 -529
|
@@ -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",
|
|
@@ -6262,6 +6209,7 @@ function startDaemonControlServer({
|
|
|
6262
6209
|
getChildren,
|
|
6263
6210
|
stopSession,
|
|
6264
6211
|
spawnSession,
|
|
6212
|
+
resumeSession,
|
|
6265
6213
|
requestShutdown,
|
|
6266
6214
|
onHappySessionWebhook,
|
|
6267
6215
|
onSessionStateEvent,
|
|
@@ -6270,7 +6218,9 @@ function startDaemonControlServer({
|
|
|
6270
6218
|
pushClipboard,
|
|
6271
6219
|
pushFilePreview,
|
|
6272
6220
|
onTerminalHook,
|
|
6273
|
-
setTerminalTitle
|
|
6221
|
+
setTerminalTitle,
|
|
6222
|
+
onSessionEdit,
|
|
6223
|
+
listPeers
|
|
6274
6224
|
}) {
|
|
6275
6225
|
return new Promise((resolve) => {
|
|
6276
6226
|
const app = fastify({
|
|
@@ -6474,6 +6424,48 @@ function startDaemonControlServer({
|
|
|
6474
6424
|
};
|
|
6475
6425
|
}
|
|
6476
6426
|
});
|
|
6427
|
+
typed.post("/resume-session", {
|
|
6428
|
+
schema: {
|
|
6429
|
+
body: z.object({
|
|
6430
|
+
sessionId: z.string().min(1).max(200),
|
|
6431
|
+
model: z.string().optional(),
|
|
6432
|
+
permissionMode: z.string().optional()
|
|
6433
|
+
}),
|
|
6434
|
+
response: {
|
|
6435
|
+
200: z.object({
|
|
6436
|
+
success: z.boolean(),
|
|
6437
|
+
sessionId: z.string().optional(),
|
|
6438
|
+
error: z.string().optional()
|
|
6439
|
+
}),
|
|
6440
|
+
500: z.object({
|
|
6441
|
+
success: z.boolean(),
|
|
6442
|
+
error: z.string().optional()
|
|
6443
|
+
}),
|
|
6444
|
+
503: z.object({
|
|
6445
|
+
success: z.boolean(),
|
|
6446
|
+
error: z.string().optional()
|
|
6447
|
+
})
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
}, async (request, reply) => {
|
|
6451
|
+
const { sessionId, model, permissionMode } = request.body;
|
|
6452
|
+
if (!resumeSession) {
|
|
6453
|
+
reply.code(503);
|
|
6454
|
+
return { success: false, error: "daemon is still starting up" };
|
|
6455
|
+
}
|
|
6456
|
+
logger.debug(`[CONTROL SERVER] Resume session request: ${sessionId} (model=${model ?? "unset"}, permissionMode=${permissionMode ?? "unset"})`);
|
|
6457
|
+
const result = await resumeSession(sessionId, { model, permissionMode });
|
|
6458
|
+
switch (result.type) {
|
|
6459
|
+
case "success":
|
|
6460
|
+
return { success: true, sessionId: result.sessionId };
|
|
6461
|
+
case "requestToApproveDirectoryCreation":
|
|
6462
|
+
reply.code(500);
|
|
6463
|
+
return { success: false, error: `Cannot resume: working directory ${result.directory} needs to be created first` };
|
|
6464
|
+
case "error":
|
|
6465
|
+
reply.code(500);
|
|
6466
|
+
return { success: false, error: result.errorMessage };
|
|
6467
|
+
}
|
|
6468
|
+
});
|
|
6477
6469
|
typed.post("/terminal-hook", {
|
|
6478
6470
|
schema: {
|
|
6479
6471
|
body: z.any(),
|
|
@@ -6551,6 +6543,45 @@ function startDaemonControlServer({
|
|
|
6551
6543
|
}
|
|
6552
6544
|
return { status: "ok" };
|
|
6553
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() : [] }));
|
|
6554
6585
|
typed.post("/stop", {
|
|
6555
6586
|
schema: {
|
|
6556
6587
|
response: {
|
|
@@ -6586,6 +6617,273 @@ function startDaemonControlServer({
|
|
|
6586
6617
|
});
|
|
6587
6618
|
}
|
|
6588
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
|
+
|
|
6589
6887
|
const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
|
|
6590
6888
|
|
|
6591
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
|
|
@@ -8505,6 +8803,73 @@ class ClaudeAuthService {
|
|
|
8505
8803
|
}
|
|
8506
8804
|
}
|
|
8507
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
|
+
|
|
8508
8873
|
const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set(["file-history-snapshot", "change", "queue-operation"]);
|
|
8509
8874
|
function parseLines(lines) {
|
|
8510
8875
|
const out = [];
|
|
@@ -8541,12 +8906,15 @@ function createMirrorScanner(opts) {
|
|
|
8541
8906
|
}
|
|
8542
8907
|
tail.offset = 0;
|
|
8543
8908
|
}
|
|
8909
|
+
let replaced = false;
|
|
8544
8910
|
if (tail.offset > size) {
|
|
8545
8911
|
logger.debug(`[MIRROR SCANNER] ${tail.path}: offset ${tail.offset} > size ${size} \u2014 file replaced, resetting (localId idempotency absorbs replays)`);
|
|
8546
8912
|
tail.offset = 0;
|
|
8913
|
+
replaced = true;
|
|
8547
8914
|
}
|
|
8548
8915
|
if (tail.offset === size) return;
|
|
8549
8916
|
const isFirstBackfillRead = tail.mode === "backfill-tail" && tail.offset === 0;
|
|
8917
|
+
const replay = isFirstBackfillRead || replaced;
|
|
8550
8918
|
const handle = await open$1(tail.path, "r");
|
|
8551
8919
|
let buf;
|
|
8552
8920
|
try {
|
|
@@ -8566,7 +8934,7 @@ function createMirrorScanner(opts) {
|
|
|
8566
8934
|
if (decision.truncated) opts.events.onBackfillTruncated?.();
|
|
8567
8935
|
tail.mode = "from-eof";
|
|
8568
8936
|
}
|
|
8569
|
-
if (messages.length > 0) opts.events.onMessages(messages);
|
|
8937
|
+
if (messages.length > 0) opts.events.onMessages(messages, { replay });
|
|
8570
8938
|
};
|
|
8571
8939
|
const sync = new InvalidateSync(async () => {
|
|
8572
8940
|
for (const tail of files.values()) {
|
|
@@ -8689,8 +9057,17 @@ function createMirrorManager(deps) {
|
|
|
8689
9057
|
binding.client.updateMetadata((current) => ({ ...current, ...patch }));
|
|
8690
9058
|
persistBinding(binding);
|
|
8691
9059
|
};
|
|
8692
|
-
const sendMessages = (binding, messages) => {
|
|
9060
|
+
const sendMessages = (binding, messages, context = { replay: false }) => {
|
|
8693
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
|
+
}
|
|
8694
9071
|
const key = mirrorLineKey(message);
|
|
8695
9072
|
binding.client.sendClaudeSessionMessage(message, key ? { localIdFor: (envelopeIndex) => mirrorLocalId(key, envelopeIndex) } : void 0);
|
|
8696
9073
|
}
|
|
@@ -8699,7 +9076,7 @@ function createMirrorManager(deps) {
|
|
|
8699
9076
|
const scanner = createMirrorScanner({
|
|
8700
9077
|
backfillLines,
|
|
8701
9078
|
events: {
|
|
8702
|
-
onMessages: (messages) => sendMessages(binding, messages),
|
|
9079
|
+
onMessages: (messages, context) => sendMessages(binding, messages, context),
|
|
8703
9080
|
onBackfillTruncated: opts.withTruncationNotice ? () => binding.client.sendSessionEvent({
|
|
8704
9081
|
type: "message",
|
|
8705
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`
|
|
@@ -8722,6 +9099,10 @@ function createMirrorManager(deps) {
|
|
|
8722
9099
|
await binding.scanner?.cleanup();
|
|
8723
9100
|
binding.scanner = null;
|
|
8724
9101
|
binding.client.closeClaudeSessionTurn("completed");
|
|
9102
|
+
try {
|
|
9103
|
+
deps.onSessionEnded?.(binding.happySessionId);
|
|
9104
|
+
} catch {
|
|
9105
|
+
}
|
|
8725
9106
|
updateBindingMetadata(binding, {
|
|
8726
9107
|
lifecycleState: "archived",
|
|
8727
9108
|
lifecycleStateSince: Date.now(),
|
|
@@ -8934,6 +9315,13 @@ function createMirrorManager(deps) {
|
|
|
8934
9315
|
}
|
|
8935
9316
|
};
|
|
8936
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
|
+
},
|
|
8937
9325
|
handleHookPayload(body) {
|
|
8938
9326
|
const event = parseTerminalHookPayload(body);
|
|
8939
9327
|
if (!event) {
|
|
@@ -9400,640 +9788,6 @@ Delegate from this working directory unless a different repository was explicitl
|
|
|
9400
9788
|
${goal}`;
|
|
9401
9789
|
}
|
|
9402
9790
|
|
|
9403
|
-
const MAX_LINE_CHARS = 500;
|
|
9404
|
-
const MAX_ARGS_CHARS = 160;
|
|
9405
|
-
const DEFAULT_LIMITS = { line: MAX_LINE_CHARS, args: MAX_ARGS_CHARS };
|
|
9406
|
-
const NO_LIMITS = { line: Number.POSITIVE_INFINITY, args: Number.POSITIVE_INFINITY };
|
|
9407
|
-
function truncateText(text, max = MAX_LINE_CHARS) {
|
|
9408
|
-
const chars = Array.from(text);
|
|
9409
|
-
if (chars.length <= max) return text;
|
|
9410
|
-
return chars.slice(0, max).join("") + ` \u2026[+${chars.length - max} chars]`;
|
|
9411
|
-
}
|
|
9412
|
-
function squash(text) {
|
|
9413
|
-
return text.replace(/\s+/g, " ").trim();
|
|
9414
|
-
}
|
|
9415
|
-
function safeJson(value, max) {
|
|
9416
|
-
try {
|
|
9417
|
-
return truncateText(JSON.stringify(value) ?? "", max);
|
|
9418
|
-
} catch {
|
|
9419
|
-
return "[unserializable]";
|
|
9420
|
-
}
|
|
9421
|
-
}
|
|
9422
|
-
function formatMessageBody(body, limits = DEFAULT_LIMITS) {
|
|
9423
|
-
if (!body || typeof body !== "object") return null;
|
|
9424
|
-
const b = body;
|
|
9425
|
-
if (b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string") {
|
|
9426
|
-
const text = squash(b.content.text);
|
|
9427
|
-
return text.length > 0 ? `user: ${truncateText(text, limits.line)}` : null;
|
|
9428
|
-
}
|
|
9429
|
-
if (b.role === "session" && b.content && typeof b.content === "object") {
|
|
9430
|
-
const env = b.content;
|
|
9431
|
-
const ev = env.ev;
|
|
9432
|
-
if (!ev || typeof ev.t !== "string") return null;
|
|
9433
|
-
switch (ev.t) {
|
|
9434
|
-
case "text": {
|
|
9435
|
-
if (ev.thinking) return null;
|
|
9436
|
-
const text = squash(String(ev.text ?? ""));
|
|
9437
|
-
if (text.length === 0) return null;
|
|
9438
|
-
const role = env.role === "user" ? "user" : "assistant";
|
|
9439
|
-
return `${role}: ${truncateText(text, limits.line)}`;
|
|
9440
|
-
}
|
|
9441
|
-
case "tool-call-start": {
|
|
9442
|
-
const name = String(ev.name ?? "tool");
|
|
9443
|
-
const title = squash(String(ev.title ?? ""));
|
|
9444
|
-
const args = ev.args ? safeJson(ev.args, limits.args) : "";
|
|
9445
|
-
const detail = title || args;
|
|
9446
|
-
return `[tool] ${name}${detail ? `: ${truncateText(detail, limits.args)}` : ""}`;
|
|
9447
|
-
}
|
|
9448
|
-
case "service": {
|
|
9449
|
-
const text = squash(String(ev.text ?? ""));
|
|
9450
|
-
return text.length > 0 ? `[service] ${truncateText(text, limits.args)}` : null;
|
|
9451
|
-
}
|
|
9452
|
-
case "turn-end": {
|
|
9453
|
-
if (ev.status && ev.status !== "completed") return `[turn ${ev.status}]`;
|
|
9454
|
-
return null;
|
|
9455
|
-
}
|
|
9456
|
-
default:
|
|
9457
|
-
return null;
|
|
9458
|
-
}
|
|
9459
|
-
}
|
|
9460
|
-
if (b.role === "agent" && b.content?.type === "acp" && b.content.data && typeof b.content.data === "object") {
|
|
9461
|
-
const d = b.content.data;
|
|
9462
|
-
switch (d.type) {
|
|
9463
|
-
case "message": {
|
|
9464
|
-
const text = squash(String(d.message ?? ""));
|
|
9465
|
-
return text.length > 0 ? `assistant: ${truncateText(text, limits.line)}` : null;
|
|
9466
|
-
}
|
|
9467
|
-
case "tool-call":
|
|
9468
|
-
return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input, limits.args)}`;
|
|
9469
|
-
case "permission-request":
|
|
9470
|
-
return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")), limits.args)}`;
|
|
9471
|
-
default:
|
|
9472
|
-
return null;
|
|
9473
|
-
}
|
|
9474
|
-
}
|
|
9475
|
-
if (b.role === "agent" && b.content?.type === "output") {
|
|
9476
|
-
const msg = b.content.data?.message;
|
|
9477
|
-
if (msg && Array.isArray(msg.content)) {
|
|
9478
|
-
const texts = msg.content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => squash(c.text)).filter((t) => t.length > 0);
|
|
9479
|
-
if (texts.length > 0) {
|
|
9480
|
-
const role = msg.role === "user" ? "user" : "assistant";
|
|
9481
|
-
return `${role}: ${truncateText(texts.join(" "), limits.line)}`;
|
|
9482
|
-
}
|
|
9483
|
-
}
|
|
9484
|
-
return null;
|
|
9485
|
-
}
|
|
9486
|
-
return null;
|
|
9487
|
-
}
|
|
9488
|
-
function formatTranscript(bodies, options = {}) {
|
|
9489
|
-
const limits = options.full ? NO_LIMITS : DEFAULT_LIMITS;
|
|
9490
|
-
const lines = [];
|
|
9491
|
-
for (const body of bodies) {
|
|
9492
|
-
const line = formatMessageBody(body, limits);
|
|
9493
|
-
if (line !== null) lines.push(line);
|
|
9494
|
-
}
|
|
9495
|
-
return lines.join("\n");
|
|
9496
|
-
}
|
|
9497
|
-
|
|
9498
|
-
function asRecord(value) {
|
|
9499
|
-
return value && typeof value === "object" ? value : null;
|
|
9500
|
-
}
|
|
9501
|
-
function sessionEnvelope(body) {
|
|
9502
|
-
const b = asRecord(body);
|
|
9503
|
-
if (!b || b.role !== "session") return null;
|
|
9504
|
-
const env = asRecord(b.content);
|
|
9505
|
-
if (!env) return null;
|
|
9506
|
-
const ev = asRecord(env.ev);
|
|
9507
|
-
return ev && typeof ev.t === "string" ? { role: env.role, ev } : null;
|
|
9508
|
-
}
|
|
9509
|
-
function isUserPrompt(body) {
|
|
9510
|
-
const b = asRecord(body);
|
|
9511
|
-
return !!b && b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string";
|
|
9512
|
-
}
|
|
9513
|
-
function agentPiece(body) {
|
|
9514
|
-
const env = sessionEnvelope(body);
|
|
9515
|
-
if (env && env.role !== "user") {
|
|
9516
|
-
if (env.ev.t === "text" && !env.ev.thinking) {
|
|
9517
|
-
const text = String(env.ev.text ?? "");
|
|
9518
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9519
|
-
}
|
|
9520
|
-
if (env.ev.t === "tool-call-start") return { kind: "tool" };
|
|
9521
|
-
return null;
|
|
9522
|
-
}
|
|
9523
|
-
const b = asRecord(body);
|
|
9524
|
-
if (b?.role === "agent" && b.content?.type === "acp") {
|
|
9525
|
-
const d = asRecord(b.content.data);
|
|
9526
|
-
if (d?.type === "message") {
|
|
9527
|
-
const text = String(d.message ?? "");
|
|
9528
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9529
|
-
}
|
|
9530
|
-
if (d?.type === "tool-call") return { kind: "tool" };
|
|
9531
|
-
}
|
|
9532
|
-
return null;
|
|
9533
|
-
}
|
|
9534
|
-
function userPromptText(body) {
|
|
9535
|
-
return isUserPrompt(body) ? body.content.text : null;
|
|
9536
|
-
}
|
|
9537
|
-
function analyzeLatestTurn(entries, options = {}) {
|
|
9538
|
-
const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
|
|
9539
|
-
let userIndex = -1;
|
|
9540
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
9541
|
-
if (isUserPrompt(entries[i].body) && (!options.anchor || options.anchor(entries[i].body))) {
|
|
9542
|
-
userIndex = i;
|
|
9543
|
-
break;
|
|
9544
|
-
}
|
|
9545
|
-
}
|
|
9546
|
-
let ended = false;
|
|
9547
|
-
let status = null;
|
|
9548
|
-
let error = null;
|
|
9549
|
-
let texts = [];
|
|
9550
|
-
let lastMarker = null;
|
|
9551
|
-
let startedAfterPrompt = false;
|
|
9552
|
-
const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
|
|
9553
|
-
for (let i = userIndex + 1; i < entries.length; i++) {
|
|
9554
|
-
const body = entries[i].body;
|
|
9555
|
-
if (options.anchor && isUserPrompt(body)) {
|
|
9556
|
-
if (ended) break;
|
|
9557
|
-
continue;
|
|
9558
|
-
}
|
|
9559
|
-
const env = sessionEnvelope(body);
|
|
9560
|
-
if (env?.ev?.t === "turn-start") {
|
|
9561
|
-
lastMarker = "start";
|
|
9562
|
-
startedAfterPrompt = true;
|
|
9563
|
-
ended = false;
|
|
9564
|
-
status = null;
|
|
9565
|
-
error = null;
|
|
9566
|
-
texts = [];
|
|
9567
|
-
continue;
|
|
9568
|
-
}
|
|
9569
|
-
if (env?.ev?.t === "turn-end") {
|
|
9570
|
-
lastMarker = "end";
|
|
9571
|
-
if (userIndex >= 0 && emitsStarts && !startedAfterPrompt) continue;
|
|
9572
|
-
ended = true;
|
|
9573
|
-
status = typeof env.ev.status === "string" ? env.ev.status : null;
|
|
9574
|
-
error = typeof env.ev.error === "string" ? env.ev.error : null;
|
|
9575
|
-
continue;
|
|
9576
|
-
}
|
|
9577
|
-
const piece = agentPiece(body);
|
|
9578
|
-
if (!piece) continue;
|
|
9579
|
-
if (piece.kind === "tool") texts = [];
|
|
9580
|
-
else texts.push(piece.text);
|
|
9581
|
-
}
|
|
9582
|
-
if (userIndex < 0) ended = lastMarker === "end";
|
|
9583
|
-
return {
|
|
9584
|
-
userSeq: userIndex >= 0 ? entries[userIndex].seq : null,
|
|
9585
|
-
ended,
|
|
9586
|
-
status: ended ? status : null,
|
|
9587
|
-
error: ended ? error : null,
|
|
9588
|
-
answer: texts.join("\n\n").trim(),
|
|
9589
|
-
lastSeq
|
|
9590
|
-
};
|
|
9591
|
-
}
|
|
9592
|
-
|
|
9593
|
-
const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
|
|
9594
|
-
async function openUserScopedSocket(token, options = {}) {
|
|
9595
|
-
const socket = io(configuration.serverUrl, {
|
|
9596
|
-
auth: {
|
|
9597
|
-
token,
|
|
9598
|
-
clientType: "user-scoped",
|
|
9599
|
-
happyClient: `cli-session-ops/${configuration.currentCliVersion}`
|
|
9600
|
-
},
|
|
9601
|
-
path: "/v1/updates",
|
|
9602
|
-
transports: ["websocket"],
|
|
9603
|
-
reconnection: false,
|
|
9604
|
-
autoConnect: false
|
|
9605
|
-
});
|
|
9606
|
-
await new Promise((resolve, reject) => {
|
|
9607
|
-
const timer = setTimeout(() => {
|
|
9608
|
-
socket.close();
|
|
9609
|
-
reject(new Error(`Timed out connecting to ${configuration.serverUrl}`));
|
|
9610
|
-
}, options.connectTimeoutMs ?? USER_SOCKET_CONNECT_TIMEOUT_MS);
|
|
9611
|
-
socket.once("connect", () => {
|
|
9612
|
-
clearTimeout(timer);
|
|
9613
|
-
resolve();
|
|
9614
|
-
});
|
|
9615
|
-
socket.once("connect_error", (error) => {
|
|
9616
|
-
clearTimeout(timer);
|
|
9617
|
-
socket.close();
|
|
9618
|
-
reject(new Error(`Could not connect to ${configuration.serverUrl}: ${error.message}`));
|
|
9619
|
-
});
|
|
9620
|
-
socket.connect();
|
|
9621
|
-
});
|
|
9622
|
-
return {
|
|
9623
|
-
async rpcCall(payload, timeoutMs) {
|
|
9624
|
-
const ack = await socket.timeout(timeoutMs).emitWithAck("rpc-call", payload);
|
|
9625
|
-
return ack;
|
|
9626
|
-
},
|
|
9627
|
-
close() {
|
|
9628
|
-
socket.close();
|
|
9629
|
-
}
|
|
9630
|
-
};
|
|
9631
|
-
}
|
|
9632
|
-
|
|
9633
|
-
const RPC_RATE_LIMIT_ERROR = "RPC rate limit reached";
|
|
9634
|
-
const RPC_ACCOUNT_RATE_LIMIT_ERROR = "RPC account rate limit reached";
|
|
9635
|
-
const RPC_RATE_RETRY_MIN_DELAY_MS = 2e3;
|
|
9636
|
-
const RPC_RATE_RETRY_MAX_DELAY_MS = 6e4;
|
|
9637
|
-
const RPC_RATE_MAX_RETRIES = 2;
|
|
9638
|
-
function isRpcRateLimitAck(ack) {
|
|
9639
|
-
if (!ack || typeof ack !== "object") return false;
|
|
9640
|
-
const a = ack;
|
|
9641
|
-
if (a.ok !== false) return false;
|
|
9642
|
-
if (a.code === "rpc_rate_limited" || a.code === "rpc_account_rate_limited") return true;
|
|
9643
|
-
return a.error === RPC_RATE_LIMIT_ERROR || a.error === RPC_ACCOUNT_RATE_LIMIT_ERROR;
|
|
9644
|
-
}
|
|
9645
|
-
function rpcRateRetryDelayMs(attempt, retryAfterMs, random = Math.random()) {
|
|
9646
|
-
if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
|
|
9647
|
-
return Math.min(RPC_RATE_RETRY_MAX_DELAY_MS, Math.round(retryAfterMs * (1 + random * 0.5)));
|
|
9648
|
-
}
|
|
9649
|
-
const exponent = Math.max(0, Math.min(attempt, 32) - 1);
|
|
9650
|
-
const ceiling = Math.min(RPC_RATE_RETRY_MIN_DELAY_MS * 2 ** exponent, RPC_RATE_RETRY_MAX_DELAY_MS);
|
|
9651
|
-
return Math.round(RPC_RATE_RETRY_MIN_DELAY_MS + random * (ceiling - RPC_RATE_RETRY_MIN_DELAY_MS));
|
|
9652
|
-
}
|
|
9653
|
-
|
|
9654
|
-
const PERMISSION_OPS_CLIENT = "session-ops";
|
|
9655
|
-
const PERMISSION_RPC_TIMEOUT_MS = 3e4;
|
|
9656
|
-
const SETTLE_TIMEOUT_MS = 5e3;
|
|
9657
|
-
const SETTLE_POLL_MS = 500;
|
|
9658
|
-
function buildPermissionRpcPayload(requestId, verdict) {
|
|
9659
|
-
if (verdict.kind === "approve") {
|
|
9660
|
-
return {
|
|
9661
|
-
id: requestId,
|
|
9662
|
-
approved: true,
|
|
9663
|
-
decision: verdict.forSession ? "approved_for_session" : "approved"
|
|
9664
|
-
};
|
|
9665
|
-
}
|
|
9666
|
-
return {
|
|
9667
|
-
id: requestId,
|
|
9668
|
-
approved: false,
|
|
9669
|
-
decision: "denied",
|
|
9670
|
-
...verdict.reason ? { reason: verdict.reason } : {}
|
|
9671
|
-
};
|
|
9672
|
-
}
|
|
9673
|
-
function interpretPermissionAck(ack, decryptResult) {
|
|
9674
|
-
if (!ack.ok) {
|
|
9675
|
-
const message = ack.error ?? "RPC call failed";
|
|
9676
|
-
if (message === "RPC method not available") {
|
|
9677
|
-
return { status: "offline", message: "no running wrapper has registered the permission RPC for this session" };
|
|
9678
|
-
}
|
|
9679
|
-
if (message === "RPC target disconnected") {
|
|
9680
|
-
return { status: "offline", message: "the wrapper disconnected while the permission RPC was in flight" };
|
|
9681
|
-
}
|
|
9682
|
-
if (/timed? ?out/i.test(message)) {
|
|
9683
|
-
return { status: "timeout", message };
|
|
9684
|
-
}
|
|
9685
|
-
if (isRpcRateLimitAck(ack)) {
|
|
9686
|
-
return { status: "rate-limited", message, retryAfterMs: rpcRateRetryDelayMs(1, ack.retryAfterMs, 0) };
|
|
9687
|
-
}
|
|
9688
|
-
return { status: "rejected", message };
|
|
9689
|
-
}
|
|
9690
|
-
let body = null;
|
|
9691
|
-
if (typeof ack.result === "string" && ack.result.length > 0) {
|
|
9692
|
-
try {
|
|
9693
|
-
body = decryptResult(ack.result);
|
|
9694
|
-
} catch {
|
|
9695
|
-
body = null;
|
|
9696
|
-
}
|
|
9697
|
-
}
|
|
9698
|
-
if (body && typeof body === "object" && typeof body.error === "string") {
|
|
9699
|
-
return { status: "handler-error", message: body.error };
|
|
9700
|
-
}
|
|
9701
|
-
return { status: "acknowledged" };
|
|
9702
|
-
}
|
|
9703
|
-
function pendingRequestsOf(agentState, now) {
|
|
9704
|
-
const requests = agentState?.requests;
|
|
9705
|
-
if (!requests || typeof requests !== "object") return [];
|
|
9706
|
-
return Object.entries(requests).map(([id, request]) => ({
|
|
9707
|
-
id,
|
|
9708
|
-
tool: typeof request?.tool === "string" ? request.tool : "unknown",
|
|
9709
|
-
...request?.kind ? { kind: request.kind } : {},
|
|
9710
|
-
...typeof request?.createdAt === "number" ? { createdAt: request.createdAt, waitingMs: Math.max(0, now - request.createdAt) } : {}
|
|
9711
|
-
})).sort((a, b) => (a.createdAt ?? Number.MAX_SAFE_INTEGER) - (b.createdAt ?? Number.MAX_SAFE_INTEGER));
|
|
9712
|
-
}
|
|
9713
|
-
const RPC_DEADLINE_GRACE_MS = 2e3;
|
|
9714
|
-
function withDeadline(promise, ms, message) {
|
|
9715
|
-
return new Promise((resolve, reject) => {
|
|
9716
|
-
const timer = setTimeout(() => reject(new Error(`${message} (timed out)`)), ms);
|
|
9717
|
-
promise.then(
|
|
9718
|
-
(value) => {
|
|
9719
|
-
clearTimeout(timer);
|
|
9720
|
-
resolve(value);
|
|
9721
|
-
},
|
|
9722
|
-
(error) => {
|
|
9723
|
-
clearTimeout(timer);
|
|
9724
|
-
reject(error);
|
|
9725
|
-
}
|
|
9726
|
-
);
|
|
9727
|
-
});
|
|
9728
|
-
}
|
|
9729
|
-
async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}) {
|
|
9730
|
-
const now = deps.now ?? Date.now;
|
|
9731
|
-
const persisted = (deps.readPersisted ?? readPersistedSessions)()[sessionId];
|
|
9732
|
-
if (!persisted) {
|
|
9733
|
-
throw new Error(
|
|
9734
|
-
`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.`
|
|
9735
|
-
);
|
|
9736
|
-
}
|
|
9737
|
-
const token = await (deps.bearerToken ?? bearerToken$1)();
|
|
9738
|
-
const fetchState = deps.fetchAgentState ?? fetchDecryptedAgentState;
|
|
9739
|
-
const before = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9740
|
-
if (!before.some((request) => request.id === requestId)) {
|
|
9741
|
-
const hint = before.length > 0 ? `Pending request ids: ${before.map((request) => `${request.id} (${request.tool})`).join(", ")}` : "The session has no pending requests.";
|
|
9742
|
-
throw new Error(`Request ${requestId} is not pending on session ${sessionId}. ${hint}`);
|
|
9743
|
-
}
|
|
9744
|
-
const payload = buildPermissionRpcPayload(requestId, verdict);
|
|
9745
|
-
const key = decodeBase64(persisted.encryptionKey);
|
|
9746
|
-
const params = encodeBase64(encrypt(key, persisted.encryptionVariant, payload));
|
|
9747
|
-
const transport = await (deps.openTransport ?? openUserScopedSocket)(token);
|
|
9748
|
-
const rpcTimeoutMs = deps.rpcTimeoutMs ?? PERMISSION_RPC_TIMEOUT_MS;
|
|
9749
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
9750
|
-
let ack;
|
|
9751
|
-
try {
|
|
9752
|
-
for (let attempt = 1; ; attempt++) {
|
|
9753
|
-
try {
|
|
9754
|
-
ack = await withDeadline(
|
|
9755
|
-
transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
|
|
9756
|
-
rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
|
|
9757
|
-
`permission RPC did not settle within ${rpcTimeoutMs}ms`
|
|
9758
|
-
);
|
|
9759
|
-
} catch (error) {
|
|
9760
|
-
ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
|
|
9761
|
-
}
|
|
9762
|
-
if (!isRpcRateLimitAck(ack) || attempt > RPC_RATE_MAX_RETRIES) break;
|
|
9763
|
-
const waitMs = rpcRateRetryDelayMs(attempt, ack.retryAfterMs, (deps.random ?? Math.random)());
|
|
9764
|
-
deps.onRateLimited?.({ attempt, waitMs, serverError: ack.error ?? "RPC rate limit reached" });
|
|
9765
|
-
await sleep(waitMs);
|
|
9766
|
-
}
|
|
9767
|
-
} finally {
|
|
9768
|
-
transport.close();
|
|
9769
|
-
}
|
|
9770
|
-
const outcome = interpretPermissionAck(ack, (encrypted) => decrypt(key, persisted.encryptionVariant, decodeBase64(encrypted)));
|
|
9771
|
-
if (outcome.status !== "acknowledged") {
|
|
9772
|
-
return { sessionId, requestId, payload, outcome };
|
|
9773
|
-
}
|
|
9774
|
-
const deadline = now() + (deps.settleTimeoutMs ?? SETTLE_TIMEOUT_MS);
|
|
9775
|
-
let settled = false;
|
|
9776
|
-
for (; ; ) {
|
|
9777
|
-
const pending = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9778
|
-
if (!pending.some((request) => request.id === requestId)) {
|
|
9779
|
-
settled = true;
|
|
9780
|
-
break;
|
|
9781
|
-
}
|
|
9782
|
-
if (now() >= deadline) break;
|
|
9783
|
-
await sleep(SETTLE_POLL_MS);
|
|
9784
|
-
}
|
|
9785
|
-
return { sessionId, requestId, payload, outcome, settled };
|
|
9786
|
-
}
|
|
9787
|
-
async function bearerToken$1() {
|
|
9788
|
-
const credentials = await readCredentialsForConfiguredRelay();
|
|
9789
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9790
|
-
return credentials.token;
|
|
9791
|
-
}
|
|
9792
|
-
async function fetchDecryptedAgentState(sessionId, persisted, token) {
|
|
9793
|
-
const response = await axios.get(
|
|
9794
|
-
`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
|
|
9795
|
-
{
|
|
9796
|
-
headers: {
|
|
9797
|
-
"Authorization": `Bearer ${token}`,
|
|
9798
|
-
"X-Happy-Client": `${PERMISSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9799
|
-
},
|
|
9800
|
-
timeout: 15e3,
|
|
9801
|
-
// 404 is a real answer ("not yours / gone"), not a transport fault.
|
|
9802
|
-
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
9803
|
-
}
|
|
9804
|
-
);
|
|
9805
|
-
if (response.status === 404) {
|
|
9806
|
-
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).`);
|
|
9807
|
-
}
|
|
9808
|
-
const raw = response.data?.session?.agentState;
|
|
9809
|
-
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
9810
|
-
return decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(raw));
|
|
9811
|
-
}
|
|
9812
|
-
|
|
9813
|
-
const SESSION_OPS_CLIENT = "session-ops";
|
|
9814
|
-
const DEFAULT_RECENT_LIMIT = 15;
|
|
9815
|
-
const MAX_READ_LIMIT = 100;
|
|
9816
|
-
function mergeSessionSummaries(live, persisted, options = {}) {
|
|
9817
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9818
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9819
|
-
const summaries = [];
|
|
9820
|
-
for (const child of live) {
|
|
9821
|
-
const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
|
|
9822
|
-
if (!id || seen.has(id)) continue;
|
|
9823
|
-
seen.add(id);
|
|
9824
|
-
summaries.push(toSummary(id, persisted[id], livenessOf(child)));
|
|
9825
|
-
}
|
|
9826
|
-
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);
|
|
9827
|
-
for (const [id, entry] of rest) {
|
|
9828
|
-
summaries.push(toSummary(id, entry, { live: false }));
|
|
9829
|
-
}
|
|
9830
|
-
if (!options.tag) return summaries;
|
|
9831
|
-
const wanted = options.tag;
|
|
9832
|
-
return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9833
|
-
}
|
|
9834
|
-
function livenessOf(child) {
|
|
9835
|
-
return { live: true, pid: typeof child.pid === "number" ? child.pid : void 0 };
|
|
9836
|
-
}
|
|
9837
|
-
function sessionLiveness(live, sessionId) {
|
|
9838
|
-
const child = live.find((entry) => entry.happySessionId === sessionId);
|
|
9839
|
-
return child ? livenessOf(child) : { live: false };
|
|
9840
|
-
}
|
|
9841
|
-
function toSummary(id, persisted, extra) {
|
|
9842
|
-
const meta = persisted?.metadata;
|
|
9843
|
-
return {
|
|
9844
|
-
id,
|
|
9845
|
-
live: extra.live,
|
|
9846
|
-
...extra.pid !== void 0 ? { pid: extra.pid } : {},
|
|
9847
|
-
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
9848
|
-
...meta?.path ? { cwd: meta.path } : {},
|
|
9849
|
-
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
9850
|
-
...meta?.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9851
|
-
...meta?.variant ? { variant: meta.variant } : {},
|
|
9852
|
-
url: sessionWebUrl(id),
|
|
9853
|
-
...persisted?.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {}
|
|
9854
|
-
};
|
|
9855
|
-
}
|
|
9856
|
-
async function listSessions(options = {}) {
|
|
9857
|
-
const live = await listDaemonSessions();
|
|
9858
|
-
return mergeSessionSummaries(live, readPersistedSessions(), options);
|
|
9859
|
-
}
|
|
9860
|
-
function summarizeAccountSession(row, persisted, liveIds, now) {
|
|
9861
|
-
const base = {
|
|
9862
|
-
id: row.id,
|
|
9863
|
-
live: liveIds.has(row.id),
|
|
9864
|
-
url: sessionWebUrl(row.id),
|
|
9865
|
-
decryptable: false,
|
|
9866
|
-
active: row.active === true,
|
|
9867
|
-
archived: typeof row.archivedAt === "number",
|
|
9868
|
-
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
9869
|
-
...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {},
|
|
9870
|
-
attention: false
|
|
9871
|
-
};
|
|
9872
|
-
if (!persisted) return base;
|
|
9873
|
-
const key = decodeBase64(persisted.encryptionKey);
|
|
9874
|
-
let metadata = null;
|
|
9875
|
-
let agentState = null;
|
|
9876
|
-
try {
|
|
9877
|
-
metadata = row.metadata ? decrypt(key, persisted.encryptionVariant, decodeBase64(row.metadata)) : null;
|
|
9878
|
-
agentState = row.agentState ? decrypt(key, persisted.encryptionVariant, decodeBase64(row.agentState)) : null;
|
|
9879
|
-
} catch {
|
|
9880
|
-
return base;
|
|
9881
|
-
}
|
|
9882
|
-
if (!metadata) return base;
|
|
9883
|
-
const meta = metadata;
|
|
9884
|
-
const pending = pendingRequestsOf(agentState, now);
|
|
9885
|
-
return {
|
|
9886
|
-
...base,
|
|
9887
|
-
decryptable: true,
|
|
9888
|
-
...meta.summary?.text ? { title: meta.summary.text } : {},
|
|
9889
|
-
...meta.path ? { cwd: meta.path } : {},
|
|
9890
|
-
...meta.flavor ? { flavor: meta.flavor } : {},
|
|
9891
|
-
...meta.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9892
|
-
...meta.variant ? { variant: meta.variant } : {},
|
|
9893
|
-
...meta.machineId ? { machineId: meta.machineId } : {},
|
|
9894
|
-
...persisted.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
|
|
9895
|
-
pending,
|
|
9896
|
-
attention: pending.length > 0
|
|
9897
|
-
};
|
|
9898
|
-
}
|
|
9899
|
-
function orderAccountSessions(summaries, options = {}) {
|
|
9900
|
-
const oldestWait = (summary) => Math.max(0, ...(summary.pending ?? []).map((request) => request.waitingMs ?? 0));
|
|
9901
|
-
const rank = (summary) => summary.attention ? 0 : summary.live ? 1 : 2;
|
|
9902
|
-
let rows = summaries.filter((summary) => summary.flavor !== "terminal-mirror").slice().sort((a, b) => {
|
|
9903
|
-
const byRank = rank(a) - rank(b);
|
|
9904
|
-
if (byRank !== 0) return byRank;
|
|
9905
|
-
if (a.attention && b.attention) return oldestWait(b) - oldestWait(a);
|
|
9906
|
-
return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
|
|
9907
|
-
});
|
|
9908
|
-
if (options.tag) {
|
|
9909
|
-
const wanted = options.tag;
|
|
9910
|
-
rows = rows.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9911
|
-
}
|
|
9912
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9913
|
-
let idleKept = 0;
|
|
9914
|
-
return rows.filter((summary) => rank(summary) < 2 || idleKept++ < recentLimit);
|
|
9915
|
-
}
|
|
9916
|
-
async function listAccountSessions(options = {}) {
|
|
9917
|
-
const token = await bearerToken();
|
|
9918
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/sessions`, {
|
|
9919
|
-
headers: {
|
|
9920
|
-
"Authorization": `Bearer ${token}`,
|
|
9921
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9922
|
-
},
|
|
9923
|
-
timeout: 15e3
|
|
9924
|
-
});
|
|
9925
|
-
const rows = Array.isArray(response.data?.sessions) ? response.data.sessions : [];
|
|
9926
|
-
const persisted = readPersistedSessions();
|
|
9927
|
-
const live = await listDaemonSessions();
|
|
9928
|
-
const liveIds = new Set(live.map((child) => child.happySessionId).filter((id) => typeof id === "string"));
|
|
9929
|
-
const now = Date.now();
|
|
9930
|
-
const summaries = rows.filter((row) => typeof row?.id === "string").map((row) => summarizeAccountSession(row, persisted[row.id], liveIds, now)).filter((summary) => options.includeArchived || !summary.archived);
|
|
9931
|
-
return orderAccountSessions(summaries, options);
|
|
9932
|
-
}
|
|
9933
|
-
async function bearerToken() {
|
|
9934
|
-
const credentials = await readCredentialsForConfiguredRelay();
|
|
9935
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9936
|
-
return credentials.token;
|
|
9937
|
-
}
|
|
9938
|
-
function requirePersisted(sessionId) {
|
|
9939
|
-
const persisted = readPersistedSessions()[sessionId];
|
|
9940
|
-
if (!persisted) {
|
|
9941
|
-
throw new Error(
|
|
9942
|
-
`No local key for session ${sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`
|
|
9943
|
-
);
|
|
9944
|
-
}
|
|
9945
|
-
return persisted;
|
|
9946
|
-
}
|
|
9947
|
-
async function readSessionLog(sessionId, persisted, limit) {
|
|
9948
|
-
const token = await bearerToken();
|
|
9949
|
-
const response = await axios.get(
|
|
9950
|
-
`${configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
9951
|
-
{
|
|
9952
|
-
params: { before_seq: 2147483647, limit },
|
|
9953
|
-
headers: {
|
|
9954
|
-
"Authorization": `Bearer ${token}`,
|
|
9955
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9956
|
-
},
|
|
9957
|
-
timeout: 15e3
|
|
9958
|
-
}
|
|
9959
|
-
);
|
|
9960
|
-
const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
|
|
9961
|
-
messages.reverse();
|
|
9962
|
-
const key = decodeBase64(persisted.encryptionKey);
|
|
9963
|
-
return messages.map((message) => {
|
|
9964
|
-
if (message.content?.t !== "encrypted") return { seq: message.seq, body: null };
|
|
9965
|
-
try {
|
|
9966
|
-
return { seq: message.seq, body: decrypt(key, persisted.encryptionVariant, decodeBase64(message.content.c)) };
|
|
9967
|
-
} catch {
|
|
9968
|
-
return { seq: message.seq, body: null };
|
|
9969
|
-
}
|
|
9970
|
-
});
|
|
9971
|
-
}
|
|
9972
|
-
const TURN_WINDOW = 500;
|
|
9973
|
-
async function readSessionTranscript(sessionId, limit, options = {}) {
|
|
9974
|
-
const persisted = requirePersisted(sessionId);
|
|
9975
|
-
const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
|
|
9976
|
-
const entries = await readSessionLog(sessionId, persisted, Math.max(bounded, TURN_WINDOW));
|
|
9977
|
-
const shown = entries.slice(-bounded);
|
|
9978
|
-
const live = await listDaemonSessions();
|
|
9979
|
-
return {
|
|
9980
|
-
summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
|
|
9981
|
-
messageCount: shown.length,
|
|
9982
|
-
transcript: formatTranscript(shown.map((entry) => entry.body), { full: options.full }),
|
|
9983
|
-
turn: analyzeLatestTurn(entries)
|
|
9984
|
-
};
|
|
9985
|
-
}
|
|
9986
|
-
async function readSessionMetadata(sessionId, persisted) {
|
|
9987
|
-
const token = await bearerToken();
|
|
9988
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
9989
|
-
headers: {
|
|
9990
|
-
"Authorization": `Bearer ${token}`,
|
|
9991
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
9992
|
-
},
|
|
9993
|
-
timeout: 15e3
|
|
9994
|
-
});
|
|
9995
|
-
const ciphertext = response.data?.session?.metadata;
|
|
9996
|
-
if (typeof ciphertext !== "string" || ciphertext.length === 0) return null;
|
|
9997
|
-
return decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(ciphertext));
|
|
9998
|
-
}
|
|
9999
|
-
class TurnWaitTimeoutError extends Error {
|
|
10000
|
-
constructor(sessionId, timeoutMs, turn) {
|
|
10001
|
-
super(`Session ${sessionId}: the latest turn did not end within ${Math.round(timeoutMs / 1e3)}s`);
|
|
10002
|
-
this.sessionId = sessionId;
|
|
10003
|
-
this.timeoutMs = timeoutMs;
|
|
10004
|
-
this.turn = turn;
|
|
10005
|
-
}
|
|
10006
|
-
}
|
|
10007
|
-
async function waitForTurnEnd(sessionId, options) {
|
|
10008
|
-
const persisted = requirePersisted(sessionId);
|
|
10009
|
-
const pollMs = options.pollMs ?? 3e3;
|
|
10010
|
-
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
10011
|
-
const deadline = Date.now() + options.timeoutMs;
|
|
10012
|
-
while (true) {
|
|
10013
|
-
const turn = analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW));
|
|
10014
|
-
if (turn.ended) return turn;
|
|
10015
|
-
if (Date.now() + pollMs > deadline) throw new TurnWaitTimeoutError(sessionId, options.timeoutMs, turn);
|
|
10016
|
-
await sleep(pollMs);
|
|
10017
|
-
}
|
|
10018
|
-
}
|
|
10019
|
-
async function stopSession(sessionId) {
|
|
10020
|
-
return await stopDaemonSession(sessionId);
|
|
10021
|
-
}
|
|
10022
|
-
async function archiveSession(sessionId) {
|
|
10023
|
-
const token = await bearerToken();
|
|
10024
|
-
await axios.post(
|
|
10025
|
-
`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}/archive`,
|
|
10026
|
-
{},
|
|
10027
|
-
{
|
|
10028
|
-
headers: {
|
|
10029
|
-
"Authorization": `Bearer ${token}`,
|
|
10030
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
|
|
10031
|
-
},
|
|
10032
|
-
timeout: 1e4
|
|
10033
|
-
}
|
|
10034
|
-
);
|
|
10035
|
-
}
|
|
10036
|
-
|
|
10037
9791
|
function receiptPath(home, operationId) {
|
|
10038
9792
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
|
|
10039
9793
|
return join(home, "teams", "effects", `${operationId}.json`);
|
|
@@ -10623,7 +10377,7 @@ function automationRunAnchor(runId) {
|
|
|
10623
10377
|
async function defaultReadTurn(sessionId, runId) {
|
|
10624
10378
|
const persisted = readPersistedSessions()[sessionId];
|
|
10625
10379
|
if (!persisted) throw new Error(`No local key for session ${sessionId}`);
|
|
10626
|
-
return analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10380
|
+
return analyzeLatestTurn(await readSessionLog$1(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10627
10381
|
}
|
|
10628
10382
|
async function defaultSend(sessionId, text, meta) {
|
|
10629
10383
|
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
@@ -11122,6 +10876,7 @@ async function startDaemon() {
|
|
|
11122
10876
|
logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
11123
10877
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
11124
10878
|
const turnActivity = new TurnActivityTracker();
|
|
10879
|
+
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
11125
10880
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
11126
10881
|
let teamWorker;
|
|
11127
10882
|
let automationRunner;
|
|
@@ -11981,6 +11736,7 @@ async function startDaemon() {
|
|
|
11981
11736
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
11982
11737
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
11983
11738
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11739
|
+
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
11984
11740
|
if (session?.happySessionId && session.encryption) {
|
|
11985
11741
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
11986
11742
|
logger.debug(`[DAEMON RUN] Process PID ${pid} exited, preserved session ${session.happySessionId} for resume`);
|
|
@@ -12059,11 +11815,17 @@ async function startDaemon() {
|
|
|
12059
11815
|
getChildren: getCurrentChildren,
|
|
12060
11816
|
stopSession,
|
|
12061
11817
|
spawnSession,
|
|
11818
|
+
resumeSession,
|
|
11819
|
+
// B-501: `very-happy send --resume`
|
|
12062
11820
|
requestShutdown: () => requestShutdown("happy-cli"),
|
|
12063
11821
|
onHappySessionWebhook,
|
|
12064
11822
|
onSessionStateEvent,
|
|
12065
11823
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
12066
11824
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
11825
|
+
onSessionEdit: (edit) => {
|
|
11826
|
+
peerCoordinator.onEdit(edit);
|
|
11827
|
+
},
|
|
11828
|
+
listPeers: () => peerCoordinator.list(),
|
|
12067
11829
|
pushClipboard: (text, terminalId) => {
|
|
12068
11830
|
if (!apiMachineRef) {
|
|
12069
11831
|
return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
|
|
@@ -12129,9 +11891,12 @@ async function startDaemon() {
|
|
|
12129
11891
|
const mirrorManager = createMirrorManager({
|
|
12130
11892
|
api,
|
|
12131
11893
|
machineId,
|
|
12132
|
-
onBindingsChanged: () => apiMachine.requestTerminalListRefresh()
|
|
11894
|
+
onBindingsChanged: () => apiMachine.requestTerminalListRefresh(),
|
|
11895
|
+
onEdit: (edit) => peerCoordinator.onEdit(edit),
|
|
11896
|
+
onSessionEnded: (sessionId) => peerCoordinator.forget(sessionId)
|
|
12133
11897
|
});
|
|
12134
11898
|
mirrorManagerRef = mirrorManager;
|
|
11899
|
+
peerCoordinator.setMirrorSource(() => mirrorManager.listActiveMirrors());
|
|
12135
11900
|
apiMachine.setMirrorIntegration({
|
|
12136
11901
|
resolveMirrorSessionId: (terminalId) => mirrorManager.resolveMirrorSessionId(terminalId),
|
|
12137
11902
|
onTerminalClosed: (terminalId) => mirrorManager.onTerminalClosed(terminalId),
|
|
@@ -12562,11 +12327,13 @@ function fail(text) {
|
|
|
12562
12327
|
function describeSession(id, persisted, extra) {
|
|
12563
12328
|
const meta = persisted?.metadata;
|
|
12564
12329
|
const parts = [id];
|
|
12330
|
+
if (extra?.live !== void 0) parts.push(extra.live ? "[running]" : "[not running]");
|
|
12565
12331
|
if (meta?.summary?.text) parts.push(`title="${meta.summary.text}"`);
|
|
12566
12332
|
if (meta?.path) parts.push(`cwd=${meta.path}`);
|
|
12567
12333
|
if (meta?.flavor) parts.push(`agent=${meta.flavor}`);
|
|
12568
12334
|
if (meta?.tags?.length) parts.push(`tags=${meta.tags.join(",")}`);
|
|
12569
12335
|
if (meta?.variant === "assistant") parts.push("(assistant)");
|
|
12336
|
+
if (extra?.pid) parts.push(`pid=${extra.pid}`);
|
|
12570
12337
|
parts.push(sessionWebUrl(id));
|
|
12571
12338
|
return parts.join(" ");
|
|
12572
12339
|
}
|
|
@@ -12620,19 +12387,27 @@ ${body}`);
|
|
|
12620
12387
|
}
|
|
12621
12388
|
});
|
|
12622
12389
|
mcp.registerTool("session_send", {
|
|
12623
|
-
description: "Send a user message into an existing session on this machine. Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
|
|
12390
|
+
description: "Send a user message into an existing session on this machine. Only delivers when a live wrapper will read it: an archived / offline session is refused (the server would store the message unread) unless resume:true brings it back on this machine first. Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
|
|
12624
12391
|
title: "Send Message to Session",
|
|
12625
12392
|
inputSchema: {
|
|
12626
12393
|
sessionId: z.string().describe("The Happy session id to message"),
|
|
12627
|
-
text: z.string().describe("The message text to send")
|
|
12394
|
+
text: z.string().describe("The message text to send"),
|
|
12395
|
+
resume: z.boolean().optional().describe("If the session is archived or offline, resume it on this machine (like the web Restore button) and wait until it is live before sending")
|
|
12628
12396
|
}
|
|
12629
12397
|
}, async (args) => {
|
|
12630
12398
|
if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12631
12399
|
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
12632
12400
|
try {
|
|
12633
12401
|
const persisted = await waitForSessionKey(args.sessionId, 0);
|
|
12634
|
-
await
|
|
12635
|
-
|
|
12402
|
+
const result = await deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12403
|
+
resume: args.resume === true,
|
|
12404
|
+
resumeHint: "Pass resume:true to bring it back on this machine, or spawn a new session."
|
|
12405
|
+
});
|
|
12406
|
+
if (!result.delivered) {
|
|
12407
|
+
const detail = result.error ?? `session is ${result.status}`;
|
|
12408
|
+
return fail(result.stored ? `Not delivered: ${detail}` : `Session ${args.sessionId} is ${result.status}: ${detail}`);
|
|
12409
|
+
}
|
|
12410
|
+
return ok(`Message delivered${result.resumed ? " (session resumed on this machine)" : ""} to ${describeSession(args.sessionId, persisted, { live: true })}`);
|
|
12636
12411
|
} catch (error) {
|
|
12637
12412
|
return fail(`Failed to send: ${error instanceof Error ? error.message : String(error)}`);
|
|
12638
12413
|
}
|
|
@@ -12819,13 +12594,14 @@ function normalizeProgress(value) {
|
|
|
12819
12594
|
return flat.length > PROGRESS_MAX_CHARS$1 ? flat.slice(0, PROGRESS_MAX_CHARS$1) : flat;
|
|
12820
12595
|
}
|
|
12821
12596
|
|
|
12822
|
-
function createMcpServer(handlers, options, sessionId) {
|
|
12597
|
+
function createMcpServer(handlers, options, sessionId, client) {
|
|
12823
12598
|
const mcp = new McpServer({
|
|
12824
12599
|
name: "Very Happy Tools",
|
|
12825
12600
|
version: "1.0.0"
|
|
12826
12601
|
});
|
|
12827
12602
|
registerTeamsTools(mcp, sessionId);
|
|
12828
12603
|
registerAutomationTools(mcp);
|
|
12604
|
+
if (client) registerSessionPeerTools(mcp, createSessionPeerToolExecutor(client));
|
|
12829
12605
|
mcp.registerTool("change_title", {
|
|
12830
12606
|
description: "Change the title of the current chat session",
|
|
12831
12607
|
title: "Change Chat Title",
|
|
@@ -13028,7 +12804,7 @@ async function startHappyServer(client, options) {
|
|
|
13028
12804
|
}
|
|
13029
12805
|
return;
|
|
13030
12806
|
}
|
|
13031
|
-
const mcp = createMcpServer(handlers, options, client.sessionId);
|
|
12807
|
+
const mcp = createMcpServer(handlers, options, client.sessionId, client);
|
|
13032
12808
|
try {
|
|
13033
12809
|
const transport = new StreamableHTTPServerTransport({
|
|
13034
12810
|
sessionIdGenerator: void 0
|
|
@@ -13069,6 +12845,7 @@ async function startHappyServer(client, options) {
|
|
|
13069
12845
|
REPORT_PROGRESS_TOOL_NAME,
|
|
13070
12846
|
...TEAM_TOOL_NAMES,
|
|
13071
12847
|
...AUTOMATION_TOOL_NAMES,
|
|
12848
|
+
...SESSION_PEER_TOOL_NAMES,
|
|
13072
12849
|
...options?.assistant && !process.env.VH_TEAM_SCOPE_FILE ? ASSISTANT_TOOL_NAMES : []
|
|
13073
12850
|
],
|
|
13074
12851
|
stop: () => {
|
|
@@ -13405,7 +13182,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13405
13182
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13406
13183
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13407
13184
|
const fork = deps.run ?? (async (input) => {
|
|
13408
|
-
const { query } = await import('./index-
|
|
13185
|
+
const { query } = await import('./index-DVVRqaVG.mjs');
|
|
13409
13186
|
return runSideQuestion(query, input);
|
|
13410
13187
|
});
|
|
13411
13188
|
const run = async (input, live) => {
|
|
@@ -14192,6 +13969,17 @@ async function runClaude(credentials, options = {}) {
|
|
|
14192
13969
|
});
|
|
14193
13970
|
session.on("claude-session-message", (body) => boardAnalyzer.noteClaudeMessage(body));
|
|
14194
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 } };
|
|
14195
13983
|
const initialScannerSessionId = forkClaudeSessionId ?? (metadata.claudeSessionId ?? null);
|
|
14196
13984
|
const remoteScanner = await createSessionScanner({
|
|
14197
13985
|
sessionId: initialScannerSessionId,
|
|
@@ -18178,6 +17966,7 @@ async function runCodex(opts) {
|
|
|
18178
17966
|
return "denied";
|
|
18179
17967
|
}
|
|
18180
17968
|
});
|
|
17969
|
+
const editThrottle = new EditReportThrottle();
|
|
18181
17970
|
client.setEventHandler((msg) => {
|
|
18182
17971
|
if (msg.type === "token_count") {
|
|
18183
17972
|
session.sendAgentUsageSnapshot("codex", msg);
|
|
@@ -18241,6 +18030,9 @@ async function runCodex(opts) {
|
|
|
18241
18030
|
}
|
|
18242
18031
|
if (msg.type === "patch_apply_begin") {
|
|
18243
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
|
+
}
|
|
18244
18036
|
const changeCount = Object.keys(changes).length;
|
|
18245
18037
|
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
18246
18038
|
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
@@ -18286,6 +18078,7 @@ async function runCodex(opts) {
|
|
|
18286
18078
|
};
|
|
18287
18079
|
let first = true;
|
|
18288
18080
|
let appendSystemPromptInjected = false;
|
|
18081
|
+
process.env.VH_PEER_SESSION_ID = session.sessionId;
|
|
18289
18082
|
try {
|
|
18290
18083
|
logger.debug("[codex]: client.connect begin");
|
|
18291
18084
|
await client.connect();
|
|
@@ -18699,7 +18492,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18699
18492
|
return;
|
|
18700
18493
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18701
18494
|
try {
|
|
18702
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-
|
|
18495
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-tIg7mANc.mjs');
|
|
18703
18496
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18704
18497
|
if (command.action === "help") {
|
|
18705
18498
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18716,7 +18509,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18716
18509
|
}
|
|
18717
18510
|
} else if (subcommand === "agent-home") {
|
|
18718
18511
|
try {
|
|
18719
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-
|
|
18512
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-ByLb2HFg.mjs');
|
|
18720
18513
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18721
18514
|
process.exit(0);
|
|
18722
18515
|
} catch (error) {
|
|
@@ -18725,7 +18518,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18725
18518
|
}
|
|
18726
18519
|
} else if (subcommand === "spawn") {
|
|
18727
18520
|
try {
|
|
18728
|
-
const { handleSpawnCommand } = await import('./spawn-
|
|
18521
|
+
const { handleSpawnCommand } = await import('./spawn-BaPnjClT.mjs');
|
|
18729
18522
|
await handleSpawnCommand(args.slice(1));
|
|
18730
18523
|
} catch (error) {
|
|
18731
18524
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18737,7 +18530,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18737
18530
|
return;
|
|
18738
18531
|
} else if (subcommand === "sessions") {
|
|
18739
18532
|
try {
|
|
18740
|
-
const { handleSessionsCommand } = await import('./sessions-
|
|
18533
|
+
const { handleSessionsCommand } = await import('./sessions-JaVXlSqj.mjs');
|
|
18741
18534
|
await handleSessionsCommand(args.slice(1));
|
|
18742
18535
|
} catch (error) {
|
|
18743
18536
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18749,7 +18542,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18749
18542
|
return;
|
|
18750
18543
|
} else if (subcommand === "send") {
|
|
18751
18544
|
try {
|
|
18752
|
-
const { handleSendCommand } = await import('./send-
|
|
18545
|
+
const { handleSendCommand } = await import('./send-DJStmLBv.mjs');
|
|
18753
18546
|
await handleSendCommand(args.slice(1));
|
|
18754
18547
|
} catch (error) {
|
|
18755
18548
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18855,9 +18648,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18855
18648
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
18856
18649
|
const projectId = args[3];
|
|
18857
18650
|
try {
|
|
18858
|
-
const { saveGoogleCloudProjectToConfig } = await import('./config-
|
|
18859
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-
|
|
18860
|
-
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; });
|
|
18861
18654
|
let userEmail = void 0;
|
|
18862
18655
|
try {
|
|
18863
18656
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -18888,7 +18681,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18888
18681
|
}
|
|
18889
18682
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
18890
18683
|
try {
|
|
18891
|
-
const { readGeminiLocalConfig } = await import('./config-
|
|
18684
|
+
const { readGeminiLocalConfig } = await import('./config-BPZ2Y6o8.mjs');
|
|
18892
18685
|
const config = readGeminiLocalConfig();
|
|
18893
18686
|
if (config.googleCloudProject) {
|
|
18894
18687
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -18928,7 +18721,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18928
18721
|
process.exit(0);
|
|
18929
18722
|
}
|
|
18930
18723
|
try {
|
|
18931
|
-
const { runGemini } = await import('./runGemini-
|
|
18724
|
+
const { runGemini } = await import('./runGemini-CDlcQ-OA.mjs');
|
|
18932
18725
|
let startedBy = void 0;
|
|
18933
18726
|
for (let i = 1; i < args.length; i++) {
|
|
18934
18727
|
if (args[i] === "--started-by") {
|
|
@@ -18951,7 +18744,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18951
18744
|
} else if (subcommand === "pi") {
|
|
18952
18745
|
if (args[1] === "--terminal") {
|
|
18953
18746
|
try {
|
|
18954
|
-
const { runPiTerminal } = await import('./piTerminal-
|
|
18747
|
+
const { runPiTerminal } = await import('./piTerminal-Dpq6yqKU.mjs');
|
|
18955
18748
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
18956
18749
|
} catch (error) {
|
|
18957
18750
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18968,7 +18761,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18968
18761
|
}
|
|
18969
18762
|
};
|
|
18970
18763
|
try {
|
|
18971
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
18764
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BjzdXb9x.mjs');
|
|
18972
18765
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
18973
18766
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
18974
18767
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -18996,7 +18789,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18996
18789
|
return;
|
|
18997
18790
|
} else if (subcommand === "acp") {
|
|
18998
18791
|
try {
|
|
18999
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
18792
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BjzdXb9x.mjs');
|
|
19000
18793
|
let startedBy = void 0;
|
|
19001
18794
|
let verbose = false;
|
|
19002
18795
|
const acpArgs = [];
|
|
@@ -19036,7 +18829,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19036
18829
|
return;
|
|
19037
18830
|
} else if (subcommand === "openclaw") {
|
|
19038
18831
|
try {
|
|
19039
|
-
const { runOpenClaw } = await import('./runOpenClaw-
|
|
18832
|
+
const { runOpenClaw } = await import('./runOpenClaw-YkhsgPs0.mjs');
|
|
19040
18833
|
let startedBy = void 0;
|
|
19041
18834
|
let verbose = false;
|
|
19042
18835
|
let gatewayUrl;
|
|
@@ -19087,7 +18880,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19087
18880
|
return;
|
|
19088
18881
|
} else if (subcommand === "todo") {
|
|
19089
18882
|
try {
|
|
19090
|
-
const { handleTodoCommand } = await import('./todo-
|
|
18883
|
+
const { handleTodoCommand } = await import('./todo-2Z1REhfx.mjs');
|
|
19091
18884
|
await handleTodoCommand(args.slice(1));
|
|
19092
18885
|
} catch (error) {
|
|
19093
18886
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -19096,7 +18889,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19096
18889
|
return;
|
|
19097
18890
|
} else if (subcommand === "teams") {
|
|
19098
18891
|
try {
|
|
19099
|
-
const { handleTeamsCommand } = await import('./teams-
|
|
18892
|
+
const { handleTeamsCommand } = await import('./teams-DXV5Pwwt.mjs');
|
|
19100
18893
|
await handleTeamsCommand(args.slice(1));
|
|
19101
18894
|
} catch (error) {
|
|
19102
18895
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -19105,7 +18898,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19105
18898
|
return;
|
|
19106
18899
|
} else if (subcommand === "auto") {
|
|
19107
18900
|
try {
|
|
19108
|
-
const { handleAutoCommand } = await import('./auto-
|
|
18901
|
+
const { handleAutoCommand } = await import('./auto-BQnD6gVx.mjs');
|
|
19109
18902
|
await handleAutoCommand(args.slice(1));
|
|
19110
18903
|
} catch (error) {
|
|
19111
18904
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -19117,7 +18910,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19117
18910
|
return;
|
|
19118
18911
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
19119
18912
|
try {
|
|
19120
|
-
const { handleMcpCommand } = await import('./mcp-
|
|
18913
|
+
const { handleMcpCommand } = await import('./mcp-o7QglAQM.mjs');
|
|
19121
18914
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
19122
18915
|
} catch (error) {
|
|
19123
18916
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -19487,4 +19280,4 @@ ${chalk.bold("Examples:")}
|
|
|
19487
19280
|
}
|
|
19488
19281
|
}
|
|
19489
19282
|
|
|
19490
|
-
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 };
|