very-happy-cli 0.2.156 → 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-BHe1_F1h.cjs → AcpBackend-BymWX-LI.cjs} +2 -2
- package/dist/{AcpBackend-C3nc1u-s.mjs → AcpBackend-Cy18U7yw.mjs} +2 -2
- package/dist/{AcpSessionManager-BVxkQnNf.cjs → AcpSessionManager-BF7zBGAi.cjs} +1 -1
- package/dist/{AcpSessionManager-dN3k1LbI.mjs → AcpSessionManager-Ds8rcp95.mjs} +1 -1
- package/dist/{agentGuidance-C604n0Zq.mjs → agentGuidance-CU6YD_Ms.mjs} +560 -29
- package/dist/{agentGuidance-C-CmV03f.cjs → agentGuidance-Db76O1kw.cjs} +579 -28
- package/dist/{agentHome-CmIJNV7f.cjs → agentHome-B0CLy0Qx.cjs} +2 -2
- package/dist/{agentHome-D2XGsoqs.mjs → agentHome-BaJ9xtxB.mjs} +2 -2
- package/dist/{auto-B1I6G_Nf.mjs → auto-B1YAwaeU.mjs} +6 -6
- package/dist/{auto-CMDq4oIu.cjs → auto-DqKGFq8U.cjs} +6 -6
- package/dist/codex/happyMcpStdioBridge.cjs +2 -2
- package/dist/codex/happyMcpStdioBridge.mjs +2 -2
- package/dist/{config-BYHSRY2E.cjs → config-CzkBjZnV.cjs} +4 -4
- package/dist/{config-BkTu25bH.mjs → config-aAbhJllb.mjs} +4 -4
- package/dist/{index-B5QbzEga.mjs → index-BVu8Cufk.mjs} +8 -8
- package/dist/{index-DAAreiRF.mjs → index-BYU1e81k.mjs} +288 -44
- package/dist/{index-BbtR-Qto.cjs → index-C9QzZsUQ.cjs} +291 -47
- package/dist/{index-B_-7NpGZ.cjs → index-CTbGqVFY.cjs} +7 -7
- package/dist/{index-CB8QnjvS.cjs → index-Cz-uqnJk.cjs} +4 -4
- package/dist/{index-PYO8Mdz4.mjs → index-DQcRkI9r.mjs} +4 -4
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{install-CO1ps6Wt.cjs → install-BMlV0Tnr.cjs} +1 -1
- package/dist/{install-CNtXzwT_.mjs → install-BuYnqtVa.mjs} +1 -1
- package/dist/{installTerminalHooks-BOAnQ1Qe.cjs → installTerminalHooks-BVVOEGhG.cjs} +4 -4
- package/dist/{installTerminalHooks-CMpOV3RU.mjs → installTerminalHooks-ELDoqh0A.mjs} +4 -4
- package/dist/lib.cjs +2 -2
- package/dist/lib.d.cts +77 -1
- package/dist/lib.d.mts +77 -1
- package/dist/lib.mjs +2 -2
- package/dist/{limits-DmMdchTz.mjs → limits-A2c-3M-_.mjs} +162 -11
- package/dist/{limits-o29KXBgj.cjs → limits-CyuIt-Vl.cjs} +169 -10
- package/dist/{mcp-C_uejotl.mjs → mcp-Bo934Kpy.mjs} +4 -4
- package/dist/{mcp-CwK1nI7Y.cjs → mcp-CXkvnztT.cjs} +4 -4
- package/dist/{piRuntime-Dj5WnqzJ.cjs → piRuntime-CJjA26oD.cjs} +2 -2
- package/dist/{piRuntime-Cu2-8nOT.mjs → piRuntime-DdEY2mE9.mjs} +2 -2
- package/dist/{piTerminal-D-BIbov9.cjs → piTerminal-CpU3hzrX.cjs} +6 -6
- package/dist/{piTerminal-DanZeBrO.mjs → piTerminal-DwWmgumi.mjs} +6 -6
- package/dist/{runGemini-CZ_lhMWY.mjs → runGemini-Cj6oJQ-I.mjs} +6 -6
- package/dist/{runGemini--QqK-Ap5.cjs → runGemini-DS-0qm0L.cjs} +6 -6
- package/dist/{runOpenClaw-C0xLhTvk.cjs → runOpenClaw-CvSm8oTc.cjs} +5 -5
- package/dist/{runOpenClaw-CxgLpm38.mjs → runOpenClaw-DUCQxkI-.mjs} +5 -5
- package/dist/{send-CTcGMVuG.cjs → send-CHxobaoI.cjs} +43 -31
- package/dist/{send-BaOkDgH0.mjs → send-D5CyRyZ_.mjs} +43 -31
- package/dist/{sessions-OmvyJb0n.cjs → sessions-BFlIrKCY.cjs} +133 -49
- package/dist/{sessions-Bn304xF1.mjs → sessions-DsE01xKc.mjs} +133 -49
- package/dist/{spawn-BKNabutV.mjs → spawn-CdihhwHs.mjs} +4 -4
- package/dist/{spawn-CC6V2KiA.cjs → spawn-HV345RN-.cjs} +4 -4
- package/dist/{teams-CLbS2_4Q.mjs → teams-BE8nMB6O.mjs} +5 -5
- package/dist/{teams-XtrH3_h3.cjs → teams-BahinMnH.cjs} +5 -5
- package/dist/{todo-CEDvKNXK.cjs → todo-CMMQ6yhU.cjs} +2 -2
- package/dist/{todo-BcPbNKt2.mjs → todo-CuQAkSp2.mjs} +2 -2
- package/dist/{types-CphWjqHc.cjs → types-B0sSi6eM.cjs} +77 -4
- package/dist/{types-BEVzYSoV.mjs → types-BrGJ7PJz.mjs} +77 -4
- 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,6 +6249,8 @@ function startDaemonControlServer({
|
|
|
6215
6249
|
onSessionStateEvent,
|
|
6216
6250
|
onClaudeAuthFailed,
|
|
6217
6251
|
onSessionTurnEvent,
|
|
6252
|
+
onSessionBackgroundTasks,
|
|
6253
|
+
getBackgroundTasks,
|
|
6218
6254
|
pushClipboard,
|
|
6219
6255
|
pushFilePreview,
|
|
6220
6256
|
onTerminalHook,
|
|
@@ -6275,8 +6311,10 @@ function startDaemonControlServer({
|
|
|
6275
6311
|
schema: {
|
|
6276
6312
|
body: z.object({
|
|
6277
6313
|
sessionId: z.string(),
|
|
6278
|
-
event: z.enum(["completed", "needs_input", "auth_failed", "turn_started", "turn_ended"]),
|
|
6279
|
-
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()
|
|
6280
6318
|
}),
|
|
6281
6319
|
response: {
|
|
6282
6320
|
200: z.object({
|
|
@@ -6287,6 +6325,10 @@ function startDaemonControlServer({
|
|
|
6287
6325
|
}, async (request) => {
|
|
6288
6326
|
const { sessionId, event, spawnedBy } = request.body;
|
|
6289
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
|
+
}
|
|
6290
6332
|
if (event === "auth_failed") {
|
|
6291
6333
|
onClaudeAuthFailed?.(sessionId);
|
|
6292
6334
|
return { status: "ok" };
|
|
@@ -6306,7 +6348,9 @@ function startDaemonControlServer({
|
|
|
6306
6348
|
startedBy: z.string(),
|
|
6307
6349
|
happySessionId: z.string(),
|
|
6308
6350
|
pid: z.number(),
|
|
6309
|
-
turnActive: z.boolean()
|
|
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()
|
|
6310
6354
|
}))
|
|
6311
6355
|
})
|
|
6312
6356
|
}
|
|
@@ -6319,7 +6363,8 @@ function startDaemonControlServer({
|
|
|
6319
6363
|
startedBy: child.startedBy,
|
|
6320
6364
|
happySessionId: child.happySessionId,
|
|
6321
6365
|
pid: child.pid,
|
|
6322
|
-
turnActive: isTurnActive ? isTurnActive(child.happySessionId) : false
|
|
6366
|
+
turnActive: isTurnActive ? isTurnActive(child.happySessionId) : false,
|
|
6367
|
+
...getBackgroundTasks ? { backgroundTasks: getBackgroundTasks(child.happySessionId) } : {}
|
|
6323
6368
|
}))
|
|
6324
6369
|
};
|
|
6325
6370
|
});
|
|
@@ -6887,6 +6932,170 @@ function createPeerCoordinator(deps) {
|
|
|
6887
6932
|
};
|
|
6888
6933
|
}
|
|
6889
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
|
+
|
|
6890
7099
|
const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
|
|
6891
7100
|
|
|
6892
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
|
|
@@ -10595,6 +10804,13 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
|
|
|
10595
10804
|
if (edgeFell) item.sawTurn = false;
|
|
10596
10805
|
const turn = await readTurn(sessionId, item.runId).catch(() => null);
|
|
10597
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;
|
|
10598
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}` });
|
|
10599
10815
|
else await finish(item, { claimId: item.claimId, status: "done", sessionId, summary: clipText(turn.answer, AUTOMATION_SUMMARY_MAX_CHARS) });
|
|
10600
10816
|
}
|
|
@@ -10889,6 +11105,7 @@ async function startDaemon() {
|
|
|
10889
11105
|
logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
10890
11106
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
10891
11107
|
const turnActivity = new TurnActivityTracker();
|
|
11108
|
+
const backgroundTasks = new BackgroundTaskTracker();
|
|
10892
11109
|
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
10893
11110
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
10894
11111
|
let teamWorker;
|
|
@@ -11749,6 +11966,7 @@ async function startDaemon() {
|
|
|
11749
11966
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
11750
11967
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
11751
11968
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11969
|
+
if (session?.happySessionId) backgroundTasks.forget(session.happySessionId);
|
|
11752
11970
|
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
11753
11971
|
if (session?.happySessionId && session.encryption) {
|
|
11754
11972
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
@@ -11835,6 +12053,8 @@ async function startDaemon() {
|
|
|
11835
12053
|
onSessionStateEvent,
|
|
11836
12054
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
11837
12055
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
12056
|
+
onSessionBackgroundTasks: (sessionId, tasks) => backgroundTasks.apply(sessionId, tasks),
|
|
12057
|
+
getBackgroundTasks: (sessionId) => backgroundTasks.report(sessionId),
|
|
11838
12058
|
onSessionEdit: (edit) => {
|
|
11839
12059
|
peerCoordinator.onEdit(edit);
|
|
11840
12060
|
},
|
|
@@ -11934,6 +12154,7 @@ async function startDaemon() {
|
|
|
11934
12154
|
listTrackedSessionIds: () => [...pidToTrackedSession.values()].map((session) => session.happySessionId).filter((sessionId) => typeof sessionId === "string"),
|
|
11935
12155
|
requestShutdown: () => requestShutdown("happy-app")
|
|
11936
12156
|
});
|
|
12157
|
+
apiMachine.setRemoteSessionOpsHandlers(createRemoteSessionOpsHandlers(machineId));
|
|
11937
12158
|
const claudeAuthService = new ClaudeAuthService({
|
|
11938
12159
|
happyHomeDir: configuration.happyHomeDir,
|
|
11939
12160
|
happyLibDir: projectPath(),
|
|
@@ -11966,8 +12187,9 @@ async function startDaemon() {
|
|
|
11966
12187
|
enabled: async () => ((await readSettings()).cliAutoUpdate ?? "idle") !== "off",
|
|
11967
12188
|
// B-466: "idle" = no agent turn in flight. Idle wrappers survive a
|
|
11968
12189
|
// handover (they talk to the server themselves) and web terminals live
|
|
11969
|
-
// in tmux, so neither is a reason to hold an update.
|
|
11970
|
-
|
|
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(),
|
|
11971
12193
|
install: installCliSafely,
|
|
11972
12194
|
publish: (state) => {
|
|
11973
12195
|
cliUpdateStateRef = state;
|
|
@@ -12022,7 +12244,7 @@ async function startDaemon() {
|
|
|
12022
12244
|
} catch {
|
|
12023
12245
|
}
|
|
12024
12246
|
}
|
|
12025
|
-
if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
|
|
12247
|
+
if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
|
|
12026
12248
|
const preflight = await preflightNewBundle(bundlePath);
|
|
12027
12249
|
if (preflight.action === "hold") {
|
|
12028
12250
|
if (lastHandoverHold !== preflight.reason) {
|
|
@@ -12032,7 +12254,7 @@ async function startDaemon() {
|
|
|
12032
12254
|
updateController.holdHandover(preflight.reason);
|
|
12033
12255
|
return;
|
|
12034
12256
|
}
|
|
12035
|
-
if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
|
|
12257
|
+
if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
|
|
12036
12258
|
lastHandoverHold = null;
|
|
12037
12259
|
logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
|
|
12038
12260
|
teamWorker?.stop();
|
|
@@ -12098,6 +12320,7 @@ async function startDaemon() {
|
|
|
12098
12320
|
spawn: spawnSession,
|
|
12099
12321
|
live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
|
|
12100
12322
|
turnActive: (id) => turnActivity.activeSessions().includes(id),
|
|
12323
|
+
backgroundTasks: (id) => backgroundTasks.count(id),
|
|
12101
12324
|
log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
|
|
12102
12325
|
});
|
|
12103
12326
|
void apiMachine.updateMachineMetadata((current) => ({
|
|
@@ -12391,36 +12614,54 @@ ${sessions.map(describeSummary).join("\n")}`);
|
|
|
12391
12614
|
}
|
|
12392
12615
|
});
|
|
12393
12616
|
mcp.registerTool("session_read", {
|
|
12394
|
-
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).",
|
|
12395
12618
|
title: "Read Session Transcript",
|
|
12396
12619
|
inputSchema: {
|
|
12397
12620
|
sessionId: z.string().describe("The Happy session id to read"),
|
|
12398
|
-
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)")
|
|
12399
12623
|
}
|
|
12400
12624
|
}, async (args) => {
|
|
12401
12625
|
if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12402
12626
|
try {
|
|
12403
|
-
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 } : {} });
|
|
12404
12636
|
const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
|
|
12405
|
-
return ok(`${describeSummary(result.summary)}
|
|
12406
|
-
--- 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" : ""} ---
|
|
12407
12639
|
${body}`);
|
|
12408
12640
|
} catch (error) {
|
|
12409
12641
|
return fail(`Failed to read session: ${error instanceof Error ? error.message : String(error)}`);
|
|
12410
12642
|
}
|
|
12411
12643
|
});
|
|
12412
12644
|
mcp.registerTool("session_send", {
|
|
12413
|
-
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.",
|
|
12414
12646
|
title: "Send Message to Session",
|
|
12415
12647
|
inputSchema: {
|
|
12416
12648
|
sessionId: z.string().describe("The Happy session id to message"),
|
|
12417
12649
|
text: z.string().describe("The message text to send"),
|
|
12418
|
-
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)")
|
|
12419
12652
|
}
|
|
12420
12653
|
}, async (args) => {
|
|
12421
12654
|
if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12422
12655
|
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
12423
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
|
+
}
|
|
12424
12665
|
const persisted = await waitForSessionKey(args.sessionId, 0);
|
|
12425
12666
|
const result = await deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12426
12667
|
resume: args.resume === true,
|
|
@@ -12432,6 +12673,7 @@ ${body}`);
|
|
|
12432
12673
|
}
|
|
12433
12674
|
return ok(`Message delivered${result.resumed ? " (session resumed on this machine)" : ""} to ${describeSession(args.sessionId, persisted, { live: true })}`);
|
|
12434
12675
|
} catch (error) {
|
|
12676
|
+
if (error instanceof RemoteSessionOpsError) return fail(`Failed to send (${error.code}): ${error.message}`);
|
|
12435
12677
|
return fail(`Failed to send: ${error instanceof Error ? error.message : String(error)}`);
|
|
12436
12678
|
}
|
|
12437
12679
|
});
|
|
@@ -13239,7 +13481,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13239
13481
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13240
13482
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13241
13483
|
const fork = deps.run ?? (async (input) => {
|
|
13242
|
-
const { query } = await import('./index-
|
|
13484
|
+
const { query } = await import('./index-DQcRkI9r.mjs');
|
|
13243
13485
|
return runSideQuestion(query, input);
|
|
13244
13486
|
});
|
|
13245
13487
|
const run = async (input, live) => {
|
|
@@ -18547,7 +18789,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18547
18789
|
return;
|
|
18548
18790
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18549
18791
|
try {
|
|
18550
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-
|
|
18792
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-ELDoqh0A.mjs');
|
|
18551
18793
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18552
18794
|
if (command.action === "help") {
|
|
18553
18795
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18564,7 +18806,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18564
18806
|
}
|
|
18565
18807
|
} else if (subcommand === "agent-home") {
|
|
18566
18808
|
try {
|
|
18567
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-
|
|
18809
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-BaJ9xtxB.mjs');
|
|
18568
18810
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18569
18811
|
process.exit(0);
|
|
18570
18812
|
} catch (error) {
|
|
@@ -18573,7 +18815,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18573
18815
|
}
|
|
18574
18816
|
} else if (subcommand === "spawn") {
|
|
18575
18817
|
try {
|
|
18576
|
-
const { handleSpawnCommand } = await import('./spawn-
|
|
18818
|
+
const { handleSpawnCommand } = await import('./spawn-CdihhwHs.mjs');
|
|
18577
18819
|
await handleSpawnCommand(args.slice(1));
|
|
18578
18820
|
} catch (error) {
|
|
18579
18821
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18585,7 +18827,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18585
18827
|
return;
|
|
18586
18828
|
} else if (subcommand === "sessions") {
|
|
18587
18829
|
try {
|
|
18588
|
-
const { handleSessionsCommand } = await import('./sessions-
|
|
18830
|
+
const { handleSessionsCommand } = await import('./sessions-DsE01xKc.mjs');
|
|
18589
18831
|
await handleSessionsCommand(args.slice(1));
|
|
18590
18832
|
} catch (error) {
|
|
18591
18833
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18597,7 +18839,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18597
18839
|
return;
|
|
18598
18840
|
} else if (subcommand === "send") {
|
|
18599
18841
|
try {
|
|
18600
|
-
const { handleSendCommand } = await import('./send-
|
|
18842
|
+
const { handleSendCommand } = await import('./send-D5CyRyZ_.mjs');
|
|
18601
18843
|
await handleSendCommand(args.slice(1));
|
|
18602
18844
|
} catch (error) {
|
|
18603
18845
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18703,9 +18945,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18703
18945
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
18704
18946
|
const projectId = args[3];
|
|
18705
18947
|
try {
|
|
18706
|
-
const { saveGoogleCloudProjectToConfig } = await import('./config-
|
|
18707
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-
|
|
18708
|
-
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; });
|
|
18709
18951
|
let userEmail = void 0;
|
|
18710
18952
|
try {
|
|
18711
18953
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -18736,7 +18978,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18736
18978
|
}
|
|
18737
18979
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
18738
18980
|
try {
|
|
18739
|
-
const { readGeminiLocalConfig } = await import('./config-
|
|
18981
|
+
const { readGeminiLocalConfig } = await import('./config-aAbhJllb.mjs');
|
|
18740
18982
|
const config = readGeminiLocalConfig();
|
|
18741
18983
|
if (config.googleCloudProject) {
|
|
18742
18984
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -18776,7 +19018,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18776
19018
|
process.exit(0);
|
|
18777
19019
|
}
|
|
18778
19020
|
try {
|
|
18779
|
-
const { runGemini } = await import('./runGemini-
|
|
19021
|
+
const { runGemini } = await import('./runGemini-Cj6oJQ-I.mjs');
|
|
18780
19022
|
let startedBy = void 0;
|
|
18781
19023
|
for (let i = 1; i < args.length; i++) {
|
|
18782
19024
|
if (args[i] === "--started-by") {
|
|
@@ -18799,7 +19041,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18799
19041
|
} else if (subcommand === "pi") {
|
|
18800
19042
|
if (args[1] === "--terminal") {
|
|
18801
19043
|
try {
|
|
18802
|
-
const { runPiTerminal } = await import('./piTerminal-
|
|
19044
|
+
const { runPiTerminal } = await import('./piTerminal-DwWmgumi.mjs');
|
|
18803
19045
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
18804
19046
|
} catch (error) {
|
|
18805
19047
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18816,7 +19058,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18816
19058
|
}
|
|
18817
19059
|
};
|
|
18818
19060
|
try {
|
|
18819
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
19061
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BVu8Cufk.mjs');
|
|
18820
19062
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
18821
19063
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
18822
19064
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -18844,7 +19086,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18844
19086
|
return;
|
|
18845
19087
|
} else if (subcommand === "acp") {
|
|
18846
19088
|
try {
|
|
18847
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
19089
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-BVu8Cufk.mjs');
|
|
18848
19090
|
let startedBy = void 0;
|
|
18849
19091
|
let verbose = false;
|
|
18850
19092
|
const acpArgs = [];
|
|
@@ -18884,7 +19126,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18884
19126
|
return;
|
|
18885
19127
|
} else if (subcommand === "openclaw") {
|
|
18886
19128
|
try {
|
|
18887
|
-
const { runOpenClaw } = await import('./runOpenClaw-
|
|
19129
|
+
const { runOpenClaw } = await import('./runOpenClaw-DUCQxkI-.mjs');
|
|
18888
19130
|
let startedBy = void 0;
|
|
18889
19131
|
let verbose = false;
|
|
18890
19132
|
let gatewayUrl;
|
|
@@ -18935,7 +19177,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18935
19177
|
return;
|
|
18936
19178
|
} else if (subcommand === "todo") {
|
|
18937
19179
|
try {
|
|
18938
|
-
const { handleTodoCommand } = await import('./todo-
|
|
19180
|
+
const { handleTodoCommand } = await import('./todo-CuQAkSp2.mjs');
|
|
18939
19181
|
await handleTodoCommand(args.slice(1));
|
|
18940
19182
|
} catch (error) {
|
|
18941
19183
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -18944,7 +19186,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18944
19186
|
return;
|
|
18945
19187
|
} else if (subcommand === "teams") {
|
|
18946
19188
|
try {
|
|
18947
|
-
const { handleTeamsCommand } = await import('./teams-
|
|
19189
|
+
const { handleTeamsCommand } = await import('./teams-BE8nMB6O.mjs');
|
|
18948
19190
|
await handleTeamsCommand(args.slice(1));
|
|
18949
19191
|
} catch (error) {
|
|
18950
19192
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -18953,7 +19195,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18953
19195
|
return;
|
|
18954
19196
|
} else if (subcommand === "auto") {
|
|
18955
19197
|
try {
|
|
18956
|
-
const { handleAutoCommand } = await import('./auto-
|
|
19198
|
+
const { handleAutoCommand } = await import('./auto-B1YAwaeU.mjs');
|
|
18957
19199
|
await handleAutoCommand(args.slice(1));
|
|
18958
19200
|
} catch (error) {
|
|
18959
19201
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -18965,7 +19207,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18965
19207
|
return;
|
|
18966
19208
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
18967
19209
|
try {
|
|
18968
|
-
const { handleMcpCommand } = await import('./mcp-
|
|
19210
|
+
const { handleMcpCommand } = await import('./mcp-Bo934Kpy.mjs');
|
|
18969
19211
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
18970
19212
|
} catch (error) {
|
|
18971
19213
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -18996,6 +19238,8 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18996
19238
|
console.log(JSON.stringify(sessions, null, 2));
|
|
18997
19239
|
const busy = sessions.filter((s) => s.turnActive === true);
|
|
18998
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(", ")}`);
|
|
18999
19243
|
}
|
|
19000
19244
|
} catch (error) {
|
|
19001
19245
|
console.log("No daemon running");
|