very-happy-cli 0.2.154 → 0.2.156
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-B14xNj5L.cjs → AcpBackend-BHe1_F1h.cjs} +3 -3
- package/dist/{AcpBackend-Ckyadhf4.mjs → AcpBackend-C3nc1u-s.mjs} +2 -2
- package/dist/{AcpSessionManager-CCHmPdrR.cjs → AcpSessionManager-BVxkQnNf.cjs} +1 -1
- package/dist/{AcpSessionManager-H8t4P-IW.mjs → AcpSessionManager-dN3k1LbI.mjs} +1 -1
- package/dist/agentGuidance-C-CmV03f.cjs +1665 -0
- package/dist/agentGuidance-C604n0Zq.mjs +1613 -0
- package/dist/{agentHome-BUsLTZnz.cjs → agentHome-CmIJNV7f.cjs} +5 -5
- package/dist/{agentHome-B20RWdgE.mjs → agentHome-D2XGsoqs.mjs} +5 -5
- package/dist/{auto-BQnncDcs.mjs → auto-B1I6G_Nf.mjs} +21 -21
- package/dist/{auto-IeldARBS.cjs → auto-CMDq4oIu.cjs} +24 -24
- package/dist/codex/happyMcpStdioBridge.cjs +6 -3
- package/dist/codex/happyMcpStdioBridge.mjs +6 -3
- package/dist/{config-DM_xI9M6.cjs → config-BYHSRY2E.cjs} +8 -8
- package/dist/{config-B-ocQh5V.mjs → config-BkTu25bH.mjs} +8 -8
- package/dist/{index-ZKuBjvHi.mjs → index-B5QbzEga.mjs} +21 -13
- package/dist/{index-BUxBxSxJ.cjs → index-B_-7NpGZ.cjs} +21 -13
- package/dist/{index-eCjkm8Y2.cjs → index-BbtR-Qto.cjs} +606 -982
- package/dist/{index-okLyUzeD.cjs → index-CB8QnjvS.cjs} +7 -7
- package/dist/{index-BELV05ko.mjs → index-DAAreiRF.mjs} +548 -911
- package/dist/{index-CIx3248o.mjs → index-PYO8Mdz4.mjs} +7 -7
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/dist/{install-CoLN3whR.mjs → install-CNtXzwT_.mjs} +1 -1
- package/dist/{install-BZOdxfPd.cjs → install-CO1ps6Wt.cjs} +1 -1
- package/dist/{installTerminalHooks-CA4OcAS7.cjs → installTerminalHooks-BOAnQ1Qe.cjs} +5 -5
- package/dist/{installTerminalHooks-Dde_8t36.mjs → installTerminalHooks-CMpOV3RU.mjs} +5 -5
- package/dist/lib.cjs +2 -2
- package/dist/lib.d.cts +24 -8
- package/dist/lib.d.mts +24 -8
- package/dist/lib.mjs +2 -2
- package/dist/{limits-Dt7_unH4.mjs → limits-DmMdchTz.mjs} +309 -13
- package/dist/{limits-CmzISka2.cjs → limits-o29KXBgj.cjs} +329 -12
- package/dist/{mcp-9Ui4nQZL.mjs → mcp-C_uejotl.mjs} +17 -17
- package/dist/{mcp-7iDHlyCI.cjs → mcp-CwK1nI7Y.cjs} +23 -23
- package/dist/{piRuntime-C4E4pgye.mjs → piRuntime-Cu2-8nOT.mjs} +2 -2
- package/dist/{piRuntime-BesGGPME.cjs → piRuntime-Dj5WnqzJ.cjs} +2 -2
- package/dist/{piTerminal-fUogwxYr.cjs → piTerminal-D-BIbov9.cjs} +6 -6
- package/dist/{piTerminal-CCEGCQOY.mjs → piTerminal-DanZeBrO.mjs} +6 -6
- package/dist/{runGemini-CNPGSsSK.cjs → runGemini--QqK-Ap5.cjs} +7 -7
- package/dist/{runGemini-BW-pEklU.mjs → runGemini-CZ_lhMWY.mjs} +6 -6
- package/dist/{runOpenClaw-Bsodkg_s.cjs → runOpenClaw-C0xLhTvk.cjs} +6 -6
- package/dist/{runOpenClaw-NRB6-_jU.mjs → runOpenClaw-CxgLpm38.mjs} +5 -5
- package/dist/{send-DYeSFg8s.mjs → send-BaOkDgH0.mjs} +5 -39
- package/dist/{send-Bd00QcHC.cjs → send-CTcGMVuG.cjs} +8 -42
- package/dist/{sessions-DlPOYKLU.mjs → sessions-Bn304xF1.mjs} +88 -45
- package/dist/{sessions-RIFUoy_g.cjs → sessions-OmvyJb0n.cjs} +99 -56
- package/dist/{spawn-D1bhyHbH.mjs → spawn-BKNabutV.mjs} +22 -22
- package/dist/{spawn-DxrfHTF4.cjs → spawn-CC6V2KiA.cjs} +30 -30
- package/dist/{teams-pfKHpkgL.mjs → teams-CLbS2_4Q.mjs} +8 -8
- package/dist/{teams-Cfy0VMCo.cjs → teams-XtrH3_h3.cjs} +8 -8
- package/dist/{todo-B2CjDg_V.mjs → todo-BcPbNKt2.mjs} +8 -6
- package/dist/{todo-B4cBFzHB.cjs → todo-CEDvKNXK.cjs} +8 -6
- package/dist/{types-D5RWyBE5.mjs → types-BEVzYSoV.mjs} +52 -291
- package/dist/{types-Bd_TGr0F.cjs → types-CphWjqHc.cjs} +64 -320
- package/package.json +7 -7
- package/dist/agentGuidance-DUBXppv-.cjs +0 -554
- package/dist/agentGuidance-TLYQxsm2.mjs +0 -530
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
4
|
var os = require('node:os');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
|
-
var index = require('./types-
|
|
7
|
-
var persistence = require('./limits-
|
|
6
|
+
var index = require('./types-CphWjqHc.cjs');
|
|
7
|
+
var persistence = require('./limits-o29KXBgj.cjs');
|
|
8
8
|
var spawn = require('cross-spawn');
|
|
9
9
|
var path = require('node:path');
|
|
10
10
|
var node_readline = require('node:readline');
|
|
11
11
|
var node_fs = require('node:fs');
|
|
12
12
|
var terminalTitleSuggest = require('./terminalTitleSuggest-Dji8I0Jy.cjs');
|
|
13
|
-
var agentGuidance = require('./agentGuidance-
|
|
13
|
+
var agentGuidance = require('./agentGuidance-C-CmV03f.cjs');
|
|
14
14
|
var promises = require('node:fs/promises');
|
|
15
15
|
var fs = require('fs/promises');
|
|
16
16
|
var ink = require('ink');
|
|
@@ -18,7 +18,7 @@ var React = require('react');
|
|
|
18
18
|
var claudeAgentSdk = require('@anthropic-ai/claude-agent-sdk');
|
|
19
19
|
var axios = require('axios');
|
|
20
20
|
require('node:events');
|
|
21
|
-
|
|
21
|
+
require('socket.io-client');
|
|
22
22
|
var tweetnacl = require('tweetnacl');
|
|
23
23
|
var os$1 = require('os');
|
|
24
24
|
var child_process = require('child_process');
|
|
@@ -296,7 +296,7 @@ class Session {
|
|
|
296
296
|
reportEventToDaemon = (event) => {
|
|
297
297
|
const spawnedBy = process.env.HAPPY_SPAWNED_BY;
|
|
298
298
|
if (!spawnedBy) return;
|
|
299
|
-
void
|
|
299
|
+
void persistence.notifyDaemonSessionEvent(this.client.sessionId, event, spawnedBy).catch((error) => persistence.logger.debug("[Session] Failed to report session event to daemon:", error));
|
|
300
300
|
};
|
|
301
301
|
/** Produce an `error` notification for an unexpected session failure. */
|
|
302
302
|
onSessionError = (message) => {
|
|
@@ -900,7 +900,7 @@ async function createSessionScanner(opts) {
|
|
|
900
900
|
let processedMessageKeys = /* @__PURE__ */ new Set();
|
|
901
901
|
let deadSessions = /* @__PURE__ */ new Set();
|
|
902
902
|
if (opts.sessionId) {
|
|
903
|
-
let messages = await readSessionLog
|
|
903
|
+
let messages = await readSessionLog(projectDir, opts.sessionId);
|
|
904
904
|
persistence.logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
|
|
905
905
|
for (let m of messages) {
|
|
906
906
|
processedMessageKeys.add(messageKey(m));
|
|
@@ -923,7 +923,7 @@ async function createSessionScanner(opts) {
|
|
|
923
923
|
}
|
|
924
924
|
}
|
|
925
925
|
for (let session of sessions) {
|
|
926
|
-
const sessionMessages = await readSessionLog
|
|
926
|
+
const sessionMessages = await readSessionLog(projectDir, session);
|
|
927
927
|
let skipped = 0;
|
|
928
928
|
let sent = 0;
|
|
929
929
|
for (let file of sessionMessages) {
|
|
@@ -1000,7 +1000,7 @@ async function createSessionScanner(opts) {
|
|
|
1000
1000
|
return;
|
|
1001
1001
|
}
|
|
1002
1002
|
if (options?.treatExistingAsProcessed) {
|
|
1003
|
-
const existing = await readSessionLog
|
|
1003
|
+
const existing = await readSessionLog(projectDir, sessionId);
|
|
1004
1004
|
persistence.logger.debug(`[SESSION_SCANNER] Pre-marking ${existing.length} existing messages as processed for new session ${sessionId}`);
|
|
1005
1005
|
for (const m of existing) {
|
|
1006
1006
|
processedMessageKeys.add(messageKey(m));
|
|
@@ -1028,7 +1028,7 @@ function messageKey(message) {
|
|
|
1028
1028
|
throw Error();
|
|
1029
1029
|
}
|
|
1030
1030
|
}
|
|
1031
|
-
async function readSessionLog
|
|
1031
|
+
async function readSessionLog(projectDir, sessionId) {
|
|
1032
1032
|
const expectedSessionFile = path.join(projectDir, `${sessionId}.jsonl`);
|
|
1033
1033
|
persistence.logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
|
|
1034
1034
|
let file;
|
|
@@ -1928,7 +1928,7 @@ async function awaitFileExist(file, timeout = 1e4) {
|
|
|
1928
1928
|
await fs.access(file);
|
|
1929
1929
|
return true;
|
|
1930
1930
|
} catch (e) {
|
|
1931
|
-
await
|
|
1931
|
+
await persistence.delay(1e3);
|
|
1932
1932
|
}
|
|
1933
1933
|
}
|
|
1934
1934
|
return false;
|
|
@@ -4295,7 +4295,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
|
|
|
4295
4295
|
},
|
|
4296
4296
|
onAuthFailure: (reason) => {
|
|
4297
4297
|
session.client.sendSessionEvent({ type: "message", message: `Claude Code auth: ${reason}`, kind: "claude-auth-failed" });
|
|
4298
|
-
void
|
|
4298
|
+
void persistence.notifyDaemonClaudeAuthFailed(session.client.sessionId).catch((error) => persistence.logger.debug("[remote]: auth_failed report to daemon failed:", error));
|
|
4299
4299
|
},
|
|
4300
4300
|
onSessionReset: () => {
|
|
4301
4301
|
persistence.logger.debug("[remote]: Session reset");
|
|
@@ -5231,7 +5231,7 @@ function getLogFiles(logDir) {
|
|
|
5231
5231
|
async function runDoctorDaemon() {
|
|
5232
5232
|
console.log(chalk.bold("\n\u{1F916} Daemon Status"));
|
|
5233
5233
|
try {
|
|
5234
|
-
const isRunning = await
|
|
5234
|
+
const isRunning = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
5235
5235
|
const state = await persistence.readDaemonState();
|
|
5236
5236
|
if (isRunning && state) {
|
|
5237
5237
|
console.log(chalk.green("\u2713 Daemon is running"));
|
|
@@ -5402,8 +5402,8 @@ ${typeLabels[type] || type}:`));
|
|
|
5402
5402
|
}
|
|
5403
5403
|
console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
|
|
5404
5404
|
try {
|
|
5405
|
-
const { resolveAgentHomesForCli } = await Promise.resolve().then(function () { return require('./agentHome-
|
|
5406
|
-
const { describeAgentHome } = await Promise.resolve().then(function () { return require('./types-
|
|
5405
|
+
const { resolveAgentHomesForCli } = await Promise.resolve().then(function () { return require('./agentHome-CmIJNV7f.cjs'); });
|
|
5406
|
+
const { describeAgentHome } = await Promise.resolve().then(function () { return require('./types-CphWjqHc.cjs'); }).then(function (n) { return n.index; });
|
|
5407
5407
|
const homes = await resolveAgentHomesForCli();
|
|
5408
5408
|
console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
|
|
5409
5409
|
console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
|
|
@@ -5444,7 +5444,7 @@ ${typeLabels[type] || type}:`));
|
|
|
5444
5444
|
}
|
|
5445
5445
|
console.log(chalk.bold("\n\u{1F916} Daemon Status"));
|
|
5446
5446
|
try {
|
|
5447
|
-
const isRunning = await
|
|
5447
|
+
const isRunning = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
5448
5448
|
const state = await persistence.readDaemonState();
|
|
5449
5449
|
const summary = daemonReadiness(authenticated, isRunning, Boolean(state));
|
|
5450
5450
|
if (summary.level === "ready" && state) {
|
|
@@ -5773,7 +5773,7 @@ async function waitForAuthentication(keypair, claimSecret) {
|
|
|
5773
5773
|
}
|
|
5774
5774
|
process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
|
|
5775
5775
|
dots++;
|
|
5776
|
-
await
|
|
5776
|
+
await persistence.delay(1e3);
|
|
5777
5777
|
}
|
|
5778
5778
|
} finally {
|
|
5779
5779
|
process.off("SIGINT", handleInterrupt);
|
|
@@ -6085,59 +6085,6 @@ function formatAssistantReportMessage(title, sessionId, event) {
|
|
|
6085
6085
|
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`;
|
|
6086
6086
|
}
|
|
6087
6087
|
|
|
6088
|
-
function sessionWebUrl(sessionId) {
|
|
6089
|
-
return `${persistence.configuration.webappUrl.replace(/\/+$/, "")}/session/${sessionId}`;
|
|
6090
|
-
}
|
|
6091
|
-
async function waitForSessionKey(sessionId, timeoutMs) {
|
|
6092
|
-
const deadline = Date.now() + timeoutMs;
|
|
6093
|
-
while (true) {
|
|
6094
|
-
const entry = persistence.readPersistedSessions()[sessionId];
|
|
6095
|
-
if (entry) return entry;
|
|
6096
|
-
if (Date.now() >= deadline) {
|
|
6097
|
-
throw new Error(
|
|
6098
|
-
`Session ${sessionId} has no encryption key in ${persistence.configuration.sessionsFile}. Either it was not spawned by this machine's daemon, or the daemon is too old to persist session keys.`
|
|
6099
|
-
);
|
|
6100
|
-
}
|
|
6101
|
-
await index.delay(200);
|
|
6102
|
-
}
|
|
6103
|
-
}
|
|
6104
|
-
async function sendUserMessage(sessionId, persisted, text, client, options = {}) {
|
|
6105
|
-
const credentials = await persistence.readCredentialsForConfiguredRelay();
|
|
6106
|
-
if (!credentials) {
|
|
6107
|
-
throw new Error("Not authenticated. Run `very-happy auth login` first.");
|
|
6108
|
-
}
|
|
6109
|
-
const envelope = {
|
|
6110
|
-
role: "user",
|
|
6111
|
-
content: {
|
|
6112
|
-
type: "text",
|
|
6113
|
-
text
|
|
6114
|
-
},
|
|
6115
|
-
meta: {
|
|
6116
|
-
sentFrom: options.sentFrom ?? "cli",
|
|
6117
|
-
...options.model !== void 0 ? { model: options.model } : {},
|
|
6118
|
-
...options.permissionMode !== void 0 ? { permissionMode: options.permissionMode } : {},
|
|
6119
|
-
...options.effort !== void 0 ? { effort: options.effort } : {}
|
|
6120
|
-
}
|
|
6121
|
-
};
|
|
6122
|
-
const encrypted = persistence.encodeBase64(persistence.encrypt(
|
|
6123
|
-
persistence.decodeBase64(persisted.encryptionKey),
|
|
6124
|
-
persisted.encryptionVariant,
|
|
6125
|
-
envelope
|
|
6126
|
-
));
|
|
6127
|
-
await axios.post(
|
|
6128
|
-
`${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
6129
|
-
{ messages: [{ content: encrypted, localId: options.localId ?? node_crypto.randomUUID() }] },
|
|
6130
|
-
{
|
|
6131
|
-
headers: {
|
|
6132
|
-
"Authorization": `Bearer ${credentials.token}`,
|
|
6133
|
-
"Content-Type": "application/json",
|
|
6134
|
-
"X-Happy-Client": `${client}/${persistence.configuration.currentCliVersion}`
|
|
6135
|
-
},
|
|
6136
|
-
timeout: 3e4
|
|
6137
|
-
}
|
|
6138
|
-
);
|
|
6139
|
-
}
|
|
6140
|
-
|
|
6141
6088
|
const ALLOWED_SPAWN_PERMISSION_MODES = [
|
|
6142
6089
|
"default",
|
|
6143
6090
|
"acceptEdits",
|
|
@@ -6293,7 +6240,10 @@ function startDaemonControlServer({
|
|
|
6293
6240
|
pushClipboard,
|
|
6294
6241
|
pushFilePreview,
|
|
6295
6242
|
onTerminalHook,
|
|
6296
|
-
setTerminalTitle
|
|
6243
|
+
setTerminalTitle,
|
|
6244
|
+
onSessionEdit,
|
|
6245
|
+
listPeers,
|
|
6246
|
+
isTurnActive
|
|
6297
6247
|
}) {
|
|
6298
6248
|
return new Promise((resolve) => {
|
|
6299
6249
|
const app = fastify({
|
|
@@ -6377,7 +6327,8 @@ function startDaemonControlServer({
|
|
|
6377
6327
|
children: z.z.array(z.z.object({
|
|
6378
6328
|
startedBy: z.z.string(),
|
|
6379
6329
|
happySessionId: z.z.string(),
|
|
6380
|
-
pid: z.z.number()
|
|
6330
|
+
pid: z.z.number(),
|
|
6331
|
+
turnActive: z.z.boolean()
|
|
6381
6332
|
}))
|
|
6382
6333
|
})
|
|
6383
6334
|
}
|
|
@@ -6389,7 +6340,8 @@ function startDaemonControlServer({
|
|
|
6389
6340
|
children: children.filter((child) => child.happySessionId !== void 0).map((child) => ({
|
|
6390
6341
|
startedBy: child.startedBy,
|
|
6391
6342
|
happySessionId: child.happySessionId,
|
|
6392
|
-
pid: child.pid
|
|
6343
|
+
pid: child.pid,
|
|
6344
|
+
turnActive: isTurnActive ? isTurnActive(child.happySessionId) : false
|
|
6393
6345
|
}))
|
|
6394
6346
|
};
|
|
6395
6347
|
});
|
|
@@ -6616,6 +6568,45 @@ function startDaemonControlServer({
|
|
|
6616
6568
|
}
|
|
6617
6569
|
return { status: "ok" };
|
|
6618
6570
|
});
|
|
6571
|
+
typed.post("/session-edit", {
|
|
6572
|
+
schema: {
|
|
6573
|
+
body: z.z.object({
|
|
6574
|
+
sessionId: z.z.string().regex(/^[a-zA-Z0-9_-]{1,128}$/),
|
|
6575
|
+
path: z.z.string().min(1).max(4096),
|
|
6576
|
+
tool: z.z.string().min(1).max(64),
|
|
6577
|
+
cwd: z.z.string().max(4096).optional(),
|
|
6578
|
+
/** When the runner saw the call (ms). Clamped to now by the daemon;
|
|
6579
|
+
* older than the conflict window = ignored (replayed history). */
|
|
6580
|
+
at: z.z.number().int().nonnegative().optional()
|
|
6581
|
+
}),
|
|
6582
|
+
response: { 200: z.z.object({ status: z.z.literal("ok") }) }
|
|
6583
|
+
}
|
|
6584
|
+
}, async (request) => {
|
|
6585
|
+
try {
|
|
6586
|
+
onSessionEdit?.(request.body);
|
|
6587
|
+
} catch (error) {
|
|
6588
|
+
persistence.logger.debug("[CONTROL SERVER] session-edit handler failed:", error);
|
|
6589
|
+
}
|
|
6590
|
+
return { status: "ok" };
|
|
6591
|
+
});
|
|
6592
|
+
typed.post("/peers", {
|
|
6593
|
+
schema: {
|
|
6594
|
+
response: {
|
|
6595
|
+
200: z.z.object({
|
|
6596
|
+
sessions: z.z.array(z.z.object({
|
|
6597
|
+
sessionId: z.z.string(),
|
|
6598
|
+
kind: z.z.enum(["managed", "mirror"]),
|
|
6599
|
+
pid: z.z.number().optional(),
|
|
6600
|
+
cwd: z.z.string().optional(),
|
|
6601
|
+
flavor: z.z.string().optional(),
|
|
6602
|
+
title: z.z.string().optional(),
|
|
6603
|
+
variant: z.z.string().optional(),
|
|
6604
|
+
edits: z.z.array(z.z.object({ path: z.z.string(), tool: z.z.string(), at: z.z.number() }))
|
|
6605
|
+
}))
|
|
6606
|
+
})
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
}, async () => ({ sessions: listPeers ? listPeers() : [] }));
|
|
6619
6610
|
typed.post("/stop", {
|
|
6620
6611
|
schema: {
|
|
6621
6612
|
response: {
|
|
@@ -6651,6 +6642,273 @@ function startDaemonControlServer({
|
|
|
6651
6642
|
});
|
|
6652
6643
|
}
|
|
6653
6644
|
|
|
6645
|
+
const EDIT_CONFLICT_WINDOW_MS = 30 * 6e4;
|
|
6646
|
+
function pairKey$1(a, b, path) {
|
|
6647
|
+
return a < b ? `${a}\0${b}\0${path}` : `${b}\0${a}\0${path}`;
|
|
6648
|
+
}
|
|
6649
|
+
class EditConflictTracker {
|
|
6650
|
+
windowMs;
|
|
6651
|
+
maxPaths;
|
|
6652
|
+
/** real path → sessionId → latest edit */
|
|
6653
|
+
byPath = /* @__PURE__ */ new Map();
|
|
6654
|
+
/** (sorted pair, path) → when the notice went out */
|
|
6655
|
+
notified = /* @__PURE__ */ new Map();
|
|
6656
|
+
constructor(options = {}) {
|
|
6657
|
+
this.windowMs = options.windowMs ?? EDIT_CONFLICT_WINDOW_MS;
|
|
6658
|
+
this.maxPaths = options.maxPaths ?? 5e3;
|
|
6659
|
+
}
|
|
6660
|
+
/**
|
|
6661
|
+
* Remember an edit and return the peers to notify about it. `isLive`
|
|
6662
|
+
* decides whether another session still counts (dead ones are dropped
|
|
6663
|
+
* from the table on the spot).
|
|
6664
|
+
*/
|
|
6665
|
+
record(edit, isLive) {
|
|
6666
|
+
this.prune(edit.at);
|
|
6667
|
+
let sessions = this.byPath.get(edit.path);
|
|
6668
|
+
if (!sessions) {
|
|
6669
|
+
sessions = /* @__PURE__ */ new Map();
|
|
6670
|
+
this.byPath.set(edit.path, sessions);
|
|
6671
|
+
}
|
|
6672
|
+
const conflicts = [];
|
|
6673
|
+
for (const [sessionId, peer] of sessions) {
|
|
6674
|
+
if (sessionId === edit.sessionId) continue;
|
|
6675
|
+
if (!isLive(sessionId)) {
|
|
6676
|
+
sessions.delete(sessionId);
|
|
6677
|
+
continue;
|
|
6678
|
+
}
|
|
6679
|
+
if (edit.at - peer.at > this.windowMs) continue;
|
|
6680
|
+
const key = pairKey$1(edit.sessionId, sessionId, edit.path);
|
|
6681
|
+
const notifiedAt = this.notified.get(key);
|
|
6682
|
+
if (notifiedAt !== void 0 && edit.at - notifiedAt <= this.windowMs) continue;
|
|
6683
|
+
this.notified.set(key, edit.at);
|
|
6684
|
+
conflicts.push({ editor: edit, peer });
|
|
6685
|
+
}
|
|
6686
|
+
sessions.set(edit.sessionId, edit);
|
|
6687
|
+
return conflicts;
|
|
6688
|
+
}
|
|
6689
|
+
/** Paths this session edited inside the window, newest first. */
|
|
6690
|
+
editsOf(sessionId, now, limit = 20) {
|
|
6691
|
+
const out = [];
|
|
6692
|
+
for (const [path, sessions] of this.byPath) {
|
|
6693
|
+
const edit = sessions.get(sessionId);
|
|
6694
|
+
if (edit && now - edit.at <= this.windowMs) out.push({ path, tool: edit.tool, at: edit.at });
|
|
6695
|
+
}
|
|
6696
|
+
return out.sort((a, b) => b.at - a.at).slice(0, limit);
|
|
6697
|
+
}
|
|
6698
|
+
/** Session ended: nothing it did counts any more. */
|
|
6699
|
+
forget(sessionId) {
|
|
6700
|
+
for (const [path, sessions] of this.byPath) {
|
|
6701
|
+
sessions.delete(sessionId);
|
|
6702
|
+
if (sessions.size === 0) this.byPath.delete(path);
|
|
6703
|
+
}
|
|
6704
|
+
for (const key of this.notified.keys()) {
|
|
6705
|
+
const [a, b] = key.split("\0");
|
|
6706
|
+
if (a === sessionId || b === sessionId) this.notified.delete(key);
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
/** Drop everything older than the window (and cap the table size). */
|
|
6710
|
+
prune(now) {
|
|
6711
|
+
for (const [path, sessions] of this.byPath) {
|
|
6712
|
+
for (const [sessionId, edit] of sessions) if (now - edit.at > this.windowMs) sessions.delete(sessionId);
|
|
6713
|
+
if (sessions.size === 0) this.byPath.delete(path);
|
|
6714
|
+
}
|
|
6715
|
+
for (const [key, at] of this.notified) if (now - at > this.windowMs) this.notified.delete(key);
|
|
6716
|
+
if (this.byPath.size > this.maxPaths) {
|
|
6717
|
+
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);
|
|
6718
|
+
for (const entry of oldest.slice(0, this.byPath.size - this.maxPaths)) this.byPath.delete(entry.path);
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
/** Test / diagnostics helper. */
|
|
6722
|
+
size() {
|
|
6723
|
+
return this.byPath.size;
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
function normalizeEditPath(path$1, cwd, realpath = node_fs.realpathSync.native ?? node_fs.realpathSync) {
|
|
6727
|
+
const absolute = path.isAbsolute(path$1) ? path.resolve(path$1) : path.resolve(cwd, path$1);
|
|
6728
|
+
let existing = absolute;
|
|
6729
|
+
const tail = [];
|
|
6730
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
6731
|
+
try {
|
|
6732
|
+
const real = realpath(existing);
|
|
6733
|
+
return tail.length ? path.join(real, ...tail) : real;
|
|
6734
|
+
} catch {
|
|
6735
|
+
const parent = path.dirname(existing);
|
|
6736
|
+
if (parent === existing) break;
|
|
6737
|
+
tail.unshift(existing.slice(parent.length).replace(new RegExp(`^\\${path.sep}`), ""));
|
|
6738
|
+
existing = parent;
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
return absolute;
|
|
6742
|
+
}
|
|
6743
|
+
|
|
6744
|
+
const CONFLICT_COALESCE_MS = 1e4;
|
|
6745
|
+
const MAX_NOTICES_PER_PAIR = 5;
|
|
6746
|
+
function pairKey(a, b) {
|
|
6747
|
+
return a < b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
6748
|
+
}
|
|
6749
|
+
function senderOf(info, sessionId) {
|
|
6750
|
+
return { sessionId, title: info?.title, flavor: info?.flavor, cwd: info?.cwd };
|
|
6751
|
+
}
|
|
6752
|
+
function createPeerCoordinator(deps) {
|
|
6753
|
+
const now = deps.now ?? (() => Date.now());
|
|
6754
|
+
const windowMs = deps.windowMs ?? EDIT_CONFLICT_WINDOW_MS;
|
|
6755
|
+
const coalesceMs = deps.coalesceMs ?? CONFLICT_COALESCE_MS;
|
|
6756
|
+
const maxNoticesPerPair = deps.maxNoticesPerPair ?? MAX_NOTICES_PER_PAIR;
|
|
6757
|
+
const tracker = new EditConflictTracker({ windowMs });
|
|
6758
|
+
const readPersisted = deps.readPersisted ?? persistence.readPersistedSessions;
|
|
6759
|
+
const send = deps.send ?? agentGuidance.sendUserMessage;
|
|
6760
|
+
const normalize = deps.normalize ?? normalizeEditPath;
|
|
6761
|
+
const liveTitle = deps.liveTitle ?? (async (sessionId, persisted) => (await agentGuidance.readSessionMetadata(sessionId, persisted))?.summary?.text);
|
|
6762
|
+
const schedule = deps.schedule ?? ((fn, ms) => {
|
|
6763
|
+
const t = setTimeout(fn, ms);
|
|
6764
|
+
t.unref?.();
|
|
6765
|
+
return t;
|
|
6766
|
+
});
|
|
6767
|
+
let getMirrors = deps.getMirrors ?? (() => []);
|
|
6768
|
+
const pending = /* @__PURE__ */ new Map();
|
|
6769
|
+
const noticesSent = /* @__PURE__ */ new Map();
|
|
6770
|
+
let inflight = Promise.resolve();
|
|
6771
|
+
const snapshot = () => {
|
|
6772
|
+
const at = now();
|
|
6773
|
+
const out = /* @__PURE__ */ new Map();
|
|
6774
|
+
for (const child of deps.getChildren()) {
|
|
6775
|
+
const id = child.happySessionId;
|
|
6776
|
+
if (!id || out.has(id)) continue;
|
|
6777
|
+
const meta = child.happySessionMetadataFromLocalWebhook;
|
|
6778
|
+
out.set(id, {
|
|
6779
|
+
sessionId: id,
|
|
6780
|
+
kind: "managed",
|
|
6781
|
+
pid: child.pid,
|
|
6782
|
+
...meta?.path ? { cwd: meta.path } : {},
|
|
6783
|
+
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
6784
|
+
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
6785
|
+
...meta?.variant ?? child.variant ? { variant: meta?.variant ?? child.variant } : {},
|
|
6786
|
+
edits: tracker.editsOf(id, at)
|
|
6787
|
+
});
|
|
6788
|
+
}
|
|
6789
|
+
for (const mirror of getMirrors()) {
|
|
6790
|
+
if (out.has(mirror.sessionId)) continue;
|
|
6791
|
+
out.set(mirror.sessionId, {
|
|
6792
|
+
sessionId: mirror.sessionId,
|
|
6793
|
+
kind: "mirror",
|
|
6794
|
+
flavor: "terminal-mirror",
|
|
6795
|
+
...mirror.cwd ? { cwd: mirror.cwd } : {},
|
|
6796
|
+
...mirror.title ? { title: mirror.title } : {},
|
|
6797
|
+
edits: tracker.editsOf(mirror.sessionId, at)
|
|
6798
|
+
});
|
|
6799
|
+
}
|
|
6800
|
+
return out;
|
|
6801
|
+
};
|
|
6802
|
+
const deliver = async (recipient, peer, peerId, paths, peerEditedAgoMs, noticeId) => {
|
|
6803
|
+
if (recipient.kind === "mirror") return;
|
|
6804
|
+
const persisted = readPersisted()[recipient.sessionId];
|
|
6805
|
+
if (!persisted) {
|
|
6806
|
+
persistence.logger.debug(`[PEERS] no key for ${recipient.sessionId}; conflict notice ${noticeId} not delivered`);
|
|
6807
|
+
return;
|
|
6808
|
+
}
|
|
6809
|
+
const peerSender = senderOf(peer, peerId);
|
|
6810
|
+
if (!peerSender.title && peer?.kind === "managed") {
|
|
6811
|
+
const peerKey = readPersisted()[peer.sessionId];
|
|
6812
|
+
if (peerKey) {
|
|
6813
|
+
try {
|
|
6814
|
+
peerSender.title = await liveTitle(peer.sessionId, peerKey);
|
|
6815
|
+
} catch {
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
const text = agentGuidance.formatEditConflictNotice({ id: noticeId, path: paths[0], paths, peer: peerSender, peerEditedAgoMs, windowMs });
|
|
6820
|
+
try {
|
|
6821
|
+
await send(recipient.sessionId, persisted, text, "edit-conflict", {
|
|
6822
|
+
sentFrom: agentGuidance.SESSION_PEER_SENT_FROM,
|
|
6823
|
+
delivery: "steer",
|
|
6824
|
+
localId: `edit-conflict-${noticeId}-${recipient.sessionId}`
|
|
6825
|
+
});
|
|
6826
|
+
persistence.logger.debug(`[PEERS] conflict notice ${noticeId} \u2192 ${recipient.sessionId} (${paths.length} file(s), first ${paths[0]}, peer ${peerId})`);
|
|
6827
|
+
} catch (error) {
|
|
6828
|
+
persistence.logger.debug(`[PEERS] conflict notice ${noticeId} \u2192 ${recipient.sessionId} failed:`, error);
|
|
6829
|
+
}
|
|
6830
|
+
};
|
|
6831
|
+
const flushPair = async (key) => {
|
|
6832
|
+
const pair = pending.get(key);
|
|
6833
|
+
if (!pair) return;
|
|
6834
|
+
pending.delete(key);
|
|
6835
|
+
const at = now();
|
|
6836
|
+
const sent = (noticesSent.get(key) ?? []).filter((t) => at - t <= windowMs);
|
|
6837
|
+
if (sent.length >= maxNoticesPerPair) {
|
|
6838
|
+
noticesSent.set(key, sent);
|
|
6839
|
+
persistence.logger.debug(`[PEERS] pair ${pair.a}/${pair.b}: ${maxNoticesPerPair} notices already sent this window; ${pair.paths.size} conflicting path(s) not announced`);
|
|
6840
|
+
return;
|
|
6841
|
+
}
|
|
6842
|
+
sent.push(at);
|
|
6843
|
+
noticesSent.set(key, sent);
|
|
6844
|
+
const sessions = snapshot();
|
|
6845
|
+
const a = sessions.get(pair.a);
|
|
6846
|
+
const b = sessions.get(pair.b);
|
|
6847
|
+
const paths = [...pair.paths.keys()];
|
|
6848
|
+
const noticeId = node_crypto.randomUUID().slice(0, 8);
|
|
6849
|
+
const latestBy = (sessionId) => Math.max(...[...pair.paths.values()].map((c) => c.editor.sessionId === sessionId ? c.editor.at : c.peer.at));
|
|
6850
|
+
const agoFor = (recipientId, otherId) => Math.max(0, latestBy(recipientId) - latestBy(otherId));
|
|
6851
|
+
if (a) await deliver(a, b, pair.b, paths, agoFor(pair.a, pair.b), noticeId);
|
|
6852
|
+
if (b) await deliver(b, a, pair.a, paths, agoFor(pair.b, pair.a), noticeId);
|
|
6853
|
+
};
|
|
6854
|
+
const enqueue = (conflict) => {
|
|
6855
|
+
const key = pairKey(conflict.editor.sessionId, conflict.peer.sessionId);
|
|
6856
|
+
let pair = pending.get(key);
|
|
6857
|
+
if (!pair) {
|
|
6858
|
+
pair = { a: conflict.editor.sessionId, b: conflict.peer.sessionId, paths: /* @__PURE__ */ new Map(), timer: null };
|
|
6859
|
+
pair.timer = schedule(() => {
|
|
6860
|
+
inflight = inflight.then(() => flushPair(key)).catch(() => {
|
|
6861
|
+
});
|
|
6862
|
+
}, coalesceMs);
|
|
6863
|
+
pending.set(key, pair);
|
|
6864
|
+
}
|
|
6865
|
+
pair.paths.set(conflict.editor.path, conflict);
|
|
6866
|
+
};
|
|
6867
|
+
return {
|
|
6868
|
+
list: () => [...snapshot().values()],
|
|
6869
|
+
onEdit(edit) {
|
|
6870
|
+
const sessions = snapshot();
|
|
6871
|
+
const self = sessions.get(edit.sessionId);
|
|
6872
|
+
if (!self) {
|
|
6873
|
+
persistence.logger.debug(`[PEERS] edit from unknown/dead session ${edit.sessionId} ignored`);
|
|
6874
|
+
return [];
|
|
6875
|
+
}
|
|
6876
|
+
const current = now();
|
|
6877
|
+
const at = typeof edit.at === "number" && Number.isFinite(edit.at) ? Math.min(edit.at, current) : current;
|
|
6878
|
+
if (current - at > windowMs) {
|
|
6879
|
+
persistence.logger.debug(`[PEERS] edit from ${edit.sessionId} on ${edit.path} is ${Math.round((current - at) / 6e4)}m old (replayed history); ignored`);
|
|
6880
|
+
return [];
|
|
6881
|
+
}
|
|
6882
|
+
const cwd = edit.cwd ?? self.cwd ?? process.cwd();
|
|
6883
|
+
const path = normalize(edit.path, cwd);
|
|
6884
|
+
const conflicts = tracker.record({ sessionId: edit.sessionId, path, tool: edit.tool, at }, (id) => sessions.has(id));
|
|
6885
|
+
for (const conflict of conflicts) enqueue(conflict);
|
|
6886
|
+
return conflicts;
|
|
6887
|
+
},
|
|
6888
|
+
async flush() {
|
|
6889
|
+
for (const key of [...pending.keys()]) {
|
|
6890
|
+
const pair = pending.get(key);
|
|
6891
|
+
if (pair?.timer && typeof pair.timer === "object") clearTimeout(pair.timer);
|
|
6892
|
+
inflight = inflight.then(() => flushPair(key)).catch(() => {
|
|
6893
|
+
});
|
|
6894
|
+
}
|
|
6895
|
+
await inflight;
|
|
6896
|
+
},
|
|
6897
|
+
forget(sessionId) {
|
|
6898
|
+
tracker.forget(sessionId);
|
|
6899
|
+
for (const [key, pair] of pending) {
|
|
6900
|
+
if (pair.a === sessionId || pair.b === sessionId) {
|
|
6901
|
+
if (pair.timer && typeof pair.timer === "object") clearTimeout(pair.timer);
|
|
6902
|
+
pending.delete(key);
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
},
|
|
6906
|
+
setMirrorSource(source) {
|
|
6907
|
+
getMirrors = source;
|
|
6908
|
+
}
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6911
|
+
|
|
6654
6912
|
const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
|
|
6655
6913
|
|
|
6656
6914
|
\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
|
|
@@ -8187,7 +8445,7 @@ function parseJsonObject(text) {
|
|
|
8187
8445
|
return null;
|
|
8188
8446
|
}
|
|
8189
8447
|
}
|
|
8190
|
-
function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
8448
|
+
function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BbtR-Qto.cjs', document.baseURI).href))).resolve(s)) {
|
|
8191
8449
|
const platformArch = `${platform}-${arch}`;
|
|
8192
8450
|
const candidates = [
|
|
8193
8451
|
`@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
|
|
@@ -8570,6 +8828,73 @@ class ClaudeAuthService {
|
|
|
8570
8828
|
}
|
|
8571
8829
|
}
|
|
8572
8830
|
|
|
8831
|
+
const CLAUDE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
8832
|
+
function pathOf(input) {
|
|
8833
|
+
if (!input || typeof input !== "object") return null;
|
|
8834
|
+
const record = input;
|
|
8835
|
+
const candidate = record.file_path ?? record.notebook_path;
|
|
8836
|
+
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
8837
|
+
}
|
|
8838
|
+
function extractClaudeEditPaths(body) {
|
|
8839
|
+
if (!body || typeof body !== "object") return [];
|
|
8840
|
+
const line = body;
|
|
8841
|
+
if (line.type !== "assistant") return [];
|
|
8842
|
+
const content = line.message?.content;
|
|
8843
|
+
if (!Array.isArray(content)) return [];
|
|
8844
|
+
const stamp = line.timestamp;
|
|
8845
|
+
const at = typeof stamp === "string" ? Date.parse(stamp) : typeof stamp === "number" ? stamp : NaN;
|
|
8846
|
+
const edits = [];
|
|
8847
|
+
for (const block of content) {
|
|
8848
|
+
if (!block || typeof block !== "object") continue;
|
|
8849
|
+
const b = block;
|
|
8850
|
+
if (b.type !== "tool_use" || typeof b.name !== "string" || !CLAUDE_EDIT_TOOLS.has(b.name)) continue;
|
|
8851
|
+
const path = pathOf(b.input);
|
|
8852
|
+
if (path) edits.push({ path, tool: b.name, ...Number.isFinite(at) ? { at } : {} });
|
|
8853
|
+
}
|
|
8854
|
+
return edits;
|
|
8855
|
+
}
|
|
8856
|
+
function extractCodexPatchPaths(changes) {
|
|
8857
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return [];
|
|
8858
|
+
return Object.keys(changes).filter((path) => path.trim() !== "").map((path) => ({ path, tool: "CodexPatch" }));
|
|
8859
|
+
}
|
|
8860
|
+
function extractAcpEditPaths(toolKind, args) {
|
|
8861
|
+
if (!args || typeof args !== "object") return [];
|
|
8862
|
+
const a = args;
|
|
8863
|
+
if (a.piTool === "write" || a.piTool === "edit") {
|
|
8864
|
+
const raw = a.rawInput;
|
|
8865
|
+
const path = raw && typeof raw.path === "string" && raw.path.trim() !== "" ? raw.path : null;
|
|
8866
|
+
return path ? [{ path, tool: a.piTool }] : [];
|
|
8867
|
+
}
|
|
8868
|
+
if (typeof a.piTool === "string") return [];
|
|
8869
|
+
if (toolKind !== "edit" || !Array.isArray(a.locations)) return [];
|
|
8870
|
+
const edits = [];
|
|
8871
|
+
for (const location of a.locations) {
|
|
8872
|
+
const path = location && typeof location === "object" ? location.path : void 0;
|
|
8873
|
+
if (typeof path === "string" && path.trim() !== "" && !edits.some((e) => e.path === path)) edits.push({ path, tool: "edit" });
|
|
8874
|
+
}
|
|
8875
|
+
return edits;
|
|
8876
|
+
}
|
|
8877
|
+
class EditReportThrottle {
|
|
8878
|
+
constructor(intervalMs = 5e3) {
|
|
8879
|
+
this.intervalMs = intervalMs;
|
|
8880
|
+
}
|
|
8881
|
+
lastReportedAt = /* @__PURE__ */ new Map();
|
|
8882
|
+
/** Returns the edits that should be reported now and remembers them. */
|
|
8883
|
+
take(edits, now = Date.now()) {
|
|
8884
|
+
const due = [];
|
|
8885
|
+
for (const edit of edits) {
|
|
8886
|
+
const last = this.lastReportedAt.get(edit.path);
|
|
8887
|
+
if (last !== void 0 && now - last < this.intervalMs) continue;
|
|
8888
|
+
this.lastReportedAt.set(edit.path, now);
|
|
8889
|
+
due.push(edit);
|
|
8890
|
+
}
|
|
8891
|
+
if (this.lastReportedAt.size > 512) {
|
|
8892
|
+
for (const [path, at] of this.lastReportedAt) if (now - at > this.intervalMs * 10) this.lastReportedAt.delete(path);
|
|
8893
|
+
}
|
|
8894
|
+
return due;
|
|
8895
|
+
}
|
|
8896
|
+
}
|
|
8897
|
+
|
|
8573
8898
|
const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set(["file-history-snapshot", "change", "queue-operation"]);
|
|
8574
8899
|
function parseLines(lines) {
|
|
8575
8900
|
const out = [];
|
|
@@ -8606,12 +8931,15 @@ function createMirrorScanner(opts) {
|
|
|
8606
8931
|
}
|
|
8607
8932
|
tail.offset = 0;
|
|
8608
8933
|
}
|
|
8934
|
+
let replaced = false;
|
|
8609
8935
|
if (tail.offset > size) {
|
|
8610
8936
|
persistence.logger.debug(`[MIRROR SCANNER] ${tail.path}: offset ${tail.offset} > size ${size} \u2014 file replaced, resetting (localId idempotency absorbs replays)`);
|
|
8611
8937
|
tail.offset = 0;
|
|
8938
|
+
replaced = true;
|
|
8612
8939
|
}
|
|
8613
8940
|
if (tail.offset === size) return;
|
|
8614
8941
|
const isFirstBackfillRead = tail.mode === "backfill-tail" && tail.offset === 0;
|
|
8942
|
+
const replay = isFirstBackfillRead || replaced;
|
|
8615
8943
|
const handle = await promises.open(tail.path, "r");
|
|
8616
8944
|
let buf;
|
|
8617
8945
|
try {
|
|
@@ -8631,7 +8959,7 @@ function createMirrorScanner(opts) {
|
|
|
8631
8959
|
if (decision.truncated) opts.events.onBackfillTruncated?.();
|
|
8632
8960
|
tail.mode = "from-eof";
|
|
8633
8961
|
}
|
|
8634
|
-
if (messages.length > 0) opts.events.onMessages(messages);
|
|
8962
|
+
if (messages.length > 0) opts.events.onMessages(messages, { replay });
|
|
8635
8963
|
};
|
|
8636
8964
|
const sync = new index.InvalidateSync(async () => {
|
|
8637
8965
|
for (const tail of files.values()) {
|
|
@@ -8754,8 +9082,17 @@ function createMirrorManager(deps) {
|
|
|
8754
9082
|
binding.client.updateMetadata((current) => ({ ...current, ...patch }));
|
|
8755
9083
|
persistBinding(binding);
|
|
8756
9084
|
};
|
|
8757
|
-
const sendMessages = (binding, messages) => {
|
|
9085
|
+
const sendMessages = (binding, messages, context = { replay: false }) => {
|
|
8758
9086
|
for (const message of messages) {
|
|
9087
|
+
if (deps.onEdit && binding.status === "active" && !context.replay) {
|
|
9088
|
+
for (const edit of extractClaudeEditPaths(message)) {
|
|
9089
|
+
try {
|
|
9090
|
+
deps.onEdit({ sessionId: binding.happySessionId, path: edit.path, tool: edit.tool, cwd: binding.metadata.path, ...edit.at !== void 0 ? { at: edit.at } : {} });
|
|
9091
|
+
} catch (error) {
|
|
9092
|
+
persistence.logger.debug(`[MIRROR] edit tap failed for terminal ${binding.terminalId}:`, error);
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
8759
9096
|
const key = index.mirrorLineKey(message);
|
|
8760
9097
|
binding.client.sendClaudeSessionMessage(message, key ? { localIdFor: (envelopeIndex) => index.mirrorLocalId(key, envelopeIndex) } : void 0);
|
|
8761
9098
|
}
|
|
@@ -8764,7 +9101,7 @@ function createMirrorManager(deps) {
|
|
|
8764
9101
|
const scanner = createMirrorScanner({
|
|
8765
9102
|
backfillLines,
|
|
8766
9103
|
events: {
|
|
8767
|
-
onMessages: (messages) => sendMessages(binding, messages),
|
|
9104
|
+
onMessages: (messages, context) => sendMessages(binding, messages, context),
|
|
8768
9105
|
onBackfillTruncated: opts.withTruncationNotice ? () => binding.client.sendSessionEvent({
|
|
8769
9106
|
type: "message",
|
|
8770
9107
|
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`
|
|
@@ -8787,6 +9124,10 @@ function createMirrorManager(deps) {
|
|
|
8787
9124
|
await binding.scanner?.cleanup();
|
|
8788
9125
|
binding.scanner = null;
|
|
8789
9126
|
binding.client.closeClaudeSessionTurn("completed");
|
|
9127
|
+
try {
|
|
9128
|
+
deps.onSessionEnded?.(binding.happySessionId);
|
|
9129
|
+
} catch {
|
|
9130
|
+
}
|
|
8790
9131
|
updateBindingMetadata(binding, {
|
|
8791
9132
|
lifecycleState: "archived",
|
|
8792
9133
|
lifecycleStateSince: Date.now(),
|
|
@@ -8794,9 +9135,9 @@ function createMirrorManager(deps) {
|
|
|
8794
9135
|
archiveReason: reason
|
|
8795
9136
|
});
|
|
8796
9137
|
try {
|
|
8797
|
-
await deps.api.
|
|
9138
|
+
await deps.api.archiveSession(binding.happySessionId);
|
|
8798
9139
|
} catch (error) {
|
|
8799
|
-
persistence.logger.debug(`[MIRROR]
|
|
9140
|
+
persistence.logger.debug(`[MIRROR] archiveSession failed for ${binding.happySessionId}:`, error);
|
|
8800
9141
|
}
|
|
8801
9142
|
deps.onBindingsChanged?.();
|
|
8802
9143
|
};
|
|
@@ -8999,6 +9340,13 @@ function createMirrorManager(deps) {
|
|
|
8999
9340
|
}
|
|
9000
9341
|
};
|
|
9001
9342
|
return {
|
|
9343
|
+
listActiveMirrors() {
|
|
9344
|
+
return [...bindings.values()].filter((binding) => binding.status === "active").map((binding) => ({
|
|
9345
|
+
sessionId: binding.happySessionId,
|
|
9346
|
+
...binding.metadata.path ? { cwd: binding.metadata.path } : {},
|
|
9347
|
+
...binding.metadata.summary?.text ? { title: binding.metadata.summary.text } : {}
|
|
9348
|
+
}));
|
|
9349
|
+
},
|
|
9002
9350
|
handleHookPayload(body) {
|
|
9003
9351
|
const event = index.parseTerminalHookPayload(body);
|
|
9004
9352
|
if (!event) {
|
|
@@ -9465,683 +9813,6 @@ Delegate from this working directory unless a different repository was explicitl
|
|
|
9465
9813
|
${goal}`;
|
|
9466
9814
|
}
|
|
9467
9815
|
|
|
9468
|
-
const MAX_LINE_CHARS = 500;
|
|
9469
|
-
const MAX_ARGS_CHARS = 160;
|
|
9470
|
-
const DEFAULT_LIMITS = { line: MAX_LINE_CHARS, args: MAX_ARGS_CHARS };
|
|
9471
|
-
const NO_LIMITS = { line: Number.POSITIVE_INFINITY, args: Number.POSITIVE_INFINITY };
|
|
9472
|
-
function truncateText(text, max = MAX_LINE_CHARS) {
|
|
9473
|
-
const chars = Array.from(text);
|
|
9474
|
-
if (chars.length <= max) return text;
|
|
9475
|
-
return chars.slice(0, max).join("") + ` \u2026[+${chars.length - max} chars]`;
|
|
9476
|
-
}
|
|
9477
|
-
function squash(text) {
|
|
9478
|
-
return text.replace(/\s+/g, " ").trim();
|
|
9479
|
-
}
|
|
9480
|
-
function safeJson(value, max) {
|
|
9481
|
-
try {
|
|
9482
|
-
return truncateText(JSON.stringify(value) ?? "", max);
|
|
9483
|
-
} catch {
|
|
9484
|
-
return "[unserializable]";
|
|
9485
|
-
}
|
|
9486
|
-
}
|
|
9487
|
-
function formatMessageBody(body, limits = DEFAULT_LIMITS) {
|
|
9488
|
-
if (!body || typeof body !== "object") return null;
|
|
9489
|
-
const b = body;
|
|
9490
|
-
if (b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string") {
|
|
9491
|
-
const text = squash(b.content.text);
|
|
9492
|
-
return text.length > 0 ? `user: ${truncateText(text, limits.line)}` : null;
|
|
9493
|
-
}
|
|
9494
|
-
if (b.role === "session" && b.content && typeof b.content === "object") {
|
|
9495
|
-
const env = b.content;
|
|
9496
|
-
const ev = env.ev;
|
|
9497
|
-
if (!ev || typeof ev.t !== "string") return null;
|
|
9498
|
-
switch (ev.t) {
|
|
9499
|
-
case "text": {
|
|
9500
|
-
if (ev.thinking) return null;
|
|
9501
|
-
const text = squash(String(ev.text ?? ""));
|
|
9502
|
-
if (text.length === 0) return null;
|
|
9503
|
-
const role = env.role === "user" ? "user" : "assistant";
|
|
9504
|
-
return `${role}: ${truncateText(text, limits.line)}`;
|
|
9505
|
-
}
|
|
9506
|
-
case "tool-call-start": {
|
|
9507
|
-
const name = String(ev.name ?? "tool");
|
|
9508
|
-
const title = squash(String(ev.title ?? ""));
|
|
9509
|
-
const args = ev.args ? safeJson(ev.args, limits.args) : "";
|
|
9510
|
-
const detail = title || args;
|
|
9511
|
-
return `[tool] ${name}${detail ? `: ${truncateText(detail, limits.args)}` : ""}`;
|
|
9512
|
-
}
|
|
9513
|
-
case "service": {
|
|
9514
|
-
const text = squash(String(ev.text ?? ""));
|
|
9515
|
-
return text.length > 0 ? `[service] ${truncateText(text, limits.args)}` : null;
|
|
9516
|
-
}
|
|
9517
|
-
case "turn-end": {
|
|
9518
|
-
if (ev.status && ev.status !== "completed") return `[turn ${ev.status}]`;
|
|
9519
|
-
return null;
|
|
9520
|
-
}
|
|
9521
|
-
default:
|
|
9522
|
-
return null;
|
|
9523
|
-
}
|
|
9524
|
-
}
|
|
9525
|
-
if (b.role === "agent" && b.content?.type === "acp" && b.content.data && typeof b.content.data === "object") {
|
|
9526
|
-
const d = b.content.data;
|
|
9527
|
-
switch (d.type) {
|
|
9528
|
-
case "message": {
|
|
9529
|
-
const text = squash(String(d.message ?? ""));
|
|
9530
|
-
return text.length > 0 ? `assistant: ${truncateText(text, limits.line)}` : null;
|
|
9531
|
-
}
|
|
9532
|
-
case "tool-call":
|
|
9533
|
-
return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input, limits.args)}`;
|
|
9534
|
-
case "permission-request":
|
|
9535
|
-
return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")), limits.args)}`;
|
|
9536
|
-
default:
|
|
9537
|
-
return null;
|
|
9538
|
-
}
|
|
9539
|
-
}
|
|
9540
|
-
if (b.role === "agent" && b.content?.type === "output") {
|
|
9541
|
-
const msg = b.content.data?.message;
|
|
9542
|
-
if (msg && Array.isArray(msg.content)) {
|
|
9543
|
-
const texts = msg.content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => squash(c.text)).filter((t) => t.length > 0);
|
|
9544
|
-
if (texts.length > 0) {
|
|
9545
|
-
const role = msg.role === "user" ? "user" : "assistant";
|
|
9546
|
-
return `${role}: ${truncateText(texts.join(" "), limits.line)}`;
|
|
9547
|
-
}
|
|
9548
|
-
}
|
|
9549
|
-
return null;
|
|
9550
|
-
}
|
|
9551
|
-
return null;
|
|
9552
|
-
}
|
|
9553
|
-
function formatTranscript(bodies, options = {}) {
|
|
9554
|
-
const limits = options.full ? NO_LIMITS : DEFAULT_LIMITS;
|
|
9555
|
-
const lines = [];
|
|
9556
|
-
for (const body of bodies) {
|
|
9557
|
-
const line = formatMessageBody(body, limits);
|
|
9558
|
-
if (line !== null) lines.push(line);
|
|
9559
|
-
}
|
|
9560
|
-
return lines.join("\n");
|
|
9561
|
-
}
|
|
9562
|
-
|
|
9563
|
-
function asRecord(value) {
|
|
9564
|
-
return value && typeof value === "object" ? value : null;
|
|
9565
|
-
}
|
|
9566
|
-
function sessionEnvelope(body) {
|
|
9567
|
-
const b = asRecord(body);
|
|
9568
|
-
if (!b || b.role !== "session") return null;
|
|
9569
|
-
const env = asRecord(b.content);
|
|
9570
|
-
if (!env) return null;
|
|
9571
|
-
const ev = asRecord(env.ev);
|
|
9572
|
-
return ev && typeof ev.t === "string" ? { role: env.role, ev } : null;
|
|
9573
|
-
}
|
|
9574
|
-
function isUserPrompt(body) {
|
|
9575
|
-
const b = asRecord(body);
|
|
9576
|
-
return !!b && b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string";
|
|
9577
|
-
}
|
|
9578
|
-
function agentPiece(body) {
|
|
9579
|
-
const env = sessionEnvelope(body);
|
|
9580
|
-
if (env && env.role !== "user") {
|
|
9581
|
-
if (env.ev.t === "text" && !env.ev.thinking) {
|
|
9582
|
-
const text = String(env.ev.text ?? "");
|
|
9583
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9584
|
-
}
|
|
9585
|
-
if (env.ev.t === "tool-call-start") return { kind: "tool" };
|
|
9586
|
-
return null;
|
|
9587
|
-
}
|
|
9588
|
-
const b = asRecord(body);
|
|
9589
|
-
if (b?.role === "agent" && b.content?.type === "acp") {
|
|
9590
|
-
const d = asRecord(b.content.data);
|
|
9591
|
-
if (d?.type === "message") {
|
|
9592
|
-
const text = String(d.message ?? "");
|
|
9593
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9594
|
-
}
|
|
9595
|
-
if (d?.type === "tool-call") return { kind: "tool" };
|
|
9596
|
-
}
|
|
9597
|
-
return null;
|
|
9598
|
-
}
|
|
9599
|
-
function userPromptText(body) {
|
|
9600
|
-
return isUserPrompt(body) ? body.content.text : null;
|
|
9601
|
-
}
|
|
9602
|
-
function analyzeLatestTurn(entries, options = {}) {
|
|
9603
|
-
const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
|
|
9604
|
-
let userIndex = -1;
|
|
9605
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
9606
|
-
if (isUserPrompt(entries[i].body) && (!options.anchor || options.anchor(entries[i].body))) {
|
|
9607
|
-
userIndex = i;
|
|
9608
|
-
break;
|
|
9609
|
-
}
|
|
9610
|
-
}
|
|
9611
|
-
let ended = false;
|
|
9612
|
-
let status = null;
|
|
9613
|
-
let error = null;
|
|
9614
|
-
let texts = [];
|
|
9615
|
-
let lastMarker = null;
|
|
9616
|
-
let startedAfterPrompt = false;
|
|
9617
|
-
const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
|
|
9618
|
-
for (let i = userIndex + 1; i < entries.length; i++) {
|
|
9619
|
-
const body = entries[i].body;
|
|
9620
|
-
if (options.anchor && isUserPrompt(body)) {
|
|
9621
|
-
if (ended) break;
|
|
9622
|
-
continue;
|
|
9623
|
-
}
|
|
9624
|
-
const env = sessionEnvelope(body);
|
|
9625
|
-
if (env?.ev?.t === "turn-start") {
|
|
9626
|
-
lastMarker = "start";
|
|
9627
|
-
startedAfterPrompt = true;
|
|
9628
|
-
ended = false;
|
|
9629
|
-
status = null;
|
|
9630
|
-
error = null;
|
|
9631
|
-
texts = [];
|
|
9632
|
-
continue;
|
|
9633
|
-
}
|
|
9634
|
-
if (env?.ev?.t === "turn-end") {
|
|
9635
|
-
lastMarker = "end";
|
|
9636
|
-
if (userIndex >= 0 && emitsStarts && !startedAfterPrompt) continue;
|
|
9637
|
-
ended = true;
|
|
9638
|
-
status = typeof env.ev.status === "string" ? env.ev.status : null;
|
|
9639
|
-
error = typeof env.ev.error === "string" ? env.ev.error : null;
|
|
9640
|
-
continue;
|
|
9641
|
-
}
|
|
9642
|
-
const piece = agentPiece(body);
|
|
9643
|
-
if (!piece) continue;
|
|
9644
|
-
if (piece.kind === "tool") texts = [];
|
|
9645
|
-
else texts.push(piece.text);
|
|
9646
|
-
}
|
|
9647
|
-
if (userIndex < 0) ended = lastMarker === "end";
|
|
9648
|
-
return {
|
|
9649
|
-
userSeq: userIndex >= 0 ? entries[userIndex].seq : null,
|
|
9650
|
-
ended,
|
|
9651
|
-
status: ended ? status : null,
|
|
9652
|
-
error: ended ? error : null,
|
|
9653
|
-
answer: texts.join("\n\n").trim(),
|
|
9654
|
-
lastSeq
|
|
9655
|
-
};
|
|
9656
|
-
}
|
|
9657
|
-
|
|
9658
|
-
const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
|
|
9659
|
-
async function openUserScopedSocket(token, options = {}) {
|
|
9660
|
-
const socket = socket_ioClient.io(persistence.configuration.serverUrl, {
|
|
9661
|
-
auth: {
|
|
9662
|
-
token,
|
|
9663
|
-
clientType: "user-scoped",
|
|
9664
|
-
happyClient: `cli-session-ops/${persistence.configuration.currentCliVersion}`
|
|
9665
|
-
},
|
|
9666
|
-
path: "/v1/updates",
|
|
9667
|
-
transports: ["websocket"],
|
|
9668
|
-
reconnection: false,
|
|
9669
|
-
autoConnect: false
|
|
9670
|
-
});
|
|
9671
|
-
await new Promise((resolve, reject) => {
|
|
9672
|
-
const timer = setTimeout(() => {
|
|
9673
|
-
socket.close();
|
|
9674
|
-
reject(new Error(`Timed out connecting to ${persistence.configuration.serverUrl}`));
|
|
9675
|
-
}, options.connectTimeoutMs ?? USER_SOCKET_CONNECT_TIMEOUT_MS);
|
|
9676
|
-
socket.once("connect", () => {
|
|
9677
|
-
clearTimeout(timer);
|
|
9678
|
-
resolve();
|
|
9679
|
-
});
|
|
9680
|
-
socket.once("connect_error", (error) => {
|
|
9681
|
-
clearTimeout(timer);
|
|
9682
|
-
socket.close();
|
|
9683
|
-
reject(new Error(`Could not connect to ${persistence.configuration.serverUrl}: ${error.message}`));
|
|
9684
|
-
});
|
|
9685
|
-
socket.connect();
|
|
9686
|
-
});
|
|
9687
|
-
return {
|
|
9688
|
-
async rpcCall(payload, timeoutMs) {
|
|
9689
|
-
const ack = await socket.timeout(timeoutMs).emitWithAck("rpc-call", payload);
|
|
9690
|
-
return ack;
|
|
9691
|
-
},
|
|
9692
|
-
close() {
|
|
9693
|
-
socket.close();
|
|
9694
|
-
}
|
|
9695
|
-
};
|
|
9696
|
-
}
|
|
9697
|
-
|
|
9698
|
-
const RPC_RATE_LIMIT_ERROR = "RPC rate limit reached";
|
|
9699
|
-
const RPC_ACCOUNT_RATE_LIMIT_ERROR = "RPC account rate limit reached";
|
|
9700
|
-
const RPC_RATE_RETRY_MIN_DELAY_MS = 2e3;
|
|
9701
|
-
const RPC_RATE_RETRY_MAX_DELAY_MS = 6e4;
|
|
9702
|
-
const RPC_RATE_MAX_RETRIES = 2;
|
|
9703
|
-
function isRpcRateLimitAck(ack) {
|
|
9704
|
-
if (!ack || typeof ack !== "object") return false;
|
|
9705
|
-
const a = ack;
|
|
9706
|
-
if (a.ok !== false) return false;
|
|
9707
|
-
if (a.code === "rpc_rate_limited" || a.code === "rpc_account_rate_limited") return true;
|
|
9708
|
-
return a.error === RPC_RATE_LIMIT_ERROR || a.error === RPC_ACCOUNT_RATE_LIMIT_ERROR;
|
|
9709
|
-
}
|
|
9710
|
-
function rpcRateRetryDelayMs(attempt, retryAfterMs, random = Math.random()) {
|
|
9711
|
-
if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
|
|
9712
|
-
return Math.min(RPC_RATE_RETRY_MAX_DELAY_MS, Math.round(retryAfterMs * (1 + random * 0.5)));
|
|
9713
|
-
}
|
|
9714
|
-
const exponent = Math.max(0, Math.min(attempt, 32) - 1);
|
|
9715
|
-
const ceiling = Math.min(RPC_RATE_RETRY_MIN_DELAY_MS * 2 ** exponent, RPC_RATE_RETRY_MAX_DELAY_MS);
|
|
9716
|
-
return Math.round(RPC_RATE_RETRY_MIN_DELAY_MS + random * (ceiling - RPC_RATE_RETRY_MIN_DELAY_MS));
|
|
9717
|
-
}
|
|
9718
|
-
|
|
9719
|
-
const PERMISSION_OPS_CLIENT = "session-ops";
|
|
9720
|
-
const PERMISSION_RPC_TIMEOUT_MS = 3e4;
|
|
9721
|
-
const SETTLE_TIMEOUT_MS = 5e3;
|
|
9722
|
-
const SETTLE_POLL_MS = 500;
|
|
9723
|
-
function buildPermissionRpcPayload(requestId, verdict) {
|
|
9724
|
-
if (verdict.kind === "approve") {
|
|
9725
|
-
return {
|
|
9726
|
-
id: requestId,
|
|
9727
|
-
approved: true,
|
|
9728
|
-
decision: verdict.forSession ? "approved_for_session" : "approved"
|
|
9729
|
-
};
|
|
9730
|
-
}
|
|
9731
|
-
return {
|
|
9732
|
-
id: requestId,
|
|
9733
|
-
approved: false,
|
|
9734
|
-
decision: "denied",
|
|
9735
|
-
...verdict.reason ? { reason: verdict.reason } : {}
|
|
9736
|
-
};
|
|
9737
|
-
}
|
|
9738
|
-
function interpretPermissionAck(ack, decryptResult) {
|
|
9739
|
-
if (!ack.ok) {
|
|
9740
|
-
const message = ack.error ?? "RPC call failed";
|
|
9741
|
-
if (message === "RPC method not available") {
|
|
9742
|
-
return { status: "offline", message: "no running wrapper has registered the permission RPC for this session" };
|
|
9743
|
-
}
|
|
9744
|
-
if (message === "RPC target disconnected") {
|
|
9745
|
-
return { status: "offline", message: "the wrapper disconnected while the permission RPC was in flight" };
|
|
9746
|
-
}
|
|
9747
|
-
if (/timed? ?out/i.test(message)) {
|
|
9748
|
-
return { status: "timeout", message };
|
|
9749
|
-
}
|
|
9750
|
-
if (isRpcRateLimitAck(ack)) {
|
|
9751
|
-
return { status: "rate-limited", message, retryAfterMs: rpcRateRetryDelayMs(1, ack.retryAfterMs, 0) };
|
|
9752
|
-
}
|
|
9753
|
-
return { status: "rejected", message };
|
|
9754
|
-
}
|
|
9755
|
-
let body = null;
|
|
9756
|
-
if (typeof ack.result === "string" && ack.result.length > 0) {
|
|
9757
|
-
try {
|
|
9758
|
-
body = decryptResult(ack.result);
|
|
9759
|
-
} catch {
|
|
9760
|
-
body = null;
|
|
9761
|
-
}
|
|
9762
|
-
}
|
|
9763
|
-
if (body && typeof body === "object" && typeof body.error === "string") {
|
|
9764
|
-
return { status: "handler-error", message: body.error };
|
|
9765
|
-
}
|
|
9766
|
-
return { status: "acknowledged" };
|
|
9767
|
-
}
|
|
9768
|
-
function pendingRequestsOf(agentState, now) {
|
|
9769
|
-
const requests = agentState?.requests;
|
|
9770
|
-
if (!requests || typeof requests !== "object") return [];
|
|
9771
|
-
return Object.entries(requests).map(([id, request]) => ({
|
|
9772
|
-
id,
|
|
9773
|
-
tool: typeof request?.tool === "string" ? request.tool : "unknown",
|
|
9774
|
-
...request?.kind ? { kind: request.kind } : {},
|
|
9775
|
-
...typeof request?.createdAt === "number" ? { createdAt: request.createdAt, waitingMs: Math.max(0, now - request.createdAt) } : {}
|
|
9776
|
-
})).sort((a, b) => (a.createdAt ?? Number.MAX_SAFE_INTEGER) - (b.createdAt ?? Number.MAX_SAFE_INTEGER));
|
|
9777
|
-
}
|
|
9778
|
-
const RPC_DEADLINE_GRACE_MS = 2e3;
|
|
9779
|
-
function withDeadline(promise, ms, message) {
|
|
9780
|
-
return new Promise((resolve, reject) => {
|
|
9781
|
-
const timer = setTimeout(() => reject(new Error(`${message} (timed out)`)), ms);
|
|
9782
|
-
promise.then(
|
|
9783
|
-
(value) => {
|
|
9784
|
-
clearTimeout(timer);
|
|
9785
|
-
resolve(value);
|
|
9786
|
-
},
|
|
9787
|
-
(error) => {
|
|
9788
|
-
clearTimeout(timer);
|
|
9789
|
-
reject(error);
|
|
9790
|
-
}
|
|
9791
|
-
);
|
|
9792
|
-
});
|
|
9793
|
-
}
|
|
9794
|
-
async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}) {
|
|
9795
|
-
const now = deps.now ?? Date.now;
|
|
9796
|
-
const persisted = (deps.readPersisted ?? persistence.readPersistedSessions)()[sessionId];
|
|
9797
|
-
if (!persisted) {
|
|
9798
|
-
throw new Error(
|
|
9799
|
-
`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.`
|
|
9800
|
-
);
|
|
9801
|
-
}
|
|
9802
|
-
const token = await (deps.bearerToken ?? bearerToken$1)();
|
|
9803
|
-
const fetchState = deps.fetchAgentState ?? fetchDecryptedAgentState;
|
|
9804
|
-
const before = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9805
|
-
if (!before.some((request) => request.id === requestId)) {
|
|
9806
|
-
const hint = before.length > 0 ? `Pending request ids: ${before.map((request) => `${request.id} (${request.tool})`).join(", ")}` : "The session has no pending requests.";
|
|
9807
|
-
throw new Error(`Request ${requestId} is not pending on session ${sessionId}. ${hint}`);
|
|
9808
|
-
}
|
|
9809
|
-
const payload = buildPermissionRpcPayload(requestId, verdict);
|
|
9810
|
-
const key = persistence.decodeBase64(persisted.encryptionKey);
|
|
9811
|
-
const params = persistence.encodeBase64(persistence.encrypt(key, persisted.encryptionVariant, payload));
|
|
9812
|
-
const transport = await (deps.openTransport ?? openUserScopedSocket)(token);
|
|
9813
|
-
const rpcTimeoutMs = deps.rpcTimeoutMs ?? PERMISSION_RPC_TIMEOUT_MS;
|
|
9814
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
9815
|
-
let ack;
|
|
9816
|
-
try {
|
|
9817
|
-
for (let attempt = 1; ; attempt++) {
|
|
9818
|
-
try {
|
|
9819
|
-
ack = await withDeadline(
|
|
9820
|
-
transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
|
|
9821
|
-
rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
|
|
9822
|
-
`permission RPC did not settle within ${rpcTimeoutMs}ms`
|
|
9823
|
-
);
|
|
9824
|
-
} catch (error) {
|
|
9825
|
-
ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
|
|
9826
|
-
}
|
|
9827
|
-
if (!isRpcRateLimitAck(ack) || attempt > RPC_RATE_MAX_RETRIES) break;
|
|
9828
|
-
const waitMs = rpcRateRetryDelayMs(attempt, ack.retryAfterMs, (deps.random ?? Math.random)());
|
|
9829
|
-
deps.onRateLimited?.({ attempt, waitMs, serverError: ack.error ?? "RPC rate limit reached" });
|
|
9830
|
-
await sleep(waitMs);
|
|
9831
|
-
}
|
|
9832
|
-
} finally {
|
|
9833
|
-
transport.close();
|
|
9834
|
-
}
|
|
9835
|
-
const outcome = interpretPermissionAck(ack, (encrypted) => persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(encrypted)));
|
|
9836
|
-
if (outcome.status !== "acknowledged") {
|
|
9837
|
-
return { sessionId, requestId, payload, outcome };
|
|
9838
|
-
}
|
|
9839
|
-
const deadline = now() + (deps.settleTimeoutMs ?? SETTLE_TIMEOUT_MS);
|
|
9840
|
-
let settled = false;
|
|
9841
|
-
for (; ; ) {
|
|
9842
|
-
const pending = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9843
|
-
if (!pending.some((request) => request.id === requestId)) {
|
|
9844
|
-
settled = true;
|
|
9845
|
-
break;
|
|
9846
|
-
}
|
|
9847
|
-
if (now() >= deadline) break;
|
|
9848
|
-
await sleep(SETTLE_POLL_MS);
|
|
9849
|
-
}
|
|
9850
|
-
return { sessionId, requestId, payload, outcome, settled };
|
|
9851
|
-
}
|
|
9852
|
-
async function bearerToken$1() {
|
|
9853
|
-
const credentials = await persistence.readCredentialsForConfiguredRelay();
|
|
9854
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9855
|
-
return credentials.token;
|
|
9856
|
-
}
|
|
9857
|
-
async function fetchDecryptedAgentState(sessionId, persisted, token) {
|
|
9858
|
-
const response = await axios.get(
|
|
9859
|
-
`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
|
|
9860
|
-
{
|
|
9861
|
-
headers: {
|
|
9862
|
-
"Authorization": `Bearer ${token}`,
|
|
9863
|
-
"X-Happy-Client": `${PERMISSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
9864
|
-
},
|
|
9865
|
-
timeout: 15e3,
|
|
9866
|
-
// 404 is a real answer ("not yours / gone"), not a transport fault.
|
|
9867
|
-
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
9868
|
-
}
|
|
9869
|
-
);
|
|
9870
|
-
if (response.status === 404) {
|
|
9871
|
-
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).`);
|
|
9872
|
-
}
|
|
9873
|
-
const raw = response.data?.session?.agentState;
|
|
9874
|
-
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
9875
|
-
return persistence.decrypt(persistence.decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, persistence.decodeBase64(raw));
|
|
9876
|
-
}
|
|
9877
|
-
|
|
9878
|
-
const SESSION_OPS_CLIENT = "session-ops";
|
|
9879
|
-
const DEFAULT_RECENT_LIMIT = 15;
|
|
9880
|
-
const MAX_READ_LIMIT = 100;
|
|
9881
|
-
function mergeSessionSummaries(live, persisted, options = {}) {
|
|
9882
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9883
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9884
|
-
const summaries = [];
|
|
9885
|
-
for (const child of live) {
|
|
9886
|
-
const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
|
|
9887
|
-
if (!id || seen.has(id)) continue;
|
|
9888
|
-
seen.add(id);
|
|
9889
|
-
summaries.push(toSummary(id, persisted[id], livenessOf(child)));
|
|
9890
|
-
}
|
|
9891
|
-
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);
|
|
9892
|
-
for (const [id, entry] of rest) {
|
|
9893
|
-
summaries.push(toSummary(id, entry, { live: false }));
|
|
9894
|
-
}
|
|
9895
|
-
if (!options.tag) return summaries;
|
|
9896
|
-
const wanted = options.tag;
|
|
9897
|
-
return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9898
|
-
}
|
|
9899
|
-
function livenessOf(child) {
|
|
9900
|
-
return { live: true, pid: typeof child.pid === "number" ? child.pid : void 0 };
|
|
9901
|
-
}
|
|
9902
|
-
function sessionLiveness(live, sessionId) {
|
|
9903
|
-
const child = live.find((entry) => entry.happySessionId === sessionId);
|
|
9904
|
-
return child ? livenessOf(child) : { live: false };
|
|
9905
|
-
}
|
|
9906
|
-
function toSummary(id, persisted, extra) {
|
|
9907
|
-
const meta = persisted?.metadata;
|
|
9908
|
-
return {
|
|
9909
|
-
id,
|
|
9910
|
-
live: extra.live,
|
|
9911
|
-
...extra.pid !== void 0 ? { pid: extra.pid } : {},
|
|
9912
|
-
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
9913
|
-
...meta?.path ? { cwd: meta.path } : {},
|
|
9914
|
-
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
9915
|
-
...meta?.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9916
|
-
...meta?.variant ? { variant: meta.variant } : {},
|
|
9917
|
-
url: sessionWebUrl(id),
|
|
9918
|
-
...persisted?.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {}
|
|
9919
|
-
};
|
|
9920
|
-
}
|
|
9921
|
-
async function listSessions(options = {}) {
|
|
9922
|
-
const live = await index.listDaemonSessions();
|
|
9923
|
-
return mergeSessionSummaries(live, persistence.readPersistedSessions(), options);
|
|
9924
|
-
}
|
|
9925
|
-
function summarizeAccountSession(row, persisted, liveIds, now) {
|
|
9926
|
-
const base = {
|
|
9927
|
-
id: row.id,
|
|
9928
|
-
live: liveIds.has(row.id),
|
|
9929
|
-
url: sessionWebUrl(row.id),
|
|
9930
|
-
decryptable: false,
|
|
9931
|
-
active: row.active === true,
|
|
9932
|
-
archived: typeof row.archivedAt === "number",
|
|
9933
|
-
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
9934
|
-
...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {},
|
|
9935
|
-
attention: false
|
|
9936
|
-
};
|
|
9937
|
-
if (!persisted) return base;
|
|
9938
|
-
const key = persistence.decodeBase64(persisted.encryptionKey);
|
|
9939
|
-
let metadata = null;
|
|
9940
|
-
let agentState = null;
|
|
9941
|
-
try {
|
|
9942
|
-
metadata = row.metadata ? persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(row.metadata)) : null;
|
|
9943
|
-
agentState = row.agentState ? persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(row.agentState)) : null;
|
|
9944
|
-
} catch {
|
|
9945
|
-
return base;
|
|
9946
|
-
}
|
|
9947
|
-
if (!metadata) return base;
|
|
9948
|
-
const meta = metadata;
|
|
9949
|
-
const pending = pendingRequestsOf(agentState, now);
|
|
9950
|
-
return {
|
|
9951
|
-
...base,
|
|
9952
|
-
decryptable: true,
|
|
9953
|
-
...meta.summary?.text ? { title: meta.summary.text } : {},
|
|
9954
|
-
...meta.path ? { cwd: meta.path } : {},
|
|
9955
|
-
...meta.flavor ? { flavor: meta.flavor } : {},
|
|
9956
|
-
...meta.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9957
|
-
...meta.variant ? { variant: meta.variant } : {},
|
|
9958
|
-
...meta.machineId ? { machineId: meta.machineId } : {},
|
|
9959
|
-
...persisted.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
|
|
9960
|
-
pending,
|
|
9961
|
-
attention: pending.length > 0
|
|
9962
|
-
};
|
|
9963
|
-
}
|
|
9964
|
-
function orderAccountSessions(summaries, options = {}) {
|
|
9965
|
-
const oldestWait = (summary) => Math.max(0, ...(summary.pending ?? []).map((request) => request.waitingMs ?? 0));
|
|
9966
|
-
const rank = (summary) => summary.attention ? 0 : summary.live ? 1 : 2;
|
|
9967
|
-
let rows = summaries.filter((summary) => summary.flavor !== "terminal-mirror").slice().sort((a, b) => {
|
|
9968
|
-
const byRank = rank(a) - rank(b);
|
|
9969
|
-
if (byRank !== 0) return byRank;
|
|
9970
|
-
if (a.attention && b.attention) return oldestWait(b) - oldestWait(a);
|
|
9971
|
-
return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
|
|
9972
|
-
});
|
|
9973
|
-
if (options.tag) {
|
|
9974
|
-
const wanted = options.tag;
|
|
9975
|
-
rows = rows.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9976
|
-
}
|
|
9977
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9978
|
-
let idleKept = 0;
|
|
9979
|
-
return rows.filter((summary) => rank(summary) < 2 || idleKept++ < recentLimit);
|
|
9980
|
-
}
|
|
9981
|
-
async function listAccountSessions(options = {}) {
|
|
9982
|
-
const token = await bearerToken();
|
|
9983
|
-
const response = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions`, {
|
|
9984
|
-
headers: {
|
|
9985
|
-
"Authorization": `Bearer ${token}`,
|
|
9986
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
9987
|
-
},
|
|
9988
|
-
timeout: 15e3
|
|
9989
|
-
});
|
|
9990
|
-
const rows = Array.isArray(response.data?.sessions) ? response.data.sessions : [];
|
|
9991
|
-
const persisted = persistence.readPersistedSessions();
|
|
9992
|
-
const live = await index.listDaemonSessions();
|
|
9993
|
-
const liveIds = new Set(live.map((child) => child.happySessionId).filter((id) => typeof id === "string"));
|
|
9994
|
-
const now = Date.now();
|
|
9995
|
-
const summaries = rows.filter((row) => typeof row?.id === "string").map((row) => summarizeAccountSession(row, persisted[row.id], liveIds, now)).filter((summary) => options.includeArchived || !summary.archived);
|
|
9996
|
-
return orderAccountSessions(summaries, options);
|
|
9997
|
-
}
|
|
9998
|
-
async function bearerToken() {
|
|
9999
|
-
const credentials = await persistence.readCredentialsForConfiguredRelay();
|
|
10000
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
10001
|
-
return credentials.token;
|
|
10002
|
-
}
|
|
10003
|
-
function requirePersisted(sessionId) {
|
|
10004
|
-
const persisted = persistence.readPersistedSessions()[sessionId];
|
|
10005
|
-
if (!persisted) {
|
|
10006
|
-
throw new Error(
|
|
10007
|
-
`No local key for session ${sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`
|
|
10008
|
-
);
|
|
10009
|
-
}
|
|
10010
|
-
return persisted;
|
|
10011
|
-
}
|
|
10012
|
-
async function readSessionLog(sessionId, persisted, limit) {
|
|
10013
|
-
const token = await bearerToken();
|
|
10014
|
-
const response = await axios.get(
|
|
10015
|
-
`${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
10016
|
-
{
|
|
10017
|
-
params: { before_seq: 2147483647, limit },
|
|
10018
|
-
headers: {
|
|
10019
|
-
"Authorization": `Bearer ${token}`,
|
|
10020
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
10021
|
-
},
|
|
10022
|
-
timeout: 15e3
|
|
10023
|
-
}
|
|
10024
|
-
);
|
|
10025
|
-
const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
|
|
10026
|
-
messages.reverse();
|
|
10027
|
-
const key = persistence.decodeBase64(persisted.encryptionKey);
|
|
10028
|
-
return messages.map((message) => {
|
|
10029
|
-
if (message.content?.t !== "encrypted") return { seq: message.seq, body: null };
|
|
10030
|
-
try {
|
|
10031
|
-
return { seq: message.seq, body: persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(message.content.c)) };
|
|
10032
|
-
} catch {
|
|
10033
|
-
return { seq: message.seq, body: null };
|
|
10034
|
-
}
|
|
10035
|
-
});
|
|
10036
|
-
}
|
|
10037
|
-
const TURN_WINDOW = 500;
|
|
10038
|
-
async function readSessionTranscript(sessionId, limit, options = {}) {
|
|
10039
|
-
const persisted = requirePersisted(sessionId);
|
|
10040
|
-
const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
|
|
10041
|
-
const entries = await readSessionLog(sessionId, persisted, Math.max(bounded, TURN_WINDOW));
|
|
10042
|
-
const shown = entries.slice(-bounded);
|
|
10043
|
-
const live = await index.listDaemonSessions();
|
|
10044
|
-
return {
|
|
10045
|
-
summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
|
|
10046
|
-
messageCount: shown.length,
|
|
10047
|
-
transcript: formatTranscript(shown.map((entry) => entry.body), { full: options.full }),
|
|
10048
|
-
turn: analyzeLatestTurn(entries)
|
|
10049
|
-
};
|
|
10050
|
-
}
|
|
10051
|
-
async function readSessionMetadata(sessionId, persisted) {
|
|
10052
|
-
const token = await bearerToken();
|
|
10053
|
-
const response = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
10054
|
-
headers: {
|
|
10055
|
-
"Authorization": `Bearer ${token}`,
|
|
10056
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
10057
|
-
},
|
|
10058
|
-
timeout: 15e3
|
|
10059
|
-
});
|
|
10060
|
-
const ciphertext = response.data?.session?.metadata;
|
|
10061
|
-
if (typeof ciphertext !== "string" || ciphertext.length === 0) return null;
|
|
10062
|
-
return persistence.decrypt(persistence.decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, persistence.decodeBase64(ciphertext));
|
|
10063
|
-
}
|
|
10064
|
-
async function readSessionState(sessionId, persisted) {
|
|
10065
|
-
const token = await bearerToken();
|
|
10066
|
-
const response = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
10067
|
-
headers: {
|
|
10068
|
-
"Authorization": `Bearer ${token}`,
|
|
10069
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
10070
|
-
},
|
|
10071
|
-
timeout: 15e3,
|
|
10072
|
-
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
10073
|
-
});
|
|
10074
|
-
if (response.status === 404) return { found: false, active: false };
|
|
10075
|
-
const row = response.data?.session;
|
|
10076
|
-
if (!row || typeof row.id !== "string") throw new Error(`Server returned no session row for ${sessionId}`);
|
|
10077
|
-
const state = {
|
|
10078
|
-
found: true,
|
|
10079
|
-
active: row.active === true,
|
|
10080
|
-
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
10081
|
-
archivedAt: typeof row.archivedAt === "number" ? row.archivedAt : null,
|
|
10082
|
-
...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {}
|
|
10083
|
-
};
|
|
10084
|
-
if (persisted && typeof row.metadata === "string" && row.metadata.length > 0) {
|
|
10085
|
-
try {
|
|
10086
|
-
const metadata = persistence.decrypt(persistence.decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, persistence.decodeBase64(row.metadata));
|
|
10087
|
-
if (metadata?.machineId) state.machineId = metadata.machineId;
|
|
10088
|
-
} catch {
|
|
10089
|
-
}
|
|
10090
|
-
}
|
|
10091
|
-
return state;
|
|
10092
|
-
}
|
|
10093
|
-
class TurnWaitTimeoutError extends Error {
|
|
10094
|
-
constructor(sessionId, timeoutMs, turn) {
|
|
10095
|
-
super(`Session ${sessionId}: the latest turn did not end within ${Math.round(timeoutMs / 1e3)}s`);
|
|
10096
|
-
this.sessionId = sessionId;
|
|
10097
|
-
this.timeoutMs = timeoutMs;
|
|
10098
|
-
this.turn = turn;
|
|
10099
|
-
}
|
|
10100
|
-
}
|
|
10101
|
-
async function waitForTurnEnd(sessionId, options) {
|
|
10102
|
-
const persisted = requirePersisted(sessionId);
|
|
10103
|
-
const pollMs = options.pollMs ?? 3e3;
|
|
10104
|
-
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
10105
|
-
const deadline = Date.now() + options.timeoutMs;
|
|
10106
|
-
while (true) {
|
|
10107
|
-
const turn = analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW));
|
|
10108
|
-
if (turn.ended) return turn;
|
|
10109
|
-
if (Date.now() + pollMs > deadline) throw new TurnWaitTimeoutError(sessionId, options.timeoutMs, turn);
|
|
10110
|
-
await sleep(pollMs);
|
|
10111
|
-
}
|
|
10112
|
-
}
|
|
10113
|
-
async function stopSession(sessionId) {
|
|
10114
|
-
return await index.stopDaemonSession(sessionId);
|
|
10115
|
-
}
|
|
10116
|
-
async function archiveSession(sessionId) {
|
|
10117
|
-
const token = await bearerToken();
|
|
10118
|
-
await axios.post(
|
|
10119
|
-
`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}/archive`,
|
|
10120
|
-
{},
|
|
10121
|
-
{
|
|
10122
|
-
headers: {
|
|
10123
|
-
"Authorization": `Bearer ${token}`,
|
|
10124
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
10125
|
-
},
|
|
10126
|
-
timeout: 1e4
|
|
10127
|
-
}
|
|
10128
|
-
);
|
|
10129
|
-
}
|
|
10130
|
-
async function unarchiveSession(sessionId) {
|
|
10131
|
-
const token = await bearerToken();
|
|
10132
|
-
await axios.post(
|
|
10133
|
-
`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}/unarchive`,
|
|
10134
|
-
{},
|
|
10135
|
-
{
|
|
10136
|
-
headers: {
|
|
10137
|
-
"Authorization": `Bearer ${token}`,
|
|
10138
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
10139
|
-
},
|
|
10140
|
-
timeout: 1e4
|
|
10141
|
-
}
|
|
10142
|
-
);
|
|
10143
|
-
}
|
|
10144
|
-
|
|
10145
9816
|
function receiptPath(home, operationId) {
|
|
10146
9817
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
|
|
10147
9818
|
return path.join(home, "teams", "effects", `${operationId}.json`);
|
|
@@ -10295,8 +9966,8 @@ function createTeamWorker(deps) {
|
|
|
10295
9966
|
await complete(op, receipt);
|
|
10296
9967
|
return;
|
|
10297
9968
|
}
|
|
10298
|
-
const key = await waitForSessionKey(receipt.sessionId, 1e4);
|
|
10299
|
-
await sendUserMessage(receipt.sessionId, key, teamInitialPrompt(op.prompt, bot?.root === true, receipt.directory), "teams", { localId: `teams-initial-${op.id}`, sentFrom: "team", ...op.model ? { model: op.model } : {} });
|
|
9969
|
+
const key = await agentGuidance.waitForSessionKey(receipt.sessionId, 1e4);
|
|
9970
|
+
await agentGuidance.sendUserMessage(receipt.sessionId, key, teamInitialPrompt(op.prompt, bot?.root === true, receipt.directory), "teams", { localId: `teams-initial-${op.id}`, sentFrom: "team", ...op.model ? { model: op.model } : {} });
|
|
10300
9971
|
receipt = { ...receipt, phase: "delivered" };
|
|
10301
9972
|
writeReceipt$1(home, receipt);
|
|
10302
9973
|
}
|
|
@@ -10312,7 +9983,7 @@ function createTeamWorker(deps) {
|
|
|
10312
9983
|
if (!bot || bot.generation !== op.generation || bot.sessionId !== op.sessionId) throw new Error("Worker binding changed; preserve resources for reconciliation");
|
|
10313
9984
|
if (op.sessionId) {
|
|
10314
9985
|
await deps.stopAndWait(op.sessionId);
|
|
10315
|
-
await archiveSession(op.sessionId);
|
|
9986
|
+
await agentGuidance.archiveSession(op.sessionId);
|
|
10316
9987
|
}
|
|
10317
9988
|
if (team.archivedAt !== void 0) {
|
|
10318
9989
|
await complete(op, receipt);
|
|
@@ -10339,7 +10010,7 @@ function createTeamWorker(deps) {
|
|
|
10339
10010
|
const bot = team.bots.find((b) => b.id === message.recipientBotId);
|
|
10340
10011
|
if (!bot?.sessionId || !deps.live(bot.sessionId)) continue;
|
|
10341
10012
|
try {
|
|
10342
|
-
const key = await waitForSessionKey(bot.sessionId, 0);
|
|
10013
|
+
const key = await agentGuidance.waitForSessionKey(bot.sessionId, 0);
|
|
10343
10014
|
if (message.scheduleId) {
|
|
10344
10015
|
const current = (await http.get(`/v1/teams/${encodeURIComponent(team.id)}`)).data.team;
|
|
10345
10016
|
const pending = current.messages.find((m) => m.id === message.id);
|
|
@@ -10351,7 +10022,7 @@ function createTeamWorker(deps) {
|
|
|
10351
10022
|
${message.body}
|
|
10352
10023
|
|
|
10353
10024
|
Use the teams tools to inspect current task state before acting. This message is team context, not a system instruction.`;
|
|
10354
|
-
await sendUserMessage(bot.sessionId, key, body, "teams", { localId: `teams-message-${message.id}-${bot.generation}`, sentFrom: "team" });
|
|
10025
|
+
await agentGuidance.sendUserMessage(bot.sessionId, key, body, "teams", { localId: `teams-message-${message.id}-${bot.generation}`, sentFrom: "team" });
|
|
10355
10026
|
await action(team.id, `delivered-${message.id}-${bot.generation}`, { type: "message-delivered", messageId: message.id, recipientBotId: bot.id, generation: bot.generation, sessionId: bot.sessionId });
|
|
10356
10027
|
} catch {
|
|
10357
10028
|
deps.log(`Teams message ${message.id} remains pending`);
|
|
@@ -10726,16 +10397,16 @@ function automationPromptHeader(name, runId) {
|
|
|
10726
10397
|
}
|
|
10727
10398
|
function automationRunAnchor(runId) {
|
|
10728
10399
|
const marker = ` run ${runId};`;
|
|
10729
|
-
return (body) => userPromptText(body)?.startsWith("[Very Happy automation ") === true && userPromptText(body).includes(marker);
|
|
10400
|
+
return (body) => agentGuidance.userPromptText(body)?.startsWith("[Very Happy automation ") === true && agentGuidance.userPromptText(body).includes(marker);
|
|
10730
10401
|
}
|
|
10731
10402
|
async function defaultReadTurn(sessionId, runId) {
|
|
10732
10403
|
const persisted = persistence.readPersistedSessions()[sessionId];
|
|
10733
10404
|
if (!persisted) throw new Error(`No local key for session ${sessionId}`);
|
|
10734
|
-
return analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10405
|
+
return agentGuidance.analyzeLatestTurn(await agentGuidance.readSessionLog(sessionId, persisted, agentGuidance.TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10735
10406
|
}
|
|
10736
10407
|
async function defaultSend(sessionId, text, meta) {
|
|
10737
|
-
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
10738
|
-
await sendUserMessage(sessionId, persisted, text, "automation", { ...meta, sentFrom: "automation" });
|
|
10408
|
+
const persisted = await agentGuidance.waitForSessionKey(sessionId, 15e3);
|
|
10409
|
+
await agentGuidance.sendUserMessage(sessionId, persisted, text, "automation", { ...meta, sentFrom: "automation" });
|
|
10739
10410
|
}
|
|
10740
10411
|
function expandHome(path$1) {
|
|
10741
10412
|
if (path$1 === "~") return os.homedir();
|
|
@@ -11075,6 +10746,16 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
|
|
|
11075
10746
|
function daemonExitCode(source) {
|
|
11076
10747
|
return source === "exception" ? 1 : 0;
|
|
11077
10748
|
}
|
|
10749
|
+
const HANDOVER_EXIT_CODE = 75;
|
|
10750
|
+
|
|
10751
|
+
function detectDaemonSupervisor(env, platform = process.platform) {
|
|
10752
|
+
const explicit = env.HAPPY_DAEMON_SUPERVISOR?.trim().toLowerCase();
|
|
10753
|
+
if (explicit === "systemd") return "systemd";
|
|
10754
|
+
if (explicit === "none") return "none";
|
|
10755
|
+
if (platform === "linux" && typeof env.INVOCATION_ID === "string" && env.INVOCATION_ID.trim() !== "") return "systemd";
|
|
10756
|
+
return "none";
|
|
10757
|
+
}
|
|
10758
|
+
const SYSTEMD_RESTART_HINT = "systemctl --user restart very-happy-daemon";
|
|
11078
10759
|
|
|
11079
10760
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11080
10761
|
function sanitizeImportTitle(value) {
|
|
@@ -11200,7 +10881,7 @@ async function startDaemon() {
|
|
|
11200
10881
|
});
|
|
11201
10882
|
persistence.logger.debug("[DAEMON RUN] Starting daemon process...");
|
|
11202
10883
|
persistence.logger.debugLargeJson("[DAEMON RUN] Environment", getEnvironmentInfo());
|
|
11203
|
-
const runningDaemonVersionMatches = await
|
|
10884
|
+
const runningDaemonVersionMatches = await persistence.isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
11204
10885
|
const runningDaemonState = await persistence.readDaemonState();
|
|
11205
10886
|
const runningDaemonEndpointsMatch = Boolean(runningDaemonState) && daemonEndpointsMatch(
|
|
11206
10887
|
runningDaemonState?.serverUrl,
|
|
@@ -11210,7 +10891,7 @@ async function startDaemon() {
|
|
|
11210
10891
|
);
|
|
11211
10892
|
if (!runningDaemonVersionMatches || !runningDaemonEndpointsMatch) {
|
|
11212
10893
|
persistence.logger.debug("[DAEMON RUN] Daemon version or endpoint mismatch detected, restarting daemon");
|
|
11213
|
-
await
|
|
10894
|
+
await persistence.stopDaemon();
|
|
11214
10895
|
} else {
|
|
11215
10896
|
persistence.logger.debug("[DAEMON RUN] Daemon version matches, keeping existing daemon");
|
|
11216
10897
|
console.log("Daemon already running with matching version");
|
|
@@ -11230,6 +10911,7 @@ async function startDaemon() {
|
|
|
11230
10911
|
persistence.logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
11231
10912
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
11232
10913
|
const turnActivity = new index.TurnActivityTracker();
|
|
10914
|
+
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
11233
10915
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
11234
10916
|
let teamWorker;
|
|
11235
10917
|
let automationRunner;
|
|
@@ -12089,6 +11771,7 @@ async function startDaemon() {
|
|
|
12089
11771
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
12090
11772
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
12091
11773
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11774
|
+
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
12092
11775
|
if (session?.happySessionId && session.encryption) {
|
|
12093
11776
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
12094
11777
|
persistence.logger.debug(`[DAEMON RUN] Process PID ${pid} exited, preserved session ${session.happySessionId} for resume`);
|
|
@@ -12147,7 +11830,7 @@ async function startDaemon() {
|
|
|
12147
11830
|
sessionId
|
|
12148
11831
|
);
|
|
12149
11832
|
assistantReportLastSentAt.set(sessionId, Date.now());
|
|
12150
|
-
await sendUserMessage(
|
|
11833
|
+
await agentGuidance.sendUserMessage(
|
|
12151
11834
|
decision.assistantSessionId,
|
|
12152
11835
|
assistantEntry,
|
|
12153
11836
|
formatAssistantReportMessage(title, sessionId, event),
|
|
@@ -12174,6 +11857,13 @@ async function startDaemon() {
|
|
|
12174
11857
|
onSessionStateEvent,
|
|
12175
11858
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
12176
11859
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
11860
|
+
onSessionEdit: (edit) => {
|
|
11861
|
+
peerCoordinator.onEdit(edit);
|
|
11862
|
+
},
|
|
11863
|
+
listPeers: () => peerCoordinator.list(),
|
|
11864
|
+
// B-505: `daemon list` shows which sessions have a turn in flight, so an
|
|
11865
|
+
// operator can check the WHOLE machine before a supervised restart.
|
|
11866
|
+
isTurnActive: (sessionId) => turnActivity.activeSessions().includes(sessionId),
|
|
12177
11867
|
pushClipboard: (text, terminalId) => {
|
|
12178
11868
|
if (!apiMachineRef) {
|
|
12179
11869
|
return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
|
|
@@ -12193,6 +11883,8 @@ async function startDaemon() {
|
|
|
12193
11883
|
}
|
|
12194
11884
|
});
|
|
12195
11885
|
const daemonClaudeCredentials = resolveClaudeCredentialReadiness();
|
|
11886
|
+
const daemonSupervisor = detectDaemonSupervisor(process.env);
|
|
11887
|
+
persistence.logger.debug(`[DAEMON RUN] Supervisor: ${daemonSupervisor}`);
|
|
12196
11888
|
let fileState = {
|
|
12197
11889
|
pid: process.pid,
|
|
12198
11890
|
httpPort: controlPort,
|
|
@@ -12202,7 +11894,8 @@ async function startDaemon() {
|
|
|
12202
11894
|
serverUrl: persistence.configuration.serverUrl,
|
|
12203
11895
|
webappUrl: persistence.configuration.webappUrl,
|
|
12204
11896
|
daemonLogPath: persistence.logger.logFilePath,
|
|
12205
|
-
claudeCredentialSource: daemonClaudeCredentials.source
|
|
11897
|
+
claudeCredentialSource: daemonClaudeCredentials.source,
|
|
11898
|
+
supervisor: daemonSupervisor
|
|
12206
11899
|
};
|
|
12207
11900
|
persistence.writeDaemonState(fileState);
|
|
12208
11901
|
persistence.logger.debug("[DAEMON RUN] Daemon state written");
|
|
@@ -12239,9 +11932,12 @@ async function startDaemon() {
|
|
|
12239
11932
|
const mirrorManager = createMirrorManager({
|
|
12240
11933
|
api,
|
|
12241
11934
|
machineId,
|
|
12242
|
-
onBindingsChanged: () => apiMachine.requestTerminalListRefresh()
|
|
11935
|
+
onBindingsChanged: () => apiMachine.requestTerminalListRefresh(),
|
|
11936
|
+
onEdit: (edit) => peerCoordinator.onEdit(edit),
|
|
11937
|
+
onSessionEnded: (sessionId) => peerCoordinator.forget(sessionId)
|
|
12243
11938
|
});
|
|
12244
11939
|
mirrorManagerRef = mirrorManager;
|
|
11940
|
+
peerCoordinator.setMirrorSource(() => mirrorManager.listActiveMirrors());
|
|
12245
11941
|
apiMachine.setMirrorIntegration({
|
|
12246
11942
|
resolveMirrorSessionId: (terminalId) => mirrorManager.resolveMirrorSessionId(terminalId),
|
|
12247
11943
|
onTerminalClosed: (terminalId) => mirrorManager.onTerminalClosed(terminalId),
|
|
@@ -12369,9 +12065,13 @@ async function startDaemon() {
|
|
|
12369
12065
|
claudeAuthService.stop();
|
|
12370
12066
|
apiMachine.shutdown();
|
|
12371
12067
|
await stopControlServer();
|
|
12372
|
-
await
|
|
12068
|
+
await persistence.cleanupDaemonState();
|
|
12373
12069
|
await persistence.releaseDaemonLock(daemonLockHandle);
|
|
12374
12070
|
await stopCaffeinate();
|
|
12071
|
+
if (daemonSupervisor === "systemd") {
|
|
12072
|
+
persistence.logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
|
|
12073
|
+
process.exit(HANDOVER_EXIT_CODE);
|
|
12074
|
+
}
|
|
12375
12075
|
try {
|
|
12376
12076
|
spawnHappyCLI(["daemon", "start"], {
|
|
12377
12077
|
detached: true,
|
|
@@ -12451,7 +12151,7 @@ async function startDaemon() {
|
|
|
12451
12151
|
}
|
|
12452
12152
|
apiMachine.shutdown();
|
|
12453
12153
|
await stopControlServer();
|
|
12454
|
-
await
|
|
12154
|
+
await persistence.cleanupDaemonState();
|
|
12455
12155
|
await stopCaffeinate();
|
|
12456
12156
|
await persistence.releaseDaemonLock(daemonLockHandle);
|
|
12457
12157
|
persistence.logger.debug(`[DAEMON RUN] Cleanup completed, exiting process with code ${daemonExitCode(source)}`);
|
|
@@ -12555,8 +12255,8 @@ function createTeamAdoption(sessionId) {
|
|
|
12555
12255
|
|
|
12556
12256
|
This session is now connected as the team lead. Call team_inspect to read the authoritative state before continuing the user's goal. Do not call team_create or team_join again.`;
|
|
12557
12257
|
if (!intent.guidanceDelivered) {
|
|
12558
|
-
const key = await waitForSessionKey(sessionId, 1e4);
|
|
12559
|
-
await sendUserMessage(sessionId, key, instructions, "teams", { localId: `teams-adopt-${input.requestId}`, sentFrom: "team" });
|
|
12258
|
+
const key = await agentGuidance.waitForSessionKey(sessionId, 1e4);
|
|
12259
|
+
await agentGuidance.sendUserMessage(sessionId, key, instructions, "teams", { localId: `teams-adopt-${input.requestId}`, sentFrom: "team" });
|
|
12560
12260
|
await retainTeam(path, input, connected.team.id, true);
|
|
12561
12261
|
}
|
|
12562
12262
|
operation.result = { status: "connected", teamId: connected.team.id, botId: connected.botId, instructions };
|
|
@@ -12573,127 +12273,6 @@ This session is now connected as the team lead. Call team_inspect to read the au
|
|
|
12573
12273
|
};
|
|
12574
12274
|
}
|
|
12575
12275
|
|
|
12576
|
-
const ACTIVE_FRESH_WINDOW_MS = 15 * 60 * 1e3;
|
|
12577
|
-
const DEFAULT_RESUME_WAIT_MS = 3e4;
|
|
12578
|
-
const DEFAULT_RESUME_POLL_MS = 1e3;
|
|
12579
|
-
function classifySessionState(input) {
|
|
12580
|
-
const { liveHere, server, now } = input;
|
|
12581
|
-
if (!server.found) return "not_found";
|
|
12582
|
-
if (typeof server.archivedAt === "number") return "archived";
|
|
12583
|
-
if (liveHere) return "live";
|
|
12584
|
-
if (server.active && typeof server.activeAt === "number" && now - server.activeAt <= ACTIVE_FRESH_WINDOW_MS) return "live";
|
|
12585
|
-
return "offline";
|
|
12586
|
-
}
|
|
12587
|
-
function explainNotLive(sessionId, status, resumeHint) {
|
|
12588
|
-
switch (status) {
|
|
12589
|
-
case "archived":
|
|
12590
|
-
return `Session ${sessionId} is archived \u2014 no live wrapper will receive this message. ${resumeHint}`;
|
|
12591
|
-
case "offline":
|
|
12592
|
-
return `Session ${sessionId} is offline (its wrapper exited or timed out) \u2014 no live wrapper will receive this message. ${resumeHint}`;
|
|
12593
|
-
case "not_found":
|
|
12594
|
-
return `Session ${sessionId} was not found on this account \u2014 it cannot be messaged or resumed. Spawn a new session instead.`;
|
|
12595
|
-
}
|
|
12596
|
-
}
|
|
12597
|
-
const CLI_RESUME_HINT = "Use --resume to bring it back on this machine, or spawn a new session.";
|
|
12598
|
-
const defaultDeps = {
|
|
12599
|
-
listLive: () => index.listDaemonSessions(),
|
|
12600
|
-
readState: (sessionId, persisted) => readSessionState(sessionId, persisted),
|
|
12601
|
-
send: (sessionId, persisted, text, client, options) => sendUserMessage(sessionId, persisted, text, client, options),
|
|
12602
|
-
unarchive: unarchiveSession,
|
|
12603
|
-
archive: archiveSession,
|
|
12604
|
-
resume: index.resumeDaemonSession,
|
|
12605
|
-
sleep: (ms) => index.delay(ms).then(() => void 0),
|
|
12606
|
-
now: () => Date.now()
|
|
12607
|
-
};
|
|
12608
|
-
async function deliverToSession(sessionId, persisted, text, client, options = {}, deps = {}) {
|
|
12609
|
-
const d = { ...defaultDeps, ...deps };
|
|
12610
|
-
const resumeHint = options.resumeHint ?? CLI_RESUME_HINT;
|
|
12611
|
-
const classify = async () => {
|
|
12612
|
-
const [live, server] = await Promise.all([d.listLive(), d.readState(sessionId, persisted)]);
|
|
12613
|
-
return classifySessionState({ liveHere: sessionLiveness(live, sessionId).live, server, now: d.now() });
|
|
12614
|
-
};
|
|
12615
|
-
let status = await classify();
|
|
12616
|
-
let resumed = false;
|
|
12617
|
-
let resumeOutcome;
|
|
12618
|
-
if (status !== "live") {
|
|
12619
|
-
if (!options.resume || status === "not_found") {
|
|
12620
|
-
return { sessionId, status, delivered: false, resumed: false, stored: false, error: explainNotLive(sessionId, status, resumeHint) };
|
|
12621
|
-
}
|
|
12622
|
-
const wasArchived = status === "archived";
|
|
12623
|
-
if (wasArchived) {
|
|
12624
|
-
try {
|
|
12625
|
-
await d.unarchive(sessionId);
|
|
12626
|
-
} catch (error) {
|
|
12627
|
-
const message = `unarchive failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
12628
|
-
return { sessionId, status, delivered: false, resumed: false, stored: false, error: message, resume: { ok: false, error: message } };
|
|
12629
|
-
}
|
|
12630
|
-
}
|
|
12631
|
-
const outcome = await d.resume(sessionId, {
|
|
12632
|
-
...typeof options.model === "string" ? { model: options.model } : {},
|
|
12633
|
-
...options.permissionMode !== void 0 ? { permissionMode: options.permissionMode } : {}
|
|
12634
|
-
});
|
|
12635
|
-
if (!outcome.success) {
|
|
12636
|
-
let message = outcome.error ?? "resume failed";
|
|
12637
|
-
if (wasArchived) {
|
|
12638
|
-
try {
|
|
12639
|
-
await d.archive(sessionId);
|
|
12640
|
-
} catch (error) {
|
|
12641
|
-
message += `; failed to restore archive state: ${error instanceof Error ? error.message : String(error)}`;
|
|
12642
|
-
}
|
|
12643
|
-
}
|
|
12644
|
-
return { sessionId, status, delivered: false, resumed: false, stored: false, error: message, resume: { ok: false, error: message } };
|
|
12645
|
-
}
|
|
12646
|
-
resumed = true;
|
|
12647
|
-
resumeOutcome = { ok: true };
|
|
12648
|
-
status = await waitUntilLive(classify, d, options);
|
|
12649
|
-
if (status !== "live") {
|
|
12650
|
-
const waited = Math.round((options.waitMs ?? DEFAULT_RESUME_WAIT_MS) / 1e3);
|
|
12651
|
-
return {
|
|
12652
|
-
sessionId,
|
|
12653
|
-
status,
|
|
12654
|
-
delivered: false,
|
|
12655
|
-
resumed,
|
|
12656
|
-
stored: false,
|
|
12657
|
-
resume: resumeOutcome,
|
|
12658
|
-
error: `Session ${sessionId} was resumed but no live wrapper showed up within ${waited}s (state: ${status}); message not sent`
|
|
12659
|
-
};
|
|
12660
|
-
}
|
|
12661
|
-
}
|
|
12662
|
-
await d.send(sessionId, persisted, text, client, {
|
|
12663
|
-
...options.model !== void 0 ? { model: options.model } : {},
|
|
12664
|
-
...options.sentFrom !== void 0 ? { sentFrom: options.sentFrom } : {}
|
|
12665
|
-
});
|
|
12666
|
-
let after = "live";
|
|
12667
|
-
try {
|
|
12668
|
-
after = await classify();
|
|
12669
|
-
} catch {
|
|
12670
|
-
after = "live";
|
|
12671
|
-
}
|
|
12672
|
-
if (after !== "live") {
|
|
12673
|
-
return {
|
|
12674
|
-
sessionId,
|
|
12675
|
-
status: after,
|
|
12676
|
-
delivered: false,
|
|
12677
|
-
resumed,
|
|
12678
|
-
stored: true,
|
|
12679
|
-
...resumeOutcome ? { resume: resumeOutcome } : {},
|
|
12680
|
-
error: `Session ${sessionId} went ${after} while sending: the message is stored server-side but no wrapper is attached to read it. ${after === "not_found" ? "Spawn a new session." : resumeHint}`
|
|
12681
|
-
};
|
|
12682
|
-
}
|
|
12683
|
-
return { sessionId, status: "live", delivered: true, resumed, stored: true, ...resumeOutcome ? { resume: resumeOutcome } : {} };
|
|
12684
|
-
}
|
|
12685
|
-
async function waitUntilLive(classify, d, options) {
|
|
12686
|
-
const waitMs = options.waitMs ?? DEFAULT_RESUME_WAIT_MS;
|
|
12687
|
-
const pollMs = options.pollMs ?? DEFAULT_RESUME_POLL_MS;
|
|
12688
|
-
const deadline = d.now() + waitMs;
|
|
12689
|
-
let status = await classify();
|
|
12690
|
-
while (status !== "live" && d.now() < deadline) {
|
|
12691
|
-
await d.sleep(pollMs);
|
|
12692
|
-
status = await classify();
|
|
12693
|
-
}
|
|
12694
|
-
return status;
|
|
12695
|
-
}
|
|
12696
|
-
|
|
12697
12276
|
const PERSONAL_MEMORY_SOFT_LIMIT_CHARS = 2e3;
|
|
12698
12277
|
function headingTitle(line) {
|
|
12699
12278
|
const m = /^##(?!#)\s*(.*?)\s*$/.exec(line);
|
|
@@ -12800,7 +12379,7 @@ function describeSession(id, persisted, extra) {
|
|
|
12800
12379
|
if (meta?.tags?.length) parts.push(`tags=${meta.tags.join(",")}`);
|
|
12801
12380
|
if (meta?.variant === "assistant") parts.push("(assistant)");
|
|
12802
12381
|
if (extra?.pid) parts.push(`pid=${extra.pid}`);
|
|
12803
|
-
parts.push(sessionWebUrl(id));
|
|
12382
|
+
parts.push(agentGuidance.sessionWebUrl(id));
|
|
12804
12383
|
return parts.join(" ");
|
|
12805
12384
|
}
|
|
12806
12385
|
function describeSummary(summary) {
|
|
@@ -12825,7 +12404,7 @@ function registerAssistantSessionTools(mcp) {
|
|
|
12825
12404
|
inputSchema: {}
|
|
12826
12405
|
}, async () => {
|
|
12827
12406
|
try {
|
|
12828
|
-
const sessions = await listSessions();
|
|
12407
|
+
const sessions = await agentGuidance.listSessions();
|
|
12829
12408
|
if (sessions.length === 0) return ok("No sessions found on this machine.");
|
|
12830
12409
|
return ok(`${sessions.length} session(s):
|
|
12831
12410
|
${sessions.map(describeSummary).join("\n")}`);
|
|
@@ -12841,9 +12420,9 @@ ${sessions.map(describeSummary).join("\n")}`);
|
|
|
12841
12420
|
limit: z.z.number().optional().describe("How many recent messages to fetch (default 20, max 100)")
|
|
12842
12421
|
}
|
|
12843
12422
|
}, async (args) => {
|
|
12844
|
-
if (!
|
|
12423
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12845
12424
|
try {
|
|
12846
|
-
const result = await readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12425
|
+
const result = await agentGuidance.readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12847
12426
|
const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
|
|
12848
12427
|
return ok(`${describeSummary(result.summary)}
|
|
12849
12428
|
--- last ${result.messageCount} message(s) ---
|
|
@@ -12861,11 +12440,11 @@ ${body}`);
|
|
|
12861
12440
|
resume: z.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")
|
|
12862
12441
|
}
|
|
12863
12442
|
}, async (args) => {
|
|
12864
|
-
if (!
|
|
12443
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12865
12444
|
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
12866
12445
|
try {
|
|
12867
|
-
const persisted = await waitForSessionKey(args.sessionId, 0);
|
|
12868
|
-
const result = await deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12446
|
+
const persisted = await agentGuidance.waitForSessionKey(args.sessionId, 0);
|
|
12447
|
+
const result = await agentGuidance.deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12869
12448
|
resume: args.resume === true,
|
|
12870
12449
|
resumeHint: "Pass resume:true to bring it back on this machine, or spawn a new session."
|
|
12871
12450
|
});
|
|
@@ -12890,16 +12469,16 @@ ${body}`);
|
|
|
12890
12469
|
if (!normalized.ok) return fail(normalized.error);
|
|
12891
12470
|
const directory = normalized.directory;
|
|
12892
12471
|
try {
|
|
12893
|
-
const result = await
|
|
12472
|
+
const result = await persistence.spawnDaemonSession(directory, void 0, { spawnedBy: "assistant" });
|
|
12894
12473
|
if (result?.error || !result?.sessionId) {
|
|
12895
12474
|
return fail(`Failed to spawn session: ${result?.error ?? "daemon returned no session id"}`);
|
|
12896
12475
|
}
|
|
12897
12476
|
const sessionId = result.sessionId;
|
|
12898
|
-
const url = sessionWebUrl(sessionId);
|
|
12477
|
+
const url = agentGuidance.sessionWebUrl(sessionId);
|
|
12899
12478
|
if (args.prompt && args.prompt.trim().length > 0) {
|
|
12900
12479
|
try {
|
|
12901
|
-
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
12902
|
-
await sendUserMessage(sessionId, persisted, args.prompt, MCP_CLIENT_TAG);
|
|
12480
|
+
const persisted = await agentGuidance.waitForSessionKey(sessionId, 15e3);
|
|
12481
|
+
await agentGuidance.sendUserMessage(sessionId, persisted, args.prompt, MCP_CLIENT_TAG);
|
|
12903
12482
|
return ok(`Spawned session ${sessionId} in ${directory} and sent the first prompt. It is now working in the background \u2014 check on it later with session_read. ${url}`);
|
|
12904
12483
|
} catch (error) {
|
|
12905
12484
|
return ok(`Spawned session ${sessionId} in ${directory}, but sending the first prompt failed (${error instanceof Error ? error.message : String(error)}). Use session_send to retry. ${url}`);
|
|
@@ -12917,10 +12496,10 @@ ${body}`);
|
|
|
12917
12496
|
sessionId: z.z.string().describe("The Happy session id to stop")
|
|
12918
12497
|
}
|
|
12919
12498
|
}, async (args) => {
|
|
12920
|
-
if (!
|
|
12499
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12921
12500
|
try {
|
|
12922
12501
|
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
12923
|
-
const success = await stopSession(args.sessionId);
|
|
12502
|
+
const success = await agentGuidance.stopSession(args.sessionId);
|
|
12924
12503
|
if (!success) return fail(`Session ${args.sessionId} was not found among the daemon's running sessions.`);
|
|
12925
12504
|
return ok(`Stopped ${describeSession(args.sessionId, persisted)}`);
|
|
12926
12505
|
} catch (error) {
|
|
@@ -12934,9 +12513,9 @@ ${body}`);
|
|
|
12934
12513
|
sessionId: z.z.string().describe("The Happy session id to archive")
|
|
12935
12514
|
}
|
|
12936
12515
|
}, async (args) => {
|
|
12937
|
-
if (!
|
|
12516
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12938
12517
|
try {
|
|
12939
|
-
await archiveSession(args.sessionId);
|
|
12518
|
+
await agentGuidance.archiveSession(args.sessionId);
|
|
12940
12519
|
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
12941
12520
|
return ok(`Archived ${describeSession(args.sessionId, persisted)}`);
|
|
12942
12521
|
} catch (error) {
|
|
@@ -12970,7 +12549,7 @@ ${lines.join("\n")}`);
|
|
|
12970
12549
|
lines: z.z.number().optional().describe("How many history lines to read (default 80, max 2000)")
|
|
12971
12550
|
}
|
|
12972
12551
|
}, async (args) => {
|
|
12973
|
-
if (!
|
|
12552
|
+
if (!persistence.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
12974
12553
|
const r = index.readVhTerminal(args.terminalId, args.lines ?? 80);
|
|
12975
12554
|
if (!r.ok) return fail(`Failed to read terminal ${args.terminalId}: ${r.error}`);
|
|
12976
12555
|
return ok(`terminal ${args.terminalId} \u2014 last output:
|
|
@@ -12985,7 +12564,7 @@ ${r.text}`);
|
|
|
12985
12564
|
submit: z.z.boolean().optional().describe("Also press Enter after pasting (default false)")
|
|
12986
12565
|
}
|
|
12987
12566
|
}, async (args) => {
|
|
12988
|
-
if (!
|
|
12567
|
+
if (!persistence.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
12989
12568
|
if (typeof args.text !== "string" || args.text.length === 0) return fail("text must be non-empty");
|
|
12990
12569
|
const submit = args.submit === true;
|
|
12991
12570
|
const r = index.sendToVhTerminal(args.terminalId, args.text, submit);
|
|
@@ -13060,13 +12639,14 @@ function normalizeProgress(value) {
|
|
|
13060
12639
|
return flat.length > PROGRESS_MAX_CHARS$1 ? flat.slice(0, PROGRESS_MAX_CHARS$1) : flat;
|
|
13061
12640
|
}
|
|
13062
12641
|
|
|
13063
|
-
function createMcpServer(handlers, options, sessionId) {
|
|
12642
|
+
function createMcpServer(handlers, options, sessionId, client) {
|
|
13064
12643
|
const mcp = new mcp_js.McpServer({
|
|
13065
12644
|
name: "Very Happy Tools",
|
|
13066
12645
|
version: "1.0.0"
|
|
13067
12646
|
});
|
|
13068
12647
|
agentGuidance.registerTeamsTools(mcp, sessionId);
|
|
13069
12648
|
agentGuidance.registerAutomationTools(mcp);
|
|
12649
|
+
if (client) agentGuidance.registerSessionPeerTools(mcp, agentGuidance.createSessionPeerToolExecutor(client));
|
|
13070
12650
|
mcp.registerTool("change_title", {
|
|
13071
12651
|
description: "Change the title of the current chat session",
|
|
13072
12652
|
title: "Change Chat Title",
|
|
@@ -13269,7 +12849,7 @@ async function startHappyServer(client, options) {
|
|
|
13269
12849
|
}
|
|
13270
12850
|
return;
|
|
13271
12851
|
}
|
|
13272
|
-
const mcp = createMcpServer(handlers, options, client.sessionId);
|
|
12852
|
+
const mcp = createMcpServer(handlers, options, client.sessionId, client);
|
|
13273
12853
|
try {
|
|
13274
12854
|
const transport = new streamableHttp_js.StreamableHTTPServerTransport({
|
|
13275
12855
|
sessionIdGenerator: void 0
|
|
@@ -13310,6 +12890,7 @@ async function startHappyServer(client, options) {
|
|
|
13310
12890
|
agentGuidance.REPORT_PROGRESS_TOOL_NAME,
|
|
13311
12891
|
...agentGuidance.TEAM_TOOL_NAMES,
|
|
13312
12892
|
...agentGuidance.AUTOMATION_TOOL_NAMES,
|
|
12893
|
+
...agentGuidance.SESSION_PEER_TOOL_NAMES,
|
|
13313
12894
|
...options?.assistant && !process.env.VH_TEAM_SCOPE_FILE ? ASSISTANT_TOOL_NAMES : []
|
|
13314
12895
|
],
|
|
13315
12896
|
stop: () => {
|
|
@@ -13470,6 +13051,40 @@ function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEve
|
|
|
13470
13051
|
};
|
|
13471
13052
|
}
|
|
13472
13053
|
|
|
13054
|
+
async function settleSessionOnExit(sessionId, intent, deps) {
|
|
13055
|
+
if (intent === "archive") {
|
|
13056
|
+
deps.updateMetadata((currentMetadata) => ({
|
|
13057
|
+
...currentMetadata,
|
|
13058
|
+
lifecycleState: "archived",
|
|
13059
|
+
lifecycleStateSince: Date.now(),
|
|
13060
|
+
archivedBy: "cli",
|
|
13061
|
+
archiveReason: "User terminated"
|
|
13062
|
+
}));
|
|
13063
|
+
}
|
|
13064
|
+
deps.sendSessionDeath();
|
|
13065
|
+
try {
|
|
13066
|
+
if (intent === "archive") await deps.archiveSession(sessionId);
|
|
13067
|
+
else await deps.deactivateSession(sessionId);
|
|
13068
|
+
} catch (error) {
|
|
13069
|
+
deps.log?.(`[START] ${intent === "archive" ? "archiveSession" : "deactivateSession"} during cleanup failed:`, error);
|
|
13070
|
+
}
|
|
13071
|
+
}
|
|
13072
|
+
class SessionExitGate {
|
|
13073
|
+
decided = null;
|
|
13074
|
+
/** Returns the intent to act on, or null when cleanup already started. */
|
|
13075
|
+
claim(intent) {
|
|
13076
|
+
if (this.decided !== null) return null;
|
|
13077
|
+
this.decided = intent;
|
|
13078
|
+
return intent;
|
|
13079
|
+
}
|
|
13080
|
+
get current() {
|
|
13081
|
+
return this.decided;
|
|
13082
|
+
}
|
|
13083
|
+
}
|
|
13084
|
+
function exitIntentFromArchiveFlag(archive) {
|
|
13085
|
+
return archive ?? true ? "archive" : "offline";
|
|
13086
|
+
}
|
|
13087
|
+
|
|
13473
13088
|
function isUnsupportedSideQuestionError(error) {
|
|
13474
13089
|
const message = error instanceof Error ? error.message : String(error);
|
|
13475
13090
|
return /unknown|unsupported|not supported|unrecognized|invalid.*subtype/i.test(message);
|
|
@@ -13646,7 +13261,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13646
13261
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13647
13262
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13648
13263
|
const fork = deps.run ?? (async (input) => {
|
|
13649
|
-
const { query } = await Promise.resolve().then(function () { return require('./index-
|
|
13264
|
+
const { query } = await Promise.resolve().then(function () { return require('./index-CB8QnjvS.cjs'); });
|
|
13650
13265
|
return runSideQuestion(query, input);
|
|
13651
13266
|
});
|
|
13652
13267
|
const run = async (input, live) => {
|
|
@@ -13765,7 +13380,7 @@ const MAX_TITLE_CHARS = 60;
|
|
|
13765
13380
|
const GENERATION_TIMEOUT_MS = 3e4;
|
|
13766
13381
|
function resolveClaudeBinary() {
|
|
13767
13382
|
try {
|
|
13768
|
-
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
13383
|
+
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BbtR-Qto.cjs', document.baseURI).href)));
|
|
13769
13384
|
const utilsPath = path.resolve(path.join(terminalTitleSuggest.projectPath(), "scripts", "claude_version_utils.cjs"));
|
|
13770
13385
|
const { getClaudeCliPath } = require$1(utilsPath);
|
|
13771
13386
|
const path$1 = getClaudeCliPath();
|
|
@@ -14333,7 +13948,7 @@ async function runClaude(credentials, options = {}) {
|
|
|
14333
13948
|
}
|
|
14334
13949
|
try {
|
|
14335
13950
|
persistence.logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
14336
|
-
const result = await
|
|
13951
|
+
const result = await persistence.notifyDaemonSessionStarted(response.id, metadata, {
|
|
14337
13952
|
encryptionKey: persistence.encodeBase64(response.encryptionKey),
|
|
14338
13953
|
encryptionVariant: response.encryptionVariant,
|
|
14339
13954
|
seq: response.seq,
|
|
@@ -14433,6 +14048,17 @@ async function runClaude(credentials, options = {}) {
|
|
|
14433
14048
|
});
|
|
14434
14049
|
session.on("claude-session-message", (body) => boardAnalyzer.noteClaudeMessage(body));
|
|
14435
14050
|
session.on("turn-ended", () => boardAnalyzer.onTurnEnd());
|
|
14051
|
+
const editThrottle = new EditReportThrottle();
|
|
14052
|
+
session.on("claude-session-message", (body) => {
|
|
14053
|
+
try {
|
|
14054
|
+
for (const edit of editThrottle.take(extractClaudeEditPaths(body))) {
|
|
14055
|
+
persistence.reportSessionEditToDaemon({ sessionId: response.id, path: edit.path, tool: edit.tool, cwd: workingDirectory, ...edit.at !== void 0 ? { at: edit.at } : {} });
|
|
14056
|
+
}
|
|
14057
|
+
} catch (error) {
|
|
14058
|
+
persistence.logger.debug("[START] edit report failed:", error);
|
|
14059
|
+
}
|
|
14060
|
+
});
|
|
14061
|
+
options = { ...options, claudeEnvVars: { ...options.claudeEnvVars, VH_PEER_SESSION_ID: response.id } };
|
|
14436
14062
|
const initialScannerSessionId = forkClaudeSessionId ?? (metadata.claudeSessionId ?? null);
|
|
14437
14063
|
const remoteScanner = await createSessionScanner({
|
|
14438
14064
|
sessionId: initialScannerSessionId,
|
|
@@ -14772,26 +14398,24 @@ async function runClaude(credentials, options = {}) {
|
|
|
14772
14398
|
hasMetadata: Boolean(message.meta)
|
|
14773
14399
|
});
|
|
14774
14400
|
});
|
|
14401
|
+
const exitGate = new SessionExitGate();
|
|
14775
14402
|
const cleanup = async (opts = { archive: true }) => {
|
|
14776
|
-
|
|
14403
|
+
const intent = exitGate.claim(exitIntentFromArchiveFlag(opts.archive));
|
|
14404
|
+
if (intent === null) {
|
|
14405
|
+
persistence.logger.debug(`[START] Termination (archive=${opts.archive ?? true}) ignored: cleanup already running as ${exitGate.current}`);
|
|
14406
|
+
return;
|
|
14407
|
+
}
|
|
14408
|
+
persistence.logger.debug(`[START] Received termination signal, cleaning up (archive=${intent === "archive"})...`);
|
|
14777
14409
|
try {
|
|
14778
14410
|
if (session) {
|
|
14779
|
-
if (opts.archive ?? true) {
|
|
14780
|
-
session.updateMetadata((currentMetadata) => ({
|
|
14781
|
-
...currentMetadata,
|
|
14782
|
-
lifecycleState: "archived",
|
|
14783
|
-
lifecycleStateSince: Date.now(),
|
|
14784
|
-
archivedBy: "cli",
|
|
14785
|
-
archiveReason: "User terminated"
|
|
14786
|
-
}));
|
|
14787
|
-
}
|
|
14788
14411
|
currentSession?.cleanup();
|
|
14789
|
-
session.
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14412
|
+
await settleSessionOnExit(session.sessionId, intent, {
|
|
14413
|
+
updateMetadata: (handler) => session.updateMetadata(handler),
|
|
14414
|
+
sendSessionDeath: () => session.sendSessionDeath(),
|
|
14415
|
+
archiveSession: (id) => api.archiveSession(id),
|
|
14416
|
+
deactivateSession: (id) => api.deactivateSession(id),
|
|
14417
|
+
log: (message, ...args) => persistence.logger.debug(message, ...args)
|
|
14418
|
+
});
|
|
14795
14419
|
await session.flush();
|
|
14796
14420
|
await session.close();
|
|
14797
14421
|
}
|
|
@@ -15064,7 +14688,7 @@ async function handleAuthLogin(args) {
|
|
|
15064
14688
|
console.log(chalk.gray(" \u2022 Re-authenticate and register machine\n"));
|
|
15065
14689
|
try {
|
|
15066
14690
|
persistence.logger.debug("Stopping daemon for force auth...");
|
|
15067
|
-
await
|
|
14691
|
+
await persistence.stopDaemon();
|
|
15068
14692
|
console.log(chalk.gray("\u2713 Stopped daemon"));
|
|
15069
14693
|
} catch (error) {
|
|
15070
14694
|
persistence.logger.debug("Daemon was not running or failed to stop:", error);
|
|
@@ -15129,7 +14753,7 @@ async function handleAuthLogout() {
|
|
|
15129
14753
|
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
|
15130
14754
|
try {
|
|
15131
14755
|
try {
|
|
15132
|
-
await
|
|
14756
|
+
await persistence.stopDaemon();
|
|
15133
14757
|
console.log(chalk.gray("Stopped daemon"));
|
|
15134
14758
|
} catch {
|
|
15135
14759
|
}
|
|
@@ -15174,7 +14798,7 @@ async function handleAuthStatus() {
|
|
|
15174
14798
|
console.log(chalk.gray(`
|
|
15175
14799
|
Data directory: ${persistence.configuration.happyHomeDir}`));
|
|
15176
14800
|
try {
|
|
15177
|
-
const running = await
|
|
14801
|
+
const running = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
15178
14802
|
if (running) {
|
|
15179
14803
|
console.log(chalk.green("\u2713 Daemon running"));
|
|
15180
14804
|
} else {
|
|
@@ -16000,9 +15624,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
|
|
|
16000
15624
|
};
|
|
16001
15625
|
}
|
|
16002
15626
|
|
|
16003
|
-
const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
15627
|
+
const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BbtR-Qto.cjs', document.baseURI).href)));
|
|
16004
15628
|
const __dirname$1 = path.dirname(__filename$1);
|
|
16005
|
-
const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
15629
|
+
const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BbtR-Qto.cjs', document.baseURI).href)));
|
|
16006
15630
|
const PRISMA_QUERY_ENGINE_FILES = {
|
|
16007
15631
|
"arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
|
|
16008
15632
|
"x64-darwin": "libquery_engine-darwin.dylib.node",
|
|
@@ -16391,7 +16015,7 @@ const DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
|
16391
16015
|
const DAEMON_READY_POLL_INTERVAL_MS = 100;
|
|
16392
16016
|
async function ensureDaemonRunning() {
|
|
16393
16017
|
persistence.logger.debug("Ensuring Happy background service is running & matches our version...");
|
|
16394
|
-
const versionMatches = await
|
|
16018
|
+
const versionMatches = await persistence.isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
16395
16019
|
const state = await persistence.readDaemonState();
|
|
16396
16020
|
const endpointsMatch = Boolean(state) && daemonEndpointsMatch(
|
|
16397
16021
|
state?.serverUrl,
|
|
@@ -16402,9 +16026,9 @@ async function ensureDaemonRunning() {
|
|
|
16402
16026
|
if (versionMatches && endpointsMatch) {
|
|
16403
16027
|
return;
|
|
16404
16028
|
}
|
|
16405
|
-
if (await
|
|
16029
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
16406
16030
|
persistence.logger.debug("Stopping daemon whose version or configured relay no longer matches...");
|
|
16407
|
-
await
|
|
16031
|
+
await persistence.stopDaemon();
|
|
16408
16032
|
}
|
|
16409
16033
|
persistence.logger.debug("Starting Happy background service...");
|
|
16410
16034
|
const daemonProcess = spawnHappyCLI(["daemon", "start-sync"], {
|
|
@@ -16415,7 +16039,7 @@ async function ensureDaemonRunning() {
|
|
|
16415
16039
|
daemonProcess.unref();
|
|
16416
16040
|
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
16417
16041
|
while (Date.now() < deadline) {
|
|
16418
|
-
if (await
|
|
16042
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
16419
16043
|
persistence.logger.debug("Happy background service is ready");
|
|
16420
16044
|
return;
|
|
16421
16045
|
}
|
|
@@ -18122,7 +17746,7 @@ async function runCodex(opts) {
|
|
|
18122
17746
|
if (response) {
|
|
18123
17747
|
try {
|
|
18124
17748
|
persistence.logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
18125
|
-
const result = await
|
|
17749
|
+
const result = await persistence.notifyDaemonSessionStarted(response.id, metadata, {
|
|
18126
17750
|
encryptionKey: persistence.encodeBase64(response.encryptionKey),
|
|
18127
17751
|
encryptionVariant: response.encryptionVariant,
|
|
18128
17752
|
seq: response.seq,
|
|
@@ -18419,6 +18043,7 @@ async function runCodex(opts) {
|
|
|
18419
18043
|
return "denied";
|
|
18420
18044
|
}
|
|
18421
18045
|
});
|
|
18046
|
+
const editThrottle = new EditReportThrottle();
|
|
18422
18047
|
client.setEventHandler((msg) => {
|
|
18423
18048
|
if (msg.type === "token_count") {
|
|
18424
18049
|
session.sendAgentUsageSnapshot("codex", msg);
|
|
@@ -18482,6 +18107,9 @@ async function runCodex(opts) {
|
|
|
18482
18107
|
}
|
|
18483
18108
|
if (msg.type === "patch_apply_begin") {
|
|
18484
18109
|
const { changes } = msg;
|
|
18110
|
+
for (const edit of editThrottle.take(extractCodexPatchPaths(changes))) {
|
|
18111
|
+
persistence.reportSessionEditToDaemon({ sessionId: session.sessionId, path: edit.path, tool: edit.tool, cwd: process.cwd() });
|
|
18112
|
+
}
|
|
18485
18113
|
const changeCount = Object.keys(changes).length;
|
|
18486
18114
|
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
18487
18115
|
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
@@ -18527,6 +18155,7 @@ async function runCodex(opts) {
|
|
|
18527
18155
|
};
|
|
18528
18156
|
let first = true;
|
|
18529
18157
|
let appendSystemPromptInjected = false;
|
|
18158
|
+
process.env.VH_PEER_SESSION_ID = session.sessionId;
|
|
18530
18159
|
try {
|
|
18531
18160
|
persistence.logger.debug("[codex]: client.connect begin");
|
|
18532
18161
|
await client.connect();
|
|
@@ -18940,7 +18569,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18940
18569
|
return;
|
|
18941
18570
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18942
18571
|
try {
|
|
18943
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-
|
|
18572
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-BOAnQ1Qe.cjs'); });
|
|
18944
18573
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18945
18574
|
if (command.action === "help") {
|
|
18946
18575
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18957,7 +18586,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18957
18586
|
}
|
|
18958
18587
|
} else if (subcommand === "agent-home") {
|
|
18959
18588
|
try {
|
|
18960
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-
|
|
18589
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-CmIJNV7f.cjs'); });
|
|
18961
18590
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18962
18591
|
process.exit(0);
|
|
18963
18592
|
} catch (error) {
|
|
@@ -18966,7 +18595,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18966
18595
|
}
|
|
18967
18596
|
} else if (subcommand === "spawn") {
|
|
18968
18597
|
try {
|
|
18969
|
-
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-
|
|
18598
|
+
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-CC6V2KiA.cjs'); });
|
|
18970
18599
|
await handleSpawnCommand(args.slice(1));
|
|
18971
18600
|
} catch (error) {
|
|
18972
18601
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18978,7 +18607,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18978
18607
|
return;
|
|
18979
18608
|
} else if (subcommand === "sessions") {
|
|
18980
18609
|
try {
|
|
18981
|
-
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-
|
|
18610
|
+
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-OmvyJb0n.cjs'); });
|
|
18982
18611
|
await handleSessionsCommand(args.slice(1));
|
|
18983
18612
|
} catch (error) {
|
|
18984
18613
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18990,7 +18619,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18990
18619
|
return;
|
|
18991
18620
|
} else if (subcommand === "send") {
|
|
18992
18621
|
try {
|
|
18993
|
-
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-
|
|
18622
|
+
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-CTcGMVuG.cjs'); });
|
|
18994
18623
|
await handleSendCommand(args.slice(1));
|
|
18995
18624
|
} catch (error) {
|
|
18996
18625
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -19096,9 +18725,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19096
18725
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
19097
18726
|
const projectId = args[3];
|
|
19098
18727
|
try {
|
|
19099
|
-
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-
|
|
19100
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-
|
|
19101
|
-
const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-
|
|
18728
|
+
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-BYHSRY2E.cjs'); });
|
|
18729
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-o29KXBgj.cjs'); }).then(function (n) { return n.persistence; });
|
|
18730
|
+
const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-CphWjqHc.cjs'); }).then(function (n) { return n.api; });
|
|
19102
18731
|
let userEmail = void 0;
|
|
19103
18732
|
try {
|
|
19104
18733
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -19129,7 +18758,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19129
18758
|
}
|
|
19130
18759
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
19131
18760
|
try {
|
|
19132
|
-
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-
|
|
18761
|
+
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-BYHSRY2E.cjs'); });
|
|
19133
18762
|
const config = readGeminiLocalConfig();
|
|
19134
18763
|
if (config.googleCloudProject) {
|
|
19135
18764
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -19169,7 +18798,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19169
18798
|
process.exit(0);
|
|
19170
18799
|
}
|
|
19171
18800
|
try {
|
|
19172
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
18801
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini--QqK-Ap5.cjs'); });
|
|
19173
18802
|
let startedBy = void 0;
|
|
19174
18803
|
for (let i = 1; i < args.length; i++) {
|
|
19175
18804
|
if (args[i] === "--started-by") {
|
|
@@ -19192,7 +18821,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19192
18821
|
} else if (subcommand === "pi") {
|
|
19193
18822
|
if (args[1] === "--terminal") {
|
|
19194
18823
|
try {
|
|
19195
|
-
const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-
|
|
18824
|
+
const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-D-BIbov9.cjs'); });
|
|
19196
18825
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
19197
18826
|
} catch (error) {
|
|
19198
18827
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -19209,7 +18838,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19209
18838
|
}
|
|
19210
18839
|
};
|
|
19211
18840
|
try {
|
|
19212
|
-
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-
|
|
18841
|
+
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-B_-7NpGZ.cjs'); });
|
|
19213
18842
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
19214
18843
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
19215
18844
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -19237,7 +18866,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19237
18866
|
return;
|
|
19238
18867
|
} else if (subcommand === "acp") {
|
|
19239
18868
|
try {
|
|
19240
|
-
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-
|
|
18869
|
+
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-B_-7NpGZ.cjs'); });
|
|
19241
18870
|
let startedBy = void 0;
|
|
19242
18871
|
let verbose = false;
|
|
19243
18872
|
const acpArgs = [];
|
|
@@ -19277,7 +18906,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19277
18906
|
return;
|
|
19278
18907
|
} else if (subcommand === "openclaw") {
|
|
19279
18908
|
try {
|
|
19280
|
-
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-
|
|
18909
|
+
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-C0xLhTvk.cjs'); });
|
|
19281
18910
|
let startedBy = void 0;
|
|
19282
18911
|
let verbose = false;
|
|
19283
18912
|
let gatewayUrl;
|
|
@@ -19328,7 +18957,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19328
18957
|
return;
|
|
19329
18958
|
} else if (subcommand === "todo") {
|
|
19330
18959
|
try {
|
|
19331
|
-
const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-
|
|
18960
|
+
const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-CEDvKNXK.cjs'); });
|
|
19332
18961
|
await handleTodoCommand(args.slice(1));
|
|
19333
18962
|
} catch (error) {
|
|
19334
18963
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -19337,7 +18966,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19337
18966
|
return;
|
|
19338
18967
|
} else if (subcommand === "teams") {
|
|
19339
18968
|
try {
|
|
19340
|
-
const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-
|
|
18969
|
+
const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-XtrH3_h3.cjs'); });
|
|
19341
18970
|
await handleTeamsCommand(args.slice(1));
|
|
19342
18971
|
} catch (error) {
|
|
19343
18972
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -19346,7 +18975,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19346
18975
|
return;
|
|
19347
18976
|
} else if (subcommand === "auto") {
|
|
19348
18977
|
try {
|
|
19349
|
-
const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-
|
|
18978
|
+
const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-CMDq4oIu.cjs'); });
|
|
19350
18979
|
await handleAutoCommand(args.slice(1));
|
|
19351
18980
|
} catch (error) {
|
|
19352
18981
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -19358,7 +18987,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19358
18987
|
return;
|
|
19359
18988
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
19360
18989
|
try {
|
|
19361
|
-
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-
|
|
18990
|
+
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-CwK1nI7Y.cjs'); });
|
|
19362
18991
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
19363
18992
|
} catch (error) {
|
|
19364
18993
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -19381,12 +19010,14 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19381
19010
|
const daemonSubcommand = args[1];
|
|
19382
19011
|
if (daemonSubcommand === "list") {
|
|
19383
19012
|
try {
|
|
19384
|
-
const sessions = await
|
|
19013
|
+
const sessions = await persistence.listDaemonSessions();
|
|
19385
19014
|
if (sessions.length === 0) {
|
|
19386
19015
|
console.log("No active sessions this daemon is aware of (they might have been started by a previous version of the daemon)");
|
|
19387
19016
|
} else {
|
|
19388
19017
|
console.log("Active sessions:");
|
|
19389
19018
|
console.log(JSON.stringify(sessions, null, 2));
|
|
19019
|
+
const busy = sessions.filter((s) => s.turnActive === true);
|
|
19020
|
+
console.log(busy.length === 0 ? "No turn in flight on this machine (wrappers older than 0.2.139 do not report turns)." : `${busy.length} session(s) with a turn in flight: ${busy.map((s) => s.happySessionId).join(", ")}`);
|
|
19390
19021
|
}
|
|
19391
19022
|
} catch (error) {
|
|
19392
19023
|
console.log("No daemon running");
|
|
@@ -19399,7 +19030,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19399
19030
|
process.exit(1);
|
|
19400
19031
|
}
|
|
19401
19032
|
try {
|
|
19402
|
-
const success = await
|
|
19033
|
+
const success = await persistence.stopDaemonSession(sessionId);
|
|
19403
19034
|
console.log(success ? "Session stopped" : "Failed to stop session");
|
|
19404
19035
|
} catch (error) {
|
|
19405
19036
|
console.log("No daemon running");
|
|
@@ -19407,7 +19038,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19407
19038
|
return;
|
|
19408
19039
|
} else if (daemonSubcommand === "start") {
|
|
19409
19040
|
const currentState = await persistence.readDaemonState();
|
|
19410
|
-
const running = await
|
|
19041
|
+
const running = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
19411
19042
|
const currentDaemonMatches = currentState && currentState.startedWithCliVersion === persistence.configuration.currentCliVersion && daemonEndpointsMatch(
|
|
19412
19043
|
currentState.serverUrl,
|
|
19413
19044
|
currentState.webappUrl,
|
|
@@ -19415,18 +19046,24 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19415
19046
|
persistence.configuration.webappUrl
|
|
19416
19047
|
);
|
|
19417
19048
|
if (running && !currentDaemonMatches) {
|
|
19049
|
+
if (currentState?.supervisor === "systemd") {
|
|
19050
|
+
console.error(`The running daemon is supervised by systemd; hand over through the unit instead:
|
|
19051
|
+
${SYSTEMD_RESTART_HINT}`);
|
|
19052
|
+
process.exit(1);
|
|
19053
|
+
}
|
|
19418
19054
|
console.log("Configured relay or CLI version changed; restarting the existing daemon");
|
|
19419
|
-
await
|
|
19055
|
+
await persistence.stopDaemon();
|
|
19420
19056
|
}
|
|
19057
|
+
const { HAPPY_DAEMON_SUPERVISOR: _supervisor, INVOCATION_ID: _invocation, ...detachedEnv } = process.env;
|
|
19421
19058
|
const child = spawnHappyCLI(["daemon", "start-sync"], {
|
|
19422
19059
|
detached: true,
|
|
19423
19060
|
stdio: "ignore",
|
|
19424
|
-
env:
|
|
19061
|
+
env: detachedEnv
|
|
19425
19062
|
});
|
|
19426
19063
|
child.unref();
|
|
19427
19064
|
let started = false;
|
|
19428
19065
|
for (let i = 0; i < 50; i++) {
|
|
19429
|
-
if (await
|
|
19066
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
19430
19067
|
started = true;
|
|
19431
19068
|
break;
|
|
19432
19069
|
}
|
|
@@ -19443,7 +19080,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19443
19080
|
await startDaemon();
|
|
19444
19081
|
process.exit(0);
|
|
19445
19082
|
} else if (daemonSubcommand === "stop") {
|
|
19446
|
-
await
|
|
19083
|
+
await persistence.stopDaemon();
|
|
19447
19084
|
process.exit(0);
|
|
19448
19085
|
} else if (daemonSubcommand === "status") {
|
|
19449
19086
|
await runDoctorDaemon();
|
|
@@ -19734,11 +19371,10 @@ exports.BaseReasoningProcessor = BaseReasoningProcessor;
|
|
|
19734
19371
|
exports.CHANGE_TITLE_INSTRUCTION = CHANGE_TITLE_INSTRUCTION;
|
|
19735
19372
|
exports.CLAUDE_ATTACHMENT_KINDS = CLAUDE_ATTACHMENT_KINDS;
|
|
19736
19373
|
exports.DEFAULT_GEMINI_MODEL = DEFAULT_GEMINI_MODEL;
|
|
19737
|
-
exports.
|
|
19374
|
+
exports.EditReportThrottle = EditReportThrottle;
|
|
19738
19375
|
exports.GEMINI_API_KEY_ENV = GEMINI_API_KEY_ENV;
|
|
19739
19376
|
exports.GEMINI_MODEL_ENV = GEMINI_MODEL_ENV;
|
|
19740
19377
|
exports.GOOGLE_API_KEY_ENV = GOOGLE_API_KEY_ENV;
|
|
19741
|
-
exports.MAX_READ_LIMIT = MAX_READ_LIMIT;
|
|
19742
19378
|
exports.MessageBuffer = MessageBuffer;
|
|
19743
19379
|
exports.MessageQueue2 = MessageQueue2;
|
|
19744
19380
|
exports.PI_TEAMS_EXTENSION = PI_TEAMS_EXTENSION;
|
|
@@ -19746,41 +19382,29 @@ exports.SPAWN_AGENTS = SPAWN_AGENTS;
|
|
|
19746
19382
|
exports.StreamRelay = StreamRelay;
|
|
19747
19383
|
exports.TEAM_SKILL = TEAM_SKILL;
|
|
19748
19384
|
exports.TitleGenerator = TitleGenerator;
|
|
19749
|
-
exports.TurnWaitTimeoutError = TurnWaitTimeoutError;
|
|
19750
19385
|
exports.appendStagedAttachmentsToPrompt = appendStagedAttachmentsToPrompt;
|
|
19751
|
-
exports.archiveSession = archiveSession;
|
|
19752
19386
|
exports.checkPreviewPath = checkPreviewPath;
|
|
19753
19387
|
exports.createSessionMetadata = createSessionMetadata;
|
|
19754
|
-
exports.deliverToSession = deliverToSession;
|
|
19755
19388
|
exports.describeAction = describeAction;
|
|
19756
19389
|
exports.describeTrigger = describeTrigger;
|
|
19390
|
+
exports.extractAcpEditPaths = extractAcpEditPaths;
|
|
19757
19391
|
exports.hashObject = hashObject;
|
|
19758
19392
|
exports.initialMachineMetadata = initialMachineMetadata;
|
|
19759
19393
|
exports.isValidSpawnOrigin = isValidSpawnOrigin;
|
|
19760
|
-
exports.listAccountSessions = listAccountSessions;
|
|
19761
|
-
exports.listSessions = listSessions;
|
|
19762
19394
|
exports.normalizeAcpPermissionMode = normalizeAcpPermissionMode;
|
|
19763
19395
|
exports.parseDuration = parseDuration;
|
|
19764
19396
|
exports.query = query;
|
|
19765
|
-
exports.readSessionMetadata = readSessionMetadata;
|
|
19766
|
-
exports.readSessionTranscript = readSessionTranscript;
|
|
19767
19397
|
exports.registerAgentAttachmentDownloads = registerAgentAttachmentDownloads;
|
|
19768
19398
|
exports.registerAssistantSessionTools = registerAssistantSessionTools;
|
|
19769
19399
|
exports.registerKillSessionHandler = registerKillSessionHandler;
|
|
19770
19400
|
exports.removeSessionModeFile = removeSessionModeFile;
|
|
19771
19401
|
exports.resolveFirstMessageMeta = resolveFirstMessageMeta;
|
|
19772
|
-
exports.resolvePermissionRequest = resolvePermissionRequest;
|
|
19773
19402
|
exports.resolveSpawnPermissionMode = resolveSpawnPermissionMode;
|
|
19774
19403
|
exports.sanitizeSpawnPermissionMode = sanitizeSpawnPermissionMode;
|
|
19775
|
-
exports.sendUserMessage = sendUserMessage;
|
|
19776
|
-
exports.sessionWebUrl = sessionWebUrl;
|
|
19777
19404
|
exports.setupOfflineReconnection = setupOfflineReconnection;
|
|
19778
19405
|
exports.stageClaudeAttachments = stageClaudeAttachments;
|
|
19779
19406
|
exports.startHappyServer = startHappyServer;
|
|
19780
|
-
exports.stopSession = stopSession;
|
|
19781
19407
|
exports.suggestTitleForPrompt = suggestTitleForPrompt;
|
|
19782
19408
|
exports.tmuxSupportsSessionEnv = tmuxSupportsSessionEnv;
|
|
19783
19409
|
exports.triggerFromFlags = triggerFromFlags;
|
|
19784
|
-
exports.waitForSessionKey = waitForSessionKey;
|
|
19785
|
-
exports.waitForTurnEnd = waitForTurnEnd;
|
|
19786
19410
|
exports.writeSessionModeFile = writeSessionModeFile;
|