very-happy-cli 0.2.156 → 0.2.158
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-DIDRig5G.cjs} +2 -2
- package/dist/{AcpBackend-C3nc1u-s.mjs → AcpBackend-vDVLHuYJ.mjs} +2 -2
- package/dist/{AcpSessionManager-BVxkQnNf.cjs → AcpSessionManager-C44U99CC.cjs} +1 -1
- package/dist/{AcpSessionManager-dN3k1LbI.mjs → AcpSessionManager-CmRjfhn3.mjs} +1 -1
- package/dist/{agentGuidance-C604n0Zq.mjs → agentGuidance-CrR9l62U.mjs} +562 -30
- package/dist/{agentGuidance-C-CmV03f.cjs → agentGuidance-DwOWbX55.cjs} +581 -29
- package/dist/{agentHome-D2XGsoqs.mjs → agentHome-CyLmGH5L.mjs} +2 -2
- package/dist/{agentHome-CmIJNV7f.cjs → agentHome-Dw8Jx_CJ.cjs} +2 -2
- package/dist/{auto-B1I6G_Nf.mjs → auto-BlMZPwOk.mjs} +19 -10
- package/dist/{auto-CMDq4oIu.cjs → auto-DZbNcTXx.cjs} +19 -10
- package/dist/codex/happyMcpStdioBridge.cjs +2 -2
- package/dist/codex/happyMcpStdioBridge.mjs +2 -2
- package/dist/{config-BkTu25bH.mjs → config-BtPArqRH.mjs} +4 -4
- package/dist/{config-BYHSRY2E.cjs → config-DG84bjwF.cjs} +4 -4
- package/dist/{index-BbtR-Qto.cjs → index-BG932eCh.cjs} +292 -47
- package/dist/{index-DAAreiRF.mjs → index-CVCt3kSt.mjs} +289 -44
- package/dist/{index-CB8QnjvS.cjs → index-D5ScEErM.cjs} +4 -4
- package/dist/{index-B_-7NpGZ.cjs → index-DPCsWlWK.cjs} +7 -7
- package/dist/{index-PYO8Mdz4.mjs → index-DgO0FHOx.mjs} +4 -4
- package/dist/{index-B5QbzEga.mjs → index-ZChGsuB7.mjs} +8 -8
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{install-CNtXzwT_.mjs → install-DarS_Wco.mjs} +1 -1
- package/dist/{install-CO1ps6Wt.cjs → install-wKQGgXvN.cjs} +1 -1
- package/dist/{installTerminalHooks-CMpOV3RU.mjs → installTerminalHooks-CpNbRFPz.mjs} +4 -4
- package/dist/{installTerminalHooks-BOAnQ1Qe.cjs → installTerminalHooks-DcQfee_O.cjs} +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-D2uN090G.mjs} +163 -11
- package/dist/{limits-o29KXBgj.cjs → limits-kqFqhfsh.cjs} +170 -10
- package/dist/{mcp-C_uejotl.mjs → mcp-BN6bKIGb.mjs} +4 -4
- package/dist/{mcp-CwK1nI7Y.cjs → mcp-DGdeUUDJ.cjs} +4 -4
- package/dist/{piRuntime-Dj5WnqzJ.cjs → piRuntime-3qtbKujX.cjs} +2 -2
- package/dist/{piRuntime-Cu2-8nOT.mjs → piRuntime-bkw2NAZU.mjs} +2 -2
- package/dist/{piTerminal-D-BIbov9.cjs → piTerminal-BYxp3LMr.cjs} +6 -6
- package/dist/{piTerminal-DanZeBrO.mjs → piTerminal-DilKwM7B.mjs} +6 -6
- package/dist/{runGemini--QqK-Ap5.cjs → runGemini-28O2sqyI.cjs} +6 -6
- package/dist/{runGemini-CZ_lhMWY.mjs → runGemini-Cjii069p.mjs} +6 -6
- package/dist/{runOpenClaw-C0xLhTvk.cjs → runOpenClaw-CiMZad-H.cjs} +5 -5
- package/dist/{runOpenClaw-CxgLpm38.mjs → runOpenClaw-jaLr7KF1.mjs} +5 -5
- package/dist/{send-BaOkDgH0.mjs → send-C9mOtChi.mjs} +43 -31
- package/dist/{send-CTcGMVuG.cjs → send-D5ejHPXG.cjs} +43 -31
- package/dist/{sessions-OmvyJb0n.cjs → sessions-BNMPQoLE.cjs} +133 -49
- package/dist/{sessions-Bn304xF1.mjs → sessions-W6771g3X.mjs} +133 -49
- package/dist/{spawn-BKNabutV.mjs → spawn-D0QVhzRb.mjs} +4 -4
- package/dist/{spawn-CC6V2KiA.cjs → spawn-Ddtom1cb.cjs} +4 -4
- package/dist/{teams-XtrH3_h3.cjs → teams-BZ1tayg_.cjs} +5 -5
- package/dist/{teams-CLbS2_4Q.mjs → teams-CqSwtw2O.mjs} +5 -5
- package/dist/{todo-CEDvKNXK.cjs → todo-BYMg0fR0.cjs} +2 -2
- package/dist/{todo-BcPbNKt2.mjs → todo-BbusB1zM.mjs} +2 -2
- package/dist/{types-BEVzYSoV.mjs → types-DIBF4U0Y.mjs} +78 -5
- package/dist/{types-CphWjqHc.cjs → types-DnrPeN80.cjs} +78 -5
- 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-DIBF4U0Y.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-D2uN090G.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-CrR9l62U.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-CyLmGH5L.mjs');
|
|
5406
|
+
const { describeAgentHome } = await import('./types-DIBF4U0Y.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
|
}
|
|
@@ -10679,6 +10895,7 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
|
|
|
10679
10895
|
for (const item of tracked.values()) {
|
|
10680
10896
|
if (item.sessionId !== sessionId) continue;
|
|
10681
10897
|
if (event === "exited") item.exited = true;
|
|
10898
|
+
if (event === "idle") item.attentionReported = false;
|
|
10682
10899
|
if (event === "blocked" && !item.attentionReported) {
|
|
10683
10900
|
item.attentionReported = true;
|
|
10684
10901
|
void client.report(item.runId, { claimId: item.claimId, needsAttention: true, attentionReason: "needs_input" }).catch(() => deps.log(`Automation run ${item.runId} attention report failed`));
|
|
@@ -10889,6 +11106,7 @@ async function startDaemon() {
|
|
|
10889
11106
|
logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
10890
11107
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
10891
11108
|
const turnActivity = new TurnActivityTracker();
|
|
11109
|
+
const backgroundTasks = new BackgroundTaskTracker();
|
|
10892
11110
|
const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
|
|
10893
11111
|
const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
|
|
10894
11112
|
let teamWorker;
|
|
@@ -11749,6 +11967,7 @@ async function startDaemon() {
|
|
|
11749
11967
|
if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
|
|
11750
11968
|
if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
|
|
11751
11969
|
if (session?.happySessionId) turnActivity.forget(session.happySessionId);
|
|
11970
|
+
if (session?.happySessionId) backgroundTasks.forget(session.happySessionId);
|
|
11752
11971
|
if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
|
|
11753
11972
|
if (session?.happySessionId && session.encryption) {
|
|
11754
11973
|
sessionIdToFinishedSession.set(session.happySessionId, session);
|
|
@@ -11835,6 +12054,8 @@ async function startDaemon() {
|
|
|
11835
12054
|
onSessionStateEvent,
|
|
11836
12055
|
onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
|
|
11837
12056
|
onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
|
|
12057
|
+
onSessionBackgroundTasks: (sessionId, tasks) => backgroundTasks.apply(sessionId, tasks),
|
|
12058
|
+
getBackgroundTasks: (sessionId) => backgroundTasks.report(sessionId),
|
|
11838
12059
|
onSessionEdit: (edit) => {
|
|
11839
12060
|
peerCoordinator.onEdit(edit);
|
|
11840
12061
|
},
|
|
@@ -11934,6 +12155,7 @@ async function startDaemon() {
|
|
|
11934
12155
|
listTrackedSessionIds: () => [...pidToTrackedSession.values()].map((session) => session.happySessionId).filter((sessionId) => typeof sessionId === "string"),
|
|
11935
12156
|
requestShutdown: () => requestShutdown("happy-app")
|
|
11936
12157
|
});
|
|
12158
|
+
apiMachine.setRemoteSessionOpsHandlers(createRemoteSessionOpsHandlers(machineId));
|
|
11937
12159
|
const claudeAuthService = new ClaudeAuthService({
|
|
11938
12160
|
happyHomeDir: configuration.happyHomeDir,
|
|
11939
12161
|
happyLibDir: projectPath(),
|
|
@@ -11966,8 +12188,9 @@ async function startDaemon() {
|
|
|
11966
12188
|
enabled: async () => ((await readSettings()).cliAutoUpdate ?? "idle") !== "off",
|
|
11967
12189
|
// B-466: "idle" = no agent turn in flight. Idle wrappers survive a
|
|
11968
12190
|
// handover (they talk to the server themselves) and web terminals live
|
|
11969
|
-
// in tmux, so neither is a reason to hold an update.
|
|
11970
|
-
|
|
12191
|
+
// in tmux, so neither is a reason to hold an update. B-507: a session
|
|
12192
|
+
// whose turn ended but still has background tasks is busy too.
|
|
12193
|
+
idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny(),
|
|
11971
12194
|
install: installCliSafely,
|
|
11972
12195
|
publish: (state) => {
|
|
11973
12196
|
cliUpdateStateRef = state;
|
|
@@ -12022,7 +12245,7 @@ async function startDaemon() {
|
|
|
12022
12245
|
} catch {
|
|
12023
12246
|
}
|
|
12024
12247
|
}
|
|
12025
|
-
if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
|
|
12248
|
+
if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
|
|
12026
12249
|
const preflight = await preflightNewBundle(bundlePath);
|
|
12027
12250
|
if (preflight.action === "hold") {
|
|
12028
12251
|
if (lastHandoverHold !== preflight.reason) {
|
|
@@ -12032,7 +12255,7 @@ async function startDaemon() {
|
|
|
12032
12255
|
updateController.holdHandover(preflight.reason);
|
|
12033
12256
|
return;
|
|
12034
12257
|
}
|
|
12035
|
-
if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
|
|
12258
|
+
if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
|
|
12036
12259
|
lastHandoverHold = null;
|
|
12037
12260
|
logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
|
|
12038
12261
|
teamWorker?.stop();
|
|
@@ -12098,6 +12321,7 @@ async function startDaemon() {
|
|
|
12098
12321
|
spawn: spawnSession,
|
|
12099
12322
|
live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
|
|
12100
12323
|
turnActive: (id) => turnActivity.activeSessions().includes(id),
|
|
12324
|
+
backgroundTasks: (id) => backgroundTasks.count(id),
|
|
12101
12325
|
log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
|
|
12102
12326
|
});
|
|
12103
12327
|
void apiMachine.updateMachineMetadata((current) => ({
|
|
@@ -12391,36 +12615,54 @@ ${sessions.map(describeSummary).join("\n")}`);
|
|
|
12391
12615
|
}
|
|
12392
12616
|
});
|
|
12393
12617
|
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).
|
|
12618
|
+
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
12619
|
title: "Read Session Transcript",
|
|
12396
12620
|
inputSchema: {
|
|
12397
12621
|
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)")
|
|
12622
|
+
limit: z.number().optional().describe("How many recent messages to fetch (default 20, max 100)"),
|
|
12623
|
+
machineId: z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
|
|
12399
12624
|
}
|
|
12400
12625
|
}, async (args) => {
|
|
12401
12626
|
if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12402
12627
|
try {
|
|
12403
|
-
const
|
|
12628
|
+
const local = readPersistedSessions()[args.sessionId];
|
|
12629
|
+
if (local && !args.machineId) {
|
|
12630
|
+
const result2 = await readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
12631
|
+
const body2 = result2.transcript.length > 0 ? result2.transcript : "(no readable conversation content in this range)";
|
|
12632
|
+
return ok(`${describeSummary(result2.summary)}
|
|
12633
|
+
--- last ${result2.messageCount} message(s) ---
|
|
12634
|
+
${body2}`);
|
|
12635
|
+
}
|
|
12636
|
+
const result = await readRemoteTranscript(args.sessionId, { limit: args.limit ?? 20, ...args.machineId ? { machineId: args.machineId } : {} });
|
|
12404
12637
|
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) ---
|
|
12638
|
+
return ok(`${describeSummary(result.summary)} machine=${result.host} (${result.machineId})
|
|
12639
|
+
--- last ${result.messageCount} message(s)${result.truncated ? ", truncated to fit" : ""} ---
|
|
12407
12640
|
${body}`);
|
|
12408
12641
|
} catch (error) {
|
|
12409
12642
|
return fail(`Failed to read session: ${error instanceof Error ? error.message : String(error)}`);
|
|
12410
12643
|
}
|
|
12411
12644
|
});
|
|
12412
12645
|
mcp.registerTool("session_send", {
|
|
12413
|
-
description: "Send a user message into an existing session
|
|
12646
|
+
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
12647
|
title: "Send Message to Session",
|
|
12415
12648
|
inputSchema: {
|
|
12416
12649
|
sessionId: z.string().describe("The Happy session id to message"),
|
|
12417
12650
|
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
|
|
12651
|
+
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"),
|
|
12652
|
+
machineId: z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
|
|
12419
12653
|
}
|
|
12420
12654
|
}, async (args) => {
|
|
12421
12655
|
if (!isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
12422
12656
|
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
12423
12657
|
try {
|
|
12658
|
+
if (!readPersistedSessions()[args.sessionId] || args.machineId) {
|
|
12659
|
+
const remote = await sendRemoteMessage(args.sessionId, args.text, { resume: args.resume === true, sentFrom: "assistant", ...args.machineId ? { machineId: args.machineId } : {} });
|
|
12660
|
+
if (!remote.delivered) {
|
|
12661
|
+
const detail = remote.error ?? `session is ${remote.status}`;
|
|
12662
|
+
return fail(remote.stored ? `Not delivered (via ${remote.machine.host}): ${detail}` : `Session ${args.sessionId} on ${remote.machine.host} is ${remote.status}: ${detail}`);
|
|
12663
|
+
}
|
|
12664
|
+
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)}`);
|
|
12665
|
+
}
|
|
12424
12666
|
const persisted = await waitForSessionKey(args.sessionId, 0);
|
|
12425
12667
|
const result = await deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
12426
12668
|
resume: args.resume === true,
|
|
@@ -12432,6 +12674,7 @@ ${body}`);
|
|
|
12432
12674
|
}
|
|
12433
12675
|
return ok(`Message delivered${result.resumed ? " (session resumed on this machine)" : ""} to ${describeSession(args.sessionId, persisted, { live: true })}`);
|
|
12434
12676
|
} catch (error) {
|
|
12677
|
+
if (error instanceof RemoteSessionOpsError) return fail(`Failed to send (${error.code}): ${error.message}`);
|
|
12435
12678
|
return fail(`Failed to send: ${error instanceof Error ? error.message : String(error)}`);
|
|
12436
12679
|
}
|
|
12437
12680
|
});
|
|
@@ -13239,7 +13482,7 @@ function registerSideQuestionHandler(rpc, deps) {
|
|
|
13239
13482
|
const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
|
|
13240
13483
|
const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
|
|
13241
13484
|
const fork = deps.run ?? (async (input) => {
|
|
13242
|
-
const { query } = await import('./index-
|
|
13485
|
+
const { query } = await import('./index-DgO0FHOx.mjs');
|
|
13243
13486
|
return runSideQuestion(query, input);
|
|
13244
13487
|
});
|
|
13245
13488
|
const run = async (input, live) => {
|
|
@@ -18547,7 +18790,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18547
18790
|
return;
|
|
18548
18791
|
} else if (subcommand === "install-terminal-hooks") {
|
|
18549
18792
|
try {
|
|
18550
|
-
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-
|
|
18793
|
+
const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-CpNbRFPz.mjs');
|
|
18551
18794
|
const command = parseTerminalHooksArgs(args.slice(1));
|
|
18552
18795
|
if (command.action === "help") {
|
|
18553
18796
|
console.log(TERMINAL_HOOKS_HELP);
|
|
@@ -18564,7 +18807,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18564
18807
|
}
|
|
18565
18808
|
} else if (subcommand === "agent-home") {
|
|
18566
18809
|
try {
|
|
18567
|
-
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-
|
|
18810
|
+
const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-CyLmGH5L.mjs');
|
|
18568
18811
|
await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
|
|
18569
18812
|
process.exit(0);
|
|
18570
18813
|
} catch (error) {
|
|
@@ -18573,7 +18816,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18573
18816
|
}
|
|
18574
18817
|
} else if (subcommand === "spawn") {
|
|
18575
18818
|
try {
|
|
18576
|
-
const { handleSpawnCommand } = await import('./spawn-
|
|
18819
|
+
const { handleSpawnCommand } = await import('./spawn-D0QVhzRb.mjs');
|
|
18577
18820
|
await handleSpawnCommand(args.slice(1));
|
|
18578
18821
|
} catch (error) {
|
|
18579
18822
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18585,7 +18828,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18585
18828
|
return;
|
|
18586
18829
|
} else if (subcommand === "sessions") {
|
|
18587
18830
|
try {
|
|
18588
|
-
const { handleSessionsCommand } = await import('./sessions-
|
|
18831
|
+
const { handleSessionsCommand } = await import('./sessions-W6771g3X.mjs');
|
|
18589
18832
|
await handleSessionsCommand(args.slice(1));
|
|
18590
18833
|
} catch (error) {
|
|
18591
18834
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18597,7 +18840,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18597
18840
|
return;
|
|
18598
18841
|
} else if (subcommand === "send") {
|
|
18599
18842
|
try {
|
|
18600
|
-
const { handleSendCommand } = await import('./send-
|
|
18843
|
+
const { handleSendCommand } = await import('./send-C9mOtChi.mjs');
|
|
18601
18844
|
await handleSendCommand(args.slice(1));
|
|
18602
18845
|
} catch (error) {
|
|
18603
18846
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18703,9 +18946,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18703
18946
|
if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
|
|
18704
18947
|
const projectId = args[3];
|
|
18705
18948
|
try {
|
|
18706
|
-
const { saveGoogleCloudProjectToConfig } = await import('./config-
|
|
18707
|
-
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-
|
|
18708
|
-
const { ApiClient: ApiClient2 } = await import('./types-
|
|
18949
|
+
const { saveGoogleCloudProjectToConfig } = await import('./config-BtPArqRH.mjs');
|
|
18950
|
+
const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-D2uN090G.mjs').then(function (n) { return n.aO; });
|
|
18951
|
+
const { ApiClient: ApiClient2 } = await import('./types-DIBF4U0Y.mjs').then(function (n) { return n.a1; });
|
|
18709
18952
|
let userEmail = void 0;
|
|
18710
18953
|
try {
|
|
18711
18954
|
const credentials = await readCredentialsForConfiguredRelay2();
|
|
@@ -18736,7 +18979,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18736
18979
|
}
|
|
18737
18980
|
if (geminiSubcommand === "project" && args[2] === "get") {
|
|
18738
18981
|
try {
|
|
18739
|
-
const { readGeminiLocalConfig } = await import('./config-
|
|
18982
|
+
const { readGeminiLocalConfig } = await import('./config-BtPArqRH.mjs');
|
|
18740
18983
|
const config = readGeminiLocalConfig();
|
|
18741
18984
|
if (config.googleCloudProject) {
|
|
18742
18985
|
console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
|
|
@@ -18776,7 +19019,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18776
19019
|
process.exit(0);
|
|
18777
19020
|
}
|
|
18778
19021
|
try {
|
|
18779
|
-
const { runGemini } = await import('./runGemini-
|
|
19022
|
+
const { runGemini } = await import('./runGemini-Cjii069p.mjs');
|
|
18780
19023
|
let startedBy = void 0;
|
|
18781
19024
|
for (let i = 1; i < args.length; i++) {
|
|
18782
19025
|
if (args[i] === "--started-by") {
|
|
@@ -18799,7 +19042,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18799
19042
|
} else if (subcommand === "pi") {
|
|
18800
19043
|
if (args[1] === "--terminal") {
|
|
18801
19044
|
try {
|
|
18802
|
-
const { runPiTerminal } = await import('./piTerminal-
|
|
19045
|
+
const { runPiTerminal } = await import('./piTerminal-DilKwM7B.mjs');
|
|
18803
19046
|
process.exitCode = await runPiTerminal(args.slice(2));
|
|
18804
19047
|
} catch (error) {
|
|
18805
19048
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -18816,7 +19059,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18816
19059
|
}
|
|
18817
19060
|
};
|
|
18818
19061
|
try {
|
|
18819
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
19062
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-ZChGsuB7.mjs');
|
|
18820
19063
|
const parsed = parsePiRunnerArgs(args.slice(1));
|
|
18821
19064
|
const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
|
|
18822
19065
|
const { credentials } = await authAndSetupMachineIfNeeded();
|
|
@@ -18844,7 +19087,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18844
19087
|
return;
|
|
18845
19088
|
} else if (subcommand === "acp") {
|
|
18846
19089
|
try {
|
|
18847
|
-
const { runAcp, resolveAcpAgentConfig } = await import('./index-
|
|
19090
|
+
const { runAcp, resolveAcpAgentConfig } = await import('./index-ZChGsuB7.mjs');
|
|
18848
19091
|
let startedBy = void 0;
|
|
18849
19092
|
let verbose = false;
|
|
18850
19093
|
const acpArgs = [];
|
|
@@ -18884,7 +19127,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18884
19127
|
return;
|
|
18885
19128
|
} else if (subcommand === "openclaw") {
|
|
18886
19129
|
try {
|
|
18887
|
-
const { runOpenClaw } = await import('./runOpenClaw-
|
|
19130
|
+
const { runOpenClaw } = await import('./runOpenClaw-jaLr7KF1.mjs');
|
|
18888
19131
|
let startedBy = void 0;
|
|
18889
19132
|
let verbose = false;
|
|
18890
19133
|
let gatewayUrl;
|
|
@@ -18935,7 +19178,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18935
19178
|
return;
|
|
18936
19179
|
} else if (subcommand === "todo") {
|
|
18937
19180
|
try {
|
|
18938
|
-
const { handleTodoCommand } = await import('./todo-
|
|
19181
|
+
const { handleTodoCommand } = await import('./todo-BbusB1zM.mjs');
|
|
18939
19182
|
await handleTodoCommand(args.slice(1));
|
|
18940
19183
|
} catch (error) {
|
|
18941
19184
|
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
|
|
@@ -18944,7 +19187,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18944
19187
|
return;
|
|
18945
19188
|
} else if (subcommand === "teams") {
|
|
18946
19189
|
try {
|
|
18947
|
-
const { handleTeamsCommand } = await import('./teams-
|
|
19190
|
+
const { handleTeamsCommand } = await import('./teams-CqSwtw2O.mjs');
|
|
18948
19191
|
await handleTeamsCommand(args.slice(1));
|
|
18949
19192
|
} catch (error) {
|
|
18950
19193
|
console.error(error instanceof Error ? error.message : "Teams command failed");
|
|
@@ -18953,7 +19196,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18953
19196
|
return;
|
|
18954
19197
|
} else if (subcommand === "auto") {
|
|
18955
19198
|
try {
|
|
18956
|
-
const { handleAutoCommand } = await import('./auto-
|
|
19199
|
+
const { handleAutoCommand } = await import('./auto-BlMZPwOk.mjs');
|
|
18957
19200
|
await handleAutoCommand(args.slice(1));
|
|
18958
19201
|
} catch (error) {
|
|
18959
19202
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
|
|
@@ -18965,7 +19208,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18965
19208
|
return;
|
|
18966
19209
|
} else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
|
|
18967
19210
|
try {
|
|
18968
|
-
const { handleMcpCommand } = await import('./mcp-
|
|
19211
|
+
const { handleMcpCommand } = await import('./mcp-BN6bKIGb.mjs');
|
|
18969
19212
|
await handleMcpCommand(args[1] === "--terminal-tools");
|
|
18970
19213
|
} catch (error) {
|
|
18971
19214
|
process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -18996,6 +19239,8 @@ Conversation history is preserved on the server, but in-flight tool calls are in
|
|
|
18996
19239
|
console.log(JSON.stringify(sessions, null, 2));
|
|
18997
19240
|
const busy = sessions.filter((s) => s.turnActive === true);
|
|
18998
19241
|
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(", ")}`);
|
|
19242
|
+
const background = sessions.filter((s) => (s.backgroundTasks?.count ?? 0) > 0);
|
|
19243
|
+
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
19244
|
}
|
|
19000
19245
|
} catch (error) {
|
|
19001
19246
|
console.log("No daemon running");
|