very-happy-cli 0.2.154 → 0.2.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AcpBackend-Ckyadhf4.mjs → AcpBackend-DVB0nzgR.mjs} +2 -2
- package/dist/{AcpBackend-B14xNj5L.cjs → AcpBackend-DmFcRh54.cjs} +3 -3
- package/dist/{AcpSessionManager-CCHmPdrR.cjs → AcpSessionManager-CWaSYTnT.cjs} +1 -1
- package/dist/{AcpSessionManager-H8t4P-IW.mjs → AcpSessionManager-dGDgNb-p.mjs} +1 -1
- package/dist/agentGuidance-BWxFC6UL.mjs +1613 -0
- package/dist/agentGuidance-un7YPryB.cjs +1665 -0
- package/dist/{agentHome-B20RWdgE.mjs → agentHome-ByLb2HFg.mjs} +5 -5
- package/dist/{agentHome-BUsLTZnz.cjs → agentHome-aucT8BHw.cjs} +5 -5
- package/dist/{auto-BQnncDcs.mjs → auto-BQnD6gVx.mjs} +21 -21
- package/dist/{auto-IeldARBS.cjs → auto-P9RoJaY0.cjs} +24 -24
- package/dist/codex/happyMcpStdioBridge.cjs +6 -3
- package/dist/codex/happyMcpStdioBridge.mjs +6 -3
- package/dist/{config-B-ocQh5V.mjs → config-BPZ2Y6o8.mjs} +8 -8
- package/dist/{config-DM_xI9M6.cjs → config-BoePh9PX.cjs} +8 -8
- package/dist/{index-okLyUzeD.cjs → index--KCpVs1c.cjs} +7 -7
- package/dist/{index-BELV05ko.mjs → index-B4oXnbYw.mjs} +463 -889
- package/dist/{index-ZKuBjvHi.mjs → index-BjzdXb9x.mjs} +21 -13
- package/dist/{index-BUxBxSxJ.cjs → index-DRrLaljt.cjs} +21 -13
- package/dist/{index-CIx3248o.mjs → index-DVVRqaVG.mjs} +7 -7
- package/dist/{index-eCjkm8Y2.cjs → index-D_fYUxVg.cjs} +521 -960
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/dist/{install-BZOdxfPd.cjs → install-CGigiocs.cjs} +1 -1
- package/dist/{install-CoLN3whR.mjs → install-DP5aPNEX.mjs} +1 -1
- package/dist/{installTerminalHooks-CA4OcAS7.cjs → installTerminalHooks-XrcPvfBs.cjs} +5 -5
- package/dist/{installTerminalHooks-Dde_8t36.mjs → installTerminalHooks-tIg7mANc.mjs} +5 -5
- package/dist/lib.cjs +2 -2
- package/dist/lib.mjs +2 -2
- package/dist/{limits-Dt7_unH4.mjs → limits-C_DW0M0q.mjs} +309 -13
- package/dist/{limits-CmzISka2.cjs → limits-DkfHJ3nS.cjs} +329 -12
- package/dist/{mcp-7iDHlyCI.cjs → mcp-BqlXl10F.cjs} +23 -23
- package/dist/{mcp-9Ui4nQZL.mjs → mcp-o7QglAQM.mjs} +17 -17
- package/dist/{piRuntime-BesGGPME.cjs → piRuntime-BxP4B_zY.cjs} +2 -2
- package/dist/{piRuntime-C4E4pgye.mjs → piRuntime-MjiJI-nV.mjs} +2 -2
- package/dist/{piTerminal-fUogwxYr.cjs → piTerminal-DYzRcwgb.cjs} +6 -6
- package/dist/{piTerminal-CCEGCQOY.mjs → piTerminal-Dpq6yqKU.mjs} +6 -6
- package/dist/{runGemini-CNPGSsSK.cjs → runGemini-8Pxh94Fm.cjs} +7 -7
- package/dist/{runGemini-BW-pEklU.mjs → runGemini-CDlcQ-OA.mjs} +6 -6
- package/dist/{runOpenClaw-Bsodkg_s.cjs → runOpenClaw-B5FSZIbK.cjs} +6 -6
- package/dist/{runOpenClaw-NRB6-_jU.mjs → runOpenClaw-YkhsgPs0.mjs} +5 -5
- package/dist/{send-Bd00QcHC.cjs → send-CwrYXTPr.cjs} +8 -42
- package/dist/{send-DYeSFg8s.mjs → send-DJStmLBv.mjs} +5 -39
- package/dist/{sessions-DlPOYKLU.mjs → sessions-JaVXlSqj.mjs} +88 -45
- package/dist/{sessions-RIFUoy_g.cjs → sessions-MX21NJXd.cjs} +99 -56
- package/dist/{spawn-D1bhyHbH.mjs → spawn-BaPnjClT.mjs} +22 -22
- package/dist/{spawn-DxrfHTF4.cjs → spawn-cIoglKVf.cjs} +30 -30
- package/dist/{teams-pfKHpkgL.mjs → teams-DXV5Pwwt.mjs} +8 -8
- package/dist/{teams-Cfy0VMCo.cjs → teams-Dqk7F3D-.cjs} +8 -8
- package/dist/{todo-B2CjDg_V.mjs → todo-2Z1REhfx.mjs} +8 -6
- package/dist/{todo-B4cBFzHB.cjs → todo-Buuqn9gm.cjs} +8 -6
- package/dist/{types-D5RWyBE5.mjs → types-CkbfdRzq.mjs} +4 -282
- package/dist/{types-Bd_TGr0F.cjs → types-a2xsH2UI.cjs} +16 -311
- package/package.json +7 -7
- package/dist/agentGuidance-DUBXppv-.cjs +0 -554
- package/dist/agentGuidance-TLYQxsm2.mjs +0 -530
|
@@ -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-a2xsH2UI.cjs');
|
|
7
|
+
var persistence = require('./limits-DkfHJ3nS.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-un7YPryB.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-aucT8BHw.cjs'); });
|
|
5406
|
+
const { describeAgentHome } = await Promise.resolve().then(function () { return require('./types-a2xsH2UI.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,9 @@ function startDaemonControlServer({
|
|
|
6293
6240
|
pushClipboard,
|
|
6294
6241
|
pushFilePreview,
|
|
6295
6242
|
onTerminalHook,
|
|
6296
|
-
setTerminalTitle
|
|
6243
|
+
setTerminalTitle,
|
|
6244
|
+
onSessionEdit,
|
|
6245
|
+
listPeers
|
|
6297
6246
|
}) {
|
|
6298
6247
|
return new Promise((resolve) => {
|
|
6299
6248
|
const app = fastify({
|
|
@@ -6616,6 +6565,45 @@ function startDaemonControlServer({
|
|
|
6616
6565
|
}
|
|
6617
6566
|
return { status: "ok" };
|
|
6618
6567
|
});
|
|
6568
|
+
typed.post("/session-edit", {
|
|
6569
|
+
schema: {
|
|
6570
|
+
body: z.z.object({
|
|
6571
|
+
sessionId: z.z.string().regex(/^[a-zA-Z0-9_-]{1,128}$/),
|
|
6572
|
+
path: z.z.string().min(1).max(4096),
|
|
6573
|
+
tool: z.z.string().min(1).max(64),
|
|
6574
|
+
cwd: z.z.string().max(4096).optional(),
|
|
6575
|
+
/** When the runner saw the call (ms). Clamped to now by the daemon;
|
|
6576
|
+
* older than the conflict window = ignored (replayed history). */
|
|
6577
|
+
at: z.z.number().int().nonnegative().optional()
|
|
6578
|
+
}),
|
|
6579
|
+
response: { 200: z.z.object({ status: z.z.literal("ok") }) }
|
|
6580
|
+
}
|
|
6581
|
+
}, async (request) => {
|
|
6582
|
+
try {
|
|
6583
|
+
onSessionEdit?.(request.body);
|
|
6584
|
+
} catch (error) {
|
|
6585
|
+
persistence.logger.debug("[CONTROL SERVER] session-edit handler failed:", error);
|
|
6586
|
+
}
|
|
6587
|
+
return { status: "ok" };
|
|
6588
|
+
});
|
|
6589
|
+
typed.post("/peers", {
|
|
6590
|
+
schema: {
|
|
6591
|
+
response: {
|
|
6592
|
+
200: z.z.object({
|
|
6593
|
+
sessions: z.z.array(z.z.object({
|
|
6594
|
+
sessionId: z.z.string(),
|
|
6595
|
+
kind: z.z.enum(["managed", "mirror"]),
|
|
6596
|
+
pid: z.z.number().optional(),
|
|
6597
|
+
cwd: z.z.string().optional(),
|
|
6598
|
+
flavor: z.z.string().optional(),
|
|
6599
|
+
title: z.z.string().optional(),
|
|
6600
|
+
variant: z.z.string().optional(),
|
|
6601
|
+
edits: z.z.array(z.z.object({ path: z.z.string(), tool: z.z.string(), at: z.z.number() }))
|
|
6602
|
+
}))
|
|
6603
|
+
})
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
}, async () => ({ sessions: listPeers ? listPeers() : [] }));
|
|
6619
6607
|
typed.post("/stop", {
|
|
6620
6608
|
schema: {
|
|
6621
6609
|
response: {
|
|
@@ -6651,6 +6639,273 @@ function startDaemonControlServer({
|
|
|
6651
6639
|
});
|
|
6652
6640
|
}
|
|
6653
6641
|
|
|
6642
|
+
const EDIT_CONFLICT_WINDOW_MS = 30 * 6e4;
|
|
6643
|
+
function pairKey$1(a, b, path) {
|
|
6644
|
+
return a < b ? `${a}\0${b}\0${path}` : `${b}\0${a}\0${path}`;
|
|
6645
|
+
}
|
|
6646
|
+
class EditConflictTracker {
|
|
6647
|
+
windowMs;
|
|
6648
|
+
maxPaths;
|
|
6649
|
+
/** real path → sessionId → latest edit */
|
|
6650
|
+
byPath = /* @__PURE__ */ new Map();
|
|
6651
|
+
/** (sorted pair, path) → when the notice went out */
|
|
6652
|
+
notified = /* @__PURE__ */ new Map();
|
|
6653
|
+
constructor(options = {}) {
|
|
6654
|
+
this.windowMs = options.windowMs ?? EDIT_CONFLICT_WINDOW_MS;
|
|
6655
|
+
this.maxPaths = options.maxPaths ?? 5e3;
|
|
6656
|
+
}
|
|
6657
|
+
/**
|
|
6658
|
+
* Remember an edit and return the peers to notify about it. `isLive`
|
|
6659
|
+
* decides whether another session still counts (dead ones are dropped
|
|
6660
|
+
* from the table on the spot).
|
|
6661
|
+
*/
|
|
6662
|
+
record(edit, isLive) {
|
|
6663
|
+
this.prune(edit.at);
|
|
6664
|
+
let sessions = this.byPath.get(edit.path);
|
|
6665
|
+
if (!sessions) {
|
|
6666
|
+
sessions = /* @__PURE__ */ new Map();
|
|
6667
|
+
this.byPath.set(edit.path, sessions);
|
|
6668
|
+
}
|
|
6669
|
+
const conflicts = [];
|
|
6670
|
+
for (const [sessionId, peer] of sessions) {
|
|
6671
|
+
if (sessionId === edit.sessionId) continue;
|
|
6672
|
+
if (!isLive(sessionId)) {
|
|
6673
|
+
sessions.delete(sessionId);
|
|
6674
|
+
continue;
|
|
6675
|
+
}
|
|
6676
|
+
if (edit.at - peer.at > this.windowMs) continue;
|
|
6677
|
+
const key = pairKey$1(edit.sessionId, sessionId, edit.path);
|
|
6678
|
+
const notifiedAt = this.notified.get(key);
|
|
6679
|
+
if (notifiedAt !== void 0 && edit.at - notifiedAt <= this.windowMs) continue;
|
|
6680
|
+
this.notified.set(key, edit.at);
|
|
6681
|
+
conflicts.push({ editor: edit, peer });
|
|
6682
|
+
}
|
|
6683
|
+
sessions.set(edit.sessionId, edit);
|
|
6684
|
+
return conflicts;
|
|
6685
|
+
}
|
|
6686
|
+
/** Paths this session edited inside the window, newest first. */
|
|
6687
|
+
editsOf(sessionId, now, limit = 20) {
|
|
6688
|
+
const out = [];
|
|
6689
|
+
for (const [path, sessions] of this.byPath) {
|
|
6690
|
+
const edit = sessions.get(sessionId);
|
|
6691
|
+
if (edit && now - edit.at <= this.windowMs) out.push({ path, tool: edit.tool, at: edit.at });
|
|
6692
|
+
}
|
|
6693
|
+
return out.sort((a, b) => b.at - a.at).slice(0, limit);
|
|
6694
|
+
}
|
|
6695
|
+
/** Session ended: nothing it did counts any more. */
|
|
6696
|
+
forget(sessionId) {
|
|
6697
|
+
for (const [path, sessions] of this.byPath) {
|
|
6698
|
+
sessions.delete(sessionId);
|
|
6699
|
+
if (sessions.size === 0) this.byPath.delete(path);
|
|
6700
|
+
}
|
|
6701
|
+
for (const key of this.notified.keys()) {
|
|
6702
|
+
const [a, b] = key.split("\0");
|
|
6703
|
+
if (a === sessionId || b === sessionId) this.notified.delete(key);
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
/** Drop everything older than the window (and cap the table size). */
|
|
6707
|
+
prune(now) {
|
|
6708
|
+
for (const [path, sessions] of this.byPath) {
|
|
6709
|
+
for (const [sessionId, edit] of sessions) if (now - edit.at > this.windowMs) sessions.delete(sessionId);
|
|
6710
|
+
if (sessions.size === 0) this.byPath.delete(path);
|
|
6711
|
+
}
|
|
6712
|
+
for (const [key, at] of this.notified) if (now - at > this.windowMs) this.notified.delete(key);
|
|
6713
|
+
if (this.byPath.size > this.maxPaths) {
|
|
6714
|
+
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);
|
|
6715
|
+
for (const entry of oldest.slice(0, this.byPath.size - this.maxPaths)) this.byPath.delete(entry.path);
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
/** Test / diagnostics helper. */
|
|
6719
|
+
size() {
|
|
6720
|
+
return this.byPath.size;
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
function normalizeEditPath(path$1, cwd, realpath = node_fs.realpathSync.native ?? node_fs.realpathSync) {
|
|
6724
|
+
const absolute = path.isAbsolute(path$1) ? path.resolve(path$1) : path.resolve(cwd, path$1);
|
|
6725
|
+
let existing = absolute;
|
|
6726
|
+
const tail = [];
|
|
6727
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
6728
|
+
try {
|
|
6729
|
+
const real = realpath(existing);
|
|
6730
|
+
return tail.length ? path.join(real, ...tail) : real;
|
|
6731
|
+
} catch {
|
|
6732
|
+
const parent = path.dirname(existing);
|
|
6733
|
+
if (parent === existing) break;
|
|
6734
|
+
tail.unshift(existing.slice(parent.length).replace(new RegExp(`^\\${path.sep}`), ""));
|
|
6735
|
+
existing = parent;
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
return absolute;
|
|
6739
|
+
}
|
|
6740
|
+
|
|
6741
|
+
const CONFLICT_COALESCE_MS = 1e4;
|
|
6742
|
+
const MAX_NOTICES_PER_PAIR = 5;
|
|
6743
|
+
function pairKey(a, b) {
|
|
6744
|
+
return a < b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
6745
|
+
}
|
|
6746
|
+
function senderOf(info, sessionId) {
|
|
6747
|
+
return { sessionId, title: info?.title, flavor: info?.flavor, cwd: info?.cwd };
|
|
6748
|
+
}
|
|
6749
|
+
function createPeerCoordinator(deps) {
|
|
6750
|
+
const now = deps.now ?? (() => Date.now());
|
|
6751
|
+
const windowMs = deps.windowMs ?? EDIT_CONFLICT_WINDOW_MS;
|
|
6752
|
+
const coalesceMs = deps.coalesceMs ?? CONFLICT_COALESCE_MS;
|
|
6753
|
+
const maxNoticesPerPair = deps.maxNoticesPerPair ?? MAX_NOTICES_PER_PAIR;
|
|
6754
|
+
const tracker = new EditConflictTracker({ windowMs });
|
|
6755
|
+
const readPersisted = deps.readPersisted ?? persistence.readPersistedSessions;
|
|
6756
|
+
const send = deps.send ?? agentGuidance.sendUserMessage;
|
|
6757
|
+
const normalize = deps.normalize ?? normalizeEditPath;
|
|
6758
|
+
const liveTitle = deps.liveTitle ?? (async (sessionId, persisted) => (await agentGuidance.readSessionMetadata(sessionId, persisted))?.summary?.text);
|
|
6759
|
+
const schedule = deps.schedule ?? ((fn, ms) => {
|
|
6760
|
+
const t = setTimeout(fn, ms);
|
|
6761
|
+
t.unref?.();
|
|
6762
|
+
return t;
|
|
6763
|
+
});
|
|
6764
|
+
let getMirrors = deps.getMirrors ?? (() => []);
|
|
6765
|
+
const pending = /* @__PURE__ */ new Map();
|
|
6766
|
+
const noticesSent = /* @__PURE__ */ new Map();
|
|
6767
|
+
let inflight = Promise.resolve();
|
|
6768
|
+
const snapshot = () => {
|
|
6769
|
+
const at = now();
|
|
6770
|
+
const out = /* @__PURE__ */ new Map();
|
|
6771
|
+
for (const child of deps.getChildren()) {
|
|
6772
|
+
const id = child.happySessionId;
|
|
6773
|
+
if (!id || out.has(id)) continue;
|
|
6774
|
+
const meta = child.happySessionMetadataFromLocalWebhook;
|
|
6775
|
+
out.set(id, {
|
|
6776
|
+
sessionId: id,
|
|
6777
|
+
kind: "managed",
|
|
6778
|
+
pid: child.pid,
|
|
6779
|
+
...meta?.path ? { cwd: meta.path } : {},
|
|
6780
|
+
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
6781
|
+
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
6782
|
+
...meta?.variant ?? child.variant ? { variant: meta?.variant ?? child.variant } : {},
|
|
6783
|
+
edits: tracker.editsOf(id, at)
|
|
6784
|
+
});
|
|
6785
|
+
}
|
|
6786
|
+
for (const mirror of getMirrors()) {
|
|
6787
|
+
if (out.has(mirror.sessionId)) continue;
|
|
6788
|
+
out.set(mirror.sessionId, {
|
|
6789
|
+
sessionId: mirror.sessionId,
|
|
6790
|
+
kind: "mirror",
|
|
6791
|
+
flavor: "terminal-mirror",
|
|
6792
|
+
...mirror.cwd ? { cwd: mirror.cwd } : {},
|
|
6793
|
+
...mirror.title ? { title: mirror.title } : {},
|
|
6794
|
+
edits: tracker.editsOf(mirror.sessionId, at)
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
return out;
|
|
6798
|
+
};
|
|
6799
|
+
const deliver = async (recipient, peer, peerId, paths, peerEditedAgoMs, noticeId) => {
|
|
6800
|
+
if (recipient.kind === "mirror") return;
|
|
6801
|
+
const persisted = readPersisted()[recipient.sessionId];
|
|
6802
|
+
if (!persisted) {
|
|
6803
|
+
persistence.logger.debug(`[PEERS] no key for ${recipient.sessionId}; conflict notice ${noticeId} not delivered`);
|
|
6804
|
+
return;
|
|
6805
|
+
}
|
|
6806
|
+
const peerSender = senderOf(peer, peerId);
|
|
6807
|
+
if (!peerSender.title && peer?.kind === "managed") {
|
|
6808
|
+
const peerKey = readPersisted()[peer.sessionId];
|
|
6809
|
+
if (peerKey) {
|
|
6810
|
+
try {
|
|
6811
|
+
peerSender.title = await liveTitle(peer.sessionId, peerKey);
|
|
6812
|
+
} catch {
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6816
|
+
const text = agentGuidance.formatEditConflictNotice({ id: noticeId, path: paths[0], paths, peer: peerSender, peerEditedAgoMs, windowMs });
|
|
6817
|
+
try {
|
|
6818
|
+
await send(recipient.sessionId, persisted, text, "edit-conflict", {
|
|
6819
|
+
sentFrom: agentGuidance.SESSION_PEER_SENT_FROM,
|
|
6820
|
+
delivery: "steer",
|
|
6821
|
+
localId: `edit-conflict-${noticeId}-${recipient.sessionId}`
|
|
6822
|
+
});
|
|
6823
|
+
persistence.logger.debug(`[PEERS] conflict notice ${noticeId} \u2192 ${recipient.sessionId} (${paths.length} file(s), first ${paths[0]}, peer ${peerId})`);
|
|
6824
|
+
} catch (error) {
|
|
6825
|
+
persistence.logger.debug(`[PEERS] conflict notice ${noticeId} \u2192 ${recipient.sessionId} failed:`, error);
|
|
6826
|
+
}
|
|
6827
|
+
};
|
|
6828
|
+
const flushPair = async (key) => {
|
|
6829
|
+
const pair = pending.get(key);
|
|
6830
|
+
if (!pair) return;
|
|
6831
|
+
pending.delete(key);
|
|
6832
|
+
const at = now();
|
|
6833
|
+
const sent = (noticesSent.get(key) ?? []).filter((t) => at - t <= windowMs);
|
|
6834
|
+
if (sent.length >= maxNoticesPerPair) {
|
|
6835
|
+
noticesSent.set(key, sent);
|
|
6836
|
+
persistence.logger.debug(`[PEERS] pair ${pair.a}/${pair.b}: ${maxNoticesPerPair} notices already sent this window; ${pair.paths.size} conflicting path(s) not announced`);
|
|
6837
|
+
return;
|
|
6838
|
+
}
|
|
6839
|
+
sent.push(at);
|
|
6840
|
+
noticesSent.set(key, sent);
|
|
6841
|
+
const sessions = snapshot();
|
|
6842
|
+
const a = sessions.get(pair.a);
|
|
6843
|
+
const b = sessions.get(pair.b);
|
|
6844
|
+
const paths = [...pair.paths.keys()];
|
|
6845
|
+
const noticeId = node_crypto.randomUUID().slice(0, 8);
|
|
6846
|
+
const latestBy = (sessionId) => Math.max(...[...pair.paths.values()].map((c) => c.editor.sessionId === sessionId ? c.editor.at : c.peer.at));
|
|
6847
|
+
const agoFor = (recipientId, otherId) => Math.max(0, latestBy(recipientId) - latestBy(otherId));
|
|
6848
|
+
if (a) await deliver(a, b, pair.b, paths, agoFor(pair.a, pair.b), noticeId);
|
|
6849
|
+
if (b) await deliver(b, a, pair.a, paths, agoFor(pair.b, pair.a), noticeId);
|
|
6850
|
+
};
|
|
6851
|
+
const enqueue = (conflict) => {
|
|
6852
|
+
const key = pairKey(conflict.editor.sessionId, conflict.peer.sessionId);
|
|
6853
|
+
let pair = pending.get(key);
|
|
6854
|
+
if (!pair) {
|
|
6855
|
+
pair = { a: conflict.editor.sessionId, b: conflict.peer.sessionId, paths: /* @__PURE__ */ new Map(), timer: null };
|
|
6856
|
+
pair.timer = schedule(() => {
|
|
6857
|
+
inflight = inflight.then(() => flushPair(key)).catch(() => {
|
|
6858
|
+
});
|
|
6859
|
+
}, coalesceMs);
|
|
6860
|
+
pending.set(key, pair);
|
|
6861
|
+
}
|
|
6862
|
+
pair.paths.set(conflict.editor.path, conflict);
|
|
6863
|
+
};
|
|
6864
|
+
return {
|
|
6865
|
+
list: () => [...snapshot().values()],
|
|
6866
|
+
onEdit(edit) {
|
|
6867
|
+
const sessions = snapshot();
|
|
6868
|
+
const self = sessions.get(edit.sessionId);
|
|
6869
|
+
if (!self) {
|
|
6870
|
+
persistence.logger.debug(`[PEERS] edit from unknown/dead session ${edit.sessionId} ignored`);
|
|
6871
|
+
return [];
|
|
6872
|
+
}
|
|
6873
|
+
const current = now();
|
|
6874
|
+
const at = typeof edit.at === "number" && Number.isFinite(edit.at) ? Math.min(edit.at, current) : current;
|
|
6875
|
+
if (current - at > windowMs) {
|
|
6876
|
+
persistence.logger.debug(`[PEERS] edit from ${edit.sessionId} on ${edit.path} is ${Math.round((current - at) / 6e4)}m old (replayed history); ignored`);
|
|
6877
|
+
return [];
|
|
6878
|
+
}
|
|
6879
|
+
const cwd = edit.cwd ?? self.cwd ?? process.cwd();
|
|
6880
|
+
const path = normalize(edit.path, cwd);
|
|
6881
|
+
const conflicts = tracker.record({ sessionId: edit.sessionId, path, tool: edit.tool, at }, (id) => sessions.has(id));
|
|
6882
|
+
for (const conflict of conflicts) enqueue(conflict);
|
|
6883
|
+
return conflicts;
|
|
6884
|
+
},
|
|
6885
|
+
async flush() {
|
|
6886
|
+
for (const key of [...pending.keys()]) {
|
|
6887
|
+
const pair = pending.get(key);
|
|
6888
|
+
if (pair?.timer && typeof pair.timer === "object") clearTimeout(pair.timer);
|
|
6889
|
+
inflight = inflight.then(() => flushPair(key)).catch(() => {
|
|
6890
|
+
});
|
|
6891
|
+
}
|
|
6892
|
+
await inflight;
|
|
6893
|
+
},
|
|
6894
|
+
forget(sessionId) {
|
|
6895
|
+
tracker.forget(sessionId);
|
|
6896
|
+
for (const [key, pair] of pending) {
|
|
6897
|
+
if (pair.a === sessionId || pair.b === sessionId) {
|
|
6898
|
+
if (pair.timer && typeof pair.timer === "object") clearTimeout(pair.timer);
|
|
6899
|
+
pending.delete(key);
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
},
|
|
6903
|
+
setMirrorSource(source) {
|
|
6904
|
+
getMirrors = source;
|
|
6905
|
+
}
|
|
6906
|
+
};
|
|
6907
|
+
}
|
|
6908
|
+
|
|
6654
6909
|
const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
|
|
6655
6910
|
|
|
6656
6911
|
\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 +8442,7 @@ function parseJsonObject(text) {
|
|
|
8187
8442
|
return null;
|
|
8188
8443
|
}
|
|
8189
8444
|
}
|
|
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-
|
|
8445
|
+
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-D_fYUxVg.cjs', document.baseURI).href))).resolve(s)) {
|
|
8191
8446
|
const platformArch = `${platform}-${arch}`;
|
|
8192
8447
|
const candidates = [
|
|
8193
8448
|
`@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
|
|
@@ -8570,6 +8825,73 @@ class ClaudeAuthService {
|
|
|
8570
8825
|
}
|
|
8571
8826
|
}
|
|
8572
8827
|
|
|
8828
|
+
const CLAUDE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
8829
|
+
function pathOf(input) {
|
|
8830
|
+
if (!input || typeof input !== "object") return null;
|
|
8831
|
+
const record = input;
|
|
8832
|
+
const candidate = record.file_path ?? record.notebook_path;
|
|
8833
|
+
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
8834
|
+
}
|
|
8835
|
+
function extractClaudeEditPaths(body) {
|
|
8836
|
+
if (!body || typeof body !== "object") return [];
|
|
8837
|
+
const line = body;
|
|
8838
|
+
if (line.type !== "assistant") return [];
|
|
8839
|
+
const content = line.message?.content;
|
|
8840
|
+
if (!Array.isArray(content)) return [];
|
|
8841
|
+
const stamp = line.timestamp;
|
|
8842
|
+
const at = typeof stamp === "string" ? Date.parse(stamp) : typeof stamp === "number" ? stamp : NaN;
|
|
8843
|
+
const edits = [];
|
|
8844
|
+
for (const block of content) {
|
|
8845
|
+
if (!block || typeof block !== "object") continue;
|
|
8846
|
+
const b = block;
|
|
8847
|
+
if (b.type !== "tool_use" || typeof b.name !== "string" || !CLAUDE_EDIT_TOOLS.has(b.name)) continue;
|
|
8848
|
+
const path = pathOf(b.input);
|
|
8849
|
+
if (path) edits.push({ path, tool: b.name, ...Number.isFinite(at) ? { at } : {} });
|
|
8850
|
+
}
|
|
8851
|
+
return edits;
|
|
8852
|
+
}
|
|
8853
|
+
function extractCodexPatchPaths(changes) {
|
|
8854
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return [];
|
|
8855
|
+
return Object.keys(changes).filter((path) => path.trim() !== "").map((path) => ({ path, tool: "CodexPatch" }));
|
|
8856
|
+
}
|
|
8857
|
+
function extractAcpEditPaths(toolKind, args) {
|
|
8858
|
+
if (!args || typeof args !== "object") return [];
|
|
8859
|
+
const a = args;
|
|
8860
|
+
if (a.piTool === "write" || a.piTool === "edit") {
|
|
8861
|
+
const raw = a.rawInput;
|
|
8862
|
+
const path = raw && typeof raw.path === "string" && raw.path.trim() !== "" ? raw.path : null;
|
|
8863
|
+
return path ? [{ path, tool: a.piTool }] : [];
|
|
8864
|
+
}
|
|
8865
|
+
if (typeof a.piTool === "string") return [];
|
|
8866
|
+
if (toolKind !== "edit" || !Array.isArray(a.locations)) return [];
|
|
8867
|
+
const edits = [];
|
|
8868
|
+
for (const location of a.locations) {
|
|
8869
|
+
const path = location && typeof location === "object" ? location.path : void 0;
|
|
8870
|
+
if (typeof path === "string" && path.trim() !== "" && !edits.some((e) => e.path === path)) edits.push({ path, tool: "edit" });
|
|
8871
|
+
}
|
|
8872
|
+
return edits;
|
|
8873
|
+
}
|
|
8874
|
+
class EditReportThrottle {
|
|
8875
|
+
constructor(intervalMs = 5e3) {
|
|
8876
|
+
this.intervalMs = intervalMs;
|
|
8877
|
+
}
|
|
8878
|
+
lastReportedAt = /* @__PURE__ */ new Map();
|
|
8879
|
+
/** Returns the edits that should be reported now and remembers them. */
|
|
8880
|
+
take(edits, now = Date.now()) {
|
|
8881
|
+
const due = [];
|
|
8882
|
+
for (const edit of edits) {
|
|
8883
|
+
const last = this.lastReportedAt.get(edit.path);
|
|
8884
|
+
if (last !== void 0 && now - last < this.intervalMs) continue;
|
|
8885
|
+
this.lastReportedAt.set(edit.path, now);
|
|
8886
|
+
due.push(edit);
|
|
8887
|
+
}
|
|
8888
|
+
if (this.lastReportedAt.size > 512) {
|
|
8889
|
+
for (const [path, at] of this.lastReportedAt) if (now - at > this.intervalMs * 10) this.lastReportedAt.delete(path);
|
|
8890
|
+
}
|
|
8891
|
+
return due;
|
|
8892
|
+
}
|
|
8893
|
+
}
|
|
8894
|
+
|
|
8573
8895
|
const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set(["file-history-snapshot", "change", "queue-operation"]);
|
|
8574
8896
|
function parseLines(lines) {
|
|
8575
8897
|
const out = [];
|
|
@@ -8606,12 +8928,15 @@ function createMirrorScanner(opts) {
|
|
|
8606
8928
|
}
|
|
8607
8929
|
tail.offset = 0;
|
|
8608
8930
|
}
|
|
8931
|
+
let replaced = false;
|
|
8609
8932
|
if (tail.offset > size) {
|
|
8610
8933
|
persistence.logger.debug(`[MIRROR SCANNER] ${tail.path}: offset ${tail.offset} > size ${size} \u2014 file replaced, resetting (localId idempotency absorbs replays)`);
|
|
8611
8934
|
tail.offset = 0;
|
|
8935
|
+
replaced = true;
|
|
8612
8936
|
}
|
|
8613
8937
|
if (tail.offset === size) return;
|
|
8614
8938
|
const isFirstBackfillRead = tail.mode === "backfill-tail" && tail.offset === 0;
|
|
8939
|
+
const replay = isFirstBackfillRead || replaced;
|
|
8615
8940
|
const handle = await promises.open(tail.path, "r");
|
|
8616
8941
|
let buf;
|
|
8617
8942
|
try {
|
|
@@ -8631,7 +8956,7 @@ function createMirrorScanner(opts) {
|
|
|
8631
8956
|
if (decision.truncated) opts.events.onBackfillTruncated?.();
|
|
8632
8957
|
tail.mode = "from-eof";
|
|
8633
8958
|
}
|
|
8634
|
-
if (messages.length > 0) opts.events.onMessages(messages);
|
|
8959
|
+
if (messages.length > 0) opts.events.onMessages(messages, { replay });
|
|
8635
8960
|
};
|
|
8636
8961
|
const sync = new index.InvalidateSync(async () => {
|
|
8637
8962
|
for (const tail of files.values()) {
|
|
@@ -8754,8 +9079,17 @@ function createMirrorManager(deps) {
|
|
|
8754
9079
|
binding.client.updateMetadata((current) => ({ ...current, ...patch }));
|
|
8755
9080
|
persistBinding(binding);
|
|
8756
9081
|
};
|
|
8757
|
-
const sendMessages = (binding, messages) => {
|
|
9082
|
+
const sendMessages = (binding, messages, context = { replay: false }) => {
|
|
8758
9083
|
for (const message of messages) {
|
|
9084
|
+
if (deps.onEdit && binding.status === "active" && !context.replay) {
|
|
9085
|
+
for (const edit of extractClaudeEditPaths(message)) {
|
|
9086
|
+
try {
|
|
9087
|
+
deps.onEdit({ sessionId: binding.happySessionId, path: edit.path, tool: edit.tool, cwd: binding.metadata.path, ...edit.at !== void 0 ? { at: edit.at } : {} });
|
|
9088
|
+
} catch (error) {
|
|
9089
|
+
persistence.logger.debug(`[MIRROR] edit tap failed for terminal ${binding.terminalId}:`, error);
|
|
9090
|
+
}
|
|
9091
|
+
}
|
|
9092
|
+
}
|
|
8759
9093
|
const key = index.mirrorLineKey(message);
|
|
8760
9094
|
binding.client.sendClaudeSessionMessage(message, key ? { localIdFor: (envelopeIndex) => index.mirrorLocalId(key, envelopeIndex) } : void 0);
|
|
8761
9095
|
}
|
|
@@ -8764,7 +9098,7 @@ function createMirrorManager(deps) {
|
|
|
8764
9098
|
const scanner = createMirrorScanner({
|
|
8765
9099
|
backfillLines,
|
|
8766
9100
|
events: {
|
|
8767
|
-
onMessages: (messages) => sendMessages(binding, messages),
|
|
9101
|
+
onMessages: (messages, context) => sendMessages(binding, messages, context),
|
|
8768
9102
|
onBackfillTruncated: opts.withTruncationNotice ? () => binding.client.sendSessionEvent({
|
|
8769
9103
|
type: "message",
|
|
8770
9104
|
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 +9121,10 @@ function createMirrorManager(deps) {
|
|
|
8787
9121
|
await binding.scanner?.cleanup();
|
|
8788
9122
|
binding.scanner = null;
|
|
8789
9123
|
binding.client.closeClaudeSessionTurn("completed");
|
|
9124
|
+
try {
|
|
9125
|
+
deps.onSessionEnded?.(binding.happySessionId);
|
|
9126
|
+
} catch {
|
|
9127
|
+
}
|
|
8790
9128
|
updateBindingMetadata(binding, {
|
|
8791
9129
|
lifecycleState: "archived",
|
|
8792
9130
|
lifecycleStateSince: Date.now(),
|
|
@@ -8999,6 +9337,13 @@ function createMirrorManager(deps) {
|
|
|
8999
9337
|
}
|
|
9000
9338
|
};
|
|
9001
9339
|
return {
|
|
9340
|
+
listActiveMirrors() {
|
|
9341
|
+
return [...bindings.values()].filter((binding) => binding.status === "active").map((binding) => ({
|
|
9342
|
+
sessionId: binding.happySessionId,
|
|
9343
|
+
...binding.metadata.path ? { cwd: binding.metadata.path } : {},
|
|
9344
|
+
...binding.metadata.summary?.text ? { title: binding.metadata.summary.text } : {}
|
|
9345
|
+
}));
|
|
9346
|
+
},
|
|
9002
9347
|
handleHookPayload(body) {
|
|
9003
9348
|
const event = index.parseTerminalHookPayload(body);
|
|
9004
9349
|
if (!event) {
|
|
@@ -9465,683 +9810,6 @@ Delegate from this working directory unless a different repository was explicitl
|
|
|
9465
9810
|
${goal}`;
|
|
9466
9811
|
}
|
|
9467
9812
|
|
|
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
9813
|
function receiptPath(home, operationId) {
|
|
10146
9814
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
|
|
10147
9815
|
return path.join(home, "teams", "effects", `${operationId}.json`);
|
|
@@ -10295,8 +9963,8 @@ function createTeamWorker(deps) {
|
|
|
10295
9963
|
await complete(op, receipt);
|
|
10296
9964
|
return;
|
|
10297
9965
|
}
|
|
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 } : {} });
|
|
9966
|
+
const key = await agentGuidance.waitForSessionKey(receipt.sessionId, 1e4);
|
|
9967
|
+
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
9968
|
receipt = { ...receipt, phase: "delivered" };
|
|
10301
9969
|
writeReceipt$1(home, receipt);
|
|
10302
9970
|
}
|
|
@@ -10312,7 +9980,7 @@ function createTeamWorker(deps) {
|
|
|
10312
9980
|
if (!bot || bot.generation !== op.generation || bot.sessionId !== op.sessionId) throw new Error("Worker binding changed; preserve resources for reconciliation");
|
|
10313
9981
|
if (op.sessionId) {
|
|
10314
9982
|
await deps.stopAndWait(op.sessionId);
|
|
10315
|
-
await archiveSession(op.sessionId);
|
|
9983
|
+
await agentGuidance.archiveSession(op.sessionId);
|
|
10316
9984
|
}
|
|
10317
9985
|
if (team.archivedAt !== void 0) {
|
|
10318
9986
|
await complete(op, receipt);
|
|
@@ -10339,7 +10007,7 @@ function createTeamWorker(deps) {
|
|
|
10339
10007
|
const bot = team.bots.find((b) => b.id === message.recipientBotId);
|
|
10340
10008
|
if (!bot?.sessionId || !deps.live(bot.sessionId)) continue;
|
|
10341
10009
|
try {
|
|
10342
|
-
const key = await waitForSessionKey(bot.sessionId, 0);
|
|
10010
|
+
const key = await agentGuidance.waitForSessionKey(bot.sessionId, 0);
|
|
10343
10011
|
if (message.scheduleId) {
|
|
10344
10012
|
const current = (await http.get(`/v1/teams/${encodeURIComponent(team.id)}`)).data.team;
|
|
10345
10013
|
const pending = current.messages.find((m) => m.id === message.id);
|
|
@@ -10351,7 +10019,7 @@ function createTeamWorker(deps) {
|
|
|
10351
10019
|
${message.body}
|
|
10352
10020
|
|
|
10353
10021
|
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" });
|
|
10022
|
+
await agentGuidance.sendUserMessage(bot.sessionId, key, body, "teams", { localId: `teams-message-${message.id}-${bot.generation}`, sentFrom: "team" });
|
|
10355
10023
|
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
10024
|
} catch {
|
|
10357
10025
|
deps.log(`Teams message ${message.id} remains pending`);
|
|
@@ -10726,16 +10394,16 @@ function automationPromptHeader(name, runId) {
|
|
|
10726
10394
|
}
|
|
10727
10395
|
function automationRunAnchor(runId) {
|
|
10728
10396
|
const marker = ` run ${runId};`;
|
|
10729
|
-
return (body) => userPromptText(body)?.startsWith("[Very Happy automation ") === true && userPromptText(body).includes(marker);
|
|
10397
|
+
return (body) => agentGuidance.userPromptText(body)?.startsWith("[Very Happy automation ") === true && agentGuidance.userPromptText(body).includes(marker);
|
|
10730
10398
|
}
|
|
10731
10399
|
async function defaultReadTurn(sessionId, runId) {
|
|
10732
10400
|
const persisted = persistence.readPersistedSessions()[sessionId];
|
|
10733
10401
|
if (!persisted) throw new Error(`No local key for session ${sessionId}`);
|
|
10734
|
-
return analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10402
|
+
return agentGuidance.analyzeLatestTurn(await agentGuidance.readSessionLog(sessionId, persisted, agentGuidance.TURN_WINDOW), { anchor: automationRunAnchor(runId) });
|
|
10735
10403
|
}
|
|
10736
10404
|
async function defaultSend(sessionId, text, meta) {
|
|
10737
|
-
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
10738
|
-
await sendUserMessage(sessionId, persisted, text, "automation", { ...meta, sentFrom: "automation" });
|
|
10405
|
+
const persisted = await agentGuidance.waitForSessionKey(sessionId, 15e3);
|
|
10406
|
+
await agentGuidance.sendUserMessage(sessionId, persisted, text, "automation", { ...meta, sentFrom: "automation" });
|
|
10739
10407
|
}
|
|
10740
10408
|
function expandHome(path$1) {
|
|
10741
10409
|
if (path$1 === "~") return os.homedir();
|
|
@@ -11200,7 +10868,7 @@ async function startDaemon() {
|
|
|
11200
10868
|
});
|
|
11201
10869
|
persistence.logger.debug("[DAEMON RUN] Starting daemon process...");
|
|
11202
10870
|
persistence.logger.debugLargeJson("[DAEMON RUN] Environment", getEnvironmentInfo());
|
|
11203
|
-
const runningDaemonVersionMatches = await
|
|
10871
|
+
const runningDaemonVersionMatches = await persistence.isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
11204
10872
|
const runningDaemonState = await persistence.readDaemonState();
|
|
11205
10873
|
const runningDaemonEndpointsMatch = Boolean(runningDaemonState) && daemonEndpointsMatch(
|
|
11206
10874
|
runningDaemonState?.serverUrl,
|
|
@@ -11210,7 +10878,7 @@ async function startDaemon() {
|
|
|
11210
10878
|
);
|
|
11211
10879
|
if (!runningDaemonVersionMatches || !runningDaemonEndpointsMatch) {
|
|
11212
10880
|
persistence.logger.debug("[DAEMON RUN] Daemon version or endpoint mismatch detected, restarting daemon");
|
|
11213
|
-
await
|
|
10881
|
+
await persistence.stopDaemon();
|
|
11214
10882
|
} else {
|
|
11215
10883
|
persistence.logger.debug("[DAEMON RUN] Daemon version matches, keeping existing daemon");
|
|
11216
10884
|
console.log("Daemon already running with matching version");
|
|
@@ -11230,6 +10898,7 @@ async function startDaemon() {
|
|
|
11230
10898
|
persistence.logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
11231
10899
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
11232
10900
|
const turnActivity = new index.TurnActivityTracker();
|
|
10901
|
+
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
11233
10902
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
11234
10903
|
let teamWorker;
|
|
11235
10904
|
let automationRunner;
|
|
@@ -12089,6 +11758,7 @@ async function startDaemon() {
|
|
|
12089
11758
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
12090
11759
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
12091
11760
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11761
|
+
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
12092
11762
|
if (session?.happySessionId && session.encryption) {
|
|
12093
11763
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
12094
11764
|
persistence.logger.debug(`[DAEMON RUN] Process PID ${pid} exited, preserved session ${session.happySessionId} for resume`);
|
|
@@ -12147,7 +11817,7 @@ async function startDaemon() {
|
|
|
12147
11817
|
sessionId
|
|
12148
11818
|
);
|
|
12149
11819
|
assistantReportLastSentAt.set(sessionId, Date.now());
|
|
12150
|
-
await sendUserMessage(
|
|
11820
|
+
await agentGuidance.sendUserMessage(
|
|
12151
11821
|
decision.assistantSessionId,
|
|
12152
11822
|
assistantEntry,
|
|
12153
11823
|
formatAssistantReportMessage(title, sessionId, event),
|
|
@@ -12174,6 +11844,10 @@ async function startDaemon() {
|
|
|
12174
11844
|
onSessionStateEvent,
|
|
12175
11845
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
12176
11846
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
11847
|
+
onSessionEdit: (edit) => {
|
|
11848
|
+
peerCoordinator.onEdit(edit);
|
|
11849
|
+
},
|
|
11850
|
+
listPeers: () => peerCoordinator.list(),
|
|
12177
11851
|
pushClipboard: (text, terminalId) => {
|
|
12178
11852
|
if (!apiMachineRef) {
|
|
12179
11853
|
return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
|
|
@@ -12239,9 +11913,12 @@ async function startDaemon() {
|
|
|
12239
11913
|
const mirrorManager = createMirrorManager({
|
|
12240
11914
|
api,
|
|
12241
11915
|
machineId,
|
|
12242
|
-
onBindingsChanged: () => apiMachine.requestTerminalListRefresh()
|
|
11916
|
+
onBindingsChanged: () => apiMachine.requestTerminalListRefresh(),
|
|
11917
|
+
onEdit: (edit) => peerCoordinator.onEdit(edit),
|
|
11918
|
+
onSessionEnded: (sessionId) => peerCoordinator.forget(sessionId)
|
|
12243
11919
|
});
|
|
12244
11920
|
mirrorManagerRef = mirrorManager;
|
|
11921
|
+
peerCoordinator.setMirrorSource(() => mirrorManager.listActiveMirrors());
|
|
12245
11922
|
apiMachine.setMirrorIntegration({
|
|
12246
11923
|
resolveMirrorSessionId: (terminalId) => mirrorManager.resolveMirrorSessionId(terminalId),
|
|
12247
11924
|
onTerminalClosed: (terminalId) => mirrorManager.onTerminalClosed(terminalId),
|
|
@@ -12369,7 +12046,7 @@ async function startDaemon() {
|
|
|
12369
12046
|
claudeAuthService.stop();
|
|
12370
12047
|
apiMachine.shutdown();
|
|
12371
12048
|
await stopControlServer();
|
|
12372
|
-
await
|
|
12049
|
+
await persistence.cleanupDaemonState();
|
|
12373
12050
|
await persistence.releaseDaemonLock(daemonLockHandle);
|
|
12374
12051
|
await stopCaffeinate();
|
|
12375
12052
|
try {
|
|
@@ -12451,7 +12128,7 @@ async function startDaemon() {
|
|
|
12451
12128
|
}
|
|
12452
12129
|
apiMachine.shutdown();
|
|
12453
12130
|
await stopControlServer();
|
|
12454
|
-
await
|
|
12131
|
+
await persistence.cleanupDaemonState();
|
|
12455
12132
|
await stopCaffeinate();
|
|
12456
12133
|
await persistence.releaseDaemonLock(daemonLockHandle);
|
|
12457
12134
|
persistence.logger.debug(`[DAEMON RUN] Cleanup completed, exiting process with code ${daemonExitCode(source)}`);
|
|
@@ -12555,8 +12232,8 @@ function createTeamAdoption(sessionId) {
|
|
|
12555
12232
|
|
|
12556
12233
|
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
12234
|
if (!intent.guidanceDelivered) {
|
|
12558
|
-
const key = await waitForSessionKey(sessionId, 1e4);
|
|
12559
|
-
await sendUserMessage(sessionId, key, instructions, "teams", { localId: `teams-adopt-${input.requestId}`, sentFrom: "team" });
|
|
12235
|
+
const key = await agentGuidance.waitForSessionKey(sessionId, 1e4);
|
|
12236
|
+
await agentGuidance.sendUserMessage(sessionId, key, instructions, "teams", { localId: `teams-adopt-${input.requestId}`, sentFrom: "team" });
|
|
12560
12237
|
await retainTeam(path, input, connected.team.id, true);
|
|
12561
12238
|
}
|
|
12562
12239
|
operation.result = { status: "connected", teamId: connected.team.id, botId: connected.botId, instructions };
|
|
@@ -12573,127 +12250,6 @@ This session is now connected as the team lead. Call team_inspect to read the au
|
|
|
12573
12250
|
};
|
|
12574
12251
|
}
|
|
12575
12252
|
|
|
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
12253
|
const PERSONAL_MEMORY_SOFT_LIMIT_CHARS = 2e3;
|
|
12698
12254
|
function headingTitle(line) {
|
|
12699
12255
|
const m = /^##(?!#)\s*(.*?)\s*$/.exec(line);
|
|
@@ -12800,7 +12356,7 @@ function describeSession(id, persisted, extra) {
|
|
|
12800
12356
|
if (meta?.tags?.length) parts.push(`tags=${meta.tags.join(",")}`);
|
|
12801
12357
|
if (meta?.variant === "assistant") parts.push("(assistant)");
|
|
12802
12358
|
if (extra?.pid) parts.push(`pid=${extra.pid}`);
|
|
12803
|
-
parts.push(sessionWebUrl(id));
|
|
12359
|
+
parts.push(agentGuidance.sessionWebUrl(id));
|
|
12804
12360
|
return parts.join(" ");
|
|
12805
12361
|
}
|
|
12806
12362
|
function describeSummary(summary) {
|
|
@@ -12825,7 +12381,7 @@ function registerAssistantSessionTools(mcp) {
|
|
|
12825
12381
|
inputSchema: {}
|
|
12826
12382
|
}, async () => {
|
|
12827
12383
|
try {
|
|
12828
|
-
const sessions = await listSessions();
|
|
12384
|
+
const sessions = await agentGuidance.listSessions();
|
|
12829
12385
|
if (sessions.length === 0) return ok("No sessions found on this machine.");
|
|
12830
12386
|
return ok(`${sessions.length} session(s):
|
|
12831
12387
|
${sessions.map(describeSummary).join("\n")}`);
|
|
@@ -12841,9 +12397,9 @@ ${sessions.map(describeSummary).join("\n")}`);
|
|
|
12841
12397
|
limit: z.z.number().optional().describe("How many recent messages to fetch (default 20, max 100)")
|
|
12842
12398
|
}
|
|
12843
12399
|
}, async (args) => {
|
|
12844
|
-
if (!
|
|
12400
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12845
12401
|
try {
|
|
12846
|
-
const result = await readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12402
|
+
const result = await agentGuidance.readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12847
12403
|
const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
|
|
12848
12404
|
return ok(`${describeSummary(result.summary)}
|
|
12849
12405
|
--- last ${result.messageCount} message(s) ---
|
|
@@ -12861,11 +12417,11 @@ ${body}`);
|
|
|
12861
12417
|
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
12418
|
}
|
|
12863
12419
|
}, async (args) => {
|
|
12864
|
-
if (!
|
|
12420
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12865
12421
|
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
12866
12422
|
try {
|
|
12867
|
-
const persisted = await waitForSessionKey(args.sessionId, 0);
|
|
12868
|
-
const result = await deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12423
|
+
const persisted = await agentGuidance.waitForSessionKey(args.sessionId, 0);
|
|
12424
|
+
const result = await agentGuidance.deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12869
12425
|
resume: args.resume === true,
|
|
12870
12426
|
resumeHint: "Pass resume:true to bring it back on this machine, or spawn a new session."
|
|
12871
12427
|
});
|
|
@@ -12890,16 +12446,16 @@ ${body}`);
|
|
|
12890
12446
|
if (!normalized.ok) return fail(normalized.error);
|
|
12891
12447
|
const directory = normalized.directory;
|
|
12892
12448
|
try {
|
|
12893
|
-
const result = await
|
|
12449
|
+
const result = await persistence.spawnDaemonSession(directory, void 0, { spawnedBy: "assistant" });
|
|
12894
12450
|
if (result?.error || !result?.sessionId) {
|
|
12895
12451
|
return fail(`Failed to spawn session: ${result?.error ?? "daemon returned no session id"}`);
|
|
12896
12452
|
}
|
|
12897
12453
|
const sessionId = result.sessionId;
|
|
12898
|
-
const url = sessionWebUrl(sessionId);
|
|
12454
|
+
const url = agentGuidance.sessionWebUrl(sessionId);
|
|
12899
12455
|
if (args.prompt && args.prompt.trim().length > 0) {
|
|
12900
12456
|
try {
|
|
12901
|
-
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
12902
|
-
await sendUserMessage(sessionId, persisted, args.prompt, MCP_CLIENT_TAG);
|
|
12457
|
+
const persisted = await agentGuidance.waitForSessionKey(sessionId, 15e3);
|
|
12458
|
+
await agentGuidance.sendUserMessage(sessionId, persisted, args.prompt, MCP_CLIENT_TAG);
|
|
12903
12459
|
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
12460
|
} catch (error) {
|
|
12905
12461
|
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 +12473,10 @@ ${body}`);
|
|
|
12917
12473
|
sessionId: z.z.string().describe("The Happy session id to stop")
|
|
12918
12474
|
}
|
|
12919
12475
|
}, async (args) => {
|
|
12920
|
-
if (!
|
|
12476
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12921
12477
|
try {
|
|
12922
12478
|
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
12923
|
-
const success = await stopSession(args.sessionId);
|
|
12479
|
+
const success = await agentGuidance.stopSession(args.sessionId);
|
|
12924
12480
|
if (!success) return fail(`Session ${args.sessionId} was not found among the daemon's running sessions.`);
|
|
12925
12481
|
return ok(`Stopped ${describeSession(args.sessionId, persisted)}`);
|
|
12926
12482
|
} catch (error) {
|
|
@@ -12934,9 +12490,9 @@ ${body}`);
|
|
|
12934
12490
|
sessionId: z.z.string().describe("The Happy session id to archive")
|
|
12935
12491
|
}
|
|
12936
12492
|
}, async (args) => {
|
|
12937
|
-
if (!
|
|
12493
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12938
12494
|
try {
|
|
12939
|
-
await archiveSession(args.sessionId);
|
|
12495
|
+
await agentGuidance.archiveSession(args.sessionId);
|
|
12940
12496
|
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
12941
12497
|
return ok(`Archived ${describeSession(args.sessionId, persisted)}`);
|
|
12942
12498
|
} catch (error) {
|
|
@@ -12970,7 +12526,7 @@ ${lines.join("\n")}`);
|
|
|
12970
12526
|
lines: z.z.number().optional().describe("How many history lines to read (default 80, max 2000)")
|
|
12971
12527
|
}
|
|
12972
12528
|
}, async (args) => {
|
|
12973
|
-
if (!
|
|
12529
|
+
if (!persistence.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
12974
12530
|
const r = index.readVhTerminal(args.terminalId, args.lines ?? 80);
|
|
12975
12531
|
if (!r.ok) return fail(`Failed to read terminal ${args.terminalId}: ${r.error}`);
|
|
12976
12532
|
return ok(`terminal ${args.terminalId} \u2014 last output:
|
|
@@ -12985,7 +12541,7 @@ ${r.text}`);
|
|
|
12985
12541
|
submit: z.z.boolean().optional().describe("Also press Enter after pasting (default false)")
|
|
12986
12542
|
}
|
|
12987
12543
|
}, async (args) => {
|
|
12988
|
-
if (!
|
|
12544
|
+
if (!persistence.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
12989
12545
|
if (typeof args.text !== "string" || args.text.length === 0) return fail("text must be non-empty");
|
|
12990
12546
|
const submit = args.submit === true;
|
|
12991
12547
|
const r = index.sendToVhTerminal(args.terminalId, args.text, submit);
|
|
@@ -13060,13 +12616,14 @@ function normalizeProgress(value) {
|
|
|
13060
12616
|
return flat.length > PROGRESS_MAX_CHARS$1 ? flat.slice(0, PROGRESS_MAX_CHARS$1) : flat;
|
|
13061
12617
|
}
|
|
13062
12618
|
|
|
13063
|
-
function createMcpServer(handlers, options, sessionId) {
|
|
12619
|
+
function createMcpServer(handlers, options, sessionId, client) {
|
|
13064
12620
|
const mcp = new mcp_js.McpServer({
|
|
13065
12621
|
name: "Very Happy Tools",
|
|
13066
12622
|
version: "1.0.0"
|
|
13067
12623
|
});
|
|
13068
12624
|
agentGuidance.registerTeamsTools(mcp, sessionId);
|
|
13069
12625
|
agentGuidance.registerAutomationTools(mcp);
|
|
12626
|
+
if (client) agentGuidance.registerSessionPeerTools(mcp, agentGuidance.createSessionPeerToolExecutor(client));
|
|
13070
12627
|
mcp.registerTool("change_title", {
|
|
13071
12628
|
description: "Change the title of the current chat session",
|
|
13072
12629
|
title: "Change Chat Title",
|
|
@@ -13269,7 +12826,7 @@ async function startHappyServer(client, options) {
|
|
|
13269
12826
|
}
|
|
13270
12827
|
return;
|
|
13271
12828
|
}
|
|
13272
|
-
const mcp = createMcpServer(handlers, options, client.sessionId);
|
|
12829
|
+
const mcp = createMcpServer(handlers, options, client.sessionId, client);
|
|
13273
12830
|
try {
|
|
13274
12831
|
const transport = new streamableHttp_js.StreamableHTTPServerTransport({
|
|
13275
12832
|
sessionIdGenerator: void 0
|
|
@@ -13310,6 +12867,7 @@ async function startHappyServer(client, options) {
|
|
|
13310
12867
|
agentGuidance.REPORT_PROGRESS_TOOL_NAME,
|
|
13311
12868
|
...agentGuidance.TEAM_TOOL_NAMES,
|
|
13312
12869
|
...agentGuidance.AUTOMATION_TOOL_NAMES,
|
|
12870
|
+
...agentGuidance.SESSION_PEER_TOOL_NAMES,
|
|
13313
12871
|
...options?.assistant && !process.env.VH_TEAM_SCOPE_FILE ? ASSISTANT_TOOL_NAMES : []
|
|
13314
12872
|
],
|
|
13315
12873
|
stop: () => {
|
|
@@ -13646,7 +13204,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13646
13204
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13647
13205
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13648
13206
|
const fork = deps.run ?? (async (input) => {
|
|
13649
|
-
const { query } = await Promise.resolve().then(function () { return require('./index
|
|
13207
|
+
const { query } = await Promise.resolve().then(function () { return require('./index--KCpVs1c.cjs'); });
|
|
13650
13208
|
return runSideQuestion(query, input);
|
|
13651
13209
|
});
|
|
13652
13210
|
const run = async (input, live) => {
|
|
@@ -13765,7 +13323,7 @@ const MAX_TITLE_CHARS = 60;
|
|
|
13765
13323
|
const GENERATION_TIMEOUT_MS = 3e4;
|
|
13766
13324
|
function resolveClaudeBinary() {
|
|
13767
13325
|
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-
|
|
13326
|
+
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-D_fYUxVg.cjs', document.baseURI).href)));
|
|
13769
13327
|
const utilsPath = path.resolve(path.join(terminalTitleSuggest.projectPath(), "scripts", "claude_version_utils.cjs"));
|
|
13770
13328
|
const { getClaudeCliPath } = require$1(utilsPath);
|
|
13771
13329
|
const path$1 = getClaudeCliPath();
|
|
@@ -14333,7 +13891,7 @@ async function runClaude(credentials, options = {}) {
|
|
|
14333
13891
|
}
|
|
14334
13892
|
try {
|
|
14335
13893
|
persistence.logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
14336
|
-
const result = await
|
|
13894
|
+
const result = await persistence.notifyDaemonSessionStarted(response.id, metadata, {
|
|
14337
13895
|
encryptionKey: persistence.encodeBase64(response.encryptionKey),
|
|
14338
13896
|
encryptionVariant: response.encryptionVariant,
|
|
14339
13897
|
seq: response.seq,
|
|
@@ -14433,6 +13991,17 @@ async function runClaude(credentials, options = {}) {
|
|
|
14433
13991
|
});
|
|
14434
13992
|
session.on("claude-session-message", (body) => boardAnalyzer.noteClaudeMessage(body));
|
|
14435
13993
|
session.on("turn-ended", () => boardAnalyzer.onTurnEnd());
|
|
13994
|
+
const editThrottle = new EditReportThrottle();
|
|
13995
|
+
session.on("claude-session-message", (body) => {
|
|
13996
|
+
try {
|
|
13997
|
+
for (const edit of editThrottle.take(extractClaudeEditPaths(body))) {
|
|
13998
|
+
persistence.reportSessionEditToDaemon({ sessionId: response.id, path: edit.path, tool: edit.tool, cwd: workingDirectory, ...edit.at !== void 0 ? { at: edit.at } : {} });
|
|
13999
|
+
}
|
|
14000
|
+
} catch (error) {
|
|
14001
|
+
persistence.logger.debug("[START] edit report failed:", error);
|
|
14002
|
+
}
|
|
14003
|
+
});
|
|
14004
|
+
options = { ...options, claudeEnvVars: { ...options.claudeEnvVars, VH_PEER_SESSION_ID: response.id } };
|
|
14436
14005
|
const initialScannerSessionId = forkClaudeSessionId ?? (metadata.claudeSessionId ?? null);
|
|
14437
14006
|
const remoteScanner = await createSessionScanner({
|
|
14438
14007
|
sessionId: initialScannerSessionId,
|
|
@@ -15064,7 +14633,7 @@ async function handleAuthLogin(args) {
|
|
|
15064
14633
|
console.log(chalk.gray(" \u2022 Re-authenticate and register machine\n"));
|
|
15065
14634
|
try {
|
|
15066
14635
|
persistence.logger.debug("Stopping daemon for force auth...");
|
|
15067
|
-
await
|
|
14636
|
+
await persistence.stopDaemon();
|
|
15068
14637
|
console.log(chalk.gray("\u2713 Stopped daemon"));
|
|
15069
14638
|
} catch (error) {
|
|
15070
14639
|
persistence.logger.debug("Daemon was not running or failed to stop:", error);
|
|
@@ -15129,7 +14698,7 @@ async function handleAuthLogout() {
|
|
|
15129
14698
|
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
|
15130
14699
|
try {
|
|
15131
14700
|
try {
|
|
15132
|
-
await
|
|
14701
|
+
await persistence.stopDaemon();
|
|
15133
14702
|
console.log(chalk.gray("Stopped daemon"));
|
|
15134
14703
|
} catch {
|
|
15135
14704
|
}
|
|
@@ -15174,7 +14743,7 @@ async function handleAuthStatus() {
|
|
|
15174
14743
|
console.log(chalk.gray(`
|
|
15175
14744
|
Data directory: ${persistence.configuration.happyHomeDir}`));
|
|
15176
14745
|
try {
|
|
15177
|
-
const running = await
|
|
14746
|
+
const running = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
15178
14747
|
if (running) {
|
|
15179
14748
|
console.log(chalk.green("\u2713 Daemon running"));
|
|
15180
14749
|
} else {
|
|
@@ -16000,9 +15569,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
|
|
|
16000
15569
|
};
|
|
16001
15570
|
}
|
|
16002
15571
|
|
|
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-
|
|
15572
|
+
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-D_fYUxVg.cjs', document.baseURI).href)));
|
|
16004
15573
|
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-
|
|
15574
|
+
const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D_fYUxVg.cjs', document.baseURI).href)));
|
|
16006
15575
|
const PRISMA_QUERY_ENGINE_FILES = {
|
|
16007
15576
|
"arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
|
|
16008
15577
|
"x64-darwin": "libquery_engine-darwin.dylib.node",
|
|
@@ -16391,7 +15960,7 @@ const DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
|
16391
15960
|
const DAEMON_READY_POLL_INTERVAL_MS = 100;
|
|
16392
15961
|
async function ensureDaemonRunning() {
|
|
16393
15962
|
persistence.logger.debug("Ensuring Happy background service is running & matches our version...");
|
|
16394
|
-
const versionMatches = await
|
|
15963
|
+
const versionMatches = await persistence.isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
16395
15964
|
const state = await persistence.readDaemonState();
|
|
16396
15965
|
const endpointsMatch = Boolean(state) && daemonEndpointsMatch(
|
|
16397
15966
|
state?.serverUrl,
|
|
@@ -16402,9 +15971,9 @@ async function ensureDaemonRunning() {
|
|
|
16402
15971
|
if (versionMatches && endpointsMatch) {
|
|
16403
15972
|
return;
|
|
16404
15973
|
}
|
|
16405
|
-
if (await
|
|
15974
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
16406
15975
|
persistence.logger.debug("Stopping daemon whose version or configured relay no longer matches...");
|
|
16407
|
-
await
|
|
15976
|
+
await persistence.stopDaemon();
|
|
16408
15977
|
}
|
|
16409
15978
|
persistence.logger.debug("Starting Happy background service...");
|
|
16410
15979
|
const daemonProcess = spawnHappyCLI(["daemon", "start-sync"], {
|
|
@@ -16415,7 +15984,7 @@ async function ensureDaemonRunning() {
|
|
|
16415
15984
|
daemonProcess.unref();
|
|
16416
15985
|
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
16417
15986
|
while (Date.now() < deadline) {
|
|
16418
|
-
if (await
|
|
15987
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
16419
15988
|
persistence.logger.debug("Happy background service is ready");
|
|
16420
15989
|
return;
|
|
16421
15990
|
}
|
|
@@ -18122,7 +17691,7 @@ async function runCodex(opts) {
|
|
|
18122
17691
|
if (response) {
|
|
18123
17692
|
try {
|
|
18124
17693
|
persistence.logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
18125
|
-
const result = await
|
|
17694
|
+
const result = await persistence.notifyDaemonSessionStarted(response.id, metadata, {
|
|
18126
17695
|
encryptionKey: persistence.encodeBase64(response.encryptionKey),
|
|
18127
17696
|
encryptionVariant: response.encryptionVariant,
|
|
18128
17697
|
seq: response.seq,
|
|
@@ -18419,6 +17988,7 @@ async function runCodex(opts) {
|
|
|
18419
17988
|
return "denied";
|
|
18420
17989
|
}
|
|
18421
17990
|
});
|
|
17991
|
+
const editThrottle = new EditReportThrottle();
|
|
18422
17992
|
client.setEventHandler((msg) => {
|
|
18423
17993
|
if (msg.type === "token_count") {
|
|
18424
17994
|
session.sendAgentUsageSnapshot("codex", msg);
|
|
@@ -18482,6 +18052,9 @@ async function runCodex(opts) {
|
|
|
18482
18052
|
}
|
|
18483
18053
|
if (msg.type === "patch_apply_begin") {
|
|
18484
18054
|
const { changes } = msg;
|
|
18055
|
+
for (const edit of editThrottle.take(extractCodexPatchPaths(changes))) {
|
|
18056
|
+
persistence.reportSessionEditToDaemon({ sessionId: session.sessionId, path: edit.path, tool: edit.tool, cwd: process.cwd() });
|
|
18057
|
+
}
|
|
18485
18058
|
const changeCount = Object.keys(changes).length;
|
|
18486
18059
|
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
18487
18060
|
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
@@ -18527,6 +18100,7 @@ async function runCodex(opts) {
|
|
|
18527
18100
|
};
|
|
18528
18101
|
let first = true;
|
|
18529
18102
|
let appendSystemPromptInjected = false;
|
|
18103
|
+
process.env.VH_PEER_SESSION_ID = session.sessionId;
|
|
18530
18104
|
try {
|
|
18531
18105
|
persistence.logger.debug("[codex]: client.connect begin");
|
|
18532
18106
|
await client.connect();
|
|
@@ -18940,7 +18514,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18940
18514
|
return;
|
|
18941
18515
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18942
18516
|
try {
|
|
18943
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-
|
|
18517
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-XrcPvfBs.cjs'); });
|
|
18944
18518
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18945
18519
|
if (command.action === "help") {
|
|
18946
18520
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18957,7 +18531,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18957
18531
|
}
|
|
18958
18532
|
} else if (subcommand === "agent-home") {
|
|
18959
18533
|
try {
|
|
18960
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-
|
|
18534
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-aucT8BHw.cjs'); });
|
|
18961
18535
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18962
18536
|
process.exit(0);
|
|
18963
18537
|
} catch (error) {
|
|
@@ -18966,7 +18540,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18966
18540
|
}
|
|
18967
18541
|
} else if (subcommand === "spawn") {
|
|
18968
18542
|
try {
|
|
18969
|
-
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-
|
|
18543
|
+
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-cIoglKVf.cjs'); });
|
|
18970
18544
|
await handleSpawnCommand(args.slice(1));
|
|
18971
18545
|
} catch (error) {
|
|
18972
18546
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18978,7 +18552,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18978
18552
|
return;
|
|
18979
18553
|
} else if (subcommand === "sessions") {
|
|
18980
18554
|
try {
|
|
18981
|
-
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-
|
|
18555
|
+
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-MX21NJXd.cjs'); });
|
|
18982
18556
|
await handleSessionsCommand(args.slice(1));
|
|
18983
18557
|
} catch (error) {
|
|
18984
18558
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18990,7 +18564,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18990
18564
|
return;
|
|
18991
18565
|
} else if (subcommand === "send") {
|
|
18992
18566
|
try {
|
|
18993
|
-
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-
|
|
18567
|
+
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-CwrYXTPr.cjs'); });
|
|
18994
18568
|
await handleSendCommand(args.slice(1));
|
|
18995
18569
|
} catch (error) {
|
|
18996
18570
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -19096,9 +18670,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19096
18670
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
19097
18671
|
const projectId = args[3];
|
|
19098
18672
|
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-
|
|
18673
|
+
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-BoePh9PX.cjs'); });
|
|
18674
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-DkfHJ3nS.cjs'); }).then(function (n) { return n.persistence; });
|
|
18675
|
+
const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-a2xsH2UI.cjs'); }).then(function (n) { return n.api; });
|
|
19102
18676
|
let userEmail = void 0;
|
|
19103
18677
|
try {
|
|
19104
18678
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -19129,7 +18703,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19129
18703
|
}
|
|
19130
18704
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
19131
18705
|
try {
|
|
19132
|
-
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-
|
|
18706
|
+
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-BoePh9PX.cjs'); });
|
|
19133
18707
|
const config = readGeminiLocalConfig();
|
|
19134
18708
|
if (config.googleCloudProject) {
|
|
19135
18709
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -19169,7 +18743,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19169
18743
|
process.exit(0);
|
|
19170
18744
|
}
|
|
19171
18745
|
try {
|
|
19172
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
18746
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-8Pxh94Fm.cjs'); });
|
|
19173
18747
|
let startedBy = void 0;
|
|
19174
18748
|
for (let i = 1; i < args.length; i++) {
|
|
19175
18749
|
if (args[i] === "--started-by") {
|
|
@@ -19192,7 +18766,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19192
18766
|
} else if (subcommand === "pi") {
|
|
19193
18767
|
if (args[1] === "--terminal") {
|
|
19194
18768
|
try {
|
|
19195
|
-
const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-
|
|
18769
|
+
const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-DYzRcwgb.cjs'); });
|
|
19196
18770
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
19197
18771
|
} catch (error) {
|
|
19198
18772
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -19209,7 +18783,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19209
18783
|
}
|
|
19210
18784
|
};
|
|
19211
18785
|
try {
|
|
19212
|
-
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-
|
|
18786
|
+
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-DRrLaljt.cjs'); });
|
|
19213
18787
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
19214
18788
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
19215
18789
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -19237,7 +18811,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19237
18811
|
return;
|
|
19238
18812
|
} else if (subcommand === "acp") {
|
|
19239
18813
|
try {
|
|
19240
|
-
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-
|
|
18814
|
+
const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-DRrLaljt.cjs'); });
|
|
19241
18815
|
let startedBy = void 0;
|
|
19242
18816
|
let verbose = false;
|
|
19243
18817
|
const acpArgs = [];
|
|
@@ -19277,7 +18851,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19277
18851
|
return;
|
|
19278
18852
|
} else if (subcommand === "openclaw") {
|
|
19279
18853
|
try {
|
|
19280
|
-
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-
|
|
18854
|
+
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-B5FSZIbK.cjs'); });
|
|
19281
18855
|
let startedBy = void 0;
|
|
19282
18856
|
let verbose = false;
|
|
19283
18857
|
let gatewayUrl;
|
|
@@ -19328,7 +18902,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19328
18902
|
return;
|
|
19329
18903
|
} else if (subcommand === "todo") {
|
|
19330
18904
|
try {
|
|
19331
|
-
const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-
|
|
18905
|
+
const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-Buuqn9gm.cjs'); });
|
|
19332
18906
|
await handleTodoCommand(args.slice(1));
|
|
19333
18907
|
} catch (error) {
|
|
19334
18908
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -19337,7 +18911,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19337
18911
|
return;
|
|
19338
18912
|
} else if (subcommand === "teams") {
|
|
19339
18913
|
try {
|
|
19340
|
-
const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-
|
|
18914
|
+
const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-Dqk7F3D-.cjs'); });
|
|
19341
18915
|
await handleTeamsCommand(args.slice(1));
|
|
19342
18916
|
} catch (error) {
|
|
19343
18917
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -19346,7 +18920,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19346
18920
|
return;
|
|
19347
18921
|
} else if (subcommand === "auto") {
|
|
19348
18922
|
try {
|
|
19349
|
-
const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-
|
|
18923
|
+
const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-P9RoJaY0.cjs'); });
|
|
19350
18924
|
await handleAutoCommand(args.slice(1));
|
|
19351
18925
|
} catch (error) {
|
|
19352
18926
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -19358,7 +18932,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19358
18932
|
return;
|
|
19359
18933
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
19360
18934
|
try {
|
|
19361
|
-
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-
|
|
18935
|
+
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-BqlXl10F.cjs'); });
|
|
19362
18936
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
19363
18937
|
} catch (error) {
|
|
19364
18938
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -19381,7 +18955,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19381
18955
|
const daemonSubcommand = args[1];
|
|
19382
18956
|
if (daemonSubcommand === "list") {
|
|
19383
18957
|
try {
|
|
19384
|
-
const sessions = await
|
|
18958
|
+
const sessions = await persistence.listDaemonSessions();
|
|
19385
18959
|
if (sessions.length === 0) {
|
|
19386
18960
|
console.log("No active sessions this daemon is aware of (they might have been started by a previous version of the daemon)");
|
|
19387
18961
|
} else {
|
|
@@ -19399,7 +18973,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19399
18973
|
process.exit(1);
|
|
19400
18974
|
}
|
|
19401
18975
|
try {
|
|
19402
|
-
const success = await
|
|
18976
|
+
const success = await persistence.stopDaemonSession(sessionId);
|
|
19403
18977
|
console.log(success ? "Session stopped" : "Failed to stop session");
|
|
19404
18978
|
} catch (error) {
|
|
19405
18979
|
console.log("No daemon running");
|
|
@@ -19407,7 +18981,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19407
18981
|
return;
|
|
19408
18982
|
} else if (daemonSubcommand === "start") {
|
|
19409
18983
|
const currentState = await persistence.readDaemonState();
|
|
19410
|
-
const running = await
|
|
18984
|
+
const running = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
19411
18985
|
const currentDaemonMatches = currentState && currentState.startedWithCliVersion === persistence.configuration.currentCliVersion && daemonEndpointsMatch(
|
|
19412
18986
|
currentState.serverUrl,
|
|
19413
18987
|
currentState.webappUrl,
|
|
@@ -19416,7 +18990,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19416
18990
|
);
|
|
19417
18991
|
if (running && !currentDaemonMatches) {
|
|
19418
18992
|
console.log("Configured relay or CLI version changed; restarting the existing daemon");
|
|
19419
|
-
await
|
|
18993
|
+
await persistence.stopDaemon();
|
|
19420
18994
|
}
|
|
19421
18995
|
const child = spawnHappyCLI(["daemon", "start-sync"], {
|
|
19422
18996
|
detached: true,
|
|
@@ -19426,7 +19000,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19426
19000
|
child.unref();
|
|
19427
19001
|
let started = false;
|
|
19428
19002
|
for (let i = 0; i < 50; i++) {
|
|
19429
|
-
if (await
|
|
19003
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
19430
19004
|
started = true;
|
|
19431
19005
|
break;
|
|
19432
19006
|
}
|
|
@@ -19443,7 +19017,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19443
19017
|
await startDaemon();
|
|
19444
19018
|
process.exit(0);
|
|
19445
19019
|
} else if (daemonSubcommand === "stop") {
|
|
19446
|
-
await
|
|
19020
|
+
await persistence.stopDaemon();
|
|
19447
19021
|
process.exit(0);
|
|
19448
19022
|
} else if (daemonSubcommand === "status") {
|
|
19449
19023
|
await runDoctorDaemon();
|
|
@@ -19734,11 +19308,10 @@ exports.BaseReasoningProcessor = BaseReasoningProcessor;
|
|
|
19734
19308
|
exports.CHANGE_TITLE_INSTRUCTION = CHANGE_TITLE_INSTRUCTION;
|
|
19735
19309
|
exports.CLAUDE_ATTACHMENT_KINDS = CLAUDE_ATTACHMENT_KINDS;
|
|
19736
19310
|
exports.DEFAULT_GEMINI_MODEL = DEFAULT_GEMINI_MODEL;
|
|
19737
|
-
exports.
|
|
19311
|
+
exports.EditReportThrottle = EditReportThrottle;
|
|
19738
19312
|
exports.GEMINI_API_KEY_ENV = GEMINI_API_KEY_ENV;
|
|
19739
19313
|
exports.GEMINI_MODEL_ENV = GEMINI_MODEL_ENV;
|
|
19740
19314
|
exports.GOOGLE_API_KEY_ENV = GOOGLE_API_KEY_ENV;
|
|
19741
|
-
exports.MAX_READ_LIMIT = MAX_READ_LIMIT;
|
|
19742
19315
|
exports.MessageBuffer = MessageBuffer;
|
|
19743
19316
|
exports.MessageQueue2 = MessageQueue2;
|
|
19744
19317
|
exports.PI_TEAMS_EXTENSION = PI_TEAMS_EXTENSION;
|
|
@@ -19746,41 +19319,29 @@ exports.SPAWN_AGENTS = SPAWN_AGENTS;
|
|
|
19746
19319
|
exports.StreamRelay = StreamRelay;
|
|
19747
19320
|
exports.TEAM_SKILL = TEAM_SKILL;
|
|
19748
19321
|
exports.TitleGenerator = TitleGenerator;
|
|
19749
|
-
exports.TurnWaitTimeoutError = TurnWaitTimeoutError;
|
|
19750
19322
|
exports.appendStagedAttachmentsToPrompt = appendStagedAttachmentsToPrompt;
|
|
19751
|
-
exports.archiveSession = archiveSession;
|
|
19752
19323
|
exports.checkPreviewPath = checkPreviewPath;
|
|
19753
19324
|
exports.createSessionMetadata = createSessionMetadata;
|
|
19754
|
-
exports.deliverToSession = deliverToSession;
|
|
19755
19325
|
exports.describeAction = describeAction;
|
|
19756
19326
|
exports.describeTrigger = describeTrigger;
|
|
19327
|
+
exports.extractAcpEditPaths = extractAcpEditPaths;
|
|
19757
19328
|
exports.hashObject = hashObject;
|
|
19758
19329
|
exports.initialMachineMetadata = initialMachineMetadata;
|
|
19759
19330
|
exports.isValidSpawnOrigin = isValidSpawnOrigin;
|
|
19760
|
-
exports.listAccountSessions = listAccountSessions;
|
|
19761
|
-
exports.listSessions = listSessions;
|
|
19762
19331
|
exports.normalizeAcpPermissionMode = normalizeAcpPermissionMode;
|
|
19763
19332
|
exports.parseDuration = parseDuration;
|
|
19764
19333
|
exports.query = query;
|
|
19765
|
-
exports.readSessionMetadata = readSessionMetadata;
|
|
19766
|
-
exports.readSessionTranscript = readSessionTranscript;
|
|
19767
19334
|
exports.registerAgentAttachmentDownloads = registerAgentAttachmentDownloads;
|
|
19768
19335
|
exports.registerAssistantSessionTools = registerAssistantSessionTools;
|
|
19769
19336
|
exports.registerKillSessionHandler = registerKillSessionHandler;
|
|
19770
19337
|
exports.removeSessionModeFile = removeSessionModeFile;
|
|
19771
19338
|
exports.resolveFirstMessageMeta = resolveFirstMessageMeta;
|
|
19772
|
-
exports.resolvePermissionRequest = resolvePermissionRequest;
|
|
19773
19339
|
exports.resolveSpawnPermissionMode = resolveSpawnPermissionMode;
|
|
19774
19340
|
exports.sanitizeSpawnPermissionMode = sanitizeSpawnPermissionMode;
|
|
19775
|
-
exports.sendUserMessage = sendUserMessage;
|
|
19776
|
-
exports.sessionWebUrl = sessionWebUrl;
|
|
19777
19341
|
exports.setupOfflineReconnection = setupOfflineReconnection;
|
|
19778
19342
|
exports.stageClaudeAttachments = stageClaudeAttachments;
|
|
19779
19343
|
exports.startHappyServer = startHappyServer;
|
|
19780
|
-
exports.stopSession = stopSession;
|
|
19781
19344
|
exports.suggestTitleForPrompt = suggestTitleForPrompt;
|
|
19782
19345
|
exports.tmuxSupportsSessionEnv = tmuxSupportsSessionEnv;
|
|
19783
19346
|
exports.triggerFromFlags = triggerFromFlags;
|
|
19784
|
-
exports.waitForSessionKey = waitForSessionKey;
|
|
19785
|
-
exports.waitForTurnEnd = waitForTurnEnd;
|
|
19786
19347
|
exports.writeSessionModeFile = writeSessionModeFile;
|