very-happy-cli 0.2.91 → 0.2.92
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-GqveWg8u.cjs → AcpBackend-BP7id8L6.cjs} +1 -1
- package/dist/{AcpBackend-B5RRlzLU.mjs → AcpBackend-DDEsgc1c.mjs} +1 -1
- package/dist/{AcpSessionManager-Ij1pI5Ex.mjs → AcpSessionManager-C29t6wb_.mjs} +1 -1
- package/dist/{AcpSessionManager-YcrSZwvq.cjs → AcpSessionManager-Cys1Hsb8.cjs} +1 -1
- package/dist/{config-C4NCw7Dz.cjs → config-B77vhdwT.cjs} +2 -2
- package/dist/{config-ra8iKxdU.mjs → config-CJQw0z1o.mjs} +2 -2
- package/dist/{index-UCYD5Wk3.mjs → index-8LDXVYWi.mjs} +337 -35
- package/dist/{index-FkXJNwiy.cjs → index-B63Vjt4W.cjs} +353 -51
- package/dist/{index-CB5H7YI6.mjs → index-BP9pfzdZ.mjs} +5 -5
- package/dist/{index-C1HIm7NY.cjs → index-L3o5Gz-2.cjs} +4 -4
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-CoK5wNcA.cjs → installTerminalHooks-BBc2nij4.cjs} +2 -2
- package/dist/{installTerminalHooks-TkkGzzbI.mjs → installTerminalHooks-DDRrUG_n.mjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +69 -3
- package/dist/lib.d.mts +69 -3
- package/dist/lib.mjs +1 -1
- package/dist/{mcp-C3MiqObi.mjs → mcp-CYJAl5pk.mjs} +2 -2
- package/dist/{mcp-DcRvswVI.cjs → mcp-DbYXyw1U.cjs} +2 -2
- package/dist/{runGemini-Dnvd2gWq.cjs → runGemini-BDSWSID6.cjs} +4 -4
- package/dist/{runGemini-BbC0uwUj.mjs → runGemini-DHYs_Beq.mjs} +4 -4
- package/dist/{runOpenClaw-CnuTBNQW.cjs → runOpenClaw-DC9gb1bd.cjs} +3 -3
- package/dist/{runOpenClaw--nbqTW3K.mjs → runOpenClaw-DTaXxvNd.mjs} +3 -3
- package/dist/{send-DkKOPatx.cjs → send-BEdGwmA-.cjs} +2 -2
- package/dist/{send-9nJxn09Y.mjs → send-BQSaVz9r.mjs} +2 -2
- package/dist/{spawn-DE7WhS8Y.cjs → spawn-CK1ma_qc.cjs} +2 -2
- package/dist/{spawn-CRW4ioms.mjs → spawn-DS-Uz_Hw.mjs} +2 -2
- package/dist/{types-BKyjG23_.mjs → types-B_8X_rZk.mjs} +328 -85
- package/dist/{types-ClDADuxP.cjs → types-CjlB_fKT.cjs} +329 -85
- package/package.json +7 -7
|
@@ -29,7 +29,7 @@ import { SandboxManager } from '@anthropic-ai/sandbox-runtime';
|
|
|
29
29
|
import { Expo } from 'expo-server-sdk';
|
|
30
30
|
|
|
31
31
|
var name = "very-happy-cli";
|
|
32
|
-
var version = "0.2.
|
|
32
|
+
var version = "0.2.92";
|
|
33
33
|
var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
|
|
34
34
|
var author = "Kirill Dubovitskiy";
|
|
35
35
|
var contributors = [
|
|
@@ -142,12 +142,12 @@ var dependencies = {
|
|
|
142
142
|
zod: "^4.0.0"
|
|
143
143
|
};
|
|
144
144
|
var optionalDependencies = {
|
|
145
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
146
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
147
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
148
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
149
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
150
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
145
|
+
"very-happy-tools-arm64-darwin": "0.2.92",
|
|
146
|
+
"very-happy-tools-x64-darwin": "0.2.92",
|
|
147
|
+
"very-happy-tools-arm64-linux": "0.2.92",
|
|
148
|
+
"very-happy-tools-x64-linux": "0.2.92",
|
|
149
|
+
"very-happy-tools-arm64-win32": "0.2.92",
|
|
150
|
+
"very-happy-tools-x64-win32": "0.2.92"
|
|
151
151
|
};
|
|
152
152
|
var devDependencies = {
|
|
153
153
|
"@slopus/happy-wire": "workspace:*",
|
|
@@ -858,7 +858,11 @@ const UpdateSessionBodySchema = z.object({
|
|
|
858
858
|
t: z.literal("update-session"),
|
|
859
859
|
id: z.string(),
|
|
860
860
|
metadata: VersionedEncryptedValueSchema.nullish(),
|
|
861
|
-
agentState: VersionedNullableEncryptedValueSchema.nullish()
|
|
861
|
+
agentState: VersionedNullableEncryptedValueSchema.nullish(),
|
|
862
|
+
/** B-265: server-owned archive intent (ms epoch, null = not archived).
|
|
863
|
+
* Present only on the archive / unarchive transitions; absent elsewhere.
|
|
864
|
+
* Old clients strip it (default z.object) and fall back to `active`. */
|
|
865
|
+
archivedAt: z.number().nullable().optional()
|
|
862
866
|
});
|
|
863
867
|
const VersionedMachineEncryptedValueSchema = z.object({
|
|
864
868
|
version: z.number(),
|
|
@@ -997,7 +1001,9 @@ const WebTerminalListItemSchema = z$1.object({
|
|
|
997
1001
|
mirrorSessionId: z$1.string().optional(),
|
|
998
1002
|
/** B-150: auto-restored after a restart (ms epoch) — the directory and the
|
|
999
1003
|
* conversation carried over, the processes did not. Cleared once opened. */
|
|
1000
|
-
restoredAt: z$1.number().optional()
|
|
1004
|
+
restoredAt: z$1.number().optional(),
|
|
1005
|
+
/** B-265: @vh_title_manual is set (user renamed it). */
|
|
1006
|
+
manual: z$1.boolean().optional()
|
|
1001
1007
|
});
|
|
1002
1008
|
const ClosedTerminalRecordSchema = z$1.object({
|
|
1003
1009
|
id: z$1.string(),
|
|
@@ -1011,6 +1017,9 @@ const ClosedTerminalRecordSchema = z$1.object({
|
|
|
1011
1017
|
/** B-149: 'daemon-gap' = died while no daemon was watching (restart/reboot);
|
|
1012
1018
|
* absent means an ordinary observed close. */
|
|
1013
1019
|
reason: z$1.enum(["closed", "daemon-gap"]).optional(),
|
|
1020
|
+
/** B-265: tags + manual-rename flag at close time (restored verbatim). */
|
|
1021
|
+
tags: z$1.array(z$1.string()).optional(),
|
|
1022
|
+
manual: z$1.boolean().optional(),
|
|
1014
1023
|
closedAt: z$1.number()
|
|
1015
1024
|
});
|
|
1016
1025
|
z$1.object({
|
|
@@ -1055,6 +1064,15 @@ z$1.object({
|
|
|
1055
1064
|
* ignore the field; old daemons never write it (web renders nothing).
|
|
1056
1065
|
*/
|
|
1057
1066
|
closedTerminals: z$1.array(ClosedTerminalRecordSchema).optional(),
|
|
1067
|
+
/**
|
|
1068
|
+
* B-265 capability flag: this daemon answers `restore-terminal`. Same trust
|
|
1069
|
+
* rule as webTerminals — only `detectedAt >= startedAt` counts, because a
|
|
1070
|
+
* downgraded daemon spreads the stale field forward on connect.
|
|
1071
|
+
*/
|
|
1072
|
+
terminalRestore: z$1.object({
|
|
1073
|
+
rpcAvailable: z$1.boolean(),
|
|
1074
|
+
detectedAt: z$1.number()
|
|
1075
|
+
}).optional(),
|
|
1058
1076
|
/** Relay-owned CLI compatibility/update policy last checked by this daemon. */
|
|
1059
1077
|
cliUpdate: CliUpdateStateSchema.optional()
|
|
1060
1078
|
});
|
|
@@ -3081,10 +3099,19 @@ class ApiSessionClient extends EventEmitter {
|
|
|
3081
3099
|
directInboundLocalIds = /* @__PURE__ */ new Set();
|
|
3082
3100
|
routedInboundLocalIds = /* @__PURE__ */ new Set();
|
|
3083
3101
|
handoverInFlight = null;
|
|
3084
|
-
|
|
3102
|
+
/** B-265 reconnect: the message cursor was seeded from the server, so
|
|
3103
|
+
* nothing before it is history to replay — but socket fast-path routing
|
|
3104
|
+
* must wait for the first fetch, otherwise a message that lands between
|
|
3105
|
+
* the seed and the first fetch is routed twice (socket + fetch). */
|
|
3106
|
+
awaitingInitialFetch = false;
|
|
3107
|
+
constructor(token, session, opts) {
|
|
3085
3108
|
super();
|
|
3086
3109
|
this.token = token;
|
|
3087
3110
|
this.sessionId = session.id;
|
|
3111
|
+
if (opts?.initialSeq !== void 0 && opts.initialSeq >= 0) {
|
|
3112
|
+
this.lastSeq = opts.initialSeq;
|
|
3113
|
+
this.awaitingInitialFetch = true;
|
|
3114
|
+
}
|
|
3088
3115
|
this.metadata = session.metadata;
|
|
3089
3116
|
this.metadataVersion = session.metadataVersion;
|
|
3090
3117
|
this.agentState = session.agentState;
|
|
@@ -3144,7 +3171,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
3144
3171
|
}
|
|
3145
3172
|
if (data.body.t === "new-message") {
|
|
3146
3173
|
const messageSeq = data.body.message?.seq;
|
|
3147
|
-
if (this.lastSeq === 0) {
|
|
3174
|
+
if (this.lastSeq === 0 || this.awaitingInitialFetch) {
|
|
3148
3175
|
this.receiveSync.invalidate();
|
|
3149
3176
|
return;
|
|
3150
3177
|
}
|
|
@@ -3440,6 +3467,9 @@ class ApiSessionClient extends EventEmitter {
|
|
|
3440
3467
|
if (message.localId && this.directInboundLocalIds.delete(message.localId)) {
|
|
3441
3468
|
continue;
|
|
3442
3469
|
}
|
|
3470
|
+
if (message.localId && this.routedInboundLocalIds.has(message.localId)) {
|
|
3471
|
+
continue;
|
|
3472
|
+
}
|
|
3443
3473
|
if (message.content?.t !== "encrypted") {
|
|
3444
3474
|
continue;
|
|
3445
3475
|
}
|
|
@@ -3469,6 +3499,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
3469
3499
|
break;
|
|
3470
3500
|
}
|
|
3471
3501
|
}
|
|
3502
|
+
this.awaitingInitialFetch = false;
|
|
3472
3503
|
}
|
|
3473
3504
|
static MAX_OUTBOX_BATCH_SIZE = 50;
|
|
3474
3505
|
async flushOutbox() {
|
|
@@ -4716,6 +4747,101 @@ function registerTodoHandlers(manager) {
|
|
|
4716
4747
|
});
|
|
4717
4748
|
}
|
|
4718
4749
|
|
|
4750
|
+
function sanitizeTagList(value) {
|
|
4751
|
+
if (!Array.isArray(value)) return void 0;
|
|
4752
|
+
const out = [];
|
|
4753
|
+
for (const tag of value) {
|
|
4754
|
+
if (typeof tag !== "string") continue;
|
|
4755
|
+
const clean = tag.trim();
|
|
4756
|
+
if (clean && !out.includes(clean)) out.push(clean);
|
|
4757
|
+
if (out.length >= 64) break;
|
|
4758
|
+
}
|
|
4759
|
+
return out;
|
|
4760
|
+
}
|
|
4761
|
+
const LIVE_SNAPSHOT_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
4762
|
+
const LIVE_SNAPSHOT_MAX = 100;
|
|
4763
|
+
function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE_SNAPSHOT_MAX) {
|
|
4764
|
+
const out = /* @__PURE__ */ new Map();
|
|
4765
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
|
|
4766
|
+
const rows = [];
|
|
4767
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
4768
|
+
if (!id || !value || typeof value !== "object") continue;
|
|
4769
|
+
const { title, cwd, seenAt, tags, manual } = value;
|
|
4770
|
+
if (typeof seenAt !== "number" || !Number.isFinite(seenAt)) continue;
|
|
4771
|
+
if (now - seenAt >= ttlMs) continue;
|
|
4772
|
+
const tagList = sanitizeTagList(tags);
|
|
4773
|
+
rows.push([id, {
|
|
4774
|
+
title: typeof title === "string" && title.trim() ? title : void 0,
|
|
4775
|
+
cwd: typeof cwd === "string" && cwd ? cwd : void 0,
|
|
4776
|
+
...tagList !== void 0 ? { tags: tagList } : {},
|
|
4777
|
+
...manual === true ? { manual: true } : {},
|
|
4778
|
+
seenAt
|
|
4779
|
+
}]);
|
|
4780
|
+
}
|
|
4781
|
+
rows.sort((a, b) => b[1].seenAt - a[1].seenAt);
|
|
4782
|
+
for (const [id, info] of rows.slice(0, max)) out.set(id, info);
|
|
4783
|
+
return out;
|
|
4784
|
+
}
|
|
4785
|
+
function serializeLiveSnapshot(map, max = LIVE_SNAPSHOT_MAX) {
|
|
4786
|
+
const rows = [...map.entries()].sort((a, b) => b[1].seenAt - a[1].seenAt).slice(0, max);
|
|
4787
|
+
const out = {};
|
|
4788
|
+
for (const [id, info] of rows) out[id] = info;
|
|
4789
|
+
return out;
|
|
4790
|
+
}
|
|
4791
|
+
function liveSnapshotChanged(prev, next) {
|
|
4792
|
+
if (prev.size !== next.size) return true;
|
|
4793
|
+
for (const [id, info] of next) {
|
|
4794
|
+
const before = prev.get(id);
|
|
4795
|
+
if (!before) return true;
|
|
4796
|
+
if (before.title !== info.title || before.cwd !== info.cwd) return true;
|
|
4797
|
+
if (!!before.manual !== !!info.manual) return true;
|
|
4798
|
+
if ((before.tags ?? []).join("\0") !== (info.tags ?? []).join("\0")) return true;
|
|
4799
|
+
}
|
|
4800
|
+
return false;
|
|
4801
|
+
}
|
|
4802
|
+
const CLAUDE_SESSION_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
4803
|
+
function isClaudeSessionId(value) {
|
|
4804
|
+
return typeof value === "string" && CLAUDE_SESSION_ID_RE.test(value);
|
|
4805
|
+
}
|
|
4806
|
+
function pickMirrorForTerminal(sessions, terminalId) {
|
|
4807
|
+
if (!terminalId) return null;
|
|
4808
|
+
let best = null;
|
|
4809
|
+
for (const [id, session] of Object.entries(sessions || {})) {
|
|
4810
|
+
const meta = session?.metadata;
|
|
4811
|
+
if (!meta || meta.terminalId !== terminalId) continue;
|
|
4812
|
+
const savedAt = typeof session.savedAt === "number" ? session.savedAt : 0;
|
|
4813
|
+
if (best && savedAt <= best.savedAt) continue;
|
|
4814
|
+
best = {
|
|
4815
|
+
id,
|
|
4816
|
+
savedAt,
|
|
4817
|
+
claude: isClaudeSessionId(meta.claudeSessionId) ? meta.claudeSessionId : void 0
|
|
4818
|
+
};
|
|
4819
|
+
}
|
|
4820
|
+
return best ? { sessionId: best.id, claudeSessionId: best.claude } : null;
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
const TERMINAL_ID_RE$1 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
4824
|
+
function planTerminalRestore(record, facts) {
|
|
4825
|
+
if (facts.tmuxAlive) return { kind: "already-live" };
|
|
4826
|
+
if (!record.cwd || !facts.cwdExists(record.cwd)) return { kind: "error", reason: "missing-cwd" };
|
|
4827
|
+
const resumable = isClaudeSessionId(record.claudeSessionId) && facts.conversationExists(record.cwd, record.claudeSessionId);
|
|
4828
|
+
return {
|
|
4829
|
+
kind: "create",
|
|
4830
|
+
terminalId: record.id,
|
|
4831
|
+
cwd: record.cwd,
|
|
4832
|
+
title: record.title,
|
|
4833
|
+
manual: record.manual === true,
|
|
4834
|
+
tags: record.tags,
|
|
4835
|
+
...resumable ? { command: `claude --resume ${record.claudeSessionId}` } : {}
|
|
4836
|
+
};
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
function getProjectPath(workingDirectory) {
|
|
4840
|
+
const projectId = resolve$1(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
4841
|
+
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
4842
|
+
return join(claudeConfigDir, "projects", projectId);
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4719
4845
|
var xtermHeadless = {};
|
|
4720
4846
|
|
|
4721
4847
|
var hasRequiredXtermHeadless;
|
|
@@ -9766,63 +9892,6 @@ function sendTerminalNotification(opts) {
|
|
|
9766
9892
|
});
|
|
9767
9893
|
}
|
|
9768
9894
|
|
|
9769
|
-
const LIVE_SNAPSHOT_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
9770
|
-
const LIVE_SNAPSHOT_MAX = 100;
|
|
9771
|
-
function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE_SNAPSHOT_MAX) {
|
|
9772
|
-
const out = /* @__PURE__ */ new Map();
|
|
9773
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
|
|
9774
|
-
const rows = [];
|
|
9775
|
-
for (const [id, value] of Object.entries(raw)) {
|
|
9776
|
-
if (!id || !value || typeof value !== "object") continue;
|
|
9777
|
-
const { title, cwd, seenAt } = value;
|
|
9778
|
-
if (typeof seenAt !== "number" || !Number.isFinite(seenAt)) continue;
|
|
9779
|
-
if (now - seenAt >= ttlMs) continue;
|
|
9780
|
-
rows.push([id, {
|
|
9781
|
-
title: typeof title === "string" && title.trim() ? title : void 0,
|
|
9782
|
-
cwd: typeof cwd === "string" && cwd ? cwd : void 0,
|
|
9783
|
-
seenAt
|
|
9784
|
-
}]);
|
|
9785
|
-
}
|
|
9786
|
-
rows.sort((a, b) => b[1].seenAt - a[1].seenAt);
|
|
9787
|
-
for (const [id, info] of rows.slice(0, max)) out.set(id, info);
|
|
9788
|
-
return out;
|
|
9789
|
-
}
|
|
9790
|
-
function serializeLiveSnapshot(map, max = LIVE_SNAPSHOT_MAX) {
|
|
9791
|
-
const rows = [...map.entries()].sort((a, b) => b[1].seenAt - a[1].seenAt).slice(0, max);
|
|
9792
|
-
const out = {};
|
|
9793
|
-
for (const [id, info] of rows) out[id] = info;
|
|
9794
|
-
return out;
|
|
9795
|
-
}
|
|
9796
|
-
function liveSnapshotChanged(prev, next) {
|
|
9797
|
-
if (prev.size !== next.size) return true;
|
|
9798
|
-
for (const [id, info] of next) {
|
|
9799
|
-
const before = prev.get(id);
|
|
9800
|
-
if (!before) return true;
|
|
9801
|
-
if (before.title !== info.title || before.cwd !== info.cwd) return true;
|
|
9802
|
-
}
|
|
9803
|
-
return false;
|
|
9804
|
-
}
|
|
9805
|
-
const CLAUDE_SESSION_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
9806
|
-
function isClaudeSessionId(value) {
|
|
9807
|
-
return typeof value === "string" && CLAUDE_SESSION_ID_RE.test(value);
|
|
9808
|
-
}
|
|
9809
|
-
function pickMirrorForTerminal(sessions, terminalId) {
|
|
9810
|
-
if (!terminalId) return null;
|
|
9811
|
-
let best = null;
|
|
9812
|
-
for (const [id, session] of Object.entries(sessions || {})) {
|
|
9813
|
-
const meta = session?.metadata;
|
|
9814
|
-
if (!meta || meta.terminalId !== terminalId) continue;
|
|
9815
|
-
const savedAt = typeof session.savedAt === "number" ? session.savedAt : 0;
|
|
9816
|
-
if (best && savedAt <= best.savedAt) continue;
|
|
9817
|
-
best = {
|
|
9818
|
-
id,
|
|
9819
|
-
savedAt,
|
|
9820
|
-
claude: isClaudeSessionId(meta.claudeSessionId) ? meta.claudeSessionId : void 0
|
|
9821
|
-
};
|
|
9822
|
-
}
|
|
9823
|
-
return best ? { sessionId: best.id, claudeSessionId: best.claude } : null;
|
|
9824
|
-
}
|
|
9825
|
-
|
|
9826
9895
|
const AUTO_RESTORE_DEFAULT_MAX = 6;
|
|
9827
9896
|
const AUTO_RESTORE_DEFAULT_WINDOW_HOURS = 24;
|
|
9828
9897
|
const AUTO_RESTORE_HARD_MAX = 20;
|
|
@@ -9881,6 +9950,8 @@ function selectAutoRestore(candidates, opts) {
|
|
|
9881
9950
|
}
|
|
9882
9951
|
plans.push({
|
|
9883
9952
|
terminalId: c.id,
|
|
9953
|
+
...c.tags !== void 0 ? { tags: c.tags } : {},
|
|
9954
|
+
...c.manual ? { manual: true } : {},
|
|
9884
9955
|
cwd: c.cwd,
|
|
9885
9956
|
title: c.title,
|
|
9886
9957
|
claudeSessionId: c.claudeSessionId,
|
|
@@ -9929,10 +10000,11 @@ function sanitizeClosedTerminals(raw, max = CLOSED_TERMINALS_MAX) {
|
|
|
9929
10000
|
const out = [];
|
|
9930
10001
|
for (const item of raw) {
|
|
9931
10002
|
if (!item || typeof item !== "object") continue;
|
|
9932
|
-
const { id, title, cwd, mirrorSessionId, claudeSessionId, reason, closedAt } = item;
|
|
10003
|
+
const { id, title, cwd, mirrorSessionId, claudeSessionId, reason, closedAt, tags, manual } = item;
|
|
9933
10004
|
if (typeof id !== "string" || id.length === 0 || typeof closedAt !== "number") continue;
|
|
9934
10005
|
if (seen.has(id)) continue;
|
|
9935
10006
|
seen.add(id);
|
|
10007
|
+
const tagList = sanitizeTagList(tags);
|
|
9936
10008
|
out.push({
|
|
9937
10009
|
id,
|
|
9938
10010
|
title: typeof title === "string" ? title : void 0,
|
|
@@ -9940,6 +10012,8 @@ function sanitizeClosedTerminals(raw, max = CLOSED_TERMINALS_MAX) {
|
|
|
9940
10012
|
mirrorSessionId: typeof mirrorSessionId === "string" ? mirrorSessionId : void 0,
|
|
9941
10013
|
claudeSessionId: typeof claudeSessionId === "string" ? claudeSessionId : void 0,
|
|
9942
10014
|
reason: reason === "daemon-gap" || reason === "closed" ? reason : void 0,
|
|
10015
|
+
...tagList !== void 0 ? { tags: tagList } : {},
|
|
10016
|
+
...manual === true ? { manual: true } : {},
|
|
9943
10017
|
closedAt
|
|
9944
10018
|
});
|
|
9945
10019
|
}
|
|
@@ -10063,6 +10137,9 @@ function startupInjectionArgs(tmuxSession, command) {
|
|
|
10063
10137
|
["send-keys", "-t", `=${tmuxSession}:`, "Enter"]
|
|
10064
10138
|
];
|
|
10065
10139
|
}
|
|
10140
|
+
function seenInfoOf(t) {
|
|
10141
|
+
return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual };
|
|
10142
|
+
}
|
|
10066
10143
|
function terminalListSignature(items) {
|
|
10067
10144
|
const canon = [...items].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((t) => [
|
|
10068
10145
|
t.id,
|
|
@@ -10699,7 +10776,7 @@ class WebTerminalManager {
|
|
|
10699
10776
|
}
|
|
10700
10777
|
/** Refresh the per-id {title, cwd} cache from an observed live list. */
|
|
10701
10778
|
noteSeen(list) {
|
|
10702
|
-
this.lastSeenInfo = new Map(list.map((t) => [t.id,
|
|
10779
|
+
this.lastSeenInfo = new Map(list.map((t) => [t.id, seenInfoOf(t)]));
|
|
10703
10780
|
}
|
|
10704
10781
|
/** Record one terminal as closed (dedupe/cap in the pure module) and
|
|
10705
10782
|
* persist. The confirming daemonState push rides the list refresh that
|
|
@@ -10773,7 +10850,7 @@ class WebTerminalManager {
|
|
|
10773
10850
|
const liveIds = new Set(list.map((t) => t.id));
|
|
10774
10851
|
this.reconcileRestoredSnapshot(liveIds);
|
|
10775
10852
|
const next = new Map(
|
|
10776
|
-
list.map((t) => [t.id,
|
|
10853
|
+
list.map((t) => [t.id, seenInfoOf(t)])
|
|
10777
10854
|
);
|
|
10778
10855
|
let changed = false;
|
|
10779
10856
|
const now = Date.now();
|
|
@@ -10790,6 +10867,8 @@ class WebTerminalManager {
|
|
|
10790
10867
|
id,
|
|
10791
10868
|
title: info.title,
|
|
10792
10869
|
cwd: info.cwd,
|
|
10870
|
+
tags: info.tags,
|
|
10871
|
+
manual: info.manual,
|
|
10793
10872
|
// Live binding first (authoritative while it exists), persisted
|
|
10794
10873
|
// metadata as the fallback once the mirror has been torn down.
|
|
10795
10874
|
mirrorSessionId: this.mirrorResolver?.(id) ?? mirror?.sessionId,
|
|
@@ -10840,6 +10919,8 @@ class WebTerminalManager {
|
|
|
10840
10919
|
id,
|
|
10841
10920
|
title: info.title,
|
|
10842
10921
|
cwd: info.cwd,
|
|
10922
|
+
tags: info.tags,
|
|
10923
|
+
manual: info.manual,
|
|
10843
10924
|
mirrorSessionId: mirror?.sessionId,
|
|
10844
10925
|
claudeSessionId: mirror?.claudeSessionId,
|
|
10845
10926
|
reason: "daemon-gap",
|
|
@@ -10850,6 +10931,8 @@ class WebTerminalManager {
|
|
|
10850
10931
|
id,
|
|
10851
10932
|
title: info.title,
|
|
10852
10933
|
cwd: info.cwd,
|
|
10934
|
+
tags: info.tags,
|
|
10935
|
+
manual: info.manual,
|
|
10853
10936
|
seenAt: info.seenAt,
|
|
10854
10937
|
claudeSessionId: mirror?.claudeSessionId
|
|
10855
10938
|
});
|
|
@@ -10925,6 +11008,51 @@ class WebTerminalManager {
|
|
|
10925
11008
|
* exactly like a terminal whose pty was reaped.
|
|
10926
11009
|
*/
|
|
10927
11010
|
restoreOneTerminal(plan) {
|
|
11011
|
+
return this.createDetachedTerminal(plan);
|
|
11012
|
+
}
|
|
11013
|
+
/**
|
|
11014
|
+
* B-265: `restore-terminal` — bring ONE closed terminal back with its
|
|
11015
|
+
* original id / cwd / title (+ manual flag) / tags and, when the record
|
|
11016
|
+
* knows a claude conversation whose JSONL is still on disk, `claude
|
|
11017
|
+
* --resume` injected. Idempotent: an id whose tmux session already exists
|
|
11018
|
+
* is a success. The closed record is left to `pruneClosedAgainstLive` (the
|
|
11019
|
+
* next tick sees the id live again); the tombstone is deliberately kept —
|
|
11020
|
+
* a live tmux session attaches regardless, and dropping it would re-open
|
|
11021
|
+
* the stale-client resurrection hole B-149 closed.
|
|
11022
|
+
*/
|
|
11023
|
+
restoreClosedTerminal(terminalId) {
|
|
11024
|
+
if (typeof terminalId !== "string" || !TERMINAL_ID_RE$1.test(terminalId)) return { type: "error", reason: "invalid-id" };
|
|
11025
|
+
if (!isTmuxAvailable()) return { type: "error", reason: "no-tmux" };
|
|
11026
|
+
const record = this.closedTerminals.find((r) => r.id === terminalId);
|
|
11027
|
+
if (!record) return { type: "error", reason: "no-record" };
|
|
11028
|
+
const env = ptyEnv();
|
|
11029
|
+
const alive = spawnSync(
|
|
11030
|
+
"tmux",
|
|
11031
|
+
tmuxArgs(["has-session", "-t", `=vh-${terminalId}:`]),
|
|
11032
|
+
{ stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
|
|
11033
|
+
).status === 0;
|
|
11034
|
+
const plan = planTerminalRestore(record, {
|
|
11035
|
+
tmuxAlive: alive,
|
|
11036
|
+
cwdExists: (cwd) => {
|
|
11037
|
+
try {
|
|
11038
|
+
return existsSync(cwd) && statSync(cwd).isDirectory();
|
|
11039
|
+
} catch {
|
|
11040
|
+
return false;
|
|
11041
|
+
}
|
|
11042
|
+
},
|
|
11043
|
+
conversationExists: (cwd, claudeSessionId) => existsSync(join(getProjectPath(cwd), `${claudeSessionId}.jsonl`))
|
|
11044
|
+
});
|
|
11045
|
+
if (plan.kind === "already-live") return { type: "success", terminalId };
|
|
11046
|
+
if (plan.kind === "error") return { type: "error", reason: plan.reason };
|
|
11047
|
+
if (!this.createDetachedTerminal(plan)) return { type: "error", reason: "create-failed" };
|
|
11048
|
+
logger.info(`[WEB TERMINAL] restored closed terminal ${terminalId} in ${plan.cwd}${plan.command ? " (conversation resumed)" : ""}`);
|
|
11049
|
+
this.kickListRefresh();
|
|
11050
|
+
return { type: "success", terminalId };
|
|
11051
|
+
}
|
|
11052
|
+
/** Shared cold-create for auto-restore (B-150) and restore-terminal
|
|
11053
|
+
* (B-265): same create-only env as the interactive path, title/tags
|
|
11054
|
+
* written back, optional startup command injected. */
|
|
11055
|
+
createDetachedTerminal(plan) {
|
|
10928
11056
|
const env = ptyEnv();
|
|
10929
11057
|
const name = `vh-${plan.terminalId}`;
|
|
10930
11058
|
const exists = spawnSync(
|
|
@@ -10961,18 +11089,35 @@ class WebTerminalManager {
|
|
|
10961
11089
|
tmuxArgs(["set-option", "-t", `=${name}:`, "@vh_title", plan.title]),
|
|
10962
11090
|
{ stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
|
|
10963
11091
|
);
|
|
11092
|
+
if (plan.manual) {
|
|
11093
|
+
spawnSync(
|
|
11094
|
+
"tmux",
|
|
11095
|
+
tmuxArgs(["set-option", "-t", `=${name}:`, "@vh_title_manual", "1"]),
|
|
11096
|
+
{ stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
|
|
11097
|
+
);
|
|
11098
|
+
}
|
|
11099
|
+
}
|
|
11100
|
+
const tags = plan.tags !== void 0 ? validateTerminalTags(plan.tags) : void 0;
|
|
11101
|
+
if (tags && tags.length > 0) {
|
|
11102
|
+
spawnSync(
|
|
11103
|
+
"tmux",
|
|
11104
|
+
tmuxArgs(["set-option", "-t", `=${name}:`, "@vh_tags", JSON.stringify(tags)]),
|
|
11105
|
+
{ stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
|
|
11106
|
+
);
|
|
10964
11107
|
}
|
|
10965
|
-
|
|
10966
|
-
|
|
11108
|
+
if (plan.command) {
|
|
11109
|
+
for (const argv of startupInjectionArgs(name, plan.command)) {
|
|
11110
|
+
spawnSync("tmux", tmuxArgs(argv), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
|
|
11111
|
+
}
|
|
10967
11112
|
}
|
|
10968
|
-
logger.info(`[WEB TERMINAL]
|
|
11113
|
+
logger.info(`[WEB TERMINAL] cold-created ${plan.terminalId} in ${plan.cwd}`);
|
|
10969
11114
|
return true;
|
|
10970
11115
|
}
|
|
10971
11116
|
/** Mirror the in-memory cache to disk, but only when something a restart
|
|
10972
11117
|
* would care about changed (see liveSnapshotChanged). */
|
|
10973
11118
|
persistLiveSnapshot(live, now) {
|
|
10974
11119
|
const candidate = /* @__PURE__ */ new Map();
|
|
10975
|
-
for (const [id, info] of live) candidate.set(id, { title: info.title, cwd: info.cwd, seenAt: now });
|
|
11120
|
+
for (const [id, info] of live) candidate.set(id, { title: info.title, cwd: info.cwd, tags: info.tags, manual: info.manual, seenAt: now });
|
|
10976
11121
|
if (!liveSnapshotChanged(this.lastPersistedSnapshot, candidate)) return;
|
|
10977
11122
|
this.lastPersistedSnapshot = candidate;
|
|
10978
11123
|
saveLiveSnapshot(candidate);
|
|
@@ -11547,6 +11692,8 @@ class WebTerminalManager {
|
|
|
11547
11692
|
id: terminalId,
|
|
11548
11693
|
title: info.title,
|
|
11549
11694
|
cwd: info.cwd,
|
|
11695
|
+
tags: info.tags,
|
|
11696
|
+
manual: info.manual,
|
|
11550
11697
|
mirrorSessionId: this.mirrorResolver?.(terminalId) ?? mirror?.sessionId,
|
|
11551
11698
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11552
11699
|
reason: "closed",
|
|
@@ -11630,7 +11777,8 @@ class WebTerminalManager {
|
|
|
11630
11777
|
// tmux last-activity (epoch s) → ms; optional so old daemons
|
|
11631
11778
|
// simply omit it and web clients fall back to createdAt.
|
|
11632
11779
|
activityAt: s.activity,
|
|
11633
|
-
agentState: this.probeAgentState(s.name, s.paneCurrentCommand)
|
|
11780
|
+
agentState: this.probeAgentState(s.name, s.paneCurrentCommand),
|
|
11781
|
+
manual: s.manual
|
|
11634
11782
|
});
|
|
11635
11783
|
}
|
|
11636
11784
|
return out;
|
|
@@ -12080,12 +12228,6 @@ function detectResumeSupport(happyHomeDir = configuration.happyHomeDir) {
|
|
|
12080
12228
|
};
|
|
12081
12229
|
}
|
|
12082
12230
|
|
|
12083
|
-
function getProjectPath(workingDirectory) {
|
|
12084
|
-
const projectId = resolve$1(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
12085
|
-
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
12086
|
-
return join(claudeConfigDir, "projects", projectId);
|
|
12087
|
-
}
|
|
12088
|
-
|
|
12089
12231
|
class ForkTruncateUuidNotFoundError extends Error {
|
|
12090
12232
|
constructor(cutAfterUuid, sourcePath) {
|
|
12091
12233
|
super(`Truncation UUID ${cutAfterUuid} not found in ${sourcePath}`);
|
|
@@ -13590,6 +13732,7 @@ class ApiMachineClient {
|
|
|
13590
13732
|
cliUpdatePushChain = Promise.resolve();
|
|
13591
13733
|
rpcHandlerManager;
|
|
13592
13734
|
resumeSessionHandler = null;
|
|
13735
|
+
restartSessionHandler = null;
|
|
13593
13736
|
stopSessionHandler = null;
|
|
13594
13737
|
listTrackedSessionIds = null;
|
|
13595
13738
|
reconnectInterval = null;
|
|
@@ -13656,11 +13799,13 @@ class ApiMachineClient {
|
|
|
13656
13799
|
setRPCHandlers({
|
|
13657
13800
|
spawnSession,
|
|
13658
13801
|
resumeSession,
|
|
13802
|
+
restartSession,
|
|
13659
13803
|
stopSession,
|
|
13660
13804
|
listTrackedSessionIds,
|
|
13661
13805
|
requestShutdown
|
|
13662
13806
|
}) {
|
|
13663
13807
|
this.resumeSessionHandler = resumeSession ?? null;
|
|
13808
|
+
this.restartSessionHandler = restartSession ?? null;
|
|
13664
13809
|
this.stopSessionHandler = stopSession;
|
|
13665
13810
|
this.listTrackedSessionIds = listTrackedSessionIds ?? null;
|
|
13666
13811
|
this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
|
|
@@ -13682,6 +13827,7 @@ class ApiMachineClient {
|
|
|
13682
13827
|
}
|
|
13683
13828
|
});
|
|
13684
13829
|
this.syncResumeSessionRpcRegistration();
|
|
13830
|
+
this.syncRestartSessionRpcRegistration();
|
|
13685
13831
|
this.rpcHandlerManager.registerHandler("open-terminal", async (params) => {
|
|
13686
13832
|
const { terminalId, cols, rows, cwd, fromSeq, encStream, startupCommand, resub, attachOnly, streamMode } = params || {};
|
|
13687
13833
|
const result = await this.webTerminal.open({
|
|
@@ -13757,6 +13903,14 @@ class ApiMachineClient {
|
|
|
13757
13903
|
this.encTerminals.delete(terminalId);
|
|
13758
13904
|
return { type: "success" };
|
|
13759
13905
|
});
|
|
13906
|
+
this.rpcHandlerManager.registerHandler("restore-terminal", async (params) => {
|
|
13907
|
+
const { terminalId } = params || {};
|
|
13908
|
+
const result = this.webTerminal.restoreClosedTerminal(terminalId);
|
|
13909
|
+
if (result.type === "error") {
|
|
13910
|
+
return { type: "error", reason: result.reason, errorMessage: `restore-terminal: ${result.reason}` };
|
|
13911
|
+
}
|
|
13912
|
+
return { type: "success", terminalId: result.terminalId };
|
|
13913
|
+
});
|
|
13760
13914
|
this.rpcHandlerManager.registerHandler("list-terminals", async () => {
|
|
13761
13915
|
return { type: "success", terminals: this.webTerminal.buildTerminalList() };
|
|
13762
13916
|
});
|
|
@@ -13908,6 +14062,40 @@ class ApiMachineClient {
|
|
|
13908
14062
|
return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
|
|
13909
14063
|
});
|
|
13910
14064
|
}
|
|
14065
|
+
// B-264: symmetric to resume, but the handler stops a live-but-broken
|
|
14066
|
+
// wrapper first. Old web never calls it; new web calling an old daemon that
|
|
14067
|
+
// lacks the handler gets "method not found" and shows an upgrade hint —
|
|
14068
|
+
// never a double-spawn.
|
|
14069
|
+
syncRestartSessionRpcRegistration() {
|
|
14070
|
+
const method = "restart-session";
|
|
14071
|
+
if (this.restartSessionHandler) {
|
|
14072
|
+
if (!this.rpcHandlerManager.hasHandler(method)) {
|
|
14073
|
+
this.rpcHandlerManager.registerHandler(method, async (params) => {
|
|
14074
|
+
const { sessionId, model, permissionMode } = params || {};
|
|
14075
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
14076
|
+
throw new Error("Session ID is required");
|
|
14077
|
+
}
|
|
14078
|
+
const handler = this.restartSessionHandler;
|
|
14079
|
+
if (!handler) {
|
|
14080
|
+
throw new Error("Restart session handler not available");
|
|
14081
|
+
}
|
|
14082
|
+
const result = await handler(sessionId, { model, permissionMode });
|
|
14083
|
+
switch (result.type) {
|
|
14084
|
+
case "success":
|
|
14085
|
+
return { type: "success", sessionId: result.sessionId };
|
|
14086
|
+
case "requestToApproveDirectoryCreation":
|
|
14087
|
+
return result;
|
|
14088
|
+
case "error":
|
|
14089
|
+
throw new Error(result.errorMessage);
|
|
14090
|
+
}
|
|
14091
|
+
});
|
|
14092
|
+
}
|
|
14093
|
+
return;
|
|
14094
|
+
}
|
|
14095
|
+
if (this.rpcHandlerManager.hasHandler(method)) {
|
|
14096
|
+
this.rpcHandlerManager.unregisterHandler(method);
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
13911
14099
|
syncResumeSessionRpcRegistration() {
|
|
13912
14100
|
const method = "resume-happy-session";
|
|
13913
14101
|
if (this.resumeSessionHandler) {
|
|
@@ -14115,6 +14303,9 @@ class ApiMachineClient {
|
|
|
14115
14303
|
httpPort: this.machine.daemonState?.httpPort,
|
|
14116
14304
|
startedAt: now,
|
|
14117
14305
|
webTerminals: { updatedAt: now, terminals: initialTerminals },
|
|
14306
|
+
// B-265 capability flag; restamped every connect so the
|
|
14307
|
+
// web's `detectedAt >= startedAt` trust rule holds.
|
|
14308
|
+
terminalRestore: { rpcAvailable: true, detectedAt: now },
|
|
14118
14309
|
// B-084: closed records survive daemon restarts (persisted
|
|
14119
14310
|
// in closed-terminals.json), so the connect snapshot ships
|
|
14120
14311
|
// them too — not just the incremental pushes.
|
|
@@ -14124,6 +14315,7 @@ class ApiMachineClient {
|
|
|
14124
14315
|
this.webTerminal.startListTracking((terminals) => this.pushTerminalList(terminals));
|
|
14125
14316
|
if (!rpcAlreadyRegistered) this.rpcHandlerManager.onSocketConnect(socket);
|
|
14126
14317
|
this.syncResumeSessionRpcRegistration();
|
|
14318
|
+
this.syncRestartSessionRpcRegistration();
|
|
14127
14319
|
this.startKeepAlive();
|
|
14128
14320
|
void this.reconcileArchivedSessions();
|
|
14129
14321
|
void this.refreshRelayConnection();
|
|
@@ -14359,6 +14551,7 @@ class ApiMachineClient {
|
|
|
14359
14551
|
if (this.relaySocket !== relaySocket) return;
|
|
14360
14552
|
this.rpcHandlerManager.onSocketConnect(relaySocket);
|
|
14361
14553
|
this.syncResumeSessionRpcRegistration();
|
|
14554
|
+
this.syncRestartSessionRpcRegistration();
|
|
14362
14555
|
logger.debug(`[API MACHINE] Connected to regional relay ${selected.assignment.relayId}`);
|
|
14363
14556
|
});
|
|
14364
14557
|
relaySocket.on("disconnect", () => {
|
|
@@ -15133,8 +15326,8 @@ class ApiClient {
|
|
|
15133
15326
|
throw error;
|
|
15134
15327
|
}
|
|
15135
15328
|
}
|
|
15136
|
-
sessionSyncClient(session) {
|
|
15137
|
-
return new ApiSessionClient(this.credential.token, session);
|
|
15329
|
+
sessionSyncClient(session, opts) {
|
|
15330
|
+
return new ApiSessionClient(this.credential.token, session, opts);
|
|
15138
15331
|
}
|
|
15139
15332
|
machineSyncClient(machine) {
|
|
15140
15333
|
return new ApiMachineClient(this.credential.token, machine);
|
|
@@ -15310,6 +15503,56 @@ class ApiClient {
|
|
|
15310
15503
|
return false;
|
|
15311
15504
|
}
|
|
15312
15505
|
}
|
|
15506
|
+
/**
|
|
15507
|
+
* B-265: one session by id — the message cursor (`seq`), metadata and
|
|
15508
|
+
* agent state with their versions, decrypted with the session key the
|
|
15509
|
+
* reconnecting process already holds. `unsupported` = the server has no
|
|
15510
|
+
* such route (or the row is gone): callers fall back to the legacy
|
|
15511
|
+
* skip-all-history reconnect. Transient failures are retried a few times
|
|
15512
|
+
* first, because falling back silently discards the very message a restore
|
|
15513
|
+
* is meant to deliver.
|
|
15514
|
+
*/
|
|
15515
|
+
async getSession(sessionId, encryptionKey, encryptionVariant, opts) {
|
|
15516
|
+
const attempts = opts?.attempts ?? 3;
|
|
15517
|
+
const delays = opts?.delaysMs ?? [500, 1500];
|
|
15518
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
15519
|
+
try {
|
|
15520
|
+
const response = await axios.get(
|
|
15521
|
+
`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
|
|
15522
|
+
{
|
|
15523
|
+
headers: {
|
|
15524
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
15525
|
+
"X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
|
|
15526
|
+
},
|
|
15527
|
+
timeout: opts?.timeoutMs ?? 4e3,
|
|
15528
|
+
validateStatus: () => true
|
|
15529
|
+
}
|
|
15530
|
+
);
|
|
15531
|
+
if (response.status === 404 || response.status === 401 || response.status === 403) {
|
|
15532
|
+
return { ok: false, reason: "unsupported" };
|
|
15533
|
+
}
|
|
15534
|
+
if (response.status >= 200 && response.status < 300) {
|
|
15535
|
+
const row = response.data.session;
|
|
15536
|
+
if (!row || typeof row.seq !== "number" || typeof row.metadata !== "string") {
|
|
15537
|
+
return { ok: false, reason: "unsupported" };
|
|
15538
|
+
}
|
|
15539
|
+
const metadata = decrypt(encryptionKey, encryptionVariant, decodeBase64(row.metadata));
|
|
15540
|
+
if (!metadata) return { ok: false, reason: "unavailable" };
|
|
15541
|
+
const agentState = row.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(row.agentState)) : null;
|
|
15542
|
+
return {
|
|
15543
|
+
ok: true,
|
|
15544
|
+
session: { seq: row.seq, metadata, metadataVersion: row.metadataVersion, agentState, agentStateVersion: row.agentStateVersion }
|
|
15545
|
+
};
|
|
15546
|
+
}
|
|
15547
|
+
logger.debug(`[API] getSession ${sessionId}: status ${response.status} (attempt ${attempt + 1}/${attempts})`);
|
|
15548
|
+
} catch (error) {
|
|
15549
|
+
logger.debug(`[API] getSession ${sessionId} failed (attempt ${attempt + 1}/${attempts}):`, error);
|
|
15550
|
+
}
|
|
15551
|
+
const delayMs = delays[Math.min(attempt, delays.length - 1)];
|
|
15552
|
+
if (attempt < attempts - 1 && delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
15553
|
+
}
|
|
15554
|
+
return { ok: false, reason: "unavailable" };
|
|
15555
|
+
}
|
|
15313
15556
|
/** Clear the server-owned archive tombstone before an intentional resume.
|
|
15314
15557
|
* Older servers return 404 because the endpoint does not exist; that is a
|
|
15315
15558
|
* compatible no-op because those servers also have no durable tombstone. */
|
|
@@ -15401,4 +15644,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
|
|
|
15401
15644
|
}).passthrough()
|
|
15402
15645
|
]);
|
|
15403
15646
|
|
|
15404
|
-
export {
|
|
15647
|
+
export { detectResumeSupport as $, ApiClient as A, deriveLocalCliUpdateSummary as B, updateSettings as C, encodeBase64Url as D, decodeBase64 as E, writeCredentialsLegacy as F, writeCredentialsDataKey as G, readCredentialsForConfiguredRelay as H, InvalidateSync as I, encrypt as J, isClaudeSessionId as K, getLocalHappyAgentCredentialPath as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, readLocalHappyAgentCredentials as N, decryptWithDataKey as O, decryptLegacy as P, extractCompleteLines as Q, RawJSONLinesSchema as R, decideBackfill as S, parseTerminalHookPayload as T, tmuxArgs as U, scrubTmuxClientEnv as V, decideMirrorBinding as W, persistSession as X, MIRROR_BACKFILL_LINES_DEFAULT as Y, mirrorLineKey as Z, mirrorLocalId as _, ApiSessionClient as a, detectCLIAvailability as a0, acquireDaemonLock as a1, summarizeSpawnSessionForLog as a2, deletePersistedSessions as a3, decrypt as a4, writeDaemonState as a5, fetchCliUpdateState as a6, resolveCliUpdateCheckInterval as a7, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as a8, releaseDaemonLock as a9, isValidSessionId as aa, listVhTerminals as ab, isValidTerminalId as ac, readVhTerminal as ad, sendToVhTerminal as ae, startOfflineReconnection as af, clearCredentials as ag, clearMachineId as ah, SandboxConfigSchema as ai, CodexAppServerClient as aj, getLatestDaemonLog as ak, persistence as al, api as am, createEnvelope as b, configuration as c, contentLogMetadata as d, errorLogMetadata as e, packageJson as f, delay as g, connectionState as h, readSettings as i, encodeBase64 as j, ensurePrivateDirectorySync as k, logger as l, hardenPrivateFileSync as m, readDaemonState as n, clearDaemonState as o, projectPath as p, getProjectPath as q, readPersistedSessions as r, initializeSandbox as s, wrapCommand as t, AsyncLock as u, ensurePrivateDirectory as v, writePrivateFileSync as w, hardenPrivateFile as x, readCredentials as y, credentialRelayProblem as z };
|