very-happy-cli 0.2.153 → 0.2.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AcpBackend-YDGv4E-g.mjs → AcpBackend-DVB0nzgR.mjs} +2 -2
- package/dist/{AcpBackend-DSt8nTYm.cjs → AcpBackend-DmFcRh54.cjs} +3 -3
- package/dist/{AcpSessionManager-uov6I9C8.cjs → AcpSessionManager-CWaSYTnT.cjs} +1 -1
- package/dist/{AcpSessionManager-Cxf5HQX1.mjs → AcpSessionManager-dGDgNb-p.mjs} +1 -1
- package/dist/agentGuidance-BWxFC6UL.mjs +1613 -0
- package/dist/agentGuidance-un7YPryB.cjs +1665 -0
- package/dist/{agentHome-D68Xh2ae.mjs → agentHome-ByLb2HFg.mjs} +5 -5
- package/dist/{agentHome-BJ7bde35.cjs → agentHome-aucT8BHw.cjs} +5 -5
- package/dist/{auto-CcXxfqkC.mjs → auto-BQnD6gVx.mjs} +21 -21
- package/dist/{auto-C-90ojIH.cjs → auto-P9RoJaY0.cjs} +24 -24
- package/dist/codex/happyMcpStdioBridge.cjs +6 -3
- package/dist/codex/happyMcpStdioBridge.mjs +6 -3
- package/dist/{config-CHJ189UJ.mjs → config-BPZ2Y6o8.mjs} +8 -8
- package/dist/{config-D23kF0_u.cjs → config-BoePh9PX.cjs} +8 -8
- package/dist/{index-D49WvbDT.cjs → index--KCpVs1c.cjs} +7 -7
- package/dist/{index-BacwoXzs.mjs → index-B4oXnbYw.mjs} +522 -729
- package/dist/{index-BgYZGODP.mjs → index-BjzdXb9x.mjs} +21 -13
- package/dist/{index-DEOVn46R.cjs → index-DRrLaljt.cjs} +21 -13
- package/dist/{index-BXA9fxbK.mjs → index-DVVRqaVG.mjs} +7 -7
- package/dist/{index-DHrwHxHf.cjs → index-D_fYUxVg.cjs} +579 -798
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/dist/{install-BiOXBnnS.cjs → install-CGigiocs.cjs} +1 -1
- package/dist/{install-Ca7_Z3bV.mjs → install-DP5aPNEX.mjs} +1 -1
- package/dist/{installTerminalHooks-BF0kL0ae.cjs → installTerminalHooks-XrcPvfBs.cjs} +5 -5
- package/dist/{installTerminalHooks-Bpl32K75.mjs → installTerminalHooks-tIg7mANc.mjs} +5 -5
- package/dist/lib.cjs +2 -2
- package/dist/lib.mjs +2 -2
- package/dist/{limits-BtAuKRl2.mjs → limits-C_DW0M0q.mjs} +309 -13
- package/dist/{limits-DY_sFA3J.cjs → limits-DkfHJ3nS.cjs} +329 -12
- package/dist/{mcp-JpqI2POW.cjs → mcp-BqlXl10F.cjs} +23 -23
- package/dist/{mcp-DQkmk4VV.mjs → mcp-o7QglAQM.mjs} +17 -17
- package/dist/{piRuntime-DRbbeQ72.cjs → piRuntime-BxP4B_zY.cjs} +2 -2
- package/dist/{piRuntime-DBpe12NN.mjs → piRuntime-MjiJI-nV.mjs} +2 -2
- package/dist/{piTerminal-qdJ9KZVL.cjs → piTerminal-DYzRcwgb.cjs} +6 -6
- package/dist/{piTerminal-BoHvPEZ_.mjs → piTerminal-Dpq6yqKU.mjs} +6 -6
- package/dist/{runGemini-CtOEFvhf.cjs → runGemini-8Pxh94Fm.cjs} +7 -7
- package/dist/{runGemini-YqH7a-7O.mjs → runGemini-CDlcQ-OA.mjs} +6 -6
- package/dist/{runOpenClaw-DoqZXBvL.cjs → runOpenClaw-B5FSZIbK.cjs} +6 -6
- package/dist/{runOpenClaw-DSSyFQBb.mjs → runOpenClaw-YkhsgPs0.mjs} +5 -5
- package/dist/{send-9075kHZP.cjs → send-CwrYXTPr.cjs} +58 -54
- package/dist/{send-Cs-RwqNf.mjs → send-DJStmLBv.mjs} +56 -53
- package/dist/{sessions-iBnYzVUK.mjs → sessions-JaVXlSqj.mjs} +88 -45
- package/dist/{sessions-qULzUzlI.cjs → sessions-MX21NJXd.cjs} +99 -56
- package/dist/{spawn-C6fRkedx.mjs → spawn-BaPnjClT.mjs} +22 -22
- package/dist/{spawn-od9V23cc.cjs → spawn-cIoglKVf.cjs} +30 -30
- package/dist/{teams-CkrWQ4uM.mjs → teams-DXV5Pwwt.mjs} +8 -8
- package/dist/{teams-CrCzU-AV.cjs → teams-Dqk7F3D-.cjs} +8 -8
- package/dist/{todo-aCZmSt27.mjs → todo-2Z1REhfx.mjs} +8 -6
- package/dist/{todo-BCT_ZPuV.cjs → todo-Buuqn9gm.cjs} +8 -6
- package/dist/{types-BvqOVUOW.mjs → types-CkbfdRzq.mjs} +7 -267
- package/dist/{types-CUCU7Vw8.cjs → types-a2xsH2UI.cjs} +19 -295
- package/package.json +7 -7
- package/dist/agentGuidance-BM4kMNyr.cjs +0 -553
- package/dist/agentGuidance-C8unGWp7.mjs +0 -529
|
@@ -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",
|
|
@@ -6284,6 +6231,7 @@ function startDaemonControlServer({
|
|
|
6284
6231
|
getChildren,
|
|
6285
6232
|
stopSession,
|
|
6286
6233
|
spawnSession,
|
|
6234
|
+
resumeSession,
|
|
6287
6235
|
requestShutdown,
|
|
6288
6236
|
onHappySessionWebhook,
|
|
6289
6237
|
onSessionStateEvent,
|
|
@@ -6292,7 +6240,9 @@ function startDaemonControlServer({
|
|
|
6292
6240
|
pushClipboard,
|
|
6293
6241
|
pushFilePreview,
|
|
6294
6242
|
onTerminalHook,
|
|
6295
|
-
setTerminalTitle
|
|
6243
|
+
setTerminalTitle,
|
|
6244
|
+
onSessionEdit,
|
|
6245
|
+
listPeers
|
|
6296
6246
|
}) {
|
|
6297
6247
|
return new Promise((resolve) => {
|
|
6298
6248
|
const app = fastify({
|
|
@@ -6496,6 +6446,48 @@ function startDaemonControlServer({
|
|
|
6496
6446
|
};
|
|
6497
6447
|
}
|
|
6498
6448
|
});
|
|
6449
|
+
typed.post("/resume-session", {
|
|
6450
|
+
schema: {
|
|
6451
|
+
body: z.z.object({
|
|
6452
|
+
sessionId: z.z.string().min(1).max(200),
|
|
6453
|
+
model: z.z.string().optional(),
|
|
6454
|
+
permissionMode: z.z.string().optional()
|
|
6455
|
+
}),
|
|
6456
|
+
response: {
|
|
6457
|
+
200: z.z.object({
|
|
6458
|
+
success: z.z.boolean(),
|
|
6459
|
+
sessionId: z.z.string().optional(),
|
|
6460
|
+
error: z.z.string().optional()
|
|
6461
|
+
}),
|
|
6462
|
+
500: z.z.object({
|
|
6463
|
+
success: z.z.boolean(),
|
|
6464
|
+
error: z.z.string().optional()
|
|
6465
|
+
}),
|
|
6466
|
+
503: z.z.object({
|
|
6467
|
+
success: z.z.boolean(),
|
|
6468
|
+
error: z.z.string().optional()
|
|
6469
|
+
})
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
}, async (request, reply) => {
|
|
6473
|
+
const { sessionId, model, permissionMode } = request.body;
|
|
6474
|
+
if (!resumeSession) {
|
|
6475
|
+
reply.code(503);
|
|
6476
|
+
return { success: false, error: "daemon is still starting up" };
|
|
6477
|
+
}
|
|
6478
|
+
persistence.logger.debug(`[CONTROL SERVER] Resume session request: ${sessionId} (model=${model ?? "unset"}, permissionMode=${permissionMode ?? "unset"})`);
|
|
6479
|
+
const result = await resumeSession(sessionId, { model, permissionMode });
|
|
6480
|
+
switch (result.type) {
|
|
6481
|
+
case "success":
|
|
6482
|
+
return { success: true, sessionId: result.sessionId };
|
|
6483
|
+
case "requestToApproveDirectoryCreation":
|
|
6484
|
+
reply.code(500);
|
|
6485
|
+
return { success: false, error: `Cannot resume: working directory ${result.directory} needs to be created first` };
|
|
6486
|
+
case "error":
|
|
6487
|
+
reply.code(500);
|
|
6488
|
+
return { success: false, error: result.errorMessage };
|
|
6489
|
+
}
|
|
6490
|
+
});
|
|
6499
6491
|
typed.post("/terminal-hook", {
|
|
6500
6492
|
schema: {
|
|
6501
6493
|
body: z.z.any(),
|
|
@@ -6573,6 +6565,45 @@ function startDaemonControlServer({
|
|
|
6573
6565
|
}
|
|
6574
6566
|
return { status: "ok" };
|
|
6575
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() : [] }));
|
|
6576
6607
|
typed.post("/stop", {
|
|
6577
6608
|
schema: {
|
|
6578
6609
|
response: {
|
|
@@ -6608,6 +6639,273 @@ function startDaemonControlServer({
|
|
|
6608
6639
|
});
|
|
6609
6640
|
}
|
|
6610
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
|
+
|
|
6611
6909
|
const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
|
|
6612
6910
|
|
|
6613
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
|
|
@@ -8144,7 +8442,7 @@ function parseJsonObject(text) {
|
|
|
8144
8442
|
return null;
|
|
8145
8443
|
}
|
|
8146
8444
|
}
|
|
8147
|
-
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)) {
|
|
8148
8446
|
const platformArch = `${platform}-${arch}`;
|
|
8149
8447
|
const candidates = [
|
|
8150
8448
|
`@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
|
|
@@ -8527,6 +8825,73 @@ class ClaudeAuthService {
|
|
|
8527
8825
|
}
|
|
8528
8826
|
}
|
|
8529
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
|
+
|
|
8530
8895
|
const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set(["file-history-snapshot", "change", "queue-operation"]);
|
|
8531
8896
|
function parseLines(lines) {
|
|
8532
8897
|
const out = [];
|
|
@@ -8563,12 +8928,15 @@ function createMirrorScanner(opts) {
|
|
|
8563
8928
|
}
|
|
8564
8929
|
tail.offset = 0;
|
|
8565
8930
|
}
|
|
8931
|
+
let replaced = false;
|
|
8566
8932
|
if (tail.offset > size) {
|
|
8567
8933
|
persistence.logger.debug(`[MIRROR SCANNER] ${tail.path}: offset ${tail.offset} > size ${size} \u2014 file replaced, resetting (localId idempotency absorbs replays)`);
|
|
8568
8934
|
tail.offset = 0;
|
|
8935
|
+
replaced = true;
|
|
8569
8936
|
}
|
|
8570
8937
|
if (tail.offset === size) return;
|
|
8571
8938
|
const isFirstBackfillRead = tail.mode === "backfill-tail" && tail.offset === 0;
|
|
8939
|
+
const replay = isFirstBackfillRead || replaced;
|
|
8572
8940
|
const handle = await promises.open(tail.path, "r");
|
|
8573
8941
|
let buf;
|
|
8574
8942
|
try {
|
|
@@ -8588,7 +8956,7 @@ function createMirrorScanner(opts) {
|
|
|
8588
8956
|
if (decision.truncated) opts.events.onBackfillTruncated?.();
|
|
8589
8957
|
tail.mode = "from-eof";
|
|
8590
8958
|
}
|
|
8591
|
-
if (messages.length > 0) opts.events.onMessages(messages);
|
|
8959
|
+
if (messages.length > 0) opts.events.onMessages(messages, { replay });
|
|
8592
8960
|
};
|
|
8593
8961
|
const sync = new index.InvalidateSync(async () => {
|
|
8594
8962
|
for (const tail of files.values()) {
|
|
@@ -8711,8 +9079,17 @@ function createMirrorManager(deps) {
|
|
|
8711
9079
|
binding.client.updateMetadata((current) => ({ ...current, ...patch }));
|
|
8712
9080
|
persistBinding(binding);
|
|
8713
9081
|
};
|
|
8714
|
-
const sendMessages = (binding, messages) => {
|
|
9082
|
+
const sendMessages = (binding, messages, context = { replay: false }) => {
|
|
8715
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
|
+
}
|
|
8716
9093
|
const key = index.mirrorLineKey(message);
|
|
8717
9094
|
binding.client.sendClaudeSessionMessage(message, key ? { localIdFor: (envelopeIndex) => index.mirrorLocalId(key, envelopeIndex) } : void 0);
|
|
8718
9095
|
}
|
|
@@ -8721,7 +9098,7 @@ function createMirrorManager(deps) {
|
|
|
8721
9098
|
const scanner = createMirrorScanner({
|
|
8722
9099
|
backfillLines,
|
|
8723
9100
|
events: {
|
|
8724
|
-
onMessages: (messages) => sendMessages(binding, messages),
|
|
9101
|
+
onMessages: (messages, context) => sendMessages(binding, messages, context),
|
|
8725
9102
|
onBackfillTruncated: opts.withTruncationNotice ? () => binding.client.sendSessionEvent({
|
|
8726
9103
|
type: "message",
|
|
8727
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`
|
|
@@ -8744,6 +9121,10 @@ function createMirrorManager(deps) {
|
|
|
8744
9121
|
await binding.scanner?.cleanup();
|
|
8745
9122
|
binding.scanner = null;
|
|
8746
9123
|
binding.client.closeClaudeSessionTurn("completed");
|
|
9124
|
+
try {
|
|
9125
|
+
deps.onSessionEnded?.(binding.happySessionId);
|
|
9126
|
+
} catch {
|
|
9127
|
+
}
|
|
8747
9128
|
updateBindingMetadata(binding, {
|
|
8748
9129
|
lifecycleState: "archived",
|
|
8749
9130
|
lifecycleStateSince: Date.now(),
|
|
@@ -8956,6 +9337,13 @@ function createMirrorManager(deps) {
|
|
|
8956
9337
|
}
|
|
8957
9338
|
};
|
|
8958
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
|
+
},
|
|
8959
9347
|
handleHookPayload(body) {
|
|
8960
9348
|
const event = index.parseTerminalHookPayload(body);
|
|
8961
9349
|
if (!event) {
|
|
@@ -9422,640 +9810,6 @@ Delegate from this working directory unless a different repository was explicitl
|
|
|
9422
9810
|
${goal}`;
|
|
9423
9811
|
}
|
|
9424
9812
|
|
|
9425
|
-
const MAX_LINE_CHARS = 500;
|
|
9426
|
-
const MAX_ARGS_CHARS = 160;
|
|
9427
|
-
const DEFAULT_LIMITS = { line: MAX_LINE_CHARS, args: MAX_ARGS_CHARS };
|
|
9428
|
-
const NO_LIMITS = { line: Number.POSITIVE_INFINITY, args: Number.POSITIVE_INFINITY };
|
|
9429
|
-
function truncateText(text, max = MAX_LINE_CHARS) {
|
|
9430
|
-
const chars = Array.from(text);
|
|
9431
|
-
if (chars.length <= max) return text;
|
|
9432
|
-
return chars.slice(0, max).join("") + ` \u2026[+${chars.length - max} chars]`;
|
|
9433
|
-
}
|
|
9434
|
-
function squash(text) {
|
|
9435
|
-
return text.replace(/\s+/g, " ").trim();
|
|
9436
|
-
}
|
|
9437
|
-
function safeJson(value, max) {
|
|
9438
|
-
try {
|
|
9439
|
-
return truncateText(JSON.stringify(value) ?? "", max);
|
|
9440
|
-
} catch {
|
|
9441
|
-
return "[unserializable]";
|
|
9442
|
-
}
|
|
9443
|
-
}
|
|
9444
|
-
function formatMessageBody(body, limits = DEFAULT_LIMITS) {
|
|
9445
|
-
if (!body || typeof body !== "object") return null;
|
|
9446
|
-
const b = body;
|
|
9447
|
-
if (b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string") {
|
|
9448
|
-
const text = squash(b.content.text);
|
|
9449
|
-
return text.length > 0 ? `user: ${truncateText(text, limits.line)}` : null;
|
|
9450
|
-
}
|
|
9451
|
-
if (b.role === "session" && b.content && typeof b.content === "object") {
|
|
9452
|
-
const env = b.content;
|
|
9453
|
-
const ev = env.ev;
|
|
9454
|
-
if (!ev || typeof ev.t !== "string") return null;
|
|
9455
|
-
switch (ev.t) {
|
|
9456
|
-
case "text": {
|
|
9457
|
-
if (ev.thinking) return null;
|
|
9458
|
-
const text = squash(String(ev.text ?? ""));
|
|
9459
|
-
if (text.length === 0) return null;
|
|
9460
|
-
const role = env.role === "user" ? "user" : "assistant";
|
|
9461
|
-
return `${role}: ${truncateText(text, limits.line)}`;
|
|
9462
|
-
}
|
|
9463
|
-
case "tool-call-start": {
|
|
9464
|
-
const name = String(ev.name ?? "tool");
|
|
9465
|
-
const title = squash(String(ev.title ?? ""));
|
|
9466
|
-
const args = ev.args ? safeJson(ev.args, limits.args) : "";
|
|
9467
|
-
const detail = title || args;
|
|
9468
|
-
return `[tool] ${name}${detail ? `: ${truncateText(detail, limits.args)}` : ""}`;
|
|
9469
|
-
}
|
|
9470
|
-
case "service": {
|
|
9471
|
-
const text = squash(String(ev.text ?? ""));
|
|
9472
|
-
return text.length > 0 ? `[service] ${truncateText(text, limits.args)}` : null;
|
|
9473
|
-
}
|
|
9474
|
-
case "turn-end": {
|
|
9475
|
-
if (ev.status && ev.status !== "completed") return `[turn ${ev.status}]`;
|
|
9476
|
-
return null;
|
|
9477
|
-
}
|
|
9478
|
-
default:
|
|
9479
|
-
return null;
|
|
9480
|
-
}
|
|
9481
|
-
}
|
|
9482
|
-
if (b.role === "agent" && b.content?.type === "acp" && b.content.data && typeof b.content.data === "object") {
|
|
9483
|
-
const d = b.content.data;
|
|
9484
|
-
switch (d.type) {
|
|
9485
|
-
case "message": {
|
|
9486
|
-
const text = squash(String(d.message ?? ""));
|
|
9487
|
-
return text.length > 0 ? `assistant: ${truncateText(text, limits.line)}` : null;
|
|
9488
|
-
}
|
|
9489
|
-
case "tool-call":
|
|
9490
|
-
return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input, limits.args)}`;
|
|
9491
|
-
case "permission-request":
|
|
9492
|
-
return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")), limits.args)}`;
|
|
9493
|
-
default:
|
|
9494
|
-
return null;
|
|
9495
|
-
}
|
|
9496
|
-
}
|
|
9497
|
-
if (b.role === "agent" && b.content?.type === "output") {
|
|
9498
|
-
const msg = b.content.data?.message;
|
|
9499
|
-
if (msg && Array.isArray(msg.content)) {
|
|
9500
|
-
const texts = msg.content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => squash(c.text)).filter((t) => t.length > 0);
|
|
9501
|
-
if (texts.length > 0) {
|
|
9502
|
-
const role = msg.role === "user" ? "user" : "assistant";
|
|
9503
|
-
return `${role}: ${truncateText(texts.join(" "), limits.line)}`;
|
|
9504
|
-
}
|
|
9505
|
-
}
|
|
9506
|
-
return null;
|
|
9507
|
-
}
|
|
9508
|
-
return null;
|
|
9509
|
-
}
|
|
9510
|
-
function formatTranscript(bodies, options = {}) {
|
|
9511
|
-
const limits = options.full ? NO_LIMITS : DEFAULT_LIMITS;
|
|
9512
|
-
const lines = [];
|
|
9513
|
-
for (const body of bodies) {
|
|
9514
|
-
const line = formatMessageBody(body, limits);
|
|
9515
|
-
if (line !== null) lines.push(line);
|
|
9516
|
-
}
|
|
9517
|
-
return lines.join("\n");
|
|
9518
|
-
}
|
|
9519
|
-
|
|
9520
|
-
function asRecord(value) {
|
|
9521
|
-
return value && typeof value === "object" ? value : null;
|
|
9522
|
-
}
|
|
9523
|
-
function sessionEnvelope(body) {
|
|
9524
|
-
const b = asRecord(body);
|
|
9525
|
-
if (!b || b.role !== "session") return null;
|
|
9526
|
-
const env = asRecord(b.content);
|
|
9527
|
-
if (!env) return null;
|
|
9528
|
-
const ev = asRecord(env.ev);
|
|
9529
|
-
return ev && typeof ev.t === "string" ? { role: env.role, ev } : null;
|
|
9530
|
-
}
|
|
9531
|
-
function isUserPrompt(body) {
|
|
9532
|
-
const b = asRecord(body);
|
|
9533
|
-
return !!b && b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string";
|
|
9534
|
-
}
|
|
9535
|
-
function agentPiece(body) {
|
|
9536
|
-
const env = sessionEnvelope(body);
|
|
9537
|
-
if (env && env.role !== "user") {
|
|
9538
|
-
if (env.ev.t === "text" && !env.ev.thinking) {
|
|
9539
|
-
const text = String(env.ev.text ?? "");
|
|
9540
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9541
|
-
}
|
|
9542
|
-
if (env.ev.t === "tool-call-start") return { kind: "tool" };
|
|
9543
|
-
return null;
|
|
9544
|
-
}
|
|
9545
|
-
const b = asRecord(body);
|
|
9546
|
-
if (b?.role === "agent" && b.content?.type === "acp") {
|
|
9547
|
-
const d = asRecord(b.content.data);
|
|
9548
|
-
if (d?.type === "message") {
|
|
9549
|
-
const text = String(d.message ?? "");
|
|
9550
|
-
return text.trim().length > 0 ? { kind: "text", text } : null;
|
|
9551
|
-
}
|
|
9552
|
-
if (d?.type === "tool-call") return { kind: "tool" };
|
|
9553
|
-
}
|
|
9554
|
-
return null;
|
|
9555
|
-
}
|
|
9556
|
-
function userPromptText(body) {
|
|
9557
|
-
return isUserPrompt(body) ? body.content.text : null;
|
|
9558
|
-
}
|
|
9559
|
-
function analyzeLatestTurn(entries, options = {}) {
|
|
9560
|
-
const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
|
|
9561
|
-
let userIndex = -1;
|
|
9562
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
9563
|
-
if (isUserPrompt(entries[i].body) && (!options.anchor || options.anchor(entries[i].body))) {
|
|
9564
|
-
userIndex = i;
|
|
9565
|
-
break;
|
|
9566
|
-
}
|
|
9567
|
-
}
|
|
9568
|
-
let ended = false;
|
|
9569
|
-
let status = null;
|
|
9570
|
-
let error = null;
|
|
9571
|
-
let texts = [];
|
|
9572
|
-
let lastMarker = null;
|
|
9573
|
-
let startedAfterPrompt = false;
|
|
9574
|
-
const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
|
|
9575
|
-
for (let i = userIndex + 1; i < entries.length; i++) {
|
|
9576
|
-
const body = entries[i].body;
|
|
9577
|
-
if (options.anchor && isUserPrompt(body)) {
|
|
9578
|
-
if (ended) break;
|
|
9579
|
-
continue;
|
|
9580
|
-
}
|
|
9581
|
-
const env = sessionEnvelope(body);
|
|
9582
|
-
if (env?.ev?.t === "turn-start") {
|
|
9583
|
-
lastMarker = "start";
|
|
9584
|
-
startedAfterPrompt = true;
|
|
9585
|
-
ended = false;
|
|
9586
|
-
status = null;
|
|
9587
|
-
error = null;
|
|
9588
|
-
texts = [];
|
|
9589
|
-
continue;
|
|
9590
|
-
}
|
|
9591
|
-
if (env?.ev?.t === "turn-end") {
|
|
9592
|
-
lastMarker = "end";
|
|
9593
|
-
if (userIndex >= 0 && emitsStarts && !startedAfterPrompt) continue;
|
|
9594
|
-
ended = true;
|
|
9595
|
-
status = typeof env.ev.status === "string" ? env.ev.status : null;
|
|
9596
|
-
error = typeof env.ev.error === "string" ? env.ev.error : null;
|
|
9597
|
-
continue;
|
|
9598
|
-
}
|
|
9599
|
-
const piece = agentPiece(body);
|
|
9600
|
-
if (!piece) continue;
|
|
9601
|
-
if (piece.kind === "tool") texts = [];
|
|
9602
|
-
else texts.push(piece.text);
|
|
9603
|
-
}
|
|
9604
|
-
if (userIndex < 0) ended = lastMarker === "end";
|
|
9605
|
-
return {
|
|
9606
|
-
userSeq: userIndex >= 0 ? entries[userIndex].seq : null,
|
|
9607
|
-
ended,
|
|
9608
|
-
status: ended ? status : null,
|
|
9609
|
-
error: ended ? error : null,
|
|
9610
|
-
answer: texts.join("\n\n").trim(),
|
|
9611
|
-
lastSeq
|
|
9612
|
-
};
|
|
9613
|
-
}
|
|
9614
|
-
|
|
9615
|
-
const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
|
|
9616
|
-
async function openUserScopedSocket(token, options = {}) {
|
|
9617
|
-
const socket = socket_ioClient.io(persistence.configuration.serverUrl, {
|
|
9618
|
-
auth: {
|
|
9619
|
-
token,
|
|
9620
|
-
clientType: "user-scoped",
|
|
9621
|
-
happyClient: `cli-session-ops/${persistence.configuration.currentCliVersion}`
|
|
9622
|
-
},
|
|
9623
|
-
path: "/v1/updates",
|
|
9624
|
-
transports: ["websocket"],
|
|
9625
|
-
reconnection: false,
|
|
9626
|
-
autoConnect: false
|
|
9627
|
-
});
|
|
9628
|
-
await new Promise((resolve, reject) => {
|
|
9629
|
-
const timer = setTimeout(() => {
|
|
9630
|
-
socket.close();
|
|
9631
|
-
reject(new Error(`Timed out connecting to ${persistence.configuration.serverUrl}`));
|
|
9632
|
-
}, options.connectTimeoutMs ?? USER_SOCKET_CONNECT_TIMEOUT_MS);
|
|
9633
|
-
socket.once("connect", () => {
|
|
9634
|
-
clearTimeout(timer);
|
|
9635
|
-
resolve();
|
|
9636
|
-
});
|
|
9637
|
-
socket.once("connect_error", (error) => {
|
|
9638
|
-
clearTimeout(timer);
|
|
9639
|
-
socket.close();
|
|
9640
|
-
reject(new Error(`Could not connect to ${persistence.configuration.serverUrl}: ${error.message}`));
|
|
9641
|
-
});
|
|
9642
|
-
socket.connect();
|
|
9643
|
-
});
|
|
9644
|
-
return {
|
|
9645
|
-
async rpcCall(payload, timeoutMs) {
|
|
9646
|
-
const ack = await socket.timeout(timeoutMs).emitWithAck("rpc-call", payload);
|
|
9647
|
-
return ack;
|
|
9648
|
-
},
|
|
9649
|
-
close() {
|
|
9650
|
-
socket.close();
|
|
9651
|
-
}
|
|
9652
|
-
};
|
|
9653
|
-
}
|
|
9654
|
-
|
|
9655
|
-
const RPC_RATE_LIMIT_ERROR = "RPC rate limit reached";
|
|
9656
|
-
const RPC_ACCOUNT_RATE_LIMIT_ERROR = "RPC account rate limit reached";
|
|
9657
|
-
const RPC_RATE_RETRY_MIN_DELAY_MS = 2e3;
|
|
9658
|
-
const RPC_RATE_RETRY_MAX_DELAY_MS = 6e4;
|
|
9659
|
-
const RPC_RATE_MAX_RETRIES = 2;
|
|
9660
|
-
function isRpcRateLimitAck(ack) {
|
|
9661
|
-
if (!ack || typeof ack !== "object") return false;
|
|
9662
|
-
const a = ack;
|
|
9663
|
-
if (a.ok !== false) return false;
|
|
9664
|
-
if (a.code === "rpc_rate_limited" || a.code === "rpc_account_rate_limited") return true;
|
|
9665
|
-
return a.error === RPC_RATE_LIMIT_ERROR || a.error === RPC_ACCOUNT_RATE_LIMIT_ERROR;
|
|
9666
|
-
}
|
|
9667
|
-
function rpcRateRetryDelayMs(attempt, retryAfterMs, random = Math.random()) {
|
|
9668
|
-
if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
|
|
9669
|
-
return Math.min(RPC_RATE_RETRY_MAX_DELAY_MS, Math.round(retryAfterMs * (1 + random * 0.5)));
|
|
9670
|
-
}
|
|
9671
|
-
const exponent = Math.max(0, Math.min(attempt, 32) - 1);
|
|
9672
|
-
const ceiling = Math.min(RPC_RATE_RETRY_MIN_DELAY_MS * 2 ** exponent, RPC_RATE_RETRY_MAX_DELAY_MS);
|
|
9673
|
-
return Math.round(RPC_RATE_RETRY_MIN_DELAY_MS + random * (ceiling - RPC_RATE_RETRY_MIN_DELAY_MS));
|
|
9674
|
-
}
|
|
9675
|
-
|
|
9676
|
-
const PERMISSION_OPS_CLIENT = "session-ops";
|
|
9677
|
-
const PERMISSION_RPC_TIMEOUT_MS = 3e4;
|
|
9678
|
-
const SETTLE_TIMEOUT_MS = 5e3;
|
|
9679
|
-
const SETTLE_POLL_MS = 500;
|
|
9680
|
-
function buildPermissionRpcPayload(requestId, verdict) {
|
|
9681
|
-
if (verdict.kind === "approve") {
|
|
9682
|
-
return {
|
|
9683
|
-
id: requestId,
|
|
9684
|
-
approved: true,
|
|
9685
|
-
decision: verdict.forSession ? "approved_for_session" : "approved"
|
|
9686
|
-
};
|
|
9687
|
-
}
|
|
9688
|
-
return {
|
|
9689
|
-
id: requestId,
|
|
9690
|
-
approved: false,
|
|
9691
|
-
decision: "denied",
|
|
9692
|
-
...verdict.reason ? { reason: verdict.reason } : {}
|
|
9693
|
-
};
|
|
9694
|
-
}
|
|
9695
|
-
function interpretPermissionAck(ack, decryptResult) {
|
|
9696
|
-
if (!ack.ok) {
|
|
9697
|
-
const message = ack.error ?? "RPC call failed";
|
|
9698
|
-
if (message === "RPC method not available") {
|
|
9699
|
-
return { status: "offline", message: "no running wrapper has registered the permission RPC for this session" };
|
|
9700
|
-
}
|
|
9701
|
-
if (message === "RPC target disconnected") {
|
|
9702
|
-
return { status: "offline", message: "the wrapper disconnected while the permission RPC was in flight" };
|
|
9703
|
-
}
|
|
9704
|
-
if (/timed? ?out/i.test(message)) {
|
|
9705
|
-
return { status: "timeout", message };
|
|
9706
|
-
}
|
|
9707
|
-
if (isRpcRateLimitAck(ack)) {
|
|
9708
|
-
return { status: "rate-limited", message, retryAfterMs: rpcRateRetryDelayMs(1, ack.retryAfterMs, 0) };
|
|
9709
|
-
}
|
|
9710
|
-
return { status: "rejected", message };
|
|
9711
|
-
}
|
|
9712
|
-
let body = null;
|
|
9713
|
-
if (typeof ack.result === "string" && ack.result.length > 0) {
|
|
9714
|
-
try {
|
|
9715
|
-
body = decryptResult(ack.result);
|
|
9716
|
-
} catch {
|
|
9717
|
-
body = null;
|
|
9718
|
-
}
|
|
9719
|
-
}
|
|
9720
|
-
if (body && typeof body === "object" && typeof body.error === "string") {
|
|
9721
|
-
return { status: "handler-error", message: body.error };
|
|
9722
|
-
}
|
|
9723
|
-
return { status: "acknowledged" };
|
|
9724
|
-
}
|
|
9725
|
-
function pendingRequestsOf(agentState, now) {
|
|
9726
|
-
const requests = agentState?.requests;
|
|
9727
|
-
if (!requests || typeof requests !== "object") return [];
|
|
9728
|
-
return Object.entries(requests).map(([id, request]) => ({
|
|
9729
|
-
id,
|
|
9730
|
-
tool: typeof request?.tool === "string" ? request.tool : "unknown",
|
|
9731
|
-
...request?.kind ? { kind: request.kind } : {},
|
|
9732
|
-
...typeof request?.createdAt === "number" ? { createdAt: request.createdAt, waitingMs: Math.max(0, now - request.createdAt) } : {}
|
|
9733
|
-
})).sort((a, b) => (a.createdAt ?? Number.MAX_SAFE_INTEGER) - (b.createdAt ?? Number.MAX_SAFE_INTEGER));
|
|
9734
|
-
}
|
|
9735
|
-
const RPC_DEADLINE_GRACE_MS = 2e3;
|
|
9736
|
-
function withDeadline(promise, ms, message) {
|
|
9737
|
-
return new Promise((resolve, reject) => {
|
|
9738
|
-
const timer = setTimeout(() => reject(new Error(`${message} (timed out)`)), ms);
|
|
9739
|
-
promise.then(
|
|
9740
|
-
(value) => {
|
|
9741
|
-
clearTimeout(timer);
|
|
9742
|
-
resolve(value);
|
|
9743
|
-
},
|
|
9744
|
-
(error) => {
|
|
9745
|
-
clearTimeout(timer);
|
|
9746
|
-
reject(error);
|
|
9747
|
-
}
|
|
9748
|
-
);
|
|
9749
|
-
});
|
|
9750
|
-
}
|
|
9751
|
-
async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}) {
|
|
9752
|
-
const now = deps.now ?? Date.now;
|
|
9753
|
-
const persisted = (deps.readPersisted ?? persistence.readPersistedSessions)()[sessionId];
|
|
9754
|
-
if (!persisted) {
|
|
9755
|
-
throw new Error(
|
|
9756
|
-
`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.`
|
|
9757
|
-
);
|
|
9758
|
-
}
|
|
9759
|
-
const token = await (deps.bearerToken ?? bearerToken$1)();
|
|
9760
|
-
const fetchState = deps.fetchAgentState ?? fetchDecryptedAgentState;
|
|
9761
|
-
const before = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9762
|
-
if (!before.some((request) => request.id === requestId)) {
|
|
9763
|
-
const hint = before.length > 0 ? `Pending request ids: ${before.map((request) => `${request.id} (${request.tool})`).join(", ")}` : "The session has no pending requests.";
|
|
9764
|
-
throw new Error(`Request ${requestId} is not pending on session ${sessionId}. ${hint}`);
|
|
9765
|
-
}
|
|
9766
|
-
const payload = buildPermissionRpcPayload(requestId, verdict);
|
|
9767
|
-
const key = persistence.decodeBase64(persisted.encryptionKey);
|
|
9768
|
-
const params = persistence.encodeBase64(persistence.encrypt(key, persisted.encryptionVariant, payload));
|
|
9769
|
-
const transport = await (deps.openTransport ?? openUserScopedSocket)(token);
|
|
9770
|
-
const rpcTimeoutMs = deps.rpcTimeoutMs ?? PERMISSION_RPC_TIMEOUT_MS;
|
|
9771
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
9772
|
-
let ack;
|
|
9773
|
-
try {
|
|
9774
|
-
for (let attempt = 1; ; attempt++) {
|
|
9775
|
-
try {
|
|
9776
|
-
ack = await withDeadline(
|
|
9777
|
-
transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
|
|
9778
|
-
rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
|
|
9779
|
-
`permission RPC did not settle within ${rpcTimeoutMs}ms`
|
|
9780
|
-
);
|
|
9781
|
-
} catch (error) {
|
|
9782
|
-
ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
|
|
9783
|
-
}
|
|
9784
|
-
if (!isRpcRateLimitAck(ack) || attempt > RPC_RATE_MAX_RETRIES) break;
|
|
9785
|
-
const waitMs = rpcRateRetryDelayMs(attempt, ack.retryAfterMs, (deps.random ?? Math.random)());
|
|
9786
|
-
deps.onRateLimited?.({ attempt, waitMs, serverError: ack.error ?? "RPC rate limit reached" });
|
|
9787
|
-
await sleep(waitMs);
|
|
9788
|
-
}
|
|
9789
|
-
} finally {
|
|
9790
|
-
transport.close();
|
|
9791
|
-
}
|
|
9792
|
-
const outcome = interpretPermissionAck(ack, (encrypted) => persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(encrypted)));
|
|
9793
|
-
if (outcome.status !== "acknowledged") {
|
|
9794
|
-
return { sessionId, requestId, payload, outcome };
|
|
9795
|
-
}
|
|
9796
|
-
const deadline = now() + (deps.settleTimeoutMs ?? SETTLE_TIMEOUT_MS);
|
|
9797
|
-
let settled = false;
|
|
9798
|
-
for (; ; ) {
|
|
9799
|
-
const pending = pendingRequestsOf(await fetchState(sessionId, persisted, token), now());
|
|
9800
|
-
if (!pending.some((request) => request.id === requestId)) {
|
|
9801
|
-
settled = true;
|
|
9802
|
-
break;
|
|
9803
|
-
}
|
|
9804
|
-
if (now() >= deadline) break;
|
|
9805
|
-
await sleep(SETTLE_POLL_MS);
|
|
9806
|
-
}
|
|
9807
|
-
return { sessionId, requestId, payload, outcome, settled };
|
|
9808
|
-
}
|
|
9809
|
-
async function bearerToken$1() {
|
|
9810
|
-
const credentials = await persistence.readCredentialsForConfiguredRelay();
|
|
9811
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9812
|
-
return credentials.token;
|
|
9813
|
-
}
|
|
9814
|
-
async function fetchDecryptedAgentState(sessionId, persisted, token) {
|
|
9815
|
-
const response = await axios.get(
|
|
9816
|
-
`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
|
|
9817
|
-
{
|
|
9818
|
-
headers: {
|
|
9819
|
-
"Authorization": `Bearer ${token}`,
|
|
9820
|
-
"X-Happy-Client": `${PERMISSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
9821
|
-
},
|
|
9822
|
-
timeout: 15e3,
|
|
9823
|
-
// 404 is a real answer ("not yours / gone"), not a transport fault.
|
|
9824
|
-
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
9825
|
-
}
|
|
9826
|
-
);
|
|
9827
|
-
if (response.status === 404) {
|
|
9828
|
-
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).`);
|
|
9829
|
-
}
|
|
9830
|
-
const raw = response.data?.session?.agentState;
|
|
9831
|
-
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
9832
|
-
return persistence.decrypt(persistence.decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, persistence.decodeBase64(raw));
|
|
9833
|
-
}
|
|
9834
|
-
|
|
9835
|
-
const SESSION_OPS_CLIENT = "session-ops";
|
|
9836
|
-
const DEFAULT_RECENT_LIMIT = 15;
|
|
9837
|
-
const MAX_READ_LIMIT = 100;
|
|
9838
|
-
function mergeSessionSummaries(live, persisted, options = {}) {
|
|
9839
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9840
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9841
|
-
const summaries = [];
|
|
9842
|
-
for (const child of live) {
|
|
9843
|
-
const id = typeof child.happySessionId === "string" ? child.happySessionId : void 0;
|
|
9844
|
-
if (!id || seen.has(id)) continue;
|
|
9845
|
-
seen.add(id);
|
|
9846
|
-
summaries.push(toSummary(id, persisted[id], livenessOf(child)));
|
|
9847
|
-
}
|
|
9848
|
-
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);
|
|
9849
|
-
for (const [id, entry] of rest) {
|
|
9850
|
-
summaries.push(toSummary(id, entry, { live: false }));
|
|
9851
|
-
}
|
|
9852
|
-
if (!options.tag) return summaries;
|
|
9853
|
-
const wanted = options.tag;
|
|
9854
|
-
return summaries.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9855
|
-
}
|
|
9856
|
-
function livenessOf(child) {
|
|
9857
|
-
return { live: true, pid: typeof child.pid === "number" ? child.pid : void 0 };
|
|
9858
|
-
}
|
|
9859
|
-
function sessionLiveness(live, sessionId) {
|
|
9860
|
-
const child = live.find((entry) => entry.happySessionId === sessionId);
|
|
9861
|
-
return child ? livenessOf(child) : { live: false };
|
|
9862
|
-
}
|
|
9863
|
-
function toSummary(id, persisted, extra) {
|
|
9864
|
-
const meta = persisted?.metadata;
|
|
9865
|
-
return {
|
|
9866
|
-
id,
|
|
9867
|
-
live: extra.live,
|
|
9868
|
-
...extra.pid !== void 0 ? { pid: extra.pid } : {},
|
|
9869
|
-
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
9870
|
-
...meta?.path ? { cwd: meta.path } : {},
|
|
9871
|
-
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
9872
|
-
...meta?.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9873
|
-
...meta?.variant ? { variant: meta.variant } : {},
|
|
9874
|
-
url: sessionWebUrl(id),
|
|
9875
|
-
...persisted?.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {}
|
|
9876
|
-
};
|
|
9877
|
-
}
|
|
9878
|
-
async function listSessions(options = {}) {
|
|
9879
|
-
const live = await index.listDaemonSessions();
|
|
9880
|
-
return mergeSessionSummaries(live, persistence.readPersistedSessions(), options);
|
|
9881
|
-
}
|
|
9882
|
-
function summarizeAccountSession(row, persisted, liveIds, now) {
|
|
9883
|
-
const base = {
|
|
9884
|
-
id: row.id,
|
|
9885
|
-
live: liveIds.has(row.id),
|
|
9886
|
-
url: sessionWebUrl(row.id),
|
|
9887
|
-
decryptable: false,
|
|
9888
|
-
active: row.active === true,
|
|
9889
|
-
archived: typeof row.archivedAt === "number",
|
|
9890
|
-
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
9891
|
-
...typeof row.updatedAt === "number" ? { updatedAt: row.updatedAt } : {},
|
|
9892
|
-
attention: false
|
|
9893
|
-
};
|
|
9894
|
-
if (!persisted) return base;
|
|
9895
|
-
const key = persistence.decodeBase64(persisted.encryptionKey);
|
|
9896
|
-
let metadata = null;
|
|
9897
|
-
let agentState = null;
|
|
9898
|
-
try {
|
|
9899
|
-
metadata = row.metadata ? persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(row.metadata)) : null;
|
|
9900
|
-
agentState = row.agentState ? persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(row.agentState)) : null;
|
|
9901
|
-
} catch {
|
|
9902
|
-
return base;
|
|
9903
|
-
}
|
|
9904
|
-
if (!metadata) return base;
|
|
9905
|
-
const meta = metadata;
|
|
9906
|
-
const pending = pendingRequestsOf(agentState, now);
|
|
9907
|
-
return {
|
|
9908
|
-
...base,
|
|
9909
|
-
decryptable: true,
|
|
9910
|
-
...meta.summary?.text ? { title: meta.summary.text } : {},
|
|
9911
|
-
...meta.path ? { cwd: meta.path } : {},
|
|
9912
|
-
...meta.flavor ? { flavor: meta.flavor } : {},
|
|
9913
|
-
...meta.tags?.length ? { tags: [...meta.tags] } : {},
|
|
9914
|
-
...meta.variant ? { variant: meta.variant } : {},
|
|
9915
|
-
...meta.machineId ? { machineId: meta.machineId } : {},
|
|
9916
|
-
...persisted.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
|
|
9917
|
-
pending,
|
|
9918
|
-
attention: pending.length > 0
|
|
9919
|
-
};
|
|
9920
|
-
}
|
|
9921
|
-
function orderAccountSessions(summaries, options = {}) {
|
|
9922
|
-
const oldestWait = (summary) => Math.max(0, ...(summary.pending ?? []).map((request) => request.waitingMs ?? 0));
|
|
9923
|
-
const rank = (summary) => summary.attention ? 0 : summary.live ? 1 : 2;
|
|
9924
|
-
let rows = summaries.filter((summary) => summary.flavor !== "terminal-mirror").slice().sort((a, b) => {
|
|
9925
|
-
const byRank = rank(a) - rank(b);
|
|
9926
|
-
if (byRank !== 0) return byRank;
|
|
9927
|
-
if (a.attention && b.attention) return oldestWait(b) - oldestWait(a);
|
|
9928
|
-
return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
|
|
9929
|
-
});
|
|
9930
|
-
if (options.tag) {
|
|
9931
|
-
const wanted = options.tag;
|
|
9932
|
-
rows = rows.filter((summary) => summary.tags?.includes(wanted) === true);
|
|
9933
|
-
}
|
|
9934
|
-
const recentLimit = Math.max(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
9935
|
-
let idleKept = 0;
|
|
9936
|
-
return rows.filter((summary) => rank(summary) < 2 || idleKept++ < recentLimit);
|
|
9937
|
-
}
|
|
9938
|
-
async function listAccountSessions(options = {}) {
|
|
9939
|
-
const token = await bearerToken();
|
|
9940
|
-
const response = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions`, {
|
|
9941
|
-
headers: {
|
|
9942
|
-
"Authorization": `Bearer ${token}`,
|
|
9943
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
9944
|
-
},
|
|
9945
|
-
timeout: 15e3
|
|
9946
|
-
});
|
|
9947
|
-
const rows = Array.isArray(response.data?.sessions) ? response.data.sessions : [];
|
|
9948
|
-
const persisted = persistence.readPersistedSessions();
|
|
9949
|
-
const live = await index.listDaemonSessions();
|
|
9950
|
-
const liveIds = new Set(live.map((child) => child.happySessionId).filter((id) => typeof id === "string"));
|
|
9951
|
-
const now = Date.now();
|
|
9952
|
-
const summaries = rows.filter((row) => typeof row?.id === "string").map((row) => summarizeAccountSession(row, persisted[row.id], liveIds, now)).filter((summary) => options.includeArchived || !summary.archived);
|
|
9953
|
-
return orderAccountSessions(summaries, options);
|
|
9954
|
-
}
|
|
9955
|
-
async function bearerToken() {
|
|
9956
|
-
const credentials = await persistence.readCredentialsForConfiguredRelay();
|
|
9957
|
-
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
9958
|
-
return credentials.token;
|
|
9959
|
-
}
|
|
9960
|
-
function requirePersisted(sessionId) {
|
|
9961
|
-
const persisted = persistence.readPersistedSessions()[sessionId];
|
|
9962
|
-
if (!persisted) {
|
|
9963
|
-
throw new Error(
|
|
9964
|
-
`No local key for session ${sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`
|
|
9965
|
-
);
|
|
9966
|
-
}
|
|
9967
|
-
return persisted;
|
|
9968
|
-
}
|
|
9969
|
-
async function readSessionLog(sessionId, persisted, limit) {
|
|
9970
|
-
const token = await bearerToken();
|
|
9971
|
-
const response = await axios.get(
|
|
9972
|
-
`${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
9973
|
-
{
|
|
9974
|
-
params: { before_seq: 2147483647, limit },
|
|
9975
|
-
headers: {
|
|
9976
|
-
"Authorization": `Bearer ${token}`,
|
|
9977
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
9978
|
-
},
|
|
9979
|
-
timeout: 15e3
|
|
9980
|
-
}
|
|
9981
|
-
);
|
|
9982
|
-
const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
|
|
9983
|
-
messages.reverse();
|
|
9984
|
-
const key = persistence.decodeBase64(persisted.encryptionKey);
|
|
9985
|
-
return messages.map((message) => {
|
|
9986
|
-
if (message.content?.t !== "encrypted") return { seq: message.seq, body: null };
|
|
9987
|
-
try {
|
|
9988
|
-
return { seq: message.seq, body: persistence.decrypt(key, persisted.encryptionVariant, persistence.decodeBase64(message.content.c)) };
|
|
9989
|
-
} catch {
|
|
9990
|
-
return { seq: message.seq, body: null };
|
|
9991
|
-
}
|
|
9992
|
-
});
|
|
9993
|
-
}
|
|
9994
|
-
const TURN_WINDOW = 500;
|
|
9995
|
-
async function readSessionTranscript(sessionId, limit, options = {}) {
|
|
9996
|
-
const persisted = requirePersisted(sessionId);
|
|
9997
|
-
const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
|
|
9998
|
-
const entries = await readSessionLog(sessionId, persisted, Math.max(bounded, TURN_WINDOW));
|
|
9999
|
-
const shown = entries.slice(-bounded);
|
|
10000
|
-
const live = await index.listDaemonSessions();
|
|
10001
|
-
return {
|
|
10002
|
-
summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
|
|
10003
|
-
messageCount: shown.length,
|
|
10004
|
-
transcript: formatTranscript(shown.map((entry) => entry.body), { full: options.full }),
|
|
10005
|
-
turn: analyzeLatestTurn(entries)
|
|
10006
|
-
};
|
|
10007
|
-
}
|
|
10008
|
-
async function readSessionMetadata(sessionId, persisted) {
|
|
10009
|
-
const token = await bearerToken();
|
|
10010
|
-
const response = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
10011
|
-
headers: {
|
|
10012
|
-
"Authorization": `Bearer ${token}`,
|
|
10013
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
10014
|
-
},
|
|
10015
|
-
timeout: 15e3
|
|
10016
|
-
});
|
|
10017
|
-
const ciphertext = response.data?.session?.metadata;
|
|
10018
|
-
if (typeof ciphertext !== "string" || ciphertext.length === 0) return null;
|
|
10019
|
-
return persistence.decrypt(persistence.decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, persistence.decodeBase64(ciphertext));
|
|
10020
|
-
}
|
|
10021
|
-
class TurnWaitTimeoutError extends Error {
|
|
10022
|
-
constructor(sessionId, timeoutMs, turn) {
|
|
10023
|
-
super(`Session ${sessionId}: the latest turn did not end within ${Math.round(timeoutMs / 1e3)}s`);
|
|
10024
|
-
this.sessionId = sessionId;
|
|
10025
|
-
this.timeoutMs = timeoutMs;
|
|
10026
|
-
this.turn = turn;
|
|
10027
|
-
}
|
|
10028
|
-
}
|
|
10029
|
-
async function waitForTurnEnd(sessionId, options) {
|
|
10030
|
-
const persisted = requirePersisted(sessionId);
|
|
10031
|
-
const pollMs = options.pollMs ?? 3e3;
|
|
10032
|
-
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
10033
|
-
const deadline = Date.now() + options.timeoutMs;
|
|
10034
|
-
while (true) {
|
|
10035
|
-
const turn = analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW));
|
|
10036
|
-
if (turn.ended) return turn;
|
|
10037
|
-
if (Date.now() + pollMs > deadline) throw new TurnWaitTimeoutError(sessionId, options.timeoutMs, turn);
|
|
10038
|
-
await sleep(pollMs);
|
|
10039
|
-
}
|
|
10040
|
-
}
|
|
10041
|
-
async function stopSession(sessionId) {
|
|
10042
|
-
return await index.stopDaemonSession(sessionId);
|
|
10043
|
-
}
|
|
10044
|
-
async function archiveSession(sessionId) {
|
|
10045
|
-
const token = await bearerToken();
|
|
10046
|
-
await axios.post(
|
|
10047
|
-
`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}/archive`,
|
|
10048
|
-
{},
|
|
10049
|
-
{
|
|
10050
|
-
headers: {
|
|
10051
|
-
"Authorization": `Bearer ${token}`,
|
|
10052
|
-
"X-Happy-Client": `${SESSION_OPS_CLIENT}/${persistence.configuration.currentCliVersion}`
|
|
10053
|
-
},
|
|
10054
|
-
timeout: 1e4
|
|
10055
|
-
}
|
|
10056
|
-
);
|
|
10057
|
-
}
|
|
10058
|
-
|
|
10059
9813
|
function receiptPath(home, operationId) {
|
|
10060
9814
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
|
|
10061
9815
|
return path.join(home, "teams", "effects", `${operationId}.json`);
|
|
@@ -10209,8 +9963,8 @@ function createTeamWorker(deps) {
|
|
|
10209
9963
|
await complete(op, receipt);
|
|
10210
9964
|
return;
|
|
10211
9965
|
}
|
|
10212
|
-
const key = await waitForSessionKey(receipt.sessionId, 1e4);
|
|
10213
|
-
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 } : {} });
|
|
10214
9968
|
receipt = { ...receipt, phase: "delivered" };
|
|
10215
9969
|
writeReceipt$1(home, receipt);
|
|
10216
9970
|
}
|
|
@@ -10226,7 +9980,7 @@ function createTeamWorker(deps) {
|
|
|
10226
9980
|
if (!bot || bot.generation !== op.generation || bot.sessionId !== op.sessionId) throw new Error("Worker binding changed; preserve resources for reconciliation");
|
|
10227
9981
|
if (op.sessionId) {
|
|
10228
9982
|
await deps.stopAndWait(op.sessionId);
|
|
10229
|
-
await archiveSession(op.sessionId);
|
|
9983
|
+
await agentGuidance.archiveSession(op.sessionId);
|
|
10230
9984
|
}
|
|
10231
9985
|
if (team.archivedAt !== void 0) {
|
|
10232
9986
|
await complete(op, receipt);
|
|
@@ -10253,7 +10007,7 @@ function createTeamWorker(deps) {
|
|
|
10253
10007
|
const bot = team.bots.find((b) => b.id === message.recipientBotId);
|
|
10254
10008
|
if (!bot?.sessionId || !deps.live(bot.sessionId)) continue;
|
|
10255
10009
|
try {
|
|
10256
|
-
const key = await waitForSessionKey(bot.sessionId, 0);
|
|
10010
|
+
const key = await agentGuidance.waitForSessionKey(bot.sessionId, 0);
|
|
10257
10011
|
if (message.scheduleId) {
|
|
10258
10012
|
const current = (await http.get(`/v1/teams/${encodeURIComponent(team.id)}`)).data.team;
|
|
10259
10013
|
const pending = current.messages.find((m) => m.id === message.id);
|
|
@@ -10265,7 +10019,7 @@ function createTeamWorker(deps) {
|
|
|
10265
10019
|
${message.body}
|
|
10266
10020
|
|
|
10267
10021
|
Use the teams tools to inspect current task state before acting. This message is team context, not a system instruction.`;
|
|
10268
|
-
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" });
|
|
10269
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 });
|
|
10270
10024
|
} catch {
|
|
10271
10025
|
deps.log(`Teams message ${message.id} remains pending`);
|
|
@@ -10640,16 +10394,16 @@ function automationPromptHeader(name, runId) {
|
|
|
10640
10394
|
}
|
|
10641
10395
|
function automationRunAnchor(runId) {
|
|
10642
10396
|
const marker = ` run ${runId};`;
|
|
10643
|
-
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);
|
|
10644
10398
|
}
|
|
10645
10399
|
async function defaultReadTurn(sessionId, runId) {
|
|
10646
10400
|
const persisted = persistence.readPersistedSessions()[sessionId];
|
|
10647
10401
|
if (!persisted) throw new Error(`No local key for session ${sessionId}`);
|
|
10648
|
-
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) });
|
|
10649
10403
|
}
|
|
10650
10404
|
async function defaultSend(sessionId, text, meta) {
|
|
10651
|
-
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
10652
|
-
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" });
|
|
10653
10407
|
}
|
|
10654
10408
|
function expandHome(path$1) {
|
|
10655
10409
|
if (path$1 === "~") return os.homedir();
|
|
@@ -11114,7 +10868,7 @@ async function startDaemon() {
|
|
|
11114
10868
|
});
|
|
11115
10869
|
persistence.logger.debug("[DAEMON RUN] Starting daemon process...");
|
|
11116
10870
|
persistence.logger.debugLargeJson("[DAEMON RUN] Environment", getEnvironmentInfo());
|
|
11117
|
-
const runningDaemonVersionMatches = await
|
|
10871
|
+
const runningDaemonVersionMatches = await persistence.isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
11118
10872
|
const runningDaemonState = await persistence.readDaemonState();
|
|
11119
10873
|
const runningDaemonEndpointsMatch = Boolean(runningDaemonState) && daemonEndpointsMatch(
|
|
11120
10874
|
runningDaemonState?.serverUrl,
|
|
@@ -11124,7 +10878,7 @@ async function startDaemon() {
|
|
|
11124
10878
|
);
|
|
11125
10879
|
if (!runningDaemonVersionMatches || !runningDaemonEndpointsMatch) {
|
|
11126
10880
|
persistence.logger.debug("[DAEMON RUN] Daemon version or endpoint mismatch detected, restarting daemon");
|
|
11127
|
-
await
|
|
10881
|
+
await persistence.stopDaemon();
|
|
11128
10882
|
} else {
|
|
11129
10883
|
persistence.logger.debug("[DAEMON RUN] Daemon version matches, keeping existing daemon");
|
|
11130
10884
|
console.log("Daemon already running with matching version");
|
|
@@ -11144,6 +10898,7 @@ async function startDaemon() {
|
|
|
11144
10898
|
persistence.logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
11145
10899
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
11146
10900
|
const turnActivity = new index.TurnActivityTracker();
|
|
10901
|
+
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
11147
10902
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
11148
10903
|
let teamWorker;
|
|
11149
10904
|
let automationRunner;
|
|
@@ -12003,6 +11758,7 @@ async function startDaemon() {
|
|
|
12003
11758
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
12004
11759
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
12005
11760
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11761
|
+
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
12006
11762
|
if (session?.happySessionId && session.encryption) {
|
|
12007
11763
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
12008
11764
|
persistence.logger.debug(`[DAEMON RUN] Process PID ${pid} exited, preserved session ${session.happySessionId} for resume`);
|
|
@@ -12061,7 +11817,7 @@ async function startDaemon() {
|
|
|
12061
11817
|
sessionId
|
|
12062
11818
|
);
|
|
12063
11819
|
assistantReportLastSentAt.set(sessionId, Date.now());
|
|
12064
|
-
await sendUserMessage(
|
|
11820
|
+
await agentGuidance.sendUserMessage(
|
|
12065
11821
|
decision.assistantSessionId,
|
|
12066
11822
|
assistantEntry,
|
|
12067
11823
|
formatAssistantReportMessage(title, sessionId, event),
|
|
@@ -12081,11 +11837,17 @@ async function startDaemon() {
|
|
|
12081
11837
|
getChildren: getCurrentChildren,
|
|
12082
11838
|
stopSession,
|
|
12083
11839
|
spawnSession,
|
|
11840
|
+
resumeSession,
|
|
11841
|
+
// B-501: `very-happy send --resume`
|
|
12084
11842
|
requestShutdown: () => requestShutdown("happy-cli"),
|
|
12085
11843
|
onHappySessionWebhook,
|
|
12086
11844
|
onSessionStateEvent,
|
|
12087
11845
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
12088
11846
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
11847
|
+
onSessionEdit: (edit) => {
|
|
11848
|
+
peerCoordinator.onEdit(edit);
|
|
11849
|
+
},
|
|
11850
|
+
listPeers: () => peerCoordinator.list(),
|
|
12089
11851
|
pushClipboard: (text, terminalId) => {
|
|
12090
11852
|
if (!apiMachineRef) {
|
|
12091
11853
|
return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
|
|
@@ -12151,9 +11913,12 @@ async function startDaemon() {
|
|
|
12151
11913
|
const mirrorManager = createMirrorManager({
|
|
12152
11914
|
api,
|
|
12153
11915
|
machineId,
|
|
12154
|
-
onBindingsChanged: () => apiMachine.requestTerminalListRefresh()
|
|
11916
|
+
onBindingsChanged: () => apiMachine.requestTerminalListRefresh(),
|
|
11917
|
+
onEdit: (edit) => peerCoordinator.onEdit(edit),
|
|
11918
|
+
onSessionEnded: (sessionId) => peerCoordinator.forget(sessionId)
|
|
12155
11919
|
});
|
|
12156
11920
|
mirrorManagerRef = mirrorManager;
|
|
11921
|
+
peerCoordinator.setMirrorSource(() => mirrorManager.listActiveMirrors());
|
|
12157
11922
|
apiMachine.setMirrorIntegration({
|
|
12158
11923
|
resolveMirrorSessionId: (terminalId) => mirrorManager.resolveMirrorSessionId(terminalId),
|
|
12159
11924
|
onTerminalClosed: (terminalId) => mirrorManager.onTerminalClosed(terminalId),
|
|
@@ -12281,7 +12046,7 @@ async function startDaemon() {
|
|
|
12281
12046
|
claudeAuthService.stop();
|
|
12282
12047
|
apiMachine.shutdown();
|
|
12283
12048
|
await stopControlServer();
|
|
12284
|
-
await
|
|
12049
|
+
await persistence.cleanupDaemonState();
|
|
12285
12050
|
await persistence.releaseDaemonLock(daemonLockHandle);
|
|
12286
12051
|
await stopCaffeinate();
|
|
12287
12052
|
try {
|
|
@@ -12363,7 +12128,7 @@ async function startDaemon() {
|
|
|
12363
12128
|
}
|
|
12364
12129
|
apiMachine.shutdown();
|
|
12365
12130
|
await stopControlServer();
|
|
12366
|
-
await
|
|
12131
|
+
await persistence.cleanupDaemonState();
|
|
12367
12132
|
await stopCaffeinate();
|
|
12368
12133
|
await persistence.releaseDaemonLock(daemonLockHandle);
|
|
12369
12134
|
persistence.logger.debug(`[DAEMON RUN] Cleanup completed, exiting process with code ${daemonExitCode(source)}`);
|
|
@@ -12467,8 +12232,8 @@ function createTeamAdoption(sessionId) {
|
|
|
12467
12232
|
|
|
12468
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.`;
|
|
12469
12234
|
if (!intent.guidanceDelivered) {
|
|
12470
|
-
const key = await waitForSessionKey(sessionId, 1e4);
|
|
12471
|
-
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" });
|
|
12472
12237
|
await retainTeam(path, input, connected.team.id, true);
|
|
12473
12238
|
}
|
|
12474
12239
|
operation.result = { status: "connected", teamId: connected.team.id, botId: connected.botId, instructions };
|
|
@@ -12584,12 +12349,14 @@ function fail(text) {
|
|
|
12584
12349
|
function describeSession(id, persisted, extra) {
|
|
12585
12350
|
const meta = persisted?.metadata;
|
|
12586
12351
|
const parts = [id];
|
|
12352
|
+
if (extra?.live !== void 0) parts.push(extra.live ? "[running]" : "[not running]");
|
|
12587
12353
|
if (meta?.summary?.text) parts.push(`title="${meta.summary.text}"`);
|
|
12588
12354
|
if (meta?.path) parts.push(`cwd=${meta.path}`);
|
|
12589
12355
|
if (meta?.flavor) parts.push(`agent=${meta.flavor}`);
|
|
12590
12356
|
if (meta?.tags?.length) parts.push(`tags=${meta.tags.join(",")}`);
|
|
12591
12357
|
if (meta?.variant === "assistant") parts.push("(assistant)");
|
|
12592
|
-
parts.push(
|
|
12358
|
+
if (extra?.pid) parts.push(`pid=${extra.pid}`);
|
|
12359
|
+
parts.push(agentGuidance.sessionWebUrl(id));
|
|
12593
12360
|
return parts.join(" ");
|
|
12594
12361
|
}
|
|
12595
12362
|
function describeSummary(summary) {
|
|
@@ -12614,7 +12381,7 @@ function registerAssistantSessionTools(mcp) {
|
|
|
12614
12381
|
inputSchema: {}
|
|
12615
12382
|
}, async () => {
|
|
12616
12383
|
try {
|
|
12617
|
-
const sessions = await listSessions();
|
|
12384
|
+
const sessions = await agentGuidance.listSessions();
|
|
12618
12385
|
if (sessions.length === 0) return ok("No sessions found on this machine.");
|
|
12619
12386
|
return ok(`${sessions.length} session(s):
|
|
12620
12387
|
${sessions.map(describeSummary).join("\n")}`);
|
|
@@ -12630,9 +12397,9 @@ ${sessions.map(describeSummary).join("\n")}`);
|
|
|
12630
12397
|
limit: z.z.number().optional().describe("How many recent messages to fetch (default 20, max 100)")
|
|
12631
12398
|
}
|
|
12632
12399
|
}, async (args) => {
|
|
12633
|
-
if (!
|
|
12400
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12634
12401
|
try {
|
|
12635
|
-
const result = await readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12402
|
+
const result = await agentGuidance.readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12636
12403
|
const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
|
|
12637
12404
|
return ok(`${describeSummary(result.summary)}
|
|
12638
12405
|
--- last ${result.messageCount} message(s) ---
|
|
@@ -12642,19 +12409,27 @@ ${body}`);
|
|
|
12642
12409
|
}
|
|
12643
12410
|
});
|
|
12644
12411
|
mcp.registerTool("session_send", {
|
|
12645
|
-
description: "Send a user message into an existing session on this machine. Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
|
|
12412
|
+
description: "Send a user message into an existing session on this machine. Only delivers when a live wrapper will read it: an archived / offline session is refused (the server would store the message unread) unless resume:true brings it back on this machine first. Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
|
|
12646
12413
|
title: "Send Message to Session",
|
|
12647
12414
|
inputSchema: {
|
|
12648
12415
|
sessionId: z.z.string().describe("The Happy session id to message"),
|
|
12649
|
-
text: z.z.string().describe("The message text to send")
|
|
12416
|
+
text: z.z.string().describe("The message text to send"),
|
|
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")
|
|
12650
12418
|
}
|
|
12651
12419
|
}, async (args) => {
|
|
12652
|
-
if (!
|
|
12420
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12653
12421
|
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
12654
12422
|
try {
|
|
12655
|
-
const persisted = await waitForSessionKey(args.sessionId, 0);
|
|
12656
|
-
await
|
|
12657
|
-
|
|
12423
|
+
const persisted = await agentGuidance.waitForSessionKey(args.sessionId, 0);
|
|
12424
|
+
const result = await agentGuidance.deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12425
|
+
resume: args.resume === true,
|
|
12426
|
+
resumeHint: "Pass resume:true to bring it back on this machine, or spawn a new session."
|
|
12427
|
+
});
|
|
12428
|
+
if (!result.delivered) {
|
|
12429
|
+
const detail = result.error ?? `session is ${result.status}`;
|
|
12430
|
+
return fail(result.stored ? `Not delivered: ${detail}` : `Session ${args.sessionId} is ${result.status}: ${detail}`);
|
|
12431
|
+
}
|
|
12432
|
+
return ok(`Message delivered${result.resumed ? " (session resumed on this machine)" : ""} to ${describeSession(args.sessionId, persisted, { live: true })}`);
|
|
12658
12433
|
} catch (error) {
|
|
12659
12434
|
return fail(`Failed to send: ${error instanceof Error ? error.message : String(error)}`);
|
|
12660
12435
|
}
|
|
@@ -12671,16 +12446,16 @@ ${body}`);
|
|
|
12671
12446
|
if (!normalized.ok) return fail(normalized.error);
|
|
12672
12447
|
const directory = normalized.directory;
|
|
12673
12448
|
try {
|
|
12674
|
-
const result = await
|
|
12449
|
+
const result = await persistence.spawnDaemonSession(directory, void 0, { spawnedBy: "assistant" });
|
|
12675
12450
|
if (result?.error || !result?.sessionId) {
|
|
12676
12451
|
return fail(`Failed to spawn session: ${result?.error ?? "daemon returned no session id"}`);
|
|
12677
12452
|
}
|
|
12678
12453
|
const sessionId = result.sessionId;
|
|
12679
|
-
const url = sessionWebUrl(sessionId);
|
|
12454
|
+
const url = agentGuidance.sessionWebUrl(sessionId);
|
|
12680
12455
|
if (args.prompt && args.prompt.trim().length > 0) {
|
|
12681
12456
|
try {
|
|
12682
|
-
const persisted = await waitForSessionKey(sessionId, 15e3);
|
|
12683
|
-
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);
|
|
12684
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}`);
|
|
12685
12460
|
} catch (error) {
|
|
12686
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}`);
|
|
@@ -12698,10 +12473,10 @@ ${body}`);
|
|
|
12698
12473
|
sessionId: z.z.string().describe("The Happy session id to stop")
|
|
12699
12474
|
}
|
|
12700
12475
|
}, async (args) => {
|
|
12701
|
-
if (!
|
|
12476
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12702
12477
|
try {
|
|
12703
12478
|
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
12704
|
-
const success = await stopSession(args.sessionId);
|
|
12479
|
+
const success = await agentGuidance.stopSession(args.sessionId);
|
|
12705
12480
|
if (!success) return fail(`Session ${args.sessionId} was not found among the daemon's running sessions.`);
|
|
12706
12481
|
return ok(`Stopped ${describeSession(args.sessionId, persisted)}`);
|
|
12707
12482
|
} catch (error) {
|
|
@@ -12715,9 +12490,9 @@ ${body}`);
|
|
|
12715
12490
|
sessionId: z.z.string().describe("The Happy session id to archive")
|
|
12716
12491
|
}
|
|
12717
12492
|
}, async (args) => {
|
|
12718
|
-
if (!
|
|
12493
|
+
if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12719
12494
|
try {
|
|
12720
|
-
await archiveSession(args.sessionId);
|
|
12495
|
+
await agentGuidance.archiveSession(args.sessionId);
|
|
12721
12496
|
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
12722
12497
|
return ok(`Archived ${describeSession(args.sessionId, persisted)}`);
|
|
12723
12498
|
} catch (error) {
|
|
@@ -12751,7 +12526,7 @@ ${lines.join("\n")}`);
|
|
|
12751
12526
|
lines: z.z.number().optional().describe("How many history lines to read (default 80, max 2000)")
|
|
12752
12527
|
}
|
|
12753
12528
|
}, async (args) => {
|
|
12754
|
-
if (!
|
|
12529
|
+
if (!persistence.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
12755
12530
|
const r = index.readVhTerminal(args.terminalId, args.lines ?? 80);
|
|
12756
12531
|
if (!r.ok) return fail(`Failed to read terminal ${args.terminalId}: ${r.error}`);
|
|
12757
12532
|
return ok(`terminal ${args.terminalId} \u2014 last output:
|
|
@@ -12766,7 +12541,7 @@ ${r.text}`);
|
|
|
12766
12541
|
submit: z.z.boolean().optional().describe("Also press Enter after pasting (default false)")
|
|
12767
12542
|
}
|
|
12768
12543
|
}, async (args) => {
|
|
12769
|
-
if (!
|
|
12544
|
+
if (!persistence.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
12770
12545
|
if (typeof args.text !== "string" || args.text.length === 0) return fail("text must be non-empty");
|
|
12771
12546
|
const submit = args.submit === true;
|
|
12772
12547
|
const r = index.sendToVhTerminal(args.terminalId, args.text, submit);
|
|
@@ -12841,13 +12616,14 @@ function normalizeProgress(value) {
|
|
|
12841
12616
|
return flat.length > PROGRESS_MAX_CHARS$1 ? flat.slice(0, PROGRESS_MAX_CHARS$1) : flat;
|
|
12842
12617
|
}
|
|
12843
12618
|
|
|
12844
|
-
function createMcpServer(handlers, options, sessionId) {
|
|
12619
|
+
function createMcpServer(handlers, options, sessionId, client) {
|
|
12845
12620
|
const mcp = new mcp_js.McpServer({
|
|
12846
12621
|
name: "Very Happy Tools",
|
|
12847
12622
|
version: "1.0.0"
|
|
12848
12623
|
});
|
|
12849
12624
|
agentGuidance.registerTeamsTools(mcp, sessionId);
|
|
12850
12625
|
agentGuidance.registerAutomationTools(mcp);
|
|
12626
|
+
if (client) agentGuidance.registerSessionPeerTools(mcp, agentGuidance.createSessionPeerToolExecutor(client));
|
|
12851
12627
|
mcp.registerTool("change_title", {
|
|
12852
12628
|
description: "Change the title of the current chat session",
|
|
12853
12629
|
title: "Change Chat Title",
|
|
@@ -13050,7 +12826,7 @@ async function startHappyServer(client, options) {
|
|
|
13050
12826
|
}
|
|
13051
12827
|
return;
|
|
13052
12828
|
}
|
|
13053
|
-
const mcp = createMcpServer(handlers, options, client.sessionId);
|
|
12829
|
+
const mcp = createMcpServer(handlers, options, client.sessionId, client);
|
|
13054
12830
|
try {
|
|
13055
12831
|
const transport = new streamableHttp_js.StreamableHTTPServerTransport({
|
|
13056
12832
|
sessionIdGenerator: void 0
|
|
@@ -13091,6 +12867,7 @@ async function startHappyServer(client, options) {
|
|
|
13091
12867
|
agentGuidance.REPORT_PROGRESS_TOOL_NAME,
|
|
13092
12868
|
...agentGuidance.TEAM_TOOL_NAMES,
|
|
13093
12869
|
...agentGuidance.AUTOMATION_TOOL_NAMES,
|
|
12870
|
+
...agentGuidance.SESSION_PEER_TOOL_NAMES,
|
|
13094
12871
|
...options?.assistant && !process.env.VH_TEAM_SCOPE_FILE ? ASSISTANT_TOOL_NAMES : []
|
|
13095
12872
|
],
|
|
13096
12873
|
stop: () => {
|
|
@@ -13427,7 +13204,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13427
13204
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13428
13205
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13429
13206
|
const fork = deps.run ?? (async (input) => {
|
|
13430
|
-
const { query } = await Promise.resolve().then(function () { return require('./index
|
|
13207
|
+
const { query } = await Promise.resolve().then(function () { return require('./index--KCpVs1c.cjs'); });
|
|
13431
13208
|
return runSideQuestion(query, input);
|
|
13432
13209
|
});
|
|
13433
13210
|
const run = async (input, live) => {
|
|
@@ -13546,7 +13323,7 @@ const MAX_TITLE_CHARS = 60;
|
|
|
13546
13323
|
const GENERATION_TIMEOUT_MS = 3e4;
|
|
13547
13324
|
function resolveClaudeBinary() {
|
|
13548
13325
|
try {
|
|
13549
|
-
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)));
|
|
13550
13327
|
const utilsPath = path.resolve(path.join(terminalTitleSuggest.projectPath(), "scripts", "claude_version_utils.cjs"));
|
|
13551
13328
|
const { getClaudeCliPath } = require$1(utilsPath);
|
|
13552
13329
|
const path$1 = getClaudeCliPath();
|
|
@@ -14114,7 +13891,7 @@ async function runClaude(credentials, options = {}) {
|
|
|
14114
13891
|
}
|
|
14115
13892
|
try {
|
|
14116
13893
|
persistence.logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
14117
|
-
const result = await
|
|
13894
|
+
const result = await persistence.notifyDaemonSessionStarted(response.id, metadata, {
|
|
14118
13895
|
encryptionKey: persistence.encodeBase64(response.encryptionKey),
|
|
14119
13896
|
encryptionVariant: response.encryptionVariant,
|
|
14120
13897
|
seq: response.seq,
|
|
@@ -14214,6 +13991,17 @@ async function runClaude(credentials, options = {}) {
|
|
|
14214
13991
|
});
|
|
14215
13992
|
session.on("claude-session-message", (body) => boardAnalyzer.noteClaudeMessage(body));
|
|
14216
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 } };
|
|
14217
14005
|
const initialScannerSessionId = forkClaudeSessionId ?? (metadata.claudeSessionId ?? null);
|
|
14218
14006
|
const remoteScanner = await createSessionScanner({
|
|
14219
14007
|
sessionId: initialScannerSessionId,
|
|
@@ -14845,7 +14633,7 @@ async function handleAuthLogin(args) {
|
|
|
14845
14633
|
console.log(chalk.gray(" \u2022 Re-authenticate and register machine\n"));
|
|
14846
14634
|
try {
|
|
14847
14635
|
persistence.logger.debug("Stopping daemon for force auth...");
|
|
14848
|
-
await
|
|
14636
|
+
await persistence.stopDaemon();
|
|
14849
14637
|
console.log(chalk.gray("\u2713 Stopped daemon"));
|
|
14850
14638
|
} catch (error) {
|
|
14851
14639
|
persistence.logger.debug("Daemon was not running or failed to stop:", error);
|
|
@@ -14910,7 +14698,7 @@ async function handleAuthLogout() {
|
|
|
14910
14698
|
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
|
14911
14699
|
try {
|
|
14912
14700
|
try {
|
|
14913
|
-
await
|
|
14701
|
+
await persistence.stopDaemon();
|
|
14914
14702
|
console.log(chalk.gray("Stopped daemon"));
|
|
14915
14703
|
} catch {
|
|
14916
14704
|
}
|
|
@@ -14955,7 +14743,7 @@ async function handleAuthStatus() {
|
|
|
14955
14743
|
console.log(chalk.gray(`
|
|
14956
14744
|
Data directory: ${persistence.configuration.happyHomeDir}`));
|
|
14957
14745
|
try {
|
|
14958
|
-
const running = await
|
|
14746
|
+
const running = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
14959
14747
|
if (running) {
|
|
14960
14748
|
console.log(chalk.green("\u2713 Daemon running"));
|
|
14961
14749
|
} else {
|
|
@@ -15781,9 +15569,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
|
|
|
15781
15569
|
};
|
|
15782
15570
|
}
|
|
15783
15571
|
|
|
15784
|
-
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)));
|
|
15785
15573
|
const __dirname$1 = path.dirname(__filename$1);
|
|
15786
|
-
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)));
|
|
15787
15575
|
const PRISMA_QUERY_ENGINE_FILES = {
|
|
15788
15576
|
"arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
|
|
15789
15577
|
"x64-darwin": "libquery_engine-darwin.dylib.node",
|
|
@@ -16172,7 +15960,7 @@ const DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
|
16172
15960
|
const DAEMON_READY_POLL_INTERVAL_MS = 100;
|
|
16173
15961
|
async function ensureDaemonRunning() {
|
|
16174
15962
|
persistence.logger.debug("Ensuring Happy background service is running & matches our version...");
|
|
16175
|
-
const versionMatches = await
|
|
15963
|
+
const versionMatches = await persistence.isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
16176
15964
|
const state = await persistence.readDaemonState();
|
|
16177
15965
|
const endpointsMatch = Boolean(state) && daemonEndpointsMatch(
|
|
16178
15966
|
state?.serverUrl,
|
|
@@ -16183,9 +15971,9 @@ async function ensureDaemonRunning() {
|
|
|
16183
15971
|
if (versionMatches && endpointsMatch) {
|
|
16184
15972
|
return;
|
|
16185
15973
|
}
|
|
16186
|
-
if (await
|
|
15974
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
16187
15975
|
persistence.logger.debug("Stopping daemon whose version or configured relay no longer matches...");
|
|
16188
|
-
await
|
|
15976
|
+
await persistence.stopDaemon();
|
|
16189
15977
|
}
|
|
16190
15978
|
persistence.logger.debug("Starting Happy background service...");
|
|
16191
15979
|
const daemonProcess = spawnHappyCLI(["daemon", "start-sync"], {
|
|
@@ -16196,7 +15984,7 @@ async function ensureDaemonRunning() {
|
|
|
16196
15984
|
daemonProcess.unref();
|
|
16197
15985
|
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
16198
15986
|
while (Date.now() < deadline) {
|
|
16199
|
-
if (await
|
|
15987
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
16200
15988
|
persistence.logger.debug("Happy background service is ready");
|
|
16201
15989
|
return;
|
|
16202
15990
|
}
|
|
@@ -17903,7 +17691,7 @@ async function runCodex(opts) {
|
|
|
17903
17691
|
if (response) {
|
|
17904
17692
|
try {
|
|
17905
17693
|
persistence.logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
17906
|
-
const result = await
|
|
17694
|
+
const result = await persistence.notifyDaemonSessionStarted(response.id, metadata, {
|
|
17907
17695
|
encryptionKey: persistence.encodeBase64(response.encryptionKey),
|
|
17908
17696
|
encryptionVariant: response.encryptionVariant,
|
|
17909
17697
|
seq: response.seq,
|
|
@@ -18200,6 +17988,7 @@ async function runCodex(opts) {
|
|
|
18200
17988
|
return "denied";
|
|
18201
17989
|
}
|
|
18202
17990
|
});
|
|
17991
|
+
const editThrottle = new EditReportThrottle();
|
|
18203
17992
|
client.setEventHandler((msg) => {
|
|
18204
17993
|
if (msg.type === "token_count") {
|
|
18205
17994
|
session.sendAgentUsageSnapshot("codex", msg);
|
|
@@ -18263,6 +18052,9 @@ async function runCodex(opts) {
|
|
|
18263
18052
|
}
|
|
18264
18053
|
if (msg.type === "patch_apply_begin") {
|
|
18265
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
|
+
}
|
|
18266
18058
|
const changeCount = Object.keys(changes).length;
|
|
18267
18059
|
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
18268
18060
|
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
@@ -18308,6 +18100,7 @@ async function runCodex(opts) {
|
|
|
18308
18100
|
};
|
|
18309
18101
|
let first = true;
|
|
18310
18102
|
let appendSystemPromptInjected = false;
|
|
18103
|
+
process.env.VH_PEER_SESSION_ID = session.sessionId;
|
|
18311
18104
|
try {
|
|
18312
18105
|
persistence.logger.debug("[codex]: client.connect begin");
|
|
18313
18106
|
await client.connect();
|
|
@@ -18721,7 +18514,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18721
18514
|
return;
|
|
18722
18515
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18723
18516
|
try {
|
|
18724
|
-
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'); });
|
|
18725
18518
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18726
18519
|
if (command.action === "help") {
|
|
18727
18520
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18738,7 +18531,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18738
18531
|
}
|
|
18739
18532
|
} else if (subcommand === "agent-home") {
|
|
18740
18533
|
try {
|
|
18741
|
-
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'); });
|
|
18742
18535
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18743
18536
|
process.exit(0);
|
|
18744
18537
|
} catch (error) {
|
|
@@ -18747,7 +18540,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18747
18540
|
}
|
|
18748
18541
|
} else if (subcommand === "spawn") {
|
|
18749
18542
|
try {
|
|
18750
|
-
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-
|
|
18543
|
+
const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-cIoglKVf.cjs'); });
|
|
18751
18544
|
await handleSpawnCommand(args.slice(1));
|
|
18752
18545
|
} catch (error) {
|
|
18753
18546
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18759,7 +18552,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18759
18552
|
return;
|
|
18760
18553
|
} else if (subcommand === "sessions") {
|
|
18761
18554
|
try {
|
|
18762
|
-
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-
|
|
18555
|
+
const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-MX21NJXd.cjs'); });
|
|
18763
18556
|
await handleSessionsCommand(args.slice(1));
|
|
18764
18557
|
} catch (error) {
|
|
18765
18558
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18771,7 +18564,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18771
18564
|
return;
|
|
18772
18565
|
} else if (subcommand === "send") {
|
|
18773
18566
|
try {
|
|
18774
|
-
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-
|
|
18567
|
+
const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-CwrYXTPr.cjs'); });
|
|
18775
18568
|
await handleSendCommand(args.slice(1));
|
|
18776
18569
|
} catch (error) {
|
|
18777
18570
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18877,9 +18670,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18877
18670
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
18878
18671
|
const projectId = args[3];
|
|
18879
18672
|
try {
|
|
18880
|
-
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-
|
|
18881
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-
|
|
18882
|
-
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; });
|
|
18883
18676
|
let userEmail = void 0;
|
|
18884
18677
|
try {
|
|
18885
18678
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -18910,7 +18703,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18910
18703
|
}
|
|
18911
18704
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
18912
18705
|
try {
|
|
18913
|
-
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-
|
|
18706
|
+
const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-BoePh9PX.cjs'); });
|
|
18914
18707
|
const config = readGeminiLocalConfig();
|
|
18915
18708
|
if (config.googleCloudProject) {
|
|
18916
18709
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -18950,7 +18743,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18950
18743
|
process.exit(0);
|
|
18951
18744
|
}
|
|
18952
18745
|
try {
|
|
18953
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
18746
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-8Pxh94Fm.cjs'); });
|
|
18954
18747
|
let startedBy = void 0;
|
|
18955
18748
|
for (let i = 1; i < args.length; i++) {
|
|
18956
18749
|
if (args[i] === "--started-by") {
|
|
@@ -18973,7 +18766,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18973
18766
|
} else if (subcommand === "pi") {
|
|
18974
18767
|
if (args[1] === "--terminal") {
|
|
18975
18768
|
try {
|
|
18976
|
-
const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-
|
|
18769
|
+
const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-DYzRcwgb.cjs'); });
|
|
18977
18770
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
18978
18771
|
} catch (error) {
|
|
18979
18772
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18990,7 +18783,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18990
18783
|
}
|
|
18991
18784
|
};
|
|
18992
18785
|
try {
|
|
18993
|
-
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'); });
|
|
18994
18787
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
18995
18788
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
18996
18789
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -19018,7 +18811,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19018
18811
|
return;
|
|
19019
18812
|
} else if (subcommand === "acp") {
|
|
19020
18813
|
try {
|
|
19021
|
-
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'); });
|
|
19022
18815
|
let startedBy = void 0;
|
|
19023
18816
|
let verbose = false;
|
|
19024
18817
|
const acpArgs = [];
|
|
@@ -19058,7 +18851,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19058
18851
|
return;
|
|
19059
18852
|
} else if (subcommand === "openclaw") {
|
|
19060
18853
|
try {
|
|
19061
|
-
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-
|
|
18854
|
+
const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-B5FSZIbK.cjs'); });
|
|
19062
18855
|
let startedBy = void 0;
|
|
19063
18856
|
let verbose = false;
|
|
19064
18857
|
let gatewayUrl;
|
|
@@ -19109,7 +18902,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19109
18902
|
return;
|
|
19110
18903
|
} else if (subcommand === "todo") {
|
|
19111
18904
|
try {
|
|
19112
|
-
const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-
|
|
18905
|
+
const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-Buuqn9gm.cjs'); });
|
|
19113
18906
|
await handleTodoCommand(args.slice(1));
|
|
19114
18907
|
} catch (error) {
|
|
19115
18908
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -19118,7 +18911,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19118
18911
|
return;
|
|
19119
18912
|
} else if (subcommand === "teams") {
|
|
19120
18913
|
try {
|
|
19121
|
-
const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-
|
|
18914
|
+
const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-Dqk7F3D-.cjs'); });
|
|
19122
18915
|
await handleTeamsCommand(args.slice(1));
|
|
19123
18916
|
} catch (error) {
|
|
19124
18917
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -19127,7 +18920,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19127
18920
|
return;
|
|
19128
18921
|
} else if (subcommand === "auto") {
|
|
19129
18922
|
try {
|
|
19130
|
-
const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-
|
|
18923
|
+
const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-P9RoJaY0.cjs'); });
|
|
19131
18924
|
await handleAutoCommand(args.slice(1));
|
|
19132
18925
|
} catch (error) {
|
|
19133
18926
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -19139,7 +18932,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19139
18932
|
return;
|
|
19140
18933
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
19141
18934
|
try {
|
|
19142
|
-
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-
|
|
18935
|
+
const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-BqlXl10F.cjs'); });
|
|
19143
18936
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
19144
18937
|
} catch (error) {
|
|
19145
18938
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -19162,7 +18955,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19162
18955
|
const daemonSubcommand = args[1];
|
|
19163
18956
|
if (daemonSubcommand === "list") {
|
|
19164
18957
|
try {
|
|
19165
|
-
const sessions = await
|
|
18958
|
+
const sessions = await persistence.listDaemonSessions();
|
|
19166
18959
|
if (sessions.length === 0) {
|
|
19167
18960
|
console.log("No active sessions this daemon is aware of (they might have been started by a previous version of the daemon)");
|
|
19168
18961
|
} else {
|
|
@@ -19180,7 +18973,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19180
18973
|
process.exit(1);
|
|
19181
18974
|
}
|
|
19182
18975
|
try {
|
|
19183
|
-
const success = await
|
|
18976
|
+
const success = await persistence.stopDaemonSession(sessionId);
|
|
19184
18977
|
console.log(success ? "Session stopped" : "Failed to stop session");
|
|
19185
18978
|
} catch (error) {
|
|
19186
18979
|
console.log("No daemon running");
|
|
@@ -19188,7 +18981,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19188
18981
|
return;
|
|
19189
18982
|
} else if (daemonSubcommand === "start") {
|
|
19190
18983
|
const currentState = await persistence.readDaemonState();
|
|
19191
|
-
const running = await
|
|
18984
|
+
const running = await persistence.checkIfDaemonRunningAndCleanupStaleState();
|
|
19192
18985
|
const currentDaemonMatches = currentState && currentState.startedWithCliVersion === persistence.configuration.currentCliVersion && daemonEndpointsMatch(
|
|
19193
18986
|
currentState.serverUrl,
|
|
19194
18987
|
currentState.webappUrl,
|
|
@@ -19197,7 +18990,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19197
18990
|
);
|
|
19198
18991
|
if (running && !currentDaemonMatches) {
|
|
19199
18992
|
console.log("Configured relay or CLI version changed; restarting the existing daemon");
|
|
19200
|
-
await
|
|
18993
|
+
await persistence.stopDaemon();
|
|
19201
18994
|
}
|
|
19202
18995
|
const child = spawnHappyCLI(["daemon", "start-sync"], {
|
|
19203
18996
|
detached: true,
|
|
@@ -19207,7 +19000,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19207
19000
|
child.unref();
|
|
19208
19001
|
let started = false;
|
|
19209
19002
|
for (let i = 0; i < 50; i++) {
|
|
19210
|
-
if (await
|
|
19003
|
+
if (await persistence.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
19211
19004
|
started = true;
|
|
19212
19005
|
break;
|
|
19213
19006
|
}
|
|
@@ -19224,7 +19017,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
19224
19017
|
await startDaemon();
|
|
19225
19018
|
process.exit(0);
|
|
19226
19019
|
} else if (daemonSubcommand === "stop") {
|
|
19227
|
-
await
|
|
19020
|
+
await persistence.stopDaemon();
|
|
19228
19021
|
process.exit(0);
|
|
19229
19022
|
} else if (daemonSubcommand === "status") {
|
|
19230
19023
|
await runDoctorDaemon();
|
|
@@ -19515,11 +19308,10 @@ exports.BaseReasoningProcessor = BaseReasoningProcessor;
|
|
|
19515
19308
|
exports.CHANGE_TITLE_INSTRUCTION = CHANGE_TITLE_INSTRUCTION;
|
|
19516
19309
|
exports.CLAUDE_ATTACHMENT_KINDS = CLAUDE_ATTACHMENT_KINDS;
|
|
19517
19310
|
exports.DEFAULT_GEMINI_MODEL = DEFAULT_GEMINI_MODEL;
|
|
19518
|
-
exports.
|
|
19311
|
+
exports.EditReportThrottle = EditReportThrottle;
|
|
19519
19312
|
exports.GEMINI_API_KEY_ENV = GEMINI_API_KEY_ENV;
|
|
19520
19313
|
exports.GEMINI_MODEL_ENV = GEMINI_MODEL_ENV;
|
|
19521
19314
|
exports.GOOGLE_API_KEY_ENV = GOOGLE_API_KEY_ENV;
|
|
19522
|
-
exports.MAX_READ_LIMIT = MAX_READ_LIMIT;
|
|
19523
19315
|
exports.MessageBuffer = MessageBuffer;
|
|
19524
19316
|
exports.MessageQueue2 = MessageQueue2;
|
|
19525
19317
|
exports.PI_TEAMS_EXTENSION = PI_TEAMS_EXTENSION;
|
|
@@ -19527,40 +19319,29 @@ exports.SPAWN_AGENTS = SPAWN_AGENTS;
|
|
|
19527
19319
|
exports.StreamRelay = StreamRelay;
|
|
19528
19320
|
exports.TEAM_SKILL = TEAM_SKILL;
|
|
19529
19321
|
exports.TitleGenerator = TitleGenerator;
|
|
19530
|
-
exports.TurnWaitTimeoutError = TurnWaitTimeoutError;
|
|
19531
19322
|
exports.appendStagedAttachmentsToPrompt = appendStagedAttachmentsToPrompt;
|
|
19532
|
-
exports.archiveSession = archiveSession;
|
|
19533
19323
|
exports.checkPreviewPath = checkPreviewPath;
|
|
19534
19324
|
exports.createSessionMetadata = createSessionMetadata;
|
|
19535
19325
|
exports.describeAction = describeAction;
|
|
19536
19326
|
exports.describeTrigger = describeTrigger;
|
|
19327
|
+
exports.extractAcpEditPaths = extractAcpEditPaths;
|
|
19537
19328
|
exports.hashObject = hashObject;
|
|
19538
19329
|
exports.initialMachineMetadata = initialMachineMetadata;
|
|
19539
19330
|
exports.isValidSpawnOrigin = isValidSpawnOrigin;
|
|
19540
|
-
exports.listAccountSessions = listAccountSessions;
|
|
19541
|
-
exports.listSessions = listSessions;
|
|
19542
19331
|
exports.normalizeAcpPermissionMode = normalizeAcpPermissionMode;
|
|
19543
19332
|
exports.parseDuration = parseDuration;
|
|
19544
19333
|
exports.query = query;
|
|
19545
|
-
exports.readSessionMetadata = readSessionMetadata;
|
|
19546
|
-
exports.readSessionTranscript = readSessionTranscript;
|
|
19547
19334
|
exports.registerAgentAttachmentDownloads = registerAgentAttachmentDownloads;
|
|
19548
19335
|
exports.registerAssistantSessionTools = registerAssistantSessionTools;
|
|
19549
19336
|
exports.registerKillSessionHandler = registerKillSessionHandler;
|
|
19550
19337
|
exports.removeSessionModeFile = removeSessionModeFile;
|
|
19551
19338
|
exports.resolveFirstMessageMeta = resolveFirstMessageMeta;
|
|
19552
|
-
exports.resolvePermissionRequest = resolvePermissionRequest;
|
|
19553
19339
|
exports.resolveSpawnPermissionMode = resolveSpawnPermissionMode;
|
|
19554
19340
|
exports.sanitizeSpawnPermissionMode = sanitizeSpawnPermissionMode;
|
|
19555
|
-
exports.sendUserMessage = sendUserMessage;
|
|
19556
|
-
exports.sessionWebUrl = sessionWebUrl;
|
|
19557
19341
|
exports.setupOfflineReconnection = setupOfflineReconnection;
|
|
19558
19342
|
exports.stageClaudeAttachments = stageClaudeAttachments;
|
|
19559
19343
|
exports.startHappyServer = startHappyServer;
|
|
19560
|
-
exports.stopSession = stopSession;
|
|
19561
19344
|
exports.suggestTitleForPrompt = suggestTitleForPrompt;
|
|
19562
19345
|
exports.tmuxSupportsSessionEnv = tmuxSupportsSessionEnv;
|
|
19563
19346
|
exports.triggerFromFlags = triggerFromFlags;
|
|
19564
|
-
exports.waitForSessionKey = waitForSessionKey;
|
|
19565
|
-
exports.waitForTurnEnd = waitForTurnEnd;
|
|
19566
19347
|
exports.writeSessionModeFile = writeSessionModeFile;
|