very-happy-cli 0.2.155 → 0.2.157
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-DmFcRh54.cjs → AcpBackend-BymWX-LI.cjs} +2 -2
- package/dist/{AcpBackend-DVB0nzgR.mjs → AcpBackend-Cy18U7yw.mjs} +2 -2
- package/dist/{AcpSessionManager-CWaSYTnT.cjs → AcpSessionManager-BF7zBGAi.cjs} +1 -1
- package/dist/{AcpSessionManager-dGDgNb-p.mjs → AcpSessionManager-Ds8rcp95.mjs} +1 -1
- package/dist/{agentGuidance-BWxFC6UL.mjs → agentGuidance-CU6YD_Ms.mjs} +560 -29
- package/dist/{agentGuidance-un7YPryB.cjs → agentGuidance-Db76O1kw.cjs} +579 -28
- package/dist/{agentHome-aucT8BHw.cjs → agentHome-B0CLy0Qx.cjs} +2 -2
- package/dist/{agentHome-ByLb2HFg.mjs → agentHome-BaJ9xtxB.mjs} +2 -2
- package/dist/{auto-BQnD6gVx.mjs → auto-B1YAwaeU.mjs} +6 -6
- package/dist/{auto-P9RoJaY0.cjs → auto-DqKGFq8U.cjs} +6 -6
- package/dist/codex/happyMcpStdioBridge.cjs +2 -2
- package/dist/codex/happyMcpStdioBridge.mjs +2 -2
- package/dist/{config-BoePh9PX.cjs → config-CzkBjZnV.cjs} +4 -4
- package/dist/{config-BPZ2Y6o8.mjs → config-aAbhJllb.mjs} +4 -4
- package/dist/{index-BjzdXb9x.mjs → index-BVu8Cufk.mjs} +8 -8
- package/dist/{index-B4oXnbYw.mjs → index-BYU1e81k.mjs} +372 -65
- package/dist/{index-D_fYUxVg.cjs → index-C9QzZsUQ.cjs} +375 -68
- package/dist/{index-DRrLaljt.cjs → index-CTbGqVFY.cjs} +7 -7
- package/dist/{index--KCpVs1c.cjs → index-Cz-uqnJk.cjs} +4 -4
- package/dist/{index-DVVRqaVG.mjs → index-DQcRkI9r.mjs} +4 -4
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{install-CGigiocs.cjs → install-BMlV0Tnr.cjs} +1 -1
- package/dist/{install-DP5aPNEX.mjs → install-BuYnqtVa.mjs} +1 -1
- package/dist/{installTerminalHooks-XrcPvfBs.cjs → installTerminalHooks-BVVOEGhG.cjs} +4 -4
- package/dist/{installTerminalHooks-tIg7mANc.mjs → installTerminalHooks-ELDoqh0A.mjs} +4 -4
- package/dist/lib.cjs +2 -2
- package/dist/lib.d.cts +101 -9
- package/dist/lib.d.mts +101 -9
- package/dist/lib.mjs +2 -2
- package/dist/{limits-C_DW0M0q.mjs → limits-A2c-3M-_.mjs} +162 -11
- package/dist/{limits-DkfHJ3nS.cjs → limits-CyuIt-Vl.cjs} +169 -10
- package/dist/{mcp-o7QglAQM.mjs → mcp-Bo934Kpy.mjs} +4 -4
- package/dist/{mcp-BqlXl10F.cjs → mcp-CXkvnztT.cjs} +4 -4
- package/dist/{piRuntime-BxP4B_zY.cjs → piRuntime-CJjA26oD.cjs} +2 -2
- package/dist/{piRuntime-MjiJI-nV.mjs → piRuntime-DdEY2mE9.mjs} +2 -2
- package/dist/{piTerminal-DYzRcwgb.cjs → piTerminal-CpU3hzrX.cjs} +6 -6
- package/dist/{piTerminal-Dpq6yqKU.mjs → piTerminal-DwWmgumi.mjs} +6 -6
- package/dist/{runGemini-CDlcQ-OA.mjs → runGemini-Cj6oJQ-I.mjs} +6 -6
- package/dist/{runGemini-8Pxh94Fm.cjs → runGemini-DS-0qm0L.cjs} +6 -6
- package/dist/{runOpenClaw-B5FSZIbK.cjs → runOpenClaw-CvSm8oTc.cjs} +5 -5
- package/dist/{runOpenClaw-YkhsgPs0.mjs → runOpenClaw-DUCQxkI-.mjs} +5 -5
- package/dist/{send-CwrYXTPr.cjs → send-CHxobaoI.cjs} +43 -31
- package/dist/{send-DJStmLBv.mjs → send-D5CyRyZ_.mjs} +43 -31
- package/dist/{sessions-MX21NJXd.cjs → sessions-BFlIrKCY.cjs} +133 -49
- package/dist/{sessions-JaVXlSqj.mjs → sessions-DsE01xKc.mjs} +133 -49
- package/dist/{spawn-BaPnjClT.mjs → spawn-CdihhwHs.mjs} +4 -4
- package/dist/{spawn-cIoglKVf.cjs → spawn-HV345RN-.cjs} +4 -4
- package/dist/{teams-DXV5Pwwt.mjs → teams-BE8nMB6O.mjs} +5 -5
- package/dist/{teams-Dqk7F3D-.cjs → teams-BahinMnH.cjs} +5 -5
- package/dist/{todo-Buuqn9gm.cjs → todo-CMMQ6yhU.cjs} +2 -2
- package/dist/{todo-2Z1REhfx.mjs → todo-CuQAkSp2.mjs} +2 -2
- package/dist/{types-a2xsH2UI.cjs → types-B0sSi6eM.cjs} +125 -13
- package/dist/{types-CkbfdRzq.mjs → types-BrGJ7PJz.mjs} +125 -13
- package/package.json +7 -7
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
|
|
2
|
-
import os, { homedir, userInfo, tmpdir } from 'node:os';
|
|
2
|
+
import os, { homedir, hostname, userInfo, tmpdir } from 'node:os';
|
|
3
3
|
import { randomUUID, randomInt, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
|
|
4
|
-
import { g as getProjectPath, k as initializeSandbox, w as wrapCommand, j as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, l as AsyncLock, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, m as commandOnPath, n as deriveLocalCliUpdateSummary, o as isClaudeSessionId, q as getLocalHappyAgentCredentialPath, s as readLocalHappyAgentCredentials, t as parseExactVersion, u as compareExactVersions, v as extractCompleteLines, x as decideBackfill, y as parseTerminalHookPayload, z as tmuxArgs, B as scrubTmuxClientEnv, D as decideMirrorBinding, E as MIRROR_BACKFILL_LINES_DEFAULT, G as mirrorLineKey, H as mirrorLocalId, J as detectCLIAvailability, K as detectResumeSupport, L as refreshAgentHomes, d as describeAgentHome, T as TurnActivityTracker, N as summarizeSpawnSessionForLog, O as locateClaudeConversation, P as agentHomeSpawnEnv, Q as shellescape, A as ApiClient, S as fetchCliUpdateState, U as resolveCliUpdateCheckInterval, V as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, W as listVhTerminals, X as readVhTerminal, Y as sendToVhTerminal, Z as TITLE_PROMPT_PREFIX, _ as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, $ as startOfflineReconnection, C as CodexAppServerClient } from './types-
|
|
5
|
-
import { l as logger,
|
|
4
|
+
import { g as getProjectPath, k as initializeSandbox, w as wrapCommand, j as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, l as AsyncLock, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, m as commandOnPath, n as deriveLocalCliUpdateSummary, o as isClaudeSessionId, q as getLocalHappyAgentCredentialPath, s as readLocalHappyAgentCredentials, t as parseExactVersion, u as compareExactVersions, v as extractCompleteLines, x as decideBackfill, y as parseTerminalHookPayload, z as tmuxArgs, B as scrubTmuxClientEnv, D as decideMirrorBinding, E as MIRROR_BACKFILL_LINES_DEFAULT, G as mirrorLineKey, H as mirrorLocalId, J as detectCLIAvailability, K as detectResumeSupport, L as refreshAgentHomes, d as describeAgentHome, T as TurnActivityTracker, N as summarizeSpawnSessionForLog, O as locateClaudeConversation, P as agentHomeSpawnEnv, Q as shellescape, A as ApiClient, S as fetchCliUpdateState, U as resolveCliUpdateCheckInterval, V as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, W as listVhTerminals, X as readVhTerminal, Y as sendToVhTerminal, Z as TITLE_PROMPT_PREFIX, _ as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, $ as startOfflineReconnection, C as CodexAppServerClient } from './types-BrGJ7PJz.mjs';
|
|
5
|
+
import { l as logger, aa as sameBackgroundTasks, ab as notifyDaemonSessionEvent, x as BUILTIN_TODO_DISCOVERY, g as delay, ac as STREAM_DELTA_MAX_CHARS, ad as ensurePrivateDirectory, ae as hardenPrivateFile, c as configuration, af as notifyDaemonClaudeAuthFailed, ag as createBackgroundTaskTracker, F as packageJson, r as readSettings, ah as readCredentials, a0 as credentialRelayProblem, k as checkIfDaemonRunningAndCleanupStaleState, ai as readDaemonState, aj as AGENT_VERSION_SOURCES, w as encodeBase64, ak as readSettingsOutcome, u as updateSettings, al as encodeBase64Url, M as decodeBase64, am as writeCredentialsLegacy, an as writeCredentialsDataKey, ao as BACKGROUND_TASK_DESCRIPTION_MAX, ap as BACKGROUND_TASKS_MAX, d as readPersistedSessions, E as writePrivateFileSync, aq as decryptWithDataKey, ar as decryptLegacy, as as persistSession, z as ensurePrivateDirectorySync, at as getAgentCodeDefaults, au as normalizeAgentKey, av as isClaudeOpus55Model, aw as supportsClaudeOpus55, ax as AUTOMATION_SUMMARY_MAX_CHARS, ay as AUTOMATION_CLAIM_MAX_RUNS, az as AUTOMATION_DEFAULT_LEASE_MS, f as isAutomationRunTerminal, aA as isDaemonRunningCurrentlyInstalledHappyVersion, aB as stopDaemon, aC as acquireDaemonLock, aD as BackgroundTaskTracker, aE as deletePersistedSessions, N as decrypt, aF as writeDaemonState, aG as writeSettings, aH as cleanupDaemonState, aI as releaseDaemonLock, i as isValidSessionId, m as spawnDaemonSession, a4 as isValidTerminalId, aJ as ContextUsageSchema, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, o as CLIPBOARD_MAX_BYTES, v as notifyDaemonSessionStarted, y as reportSessionEditToDaemon, aK as clearCredentials, aL as clearMachineId, h as readCredentialsForConfiguredRelay, aM as SandboxConfigSchema, e as createEnvelope, O as listDaemonSessions, n as stopDaemonSession, aN as getLatestDaemonLog } from './limits-A2c-3M-_.mjs';
|
|
6
6
|
import spawn$1, { spawn } from 'cross-spawn';
|
|
7
7
|
import path, { join, resolve, basename, delimiter, isAbsolute, dirname, sep, normalize } from 'node:path';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
9
9
|
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, realpathSync, unlinkSync, renameSync, writeFileSync, accessSync, constants as constants$1, openSync, fsyncSync, closeSync, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
|
|
10
10
|
import { p as projectPath } from './terminalTitleSuggest-DKgX3osW.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { K as trimIdent, L as AGENT_GUIDANCE, N as formatEditConflictNotice, H as sendUserMessage, S as SESSION_PEER_SENT_FROM, F as readSessionMetadata, R as RemoteCallBudget, O as REMOTE_WRITE_CALLS_PER_MINUTE, s as sendPeerMessage, j as listPeerSessions, u as deliverToSession, k as readSessionTranscript, l as listAccountSessions, g as listSessions, Q as guardRemoteSessionOpsRequest, U as remoteError, V as REMOTE_OP_DEADLINE_MS, W as formatRemoteAuditLine, X as REMOTE_SEND_COMMIT_DEADLINE_MS, Y as REMOTE_RESUME_WAIT_MS, Z as CLI_PEER_SENDER_ID, M as MAX_READ_LIMIT, _ as capRemoteTranscript, $ as capRemoteText, x as sessionWebUrl, a0 as REMOTE_ANSWER_MAX_BYTES, a1 as REMOTE_TITLE_MAX_BYTES, G as waitForSessionKey, q as archiveSession, a2 as createAutomationsClient, A as isAutomationsUnavailable, a3 as AutomationsRequestError, a4 as analyzeLatestTurn, a5 as readSessionLog$1, a6 as TURN_WINDOW, a7 as userPromptText, E as teamScopePath, a8 as TeamConnectionPreflightError, J as createTeamsClient, n as readRemoteTranscript, v as sendRemoteMessage, a9 as RemoteSessionOpsError, o as stopSession, P as PREVIEW_TOOL_NAME, aa as REPORT_PROGRESS_TOOL_NAME, ab as TEAM_TOOL_NAMES, ac as AUTOMATION_TOOL_NAMES, ad as SESSION_PEER_TOOL_NAMES, r as registerTeamsTools, c as registerAutomationTools, d as registerSessionPeerTools, ae as createSessionPeerToolExecutor, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, af as REPORT_PROGRESS_TOOL_TITLE, ag as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-CU6YD_Ms.mjs';
|
|
12
12
|
import { readFile, writeFile, rename, unlink, mkdir, access as access$1, symlink, rm, readlink, lstat, constants, stat, open as open$1, appendFile } from 'node:fs/promises';
|
|
13
13
|
import fs, { watch, access } from 'fs/promises';
|
|
14
14
|
import { useStdout, useInput, Box, Text, render } from 'ink';
|
|
@@ -115,6 +115,11 @@ class Session {
|
|
|
115
115
|
sessionId;
|
|
116
116
|
mode = "local";
|
|
117
117
|
thinking = false;
|
|
118
|
+
/** B-507: background tasks still in flight (async sub-agents, backgrounded
|
|
119
|
+
* commands, monitors) — published with every keepAlive. Always an array
|
|
120
|
+
* for the Claude runner: an empty one says "nothing running", which is a
|
|
121
|
+
* different statement from a runner that has no such notion (undefined). */
|
|
122
|
+
backgroundTasks = [];
|
|
118
123
|
/**
|
|
119
124
|
* Window after a user-initiated abort during which stray promise rejections
|
|
120
125
|
* from tearing down the SDK query (MCP transports, in-flight tool calls,
|
|
@@ -176,11 +181,21 @@ class Session {
|
|
|
176
181
|
logger.debug("[Session] Failed to create notification producer:", err);
|
|
177
182
|
this.notificationProducer = null;
|
|
178
183
|
}
|
|
179
|
-
this.client.keepAlive(this.thinking, this.mode);
|
|
184
|
+
this.client.keepAlive(this.thinking, this.mode, this.backgroundTasks);
|
|
180
185
|
this.keepAliveInterval = setInterval(() => {
|
|
181
|
-
this.client.keepAlive(this.thinking, this.mode);
|
|
186
|
+
this.client.keepAlive(this.thinking, this.mode, this.backgroundTasks);
|
|
182
187
|
}, 2e3);
|
|
183
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* B-507: the tracker's current set (`claude/backgroundTasks.ts`). A change
|
|
191
|
+
* beats at once so the daemon, agentState and the web see the edge without
|
|
192
|
+
* waiting for the 2s tick; an unchanged set is left to the interval.
|
|
193
|
+
*/
|
|
194
|
+
setBackgroundTasks = (tasks) => {
|
|
195
|
+
if (sameBackgroundTasks(this.backgroundTasks, tasks)) return;
|
|
196
|
+
this.backgroundTasks = tasks;
|
|
197
|
+
this.client.keepAlive(this.thinking, this.mode, this.backgroundTasks);
|
|
198
|
+
};
|
|
184
199
|
/**
|
|
185
200
|
* Cleanup resources (call when session is no longer needed)
|
|
186
201
|
*/
|
|
@@ -215,7 +230,7 @@ class Session {
|
|
|
215
230
|
this.lastAssistantSnippet = void 0;
|
|
216
231
|
}
|
|
217
232
|
this.thinking = thinking;
|
|
218
|
-
this.client.keepAlive(thinking, this.mode);
|
|
233
|
+
this.client.keepAlive(thinking, this.mode, this.backgroundTasks);
|
|
219
234
|
};
|
|
220
235
|
/**
|
|
221
236
|
* Record that Claude emitted an assistant text reply during this turn.
|
|
@@ -282,7 +297,7 @@ class Session {
|
|
|
282
297
|
};
|
|
283
298
|
onModeChange = (mode) => {
|
|
284
299
|
this.mode = mode;
|
|
285
|
-
this.client.keepAlive(this.thinking, mode);
|
|
300
|
+
this.client.keepAlive(this.thinking, mode, this.backgroundTasks);
|
|
286
301
|
this._onModeChange(mode);
|
|
287
302
|
};
|
|
288
303
|
onAbort = () => {
|
|
@@ -3926,6 +3941,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
|
|
|
3926
3941
|
const turnSteering = createTurnSteeringController();
|
|
3927
3942
|
const runtimeControls = createRuntimeControls(() => session.thinking);
|
|
3928
3943
|
session.client.rpcHandlerManager.registerHandler("claude-runtime-control", async (input) => runtimeControls.request(input));
|
|
3944
|
+
const backgroundTasks = createBackgroundTaskTracker();
|
|
3929
3945
|
async function abort() {
|
|
3930
3946
|
if (abortController && !abortController.signal.aborted) {
|
|
3931
3947
|
abortController.abort();
|
|
@@ -4011,6 +4027,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
|
|
|
4011
4027
|
let notifiedQuestionToolCalls = /* @__PURE__ */ new Set();
|
|
4012
4028
|
function onMessage(message) {
|
|
4013
4029
|
runtimeControls.observe(message);
|
|
4030
|
+
backgroundTasks.observe(message);
|
|
4031
|
+
session.setBackgroundTasks(backgroundTasks.list());
|
|
4014
4032
|
if (isRuntimeCheckpointReplay(message)) return;
|
|
4015
4033
|
if (message.type === "user" && isClaudeInterruptSentinelContent(message.message.content)) {
|
|
4016
4034
|
sdkToLogConverter.convert(message);
|
|
@@ -4235,6 +4253,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
|
|
|
4235
4253
|
},
|
|
4236
4254
|
onQueryReady: (q) => {
|
|
4237
4255
|
runtimeControls.setQuery(q);
|
|
4256
|
+
backgroundTasks.reset();
|
|
4257
|
+
session.setBackgroundTasks([]);
|
|
4238
4258
|
turnSteering.setInterrupt(q.interrupt);
|
|
4239
4259
|
session.setSideQuestionLive({ ask: q.sideQuestion, canControl: q.canControl });
|
|
4240
4260
|
session.setSteerHandler(async (input) => {
|
|
@@ -4331,6 +4351,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
|
|
|
4331
4351
|
}
|
|
4332
4352
|
} finally {
|
|
4333
4353
|
runtimeControls.setQuery(null);
|
|
4354
|
+
backgroundTasks.reset();
|
|
4355
|
+
session.setBackgroundTasks([]);
|
|
4334
4356
|
turnSteering.reset();
|
|
4335
4357
|
session.setSteerHandler(null);
|
|
4336
4358
|
session.setSideQuestionLive(null);
|
|
@@ -5380,8 +5402,8 @@ ${typeLabels[type] || type}:`));
|
|
|
5380
5402
|
}
|
|
5381
5403
|
console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
|
|
5382
5404
|
try {
|
|
5383
|
-
const { resolveAgentHomesForCli } = await import('./agentHome-
|
|
5384
|
-
const { describeAgentHome } = await import('./types-
|
|
5405
|
+
const { resolveAgentHomesForCli } = await import('./agentHome-BaJ9xtxB.mjs');
|
|
5406
|
+
const { describeAgentHome } = await import('./types-BrGJ7PJz.mjs').then(function (n) { return n.a0; });
|
|
5385
5407
|
const homes = await resolveAgentHomesForCli();
|
|
5386
5408
|
console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
|
|
5387
5409
|
console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
|
|
@@ -6204,6 +6226,18 @@ function checkPreviewPath(input, opts) {
|
|
|
6204
6226
|
return { resolved, deniedReason: null };
|
|
6205
6227
|
}
|
|
6206
6228
|
|
|
6229
|
+
const BackgroundTaskInfoSchema = z.object({
|
|
6230
|
+
id: z.string().min(1).max(200),
|
|
6231
|
+
type: z.string().max(64),
|
|
6232
|
+
description: z.string().max(BACKGROUND_TASK_DESCRIPTION_MAX + 1),
|
|
6233
|
+
startedAt: z.number()
|
|
6234
|
+
});
|
|
6235
|
+
const BackgroundTasksReportSchema = z.object({
|
|
6236
|
+
count: z.number().int().nonnegative(),
|
|
6237
|
+
tasks: z.array(BackgroundTaskInfoSchema),
|
|
6238
|
+
reportedAt: z.number().optional(),
|
|
6239
|
+
stale: z.boolean().optional()
|
|
6240
|
+
});
|
|
6207
6241
|
function startDaemonControlServer({
|
|
6208
6242
|
controlToken,
|
|
6209
6243
|
getChildren,
|
|
@@ -6215,12 +6249,15 @@ function startDaemonControlServer({
|
|
|
6215
6249
|
onSessionStateEvent,
|
|
6216
6250
|
onClaudeAuthFailed,
|
|
6217
6251
|
onSessionTurnEvent,
|
|
6252
|
+
onSessionBackgroundTasks,
|
|
6253
|
+
getBackgroundTasks,
|
|
6218
6254
|
pushClipboard,
|
|
6219
6255
|
pushFilePreview,
|
|
6220
6256
|
onTerminalHook,
|
|
6221
6257
|
setTerminalTitle,
|
|
6222
6258
|
onSessionEdit,
|
|
6223
|
-
listPeers
|
|
6259
|
+
listPeers,
|
|
6260
|
+
isTurnActive
|
|
6224
6261
|
}) {
|
|
6225
6262
|
return new Promise((resolve) => {
|
|
6226
6263
|
const app = fastify({
|
|
@@ -6274,8 +6311,10 @@ function startDaemonControlServer({
|
|
|
6274
6311
|
schema: {
|
|
6275
6312
|
body: z.object({
|
|
6276
6313
|
sessionId: z.string(),
|
|
6277
|
-
event: z.enum(["completed", "needs_input", "auth_failed", "turn_started", "turn_ended"]),
|
|
6278
|
-
spawnedBy: z.string().optional()
|
|
6314
|
+
event: z.enum(["completed", "needs_input", "auth_failed", "turn_started", "turn_ended", "background_tasks"]),
|
|
6315
|
+
spawnedBy: z.string().optional(),
|
|
6316
|
+
// B-507: only with event 'background_tasks' — the wrapper's full set.
|
|
6317
|
+
backgroundTasks: z.array(BackgroundTaskInfoSchema).max(BACKGROUND_TASKS_MAX).optional()
|
|
6279
6318
|
}),
|
|
6280
6319
|
response: {
|
|
6281
6320
|
200: z.object({
|
|
@@ -6286,6 +6325,10 @@ function startDaemonControlServer({
|
|
|
6286
6325
|
}, async (request) => {
|
|
6287
6326
|
const { sessionId, event, spawnedBy } = request.body;
|
|
6288
6327
|
logger.debug(`[CONTROL SERVER] Session event: ${sessionId} ${event} (spawnedBy=${spawnedBy ?? "unset"})`);
|
|
6328
|
+
if (event === "background_tasks") {
|
|
6329
|
+
onSessionBackgroundTasks?.(sessionId, request.body.backgroundTasks ?? []);
|
|
6330
|
+
return { status: "ok" };
|
|
6331
|
+
}
|
|
6289
6332
|
if (event === "auth_failed") {
|
|
6290
6333
|
onClaudeAuthFailed?.(sessionId);
|
|
6291
6334
|
return { status: "ok" };
|
|
@@ -6304,7 +6347,10 @@ function startDaemonControlServer({
|
|
|
6304
6347
|
children: z.array(z.object({
|
|
6305
6348
|
startedBy: z.string(),
|
|
6306
6349
|
happySessionId: z.string(),
|
|
6307
|
-
pid: z.number()
|
|
6350
|
+
pid: z.number(),
|
|
6351
|
+
turnActive: z.boolean(),
|
|
6352
|
+
// B-507: in-flight background tasks per the wrapper's last report (lease-expired = count 0).
|
|
6353
|
+
backgroundTasks: BackgroundTasksReportSchema.optional()
|
|
6308
6354
|
}))
|
|
6309
6355
|
})
|
|
6310
6356
|
}
|
|
@@ -6316,7 +6362,9 @@ function startDaemonControlServer({
|
|
|
6316
6362
|
children: children.filter((child) => child.happySessionId !== void 0).map((child) => ({
|
|
6317
6363
|
startedBy: child.startedBy,
|
|
6318
6364
|
happySessionId: child.happySessionId,
|
|
6319
|
-
pid: child.pid
|
|
6365
|
+
pid: child.pid,
|
|
6366
|
+
turnActive: isTurnActive ? isTurnActive(child.happySessionId) : false,
|
|
6367
|
+
...getBackgroundTasks ? { backgroundTasks: getBackgroundTasks(child.happySessionId) } : {}
|
|
6320
6368
|
}))
|
|
6321
6369
|
};
|
|
6322
6370
|
});
|
|
@@ -6884,6 +6932,170 @@ function createPeerCoordinator(deps) {
|
|
|
6884
6932
|
};
|
|
6885
6933
|
}
|
|
6886
6934
|
|
|
6935
|
+
const WRITE_METHODS = /* @__PURE__ */ new Set(["sessions.send", "sessions.message"]);
|
|
6936
|
+
const REMOTE_CLIENT_TAG = "cli-send-remote";
|
|
6937
|
+
function defaultDeps(machineId) {
|
|
6938
|
+
return {
|
|
6939
|
+
machineId,
|
|
6940
|
+
host: hostname(),
|
|
6941
|
+
enabled: async () => (await readSettings()).remoteSessionOps !== "off",
|
|
6942
|
+
readPersisted: readPersistedSessions,
|
|
6943
|
+
listLocal: (options) => listSessions(options),
|
|
6944
|
+
listAccount: (options) => listAccountSessions(options),
|
|
6945
|
+
readTranscript: (sessionId, limit, options) => readSessionTranscript(sessionId, limit, options),
|
|
6946
|
+
deliver: (sessionId, persisted, text, client, options, deps) => deliverToSession(sessionId, persisted, text, client, options, deps),
|
|
6947
|
+
listPeers: (context, scope) => listPeerSessions(context, scope),
|
|
6948
|
+
sendPeer: (context, args) => sendPeerMessage(context, args),
|
|
6949
|
+
log: (line) => logger.info(line),
|
|
6950
|
+
now: () => Date.now()
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
function createRemoteSessionOpsHandlers(machineId, overrides = {}) {
|
|
6954
|
+
const deps = { ...defaultDeps(machineId), ...overrides };
|
|
6955
|
+
const readBudget = deps.budget ?? new RemoteCallBudget(void 0, deps.now);
|
|
6956
|
+
const writeBudget = deps.writeBudget ?? new RemoteCallBudget(REMOTE_WRITE_CALLS_PER_MINUTE, deps.now);
|
|
6957
|
+
const deadlineMs = deps.deadlineMs ?? REMOTE_OP_DEADLINE_MS;
|
|
6958
|
+
const sendCommitDeadlineMs = deps.sendCommitDeadlineMs ?? REMOTE_SEND_COMMIT_DEADLINE_MS;
|
|
6959
|
+
const resumeWaitMs = deps.resumeWaitMs ?? REMOTE_RESUME_WAIT_MS;
|
|
6960
|
+
const run = (method, op, targetOf) => async (params) => {
|
|
6961
|
+
const started = deps.now();
|
|
6962
|
+
let from = null;
|
|
6963
|
+
let sessionId;
|
|
6964
|
+
const finish = (response) => {
|
|
6965
|
+
const outcome = response.ok ? "ok" : response.error.code;
|
|
6966
|
+
deps.log(formatRemoteAuditLine({ method, from, sessionId, outcome, durationMs: deps.now() - started }));
|
|
6967
|
+
return response;
|
|
6968
|
+
};
|
|
6969
|
+
let enabled = false;
|
|
6970
|
+
try {
|
|
6971
|
+
enabled = await deps.enabled();
|
|
6972
|
+
} catch {
|
|
6973
|
+
enabled = false;
|
|
6974
|
+
}
|
|
6975
|
+
const guard = guardRemoteSessionOpsRequest(method, params, { enabled });
|
|
6976
|
+
if (!guard.ok) return finish(guard);
|
|
6977
|
+
from = guard.request.from;
|
|
6978
|
+
sessionId = targetOf(guard.request.args);
|
|
6979
|
+
const budget = WRITE_METHODS.has(method) ? writeBudget : readBudget;
|
|
6980
|
+
const token = budget.take();
|
|
6981
|
+
if (!token.ok) {
|
|
6982
|
+
return finish(remoteError("rate_limited", `This machine accepts at most ${budget.perMinute} remote ${WRITE_METHODS.has(method) ? "send/message" : "list/read/peers"} calls per minute; retry in ${Math.ceil(token.retryAfterMs / 1e3)}s`));
|
|
6983
|
+
}
|
|
6984
|
+
let timer;
|
|
6985
|
+
const deadline = new Promise((_, reject) => {
|
|
6986
|
+
timer = setTimeout(() => reject(new RemoteOpFailure("timeout", `${method} did not finish within ${Math.round(deadlineMs / 1e3)}s on ${deps.host}`)), deadlineMs);
|
|
6987
|
+
timer.unref?.();
|
|
6988
|
+
});
|
|
6989
|
+
try {
|
|
6990
|
+
return finish({ ok: true, result: await Promise.race([op(guard.request.args, from, started), deadline]) });
|
|
6991
|
+
} catch (error) {
|
|
6992
|
+
const message = error instanceof Error ? error.message : "Remote session operation failed";
|
|
6993
|
+
if (error instanceof RemoteOpFailure) return finish(remoteError(error.code, message));
|
|
6994
|
+
return finish(remoteError("internal", message));
|
|
6995
|
+
} finally {
|
|
6996
|
+
if (timer) clearTimeout(timer);
|
|
6997
|
+
}
|
|
6998
|
+
};
|
|
6999
|
+
const requirePersisted = (id) => {
|
|
7000
|
+
const persisted = deps.readPersisted()[id];
|
|
7001
|
+
if (!persisted) throw new RemoteOpFailure("no_local_key", `Session ${id} is not on ${deps.host} (no local key there either)`);
|
|
7002
|
+
return persisted;
|
|
7003
|
+
};
|
|
7004
|
+
return {
|
|
7005
|
+
"sessions.list": run("sessions.list", async (args) => {
|
|
7006
|
+
const wanted = args.ids ? new Set(args.ids) : null;
|
|
7007
|
+
let sessions;
|
|
7008
|
+
if (args.all) {
|
|
7009
|
+
sessions = (await deps.listAccount({ tag: args.tag, recentLimit: wanted ? 1e4 : args.limit, includeArchived: true })).filter((row) => row.decryptable);
|
|
7010
|
+
} else if (wanted) {
|
|
7011
|
+
const persisted = deps.readPersisted();
|
|
7012
|
+
const live = await deps.listLocal({ recentLimit: 1e4 });
|
|
7013
|
+
const liveById = new Map(live.map((row) => [row.id, row]));
|
|
7014
|
+
sessions = [...wanted].filter((id) => persisted[id] !== void 0 || liveById.has(id)).map((id) => liveById.get(id) ?? summaryFromPersisted(id, persisted[id]));
|
|
7015
|
+
} else {
|
|
7016
|
+
sessions = await deps.listLocal({ tag: args.tag, recentLimit: args.limit });
|
|
7017
|
+
}
|
|
7018
|
+
if (wanted) sessions = sessions.filter((row) => wanted.has(row.id));
|
|
7019
|
+
const result = { machineId: deps.machineId, host: deps.host, sessions };
|
|
7020
|
+
return result;
|
|
7021
|
+
}, () => void 0),
|
|
7022
|
+
"sessions.read": run("sessions.read", async (args) => {
|
|
7023
|
+
requirePersisted(args.sessionId);
|
|
7024
|
+
const limit = Math.max(1, Math.min(MAX_READ_LIMIT, args.limit ?? 20));
|
|
7025
|
+
const transcript = await deps.readTranscript(args.sessionId, limit, { full: args.full === true });
|
|
7026
|
+
const capped = capRemoteTranscript(transcript.transcript);
|
|
7027
|
+
const result = {
|
|
7028
|
+
...transcript,
|
|
7029
|
+
summary: {
|
|
7030
|
+
...transcript.summary,
|
|
7031
|
+
...transcript.summary.title !== void 0 ? { title: capRemoteText(transcript.summary.title, REMOTE_TITLE_MAX_BYTES) } : {}
|
|
7032
|
+
},
|
|
7033
|
+
turn: { ...transcript.turn, answer: capRemoteText(transcript.turn.answer, REMOTE_ANSWER_MAX_BYTES, "tail") },
|
|
7034
|
+
transcript: capped.transcript,
|
|
7035
|
+
...capped.truncated ? { truncated: true } : {},
|
|
7036
|
+
machineId: deps.machineId,
|
|
7037
|
+
host: deps.host
|
|
7038
|
+
};
|
|
7039
|
+
return result;
|
|
7040
|
+
}, (args) => args.sessionId),
|
|
7041
|
+
"sessions.send": run("sessions.send", async (args, _from, started) => {
|
|
7042
|
+
const persisted = requirePersisted(args.sessionId);
|
|
7043
|
+
const send = (sessionId, key, text, client, options) => {
|
|
7044
|
+
if (deps.now() - started > sendCommitDeadlineMs) {
|
|
7045
|
+
throw new RemoteOpFailure("timeout", `Session ${sessionId} was not live within ${Math.round(sendCommitDeadlineMs / 1e3)}s on ${deps.host}; nothing was sent \u2014 retry (the same localId cannot double-deliver)`);
|
|
7046
|
+
}
|
|
7047
|
+
return sendUserMessage(sessionId, key, text, client, { ...options, ...args.localId ? { localId: args.localId } : {} });
|
|
7048
|
+
};
|
|
7049
|
+
return deps.deliver(args.sessionId, persisted, args.text, REMOTE_CLIENT_TAG, {
|
|
7050
|
+
resume: args.resume === true,
|
|
7051
|
+
waitMs: resumeWaitMs,
|
|
7052
|
+
pollMs: 1e3,
|
|
7053
|
+
...args.model !== void 0 ? { model: args.model } : {},
|
|
7054
|
+
...args.sentFrom !== void 0 ? { sentFrom: args.sentFrom } : {}
|
|
7055
|
+
}, { send });
|
|
7056
|
+
}, (args) => args.sessionId),
|
|
7057
|
+
"sessions.peers": run("sessions.peers", async (args, from) => {
|
|
7058
|
+
const scope = args.cwd ? args.scope ?? "machine" : "machine";
|
|
7059
|
+
const context = {
|
|
7060
|
+
self: () => ({ sessionId: from.sessionId ?? CLI_PEER_SENDER_ID, cwd: args.cwd ?? "/", machine: from.host }),
|
|
7061
|
+
readPersisted: deps.readPersisted
|
|
7062
|
+
};
|
|
7063
|
+
const listing = await deps.listPeers(context, scope);
|
|
7064
|
+
return { ...listing, machineId: deps.machineId, host: deps.host };
|
|
7065
|
+
}, () => void 0),
|
|
7066
|
+
"sessions.message": run("sessions.message", async (args) => {
|
|
7067
|
+
requirePersisted(args.to);
|
|
7068
|
+
const context = {
|
|
7069
|
+
self: () => ({ ...args.from, cwd: args.from.cwd ?? "/" }),
|
|
7070
|
+
readPersisted: deps.readPersisted,
|
|
7071
|
+
// The target must be HERE; never bounce a remote request onward.
|
|
7072
|
+
remoteMessage: null
|
|
7073
|
+
};
|
|
7074
|
+
return deps.sendPeer(context, { to: args.to, body: args.body, ...args.replyTo ? { replyTo: args.replyTo } : {} });
|
|
7075
|
+
}, (args) => args.to)
|
|
7076
|
+
};
|
|
7077
|
+
}
|
|
7078
|
+
class RemoteOpFailure extends Error {
|
|
7079
|
+
constructor(code, message) {
|
|
7080
|
+
super(message);
|
|
7081
|
+
this.code = code;
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
function summaryFromPersisted(id, persisted) {
|
|
7085
|
+
const meta = persisted?.metadata;
|
|
7086
|
+
return {
|
|
7087
|
+
id,
|
|
7088
|
+
live: false,
|
|
7089
|
+
...meta?.summary?.text ? { title: meta.summary.text } : {},
|
|
7090
|
+
...meta?.path ? { cwd: meta.path } : {},
|
|
7091
|
+
...meta?.flavor ? { flavor: meta.flavor } : {},
|
|
7092
|
+
...meta?.tags?.length ? { tags: [...meta.tags] } : {},
|
|
7093
|
+
...meta?.variant ? { variant: meta.variant } : {},
|
|
7094
|
+
...persisted?.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
|
|
7095
|
+
url: sessionWebUrl(id)
|
|
7096
|
+
};
|
|
7097
|
+
}
|
|
7098
|
+
|
|
6887
7099
|
const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
|
|
6888
7100
|
|
|
6889
7101
|
\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
|
|
@@ -9110,9 +9322,9 @@ function createMirrorManager(deps) {
|
|
|
9110
9322
|
archiveReason: reason
|
|
9111
9323
|
});
|
|
9112
9324
|
try {
|
|
9113
|
-
await deps.api.
|
|
9325
|
+
await deps.api.archiveSession(binding.happySessionId);
|
|
9114
9326
|
} catch (error) {
|
|
9115
|
-
logger.debug(`[MIRROR]
|
|
9327
|
+
logger.debug(`[MIRROR] archiveSession failed for ${binding.happySessionId}:`, error);
|
|
9116
9328
|
}
|
|
9117
9329
|
deps.onBindingsChanged?.();
|
|
9118
9330
|
};
|
|
@@ -10592,6 +10804,13 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
|
|
|
10592
10804
|
if (edgeFell) item.sawTurn = false;
|
|
10593
10805
|
const turn = await readTurn(sessionId, item.runId).catch(() => null);
|
|
10594
10806
|
if (!turn?.ended) continue;
|
|
10807
|
+
const background = deps.backgroundTasks?.(sessionId) ?? 0;
|
|
10808
|
+
if (background > 0 && turn.status !== "failed" && turn.status !== "cancelled") {
|
|
10809
|
+
if (!item.waitingOnBackground) deps.log(`Automation run ${item.runId}: turn ended but ${background} background task(s) still running; not done yet`);
|
|
10810
|
+
item.waitingOnBackground = true;
|
|
10811
|
+
continue;
|
|
10812
|
+
}
|
|
10813
|
+
item.waitingOnBackground = false;
|
|
10595
10814
|
if (turn.status === "failed" || turn.status === "cancelled") await finish(item, { claimId: item.claimId, status: "failed", sessionId, error: turn.error ?? `Turn ${turn.status}`, needsAttention: true, attentionReason: `turn_${turn.status}` });
|
|
10596
10815
|
else await finish(item, { claimId: item.claimId, status: "done", sessionId, summary: clipText(turn.answer, AUTOMATION_SUMMARY_MAX_CHARS) });
|
|
10597
10816
|
}
|
|
@@ -10721,6 +10940,16 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
|
|
|
10721
10940
|
function daemonExitCode(source) {
|
|
10722
10941
|
return source === "exception" ? 1 : 0;
|
|
10723
10942
|
}
|
|
10943
|
+
const HANDOVER_EXIT_CODE = 75;
|
|
10944
|
+
|
|
10945
|
+
function detectDaemonSupervisor(env, platform = process.platform) {
|
|
10946
|
+
const explicit = env.HAPPY_DAEMON_SUPERVISOR?.trim().toLowerCase();
|
|
10947
|
+
if (explicit === "systemd") return "systemd";
|
|
10948
|
+
if (explicit === "none") return "none";
|
|
10949
|
+
if (platform === "linux" && typeof env.INVOCATION_ID === "string" && env.INVOCATION_ID.trim() !== "") return "systemd";
|
|
10950
|
+
return "none";
|
|
10951
|
+
}
|
|
10952
|
+
const SYSTEMD_RESTART_HINT = "systemctl --user restart very-happy-daemon";
|
|
10724
10953
|
|
|
10725
10954
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10726
10955
|
function sanitizeImportTitle(value) {
|
|
@@ -10876,6 +11105,7 @@ async function startDaemon() {
|
|
|
10876
11105
|
logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
10877
11106
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
10878
11107
|
const turnActivity = new TurnActivityTracker();
|
|
11108
|
+
const backgroundTasks = new BackgroundTaskTracker();
|
|
10879
11109
|
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
10880
11110
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
10881
11111
|
let teamWorker;
|
|
@@ -11736,6 +11966,7 @@ async function startDaemon() {
|
|
|
11736
11966
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
11737
11967
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
11738
11968
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11969
|
+
if (session?.happySessionId) backgroundTasks.forget(session.happySessionId);
|
|
11739
11970
|
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
11740
11971
|
if (session?.happySessionId && session.encryption) {
|
|
11741
11972
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
@@ -11822,10 +12053,15 @@ async function startDaemon() {
|
|
|
11822
12053
|
onSessionStateEvent,
|
|
11823
12054
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
11824
12055
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
12056
|
+
onSessionBackgroundTasks: (sessionId, tasks) => backgroundTasks.apply(sessionId, tasks),
|
|
12057
|
+
getBackgroundTasks: (sessionId) => backgroundTasks.report(sessionId),
|
|
11825
12058
|
onSessionEdit: (edit) => {
|
|
11826
12059
|
peerCoordinator.onEdit(edit);
|
|
11827
12060
|
},
|
|
11828
12061
|
listPeers: () => peerCoordinator.list(),
|
|
12062
|
+
// B-505: `daemon list` shows which sessions have a turn in flight, so an
|
|
12063
|
+
// operator can check the WHOLE machine before a supervised restart.
|
|
12064
|
+
isTurnActive: (sessionId) => turnActivity.activeSessions().includes(sessionId),
|
|
11829
12065
|
pushClipboard: (text, terminalId) => {
|
|
11830
12066
|
if (!apiMachineRef) {
|
|
11831
12067
|
return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
|
|
@@ -11845,6 +12081,8 @@ async function startDaemon() {
|
|
|
11845
12081
|
}
|
|
11846
12082
|
});
|
|
11847
12083
|
const daemonClaudeCredentials = resolveClaudeCredentialReadiness();
|
|
12084
|
+
const daemonSupervisor = detectDaemonSupervisor(process.env);
|
|
12085
|
+
logger.debug(`[DAEMON RUN] Supervisor: ${daemonSupervisor}`);
|
|
11848
12086
|
let fileState = {
|
|
11849
12087
|
pid: process.pid,
|
|
11850
12088
|
httpPort: controlPort,
|
|
@@ -11854,7 +12092,8 @@ async function startDaemon() {
|
|
|
11854
12092
|
serverUrl: configuration.serverUrl,
|
|
11855
12093
|
webappUrl: configuration.webappUrl,
|
|
11856
12094
|
daemonLogPath: logger.logFilePath,
|
|
11857
|
-
claudeCredentialSource: daemonClaudeCredentials.source
|
|
12095
|
+
claudeCredentialSource: daemonClaudeCredentials.source,
|
|
12096
|
+
supervisor: daemonSupervisor
|
|
11858
12097
|
};
|
|
11859
12098
|
writeDaemonState(fileState);
|
|
11860
12099
|
logger.debug("[DAEMON RUN] Daemon state written");
|
|
@@ -11915,6 +12154,7 @@ async function startDaemon() {
|
|
|
11915
12154
|
listTrackedSessionIds: () => [...pidToTrackedSession.values()].map((session) => session.happySessionId).filter((sessionId) => typeof sessionId === "string"),
|
|
11916
12155
|
requestShutdown: () => requestShutdown("happy-app")
|
|
11917
12156
|
});
|
|
12157
|
+
apiMachine.setRemoteSessionOpsHandlers(createRemoteSessionOpsHandlers(machineId));
|
|
11918
12158
|
const claudeAuthService = new ClaudeAuthService({
|
|
11919
12159
|
happyHomeDir: configuration.happyHomeDir,
|
|
11920
12160
|
happyLibDir: projectPath(),
|
|
@@ -11947,8 +12187,9 @@ async function startDaemon() {
|
|
|
11947
12187
|
enabled: async () => ((await readSettings()).cliAutoUpdate ?? "idle") !== "off",
|
|
11948
12188
|
// B-466: "idle" = no agent turn in flight. Idle wrappers survive a
|
|
11949
12189
|
// handover (they talk to the server themselves) and web terminals live
|
|
11950
|
-
// in tmux, so neither is a reason to hold an update.
|
|
11951
|
-
|
|
12190
|
+
// in tmux, so neither is a reason to hold an update. B-507: a session
|
|
12191
|
+
// whose turn ended but still has background tasks is busy too.
|
|
12192
|
+
idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny(),
|
|
11952
12193
|
install: installCliSafely,
|
|
11953
12194
|
publish: (state) => {
|
|
11954
12195
|
cliUpdateStateRef = state;
|
|
@@ -12003,7 +12244,7 @@ async function startDaemon() {
|
|
|
12003
12244
|
} catch {
|
|
12004
12245
|
}
|
|
12005
12246
|
}
|
|
12006
|
-
if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
|
|
12247
|
+
if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
|
|
12007
12248
|
const preflight = await preflightNewBundle(bundlePath);
|
|
12008
12249
|
if (preflight.action === "hold") {
|
|
12009
12250
|
if (lastHandoverHold !== preflight.reason) {
|
|
@@ -12013,7 +12254,7 @@ async function startDaemon() {
|
|
|
12013
12254
|
updateController.holdHandover(preflight.reason);
|
|
12014
12255
|
return;
|
|
12015
12256
|
}
|
|
12016
|
-
if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
|
|
12257
|
+
if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
|
|
12017
12258
|
lastHandoverHold = null;
|
|
12018
12259
|
logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
|
|
12019
12260
|
teamWorker?.stop();
|
|
@@ -12027,6 +12268,10 @@ async function startDaemon() {
|
|
|
12027
12268
|
await cleanupDaemonState();
|
|
12028
12269
|
await releaseDaemonLock(daemonLockHandle);
|
|
12029
12270
|
await stopCaffeinate();
|
|
12271
|
+
if (daemonSupervisor === "systemd") {
|
|
12272
|
+
logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
|
|
12273
|
+
process.exit(HANDOVER_EXIT_CODE);
|
|
12274
|
+
}
|
|
12030
12275
|
try {
|
|
12031
12276
|
spawnHappyCLI(["daemon", "start"], {
|
|
12032
12277
|
detached: true,
|
|
@@ -12075,6 +12320,7 @@ async function startDaemon() {
|
|
|
12075
12320
|
spawn: spawnSession,
|
|
12076
12321
|
live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
|
|
12077
12322
|
turnActive: (id) => turnActivity.activeSessions().includes(id),
|
|
12323
|
+
backgroundTasks: (id) => backgroundTasks.count(id),
|
|
12078
12324
|
log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
|
|
12079
12325
|
});
|
|
12080
12326
|
void apiMachine.updateMachineMetadata((current) => ({
|
|
@@ -12368,36 +12614,54 @@ ${sessions.map(describeSummary).join("\n")}`);
|
|
|
12368
12614
|
}
|
|
12369
12615
|
});
|
|
12370
12616
|
mcp.registerTool("session_read", {
|
|
12371
|
-
description: "Read the latest messages of a session as a compact role-tagged transcript (user / assistant / tool summaries; large payloads truncated).
|
|
12617
|
+
description: "Read the latest messages of a session as a compact role-tagged transcript (user / assistant / tool summaries; large payloads truncated). Sessions spawned by this machine are read directly; a session spawned by another machine of this account is read through that machine's daemon (it must be online).",
|
|
12372
12618
|
title: "Read Session Transcript",
|
|
12373
12619
|
inputSchema: {
|
|
12374
12620
|
sessionId: z.string().describe("The Happy session id to read"),
|
|
12375
|
-
limit: z.number().optional().describe("How many recent messages to fetch (default 20, max 100)")
|
|
12621
|
+
limit: z.number().optional().describe("How many recent messages to fetch (default 20, max 100)"),
|
|
12622
|
+
machineId: z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
|
|
12376
12623
|
}
|
|
12377
12624
|
}, async (args) => {
|
|
12378
12625
|
if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12379
12626
|
try {
|
|
12380
|
-
const
|
|
12627
|
+
const local = readPersistedSessions()[args.sessionId];
|
|
12628
|
+
if (local && !args.machineId) {
|
|
12629
|
+
const result2 = await readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12630
|
+
const body2 = result2.transcript.length > 0 ? result2.transcript : "(no readable conversation content in this range)";
|
|
12631
|
+
return ok(`${describeSummary(result2.summary)}
|
|
12632
|
+
--- last ${result2.messageCount} message(s) ---
|
|
12633
|
+
${body2}`);
|
|
12634
|
+
}
|
|
12635
|
+
const result = await readRemoteTranscript(args.sessionId, { limit: args.limit ?? 20, ...args.machineId ? { machineId: args.machineId } : {} });
|
|
12381
12636
|
const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
|
|
12382
|
-
return ok(`${describeSummary(result.summary)}
|
|
12383
|
-
--- last ${result.messageCount} message(s) ---
|
|
12637
|
+
return ok(`${describeSummary(result.summary)} machine=${result.host} (${result.machineId})
|
|
12638
|
+
--- last ${result.messageCount} message(s)${result.truncated ? ", truncated to fit" : ""} ---
|
|
12384
12639
|
${body}`);
|
|
12385
12640
|
} catch (error) {
|
|
12386
12641
|
return fail(`Failed to read session: ${error instanceof Error ? error.message : String(error)}`);
|
|
12387
12642
|
}
|
|
12388
12643
|
});
|
|
12389
12644
|
mcp.registerTool("session_send", {
|
|
12390
|
-
description: "Send a user message into an existing session
|
|
12645
|
+
description: "Send a user message into an existing session. 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 its machine first. A session spawned by another machine of this account is delivered through that machine's daemon (it must be online). Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
|
|
12391
12646
|
title: "Send Message to Session",
|
|
12392
12647
|
inputSchema: {
|
|
12393
12648
|
sessionId: z.string().describe("The Happy session id to message"),
|
|
12394
12649
|
text: z.string().describe("The message text to send"),
|
|
12395
|
-
resume: z.boolean().optional().describe("If the session is archived or offline, resume it on
|
|
12650
|
+
resume: z.boolean().optional().describe("If the session is archived or offline, resume it on its machine (like the web Restore button) and wait until it is live before sending"),
|
|
12651
|
+
machineId: z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
|
|
12396
12652
|
}
|
|
12397
12653
|
}, async (args) => {
|
|
12398
12654
|
if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12399
12655
|
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
12400
12656
|
try {
|
|
12657
|
+
if (!readPersistedSessions()[args.sessionId] || args.machineId) {
|
|
12658
|
+
const remote = await sendRemoteMessage(args.sessionId, args.text, { resume: args.resume === true, sentFrom: "assistant", ...args.machineId ? { machineId: args.machineId } : {} });
|
|
12659
|
+
if (!remote.delivered) {
|
|
12660
|
+
const detail = remote.error ?? `session is ${remote.status}`;
|
|
12661
|
+
return fail(remote.stored ? `Not delivered (via ${remote.machine.host}): ${detail}` : `Session ${args.sessionId} on ${remote.machine.host} is ${remote.status}: ${detail}`);
|
|
12662
|
+
}
|
|
12663
|
+
return ok(`Message delivered${remote.resumed ? ` (session resumed on ${remote.machine.host})` : ""} to ${args.sessionId} on machine ${remote.machine.host} (${remote.machine.id}) ${sessionWebUrl(args.sessionId)}`);
|
|
12664
|
+
}
|
|
12401
12665
|
const persisted = await waitForSessionKey(args.sessionId, 0);
|
|
12402
12666
|
const result = await deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12403
12667
|
resume: args.resume === true,
|
|
@@ -12409,6 +12673,7 @@ ${body}`);
|
|
|
12409
12673
|
}
|
|
12410
12674
|
return ok(`Message delivered${result.resumed ? " (session resumed on this machine)" : ""} to ${describeSession(args.sessionId, persisted, { live: true })}`);
|
|
12411
12675
|
} catch (error) {
|
|
12676
|
+
if (error instanceof RemoteSessionOpsError) return fail(`Failed to send (${error.code}): ${error.message}`);
|
|
12412
12677
|
return fail(`Failed to send: ${error instanceof Error ? error.message : String(error)}`);
|
|
12413
12678
|
}
|
|
12414
12679
|
});
|
|
@@ -13006,6 +13271,40 @@ function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEve
|
|
|
13006
13271
|
};
|
|
13007
13272
|
}
|
|
13008
13273
|
|
|
13274
|
+
async function settleSessionOnExit(sessionId, intent, deps) {
|
|
13275
|
+
if (intent === "archive") {
|
|
13276
|
+
deps.updateMetadata((currentMetadata) => ({
|
|
13277
|
+
...currentMetadata,
|
|
13278
|
+
lifecycleState: "archived",
|
|
13279
|
+
lifecycleStateSince: Date.now(),
|
|
13280
|
+
archivedBy: "cli",
|
|
13281
|
+
archiveReason: "User terminated"
|
|
13282
|
+
}));
|
|
13283
|
+
}
|
|
13284
|
+
deps.sendSessionDeath();
|
|
13285
|
+
try {
|
|
13286
|
+
if (intent === "archive") await deps.archiveSession(sessionId);
|
|
13287
|
+
else await deps.deactivateSession(sessionId);
|
|
13288
|
+
} catch (error) {
|
|
13289
|
+
deps.log?.(`[START] ${intent === "archive" ? "archiveSession" : "deactivateSession"} during cleanup failed:`, error);
|
|
13290
|
+
}
|
|
13291
|
+
}
|
|
13292
|
+
class SessionExitGate {
|
|
13293
|
+
decided = null;
|
|
13294
|
+
/** Returns the intent to act on, or null when cleanup already started. */
|
|
13295
|
+
claim(intent) {
|
|
13296
|
+
if (this.decided !== null) return null;
|
|
13297
|
+
this.decided = intent;
|
|
13298
|
+
return intent;
|
|
13299
|
+
}
|
|
13300
|
+
get current() {
|
|
13301
|
+
return this.decided;
|
|
13302
|
+
}
|
|
13303
|
+
}
|
|
13304
|
+
function exitIntentFromArchiveFlag(archive) {
|
|
13305
|
+
return archive ?? true ? "archive" : "offline";
|
|
13306
|
+
}
|
|
13307
|
+
|
|
13009
13308
|
function isUnsupportedSideQuestionError(error) {
|
|
13010
13309
|
const message = error instanceof Error ? error.message : String(error);
|
|
13011
13310
|
return /unknown|unsupported|not supported|unrecognized|invalid.*subtype/i.test(message);
|
|
@@ -13182,7 +13481,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13182
13481
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13183
13482
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13184
13483
|
const fork = deps.run ?? (async (input) => {
|
|
13185
|
-
const { query } = await import('./index-
|
|
13484
|
+
const { query } = await import('./index-DQcRkI9r.mjs');
|
|
13186
13485
|
return runSideQuestion(query, input);
|
|
13187
13486
|
});
|
|
13188
13487
|
const run = async (input, live) => {
|
|
@@ -14319,26 +14618,24 @@ async function runClaude(credentials, options = {}) {
|
|
|
14319
14618
|
hasMetadata: Boolean(message.meta)
|
|
14320
14619
|
});
|
|
14321
14620
|
});
|
|
14621
|
+
const exitGate = new SessionExitGate();
|
|
14322
14622
|
const cleanup = async (opts = { archive: true }) => {
|
|
14323
|
-
|
|
14623
|
+
const intent = exitGate.claim(exitIntentFromArchiveFlag(opts.archive));
|
|
14624
|
+
if (intent === null) {
|
|
14625
|
+
logger.debug(`[START] Termination (archive=${opts.archive ?? true}) ignored: cleanup already running as ${exitGate.current}`);
|
|
14626
|
+
return;
|
|
14627
|
+
}
|
|
14628
|
+
logger.debug(`[START] Received termination signal, cleaning up (archive=${intent === "archive"})...`);
|
|
14324
14629
|
try {
|
|
14325
14630
|
if (session) {
|
|
14326
|
-
if (opts.archive ?? true) {
|
|
14327
|
-
session.updateMetadata((currentMetadata) => ({
|
|
14328
|
-
...currentMetadata,
|
|
14329
|
-
lifecycleState: "archived",
|
|
14330
|
-
lifecycleStateSince: Date.now(),
|
|
14331
|
-
archivedBy: "cli",
|
|
14332
|
-
archiveReason: "User terminated"
|
|
14333
|
-
}));
|
|
14334
|
-
}
|
|
14335
14631
|
currentSession?.cleanup();
|
|
14336
|
-
session.
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14632
|
+
await settleSessionOnExit(session.sessionId, intent, {
|
|
14633
|
+
updateMetadata: (handler) => session.updateMetadata(handler),
|
|
14634
|
+
sendSessionDeath: () => session.sendSessionDeath(),
|
|
14635
|
+
archiveSession: (id) => api.archiveSession(id),
|
|
14636
|
+
deactivateSession: (id) => api.deactivateSession(id),
|
|
14637
|
+
log: (message, ...args) => logger.debug(message, ...args)
|
|
14638
|
+
});
|
|
14342
14639
|
await session.flush();
|
|
14343
14640
|
await session.close();
|
|
14344
14641
|
}
|
|
@@ -18492,7 +18789,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18492
18789
|
return;
|
|
18493
18790
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18494
18791
|
try {
|
|
18495
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-
|
|
18792
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-ELDoqh0A.mjs');
|
|
18496
18793
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18497
18794
|
if (command.action === "help") {
|
|
18498
18795
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18509,7 +18806,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18509
18806
|
}
|
|
18510
18807
|
} else if (subcommand === "agent-home") {
|
|
18511
18808
|
try {
|
|
18512
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-
|
|
18809
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-BaJ9xtxB.mjs');
|
|
18513
18810
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18514
18811
|
process.exit(0);
|
|
18515
18812
|
} catch (error) {
|
|
@@ -18518,7 +18815,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18518
18815
|
}
|
|
18519
18816
|
} else if (subcommand === "spawn") {
|
|
18520
18817
|
try {
|
|
18521
|
-
const { handleSpawnCommand } = await import('./spawn-
|
|
18818
|
+
const { handleSpawnCommand } = await import('./spawn-CdihhwHs.mjs');
|
|
18522
18819
|
await handleSpawnCommand(args.slice(1));
|
|
18523
18820
|
} catch (error) {
|
|
18524
18821
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18530,7 +18827,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18530
18827
|
return;
|
|
18531
18828
|
} else if (subcommand === "sessions") {
|
|
18532
18829
|
try {
|
|
18533
|
-
const { handleSessionsCommand } = await import('./sessions-
|
|
18830
|
+
const { handleSessionsCommand } = await import('./sessions-DsE01xKc.mjs');
|
|
18534
18831
|
await handleSessionsCommand(args.slice(1));
|
|
18535
18832
|
} catch (error) {
|
|
18536
18833
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18542,7 +18839,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18542
18839
|
return;
|
|
18543
18840
|
} else if (subcommand === "send") {
|
|
18544
18841
|
try {
|
|
18545
|
-
const { handleSendCommand } = await import('./send-
|
|
18842
|
+
const { handleSendCommand } = await import('./send-D5CyRyZ_.mjs');
|
|
18546
18843
|
await handleSendCommand(args.slice(1));
|
|
18547
18844
|
} catch (error) {
|
|
18548
18845
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18648,9 +18945,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18648
18945
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
18649
18946
|
const projectId = args[3];
|
|
18650
18947
|
try {
|
|
18651
|
-
const { saveGoogleCloudProjectToConfig } = await import('./config-
|
|
18652
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-
|
|
18653
|
-
const { ApiClient: ApiClient2 } = await import('./types-
|
|
18948
|
+
const { saveGoogleCloudProjectToConfig } = await import('./config-aAbhJllb.mjs');
|
|
18949
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-A2c-3M-_.mjs').then(function (n) { return n.aO; });
|
|
18950
|
+
const { ApiClient: ApiClient2 } = await import('./types-BrGJ7PJz.mjs').then(function (n) { return n.a1; });
|
|
18654
18951
|
let userEmail = void 0;
|
|
18655
18952
|
try {
|
|
18656
18953
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -18681,7 +18978,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18681
18978
|
}
|
|
18682
18979
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
18683
18980
|
try {
|
|
18684
|
-
const { readGeminiLocalConfig } = await import('./config-
|
|
18981
|
+
const { readGeminiLocalConfig } = await import('./config-aAbhJllb.mjs');
|
|
18685
18982
|
const config = readGeminiLocalConfig();
|
|
18686
18983
|
if (config.googleCloudProject) {
|
|
18687
18984
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -18721,7 +19018,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18721
19018
|
process.exit(0);
|
|
18722
19019
|
}
|
|
18723
19020
|
try {
|
|
18724
|
-
const { runGemini } = await import('./runGemini-
|
|
19021
|
+
const { runGemini } = await import('./runGemini-Cj6oJQ-I.mjs');
|
|
18725
19022
|
let startedBy = void 0;
|
|
18726
19023
|
for (let i = 1; i < args.length; i++) {
|
|
18727
19024
|
if (args[i] === "--started-by") {
|
|
@@ -18744,7 +19041,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18744
19041
|
} else if (subcommand === "pi") {
|
|
18745
19042
|
if (args[1] === "--terminal") {
|
|
18746
19043
|
try {
|
|
18747
|
-
const { runPiTerminal } = await import('./piTerminal-
|
|
19044
|
+
const { runPiTerminal } = await import('./piTerminal-DwWmgumi.mjs');
|
|
18748
19045
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
18749
19046
|
} catch (error) {
|
|
18750
19047
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18761,7 +19058,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18761
19058
|
}
|
|
18762
19059
|
};
|
|
18763
19060
|
try {
|
|
18764
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
19061
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BVu8Cufk.mjs');
|
|
18765
19062
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
18766
19063
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
18767
19064
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -18789,7 +19086,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18789
19086
|
return;
|
|
18790
19087
|
} else if (subcommand === "acp") {
|
|
18791
19088
|
try {
|
|
18792
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
19089
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BVu8Cufk.mjs');
|
|
18793
19090
|
let startedBy = void 0;
|
|
18794
19091
|
let verbose = false;
|
|
18795
19092
|
const acpArgs = [];
|
|
@@ -18829,7 +19126,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18829
19126
|
return;
|
|
18830
19127
|
} else if (subcommand === "openclaw") {
|
|
18831
19128
|
try {
|
|
18832
|
-
const { runOpenClaw } = await import('./runOpenClaw-
|
|
19129
|
+
const { runOpenClaw } = await import('./runOpenClaw-DUCQxkI-.mjs');
|
|
18833
19130
|
let startedBy = void 0;
|
|
18834
19131
|
let verbose = false;
|
|
18835
19132
|
let gatewayUrl;
|
|
@@ -18880,7 +19177,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18880
19177
|
return;
|
|
18881
19178
|
} else if (subcommand === "todo") {
|
|
18882
19179
|
try {
|
|
18883
|
-
const { handleTodoCommand } = await import('./todo-
|
|
19180
|
+
const { handleTodoCommand } = await import('./todo-CuQAkSp2.mjs');
|
|
18884
19181
|
await handleTodoCommand(args.slice(1));
|
|
18885
19182
|
} catch (error) {
|
|
18886
19183
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -18889,7 +19186,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18889
19186
|
return;
|
|
18890
19187
|
} else if (subcommand === "teams") {
|
|
18891
19188
|
try {
|
|
18892
|
-
const { handleTeamsCommand } = await import('./teams-
|
|
19189
|
+
const { handleTeamsCommand } = await import('./teams-BE8nMB6O.mjs');
|
|
18893
19190
|
await handleTeamsCommand(args.slice(1));
|
|
18894
19191
|
} catch (error) {
|
|
18895
19192
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -18898,7 +19195,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18898
19195
|
return;
|
|
18899
19196
|
} else if (subcommand === "auto") {
|
|
18900
19197
|
try {
|
|
18901
|
-
const { handleAutoCommand } = await import('./auto-
|
|
19198
|
+
const { handleAutoCommand } = await import('./auto-B1YAwaeU.mjs');
|
|
18902
19199
|
await handleAutoCommand(args.slice(1));
|
|
18903
19200
|
} catch (error) {
|
|
18904
19201
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -18910,7 +19207,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18910
19207
|
return;
|
|
18911
19208
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
18912
19209
|
try {
|
|
18913
|
-
const { handleMcpCommand } = await import('./mcp-
|
|
19210
|
+
const { handleMcpCommand } = await import('./mcp-Bo934Kpy.mjs');
|
|
18914
19211
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
18915
19212
|
} catch (error) {
|
|
18916
19213
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -18939,6 +19236,10 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18939
19236
|
} else {
|
|
18940
19237
|
console.log("Active sessions:");
|
|
18941
19238
|
console.log(JSON.stringify(sessions, null, 2));
|
|
19239
|
+
const busy = sessions.filter((s) => s.turnActive === true);
|
|
19240
|
+
console.log(busy.length === 0 ? "No turn in flight on this machine (wrappers older than 0.2.139 do not report turns)." : `${busy.length} session(s) with a turn in flight: ${busy.map((s) => s.happySessionId).join(", ")}`);
|
|
19241
|
+
const background = sessions.filter((s) => (s.backgroundTasks?.count ?? 0) > 0);
|
|
19242
|
+
console.log(background.length === 0 ? "No background tasks in flight (wrappers older than 0.2.157 do not report them)." : `${background.length} session(s) with background tasks in flight: ${background.map((s) => `${s.happySessionId} (${s.backgroundTasks?.count})`).join(", ")}`);
|
|
18942
19243
|
}
|
|
18943
19244
|
} catch (error) {
|
|
18944
19245
|
console.log("No daemon running");
|
|
@@ -18967,13 +19268,19 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18967
19268
|
configuration.webappUrl
|
|
18968
19269
|
);
|
|
18969
19270
|
if (running && !currentDaemonMatches) {
|
|
19271
|
+
if (currentState?.supervisor === "systemd") {
|
|
19272
|
+
console.error(`The running daemon is supervised by systemd; hand over through the unit instead:
|
|
19273
|
+
${SYSTEMD_RESTART_HINT}`);
|
|
19274
|
+
process.exit(1);
|
|
19275
|
+
}
|
|
18970
19276
|
console.log("Configured relay or CLI version changed; restarting the existing daemon");
|
|
18971
19277
|
await stopDaemon();
|
|
18972
19278
|
}
|
|
19279
|
+
const { HAPPY_DAEMON_SUPERVISOR: _supervisor, INVOCATION_ID: _invocation, ...detachedEnv } = process.env;
|
|
18973
19280
|
const child = spawnHappyCLI(["daemon", "start-sync"], {
|
|
18974
19281
|
detached: true,
|
|
18975
19282
|
stdio: "ignore",
|
|
18976
|
-
env:
|
|
19283
|
+
env: detachedEnv
|
|
18977
19284
|
});
|
|
18978
19285
|
child.unref();
|
|
18979
19286
|
let started = false;
|