very-happy-cli 0.2.90 → 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-CnYZFdd0.cjs → AcpBackend-BP7id8L6.cjs} +1 -1
- package/dist/{AcpBackend-BV4wSnJ3.mjs → AcpBackend-DDEsgc1c.mjs} +1 -1
- package/dist/{AcpSessionManager-Cyne6Rmg.mjs → AcpSessionManager-C29t6wb_.mjs} +1 -1
- package/dist/{AcpSessionManager-BPBdrrME.cjs → AcpSessionManager-Cys1Hsb8.cjs} +1 -1
- package/dist/{config-CgXitj0f.cjs → config-B77vhdwT.cjs} +2 -2
- package/dist/{config-BEdyyciP.mjs → config-CJQw0z1o.mjs} +2 -2
- package/dist/{index-an09rejO.mjs → index-8LDXVYWi.mjs} +409 -51
- package/dist/{index-4YmVbx7j.cjs → index-B63Vjt4W.cjs} +425 -67
- package/dist/{index-Dfu5uvjk.mjs → index-BP9pfzdZ.mjs} +5 -5
- package/dist/{index-B1wTVXjt.cjs → index-L3o5Gz-2.cjs} +4 -4
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-DDpVxk8-.cjs → installTerminalHooks-BBc2nij4.cjs} +2 -2
- package/dist/{installTerminalHooks-C-OgZr5k.mjs → installTerminalHooks-DDRrUG_n.mjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +103 -3
- package/dist/lib.d.mts +103 -3
- package/dist/lib.mjs +1 -1
- package/dist/{mcp-Du-N0qlK.mjs → mcp-CYJAl5pk.mjs} +2 -2
- package/dist/{mcp-B6Nq-RUL.cjs → mcp-DbYXyw1U.cjs} +2 -2
- package/dist/{runGemini-ChG7cGMC.cjs → runGemini-BDSWSID6.cjs} +4 -4
- package/dist/{runGemini-DBr_TiTL.mjs → runGemini-DHYs_Beq.mjs} +4 -4
- package/dist/{runOpenClaw-D2jtVgpC.cjs → runOpenClaw-DC9gb1bd.cjs} +3 -3
- package/dist/{runOpenClaw-Cbde8t92.mjs → runOpenClaw-DTaXxvNd.mjs} +3 -3
- package/dist/{send-Bjen1TUE.cjs → send-BEdGwmA-.cjs} +2 -2
- package/dist/{send-BHliX59m.mjs → send-BQSaVz9r.mjs} +2 -2
- package/dist/{spawn-CH1Rq-LW.cjs → spawn-CK1ma_qc.cjs} +2 -2
- package/dist/{spawn-JxaPHAoP.mjs → spawn-DS-Uz_Hw.mjs} +2 -2
- package/dist/{types-DS3i_5LV.mjs → types-B_8X_rZk.mjs} +572 -93
- package/dist/{types-BNGeiP1C.cjs → types-CjlB_fKT.cjs} +573 -93
- 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:*",
|
|
@@ -639,9 +639,27 @@ const sessionToolCallStartEventSchema = z.object({
|
|
|
639
639
|
description: z.string(),
|
|
640
640
|
args: z.record(z.string(), z.unknown())
|
|
641
641
|
});
|
|
642
|
+
const sessionSubagentResultSchema = z.object({
|
|
643
|
+
text: z.string(),
|
|
644
|
+
truncated: z.boolean().optional()
|
|
645
|
+
});
|
|
646
|
+
const sessionSubagentUsageSchema = z.object({
|
|
647
|
+
toolUses: z.number().optional(),
|
|
648
|
+
totalTokens: z.number().optional(),
|
|
649
|
+
durationMs: z.number().optional()
|
|
650
|
+
});
|
|
642
651
|
const sessionToolCallEndEventSchema = z.object({
|
|
643
652
|
t: z.literal("tool-call-end"),
|
|
644
|
-
call: z.string()
|
|
653
|
+
call: z.string(),
|
|
654
|
+
// Foreground Agent/Task completion: the sub-agent's final report + run
|
|
655
|
+
// totals (SDK `tool_use_result`). Absent for ordinary tools and for the
|
|
656
|
+
// background stub ("Async agent launched").
|
|
657
|
+
result: sessionSubagentResultSchema.extend({
|
|
658
|
+
isError: z.boolean().optional(),
|
|
659
|
+
stats: sessionSubagentUsageSchema.extend({
|
|
660
|
+
toolStats: z.record(z.string(), z.unknown()).optional()
|
|
661
|
+
}).optional()
|
|
662
|
+
}).optional()
|
|
645
663
|
});
|
|
646
664
|
const sessionFileEventSchema = z.object({
|
|
647
665
|
t: z.literal("file"),
|
|
@@ -660,7 +678,10 @@ const sessionTurnStartEventSchema = z.object({
|
|
|
660
678
|
});
|
|
661
679
|
const sessionStartEventSchema = z.object({
|
|
662
680
|
t: z.literal("start"),
|
|
663
|
-
title: z.string().optional()
|
|
681
|
+
title: z.string().optional(),
|
|
682
|
+
// B-260-P2: identity from SDK task_started / Agent tool input.
|
|
683
|
+
description: z.string().optional(),
|
|
684
|
+
subagentType: z.string().optional()
|
|
664
685
|
});
|
|
665
686
|
const sessionTurnEndStatusSchema = z.enum(["completed", "failed", "cancelled"]);
|
|
666
687
|
const sessionTurnUsageSchema = z.object({
|
|
@@ -683,7 +704,21 @@ const sessionTurnEndEventSchema = z.object({
|
|
|
683
704
|
usage: sessionTurnUsageSchema.optional()
|
|
684
705
|
});
|
|
685
706
|
const sessionStopEventSchema = z.object({
|
|
686
|
-
t: z.literal("stop")
|
|
707
|
+
t: z.literal("stop"),
|
|
708
|
+
// B-260-P2: from SDK task_notification. A second `stop` for the same
|
|
709
|
+
// subagent may follow carrying `result` once the notification user message
|
|
710
|
+
// (which holds the <result> block) arrives; consumers fold by subagent id.
|
|
711
|
+
status: z.enum(["completed", "failed", "stopped"]).optional(),
|
|
712
|
+
result: sessionSubagentResultSchema.optional(),
|
|
713
|
+
usage: sessionSubagentUsageSchema.optional()
|
|
714
|
+
});
|
|
715
|
+
const sessionProgressEventSchema = z.object({
|
|
716
|
+
t: z.literal("progress"),
|
|
717
|
+
toolUses: z.number(),
|
|
718
|
+
lastTool: z.string().optional(),
|
|
719
|
+
totalTokens: z.number().optional(),
|
|
720
|
+
durationMs: z.number().optional(),
|
|
721
|
+
summary: z.string().optional()
|
|
687
722
|
});
|
|
688
723
|
const sessionEventSchema = z.discriminatedUnion("t", [
|
|
689
724
|
sessionTextEventSchema,
|
|
@@ -694,7 +729,8 @@ const sessionEventSchema = z.discriminatedUnion("t", [
|
|
|
694
729
|
sessionTurnStartEventSchema,
|
|
695
730
|
sessionStartEventSchema,
|
|
696
731
|
sessionTurnEndEventSchema,
|
|
697
|
-
sessionStopEventSchema
|
|
732
|
+
sessionStopEventSchema,
|
|
733
|
+
sessionProgressEventSchema
|
|
698
734
|
]);
|
|
699
735
|
const sessionEnvelopeSchema = z.object({
|
|
700
736
|
id: z.string(),
|
|
@@ -725,7 +761,14 @@ const sessionEnvelopeSchema = z.object({
|
|
|
725
761
|
path: ["role"]
|
|
726
762
|
});
|
|
727
763
|
}
|
|
728
|
-
if (
|
|
764
|
+
if (envelope.ev.t === "progress" && !envelope.subagent) {
|
|
765
|
+
ctx.addIssue({
|
|
766
|
+
code: z.ZodIssueCode.custom,
|
|
767
|
+
message: "progress events must carry a subagent",
|
|
768
|
+
path: ["subagent"]
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
if ((envelope.ev.t === "start" || envelope.ev.t === "stop" || envelope.ev.t === "progress") && envelope.role !== "agent") {
|
|
729
772
|
ctx.addIssue({
|
|
730
773
|
code: z.ZodIssueCode.custom,
|
|
731
774
|
message: `${envelope.ev.t} events must use role "agent"`,
|
|
@@ -815,7 +858,11 @@ const UpdateSessionBodySchema = z.object({
|
|
|
815
858
|
t: z.literal("update-session"),
|
|
816
859
|
id: z.string(),
|
|
817
860
|
metadata: VersionedEncryptedValueSchema.nullish(),
|
|
818
|
-
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()
|
|
819
866
|
});
|
|
820
867
|
const VersionedMachineEncryptedValueSchema = z.object({
|
|
821
868
|
version: z.number(),
|
|
@@ -954,7 +1001,9 @@ const WebTerminalListItemSchema = z$1.object({
|
|
|
954
1001
|
mirrorSessionId: z$1.string().optional(),
|
|
955
1002
|
/** B-150: auto-restored after a restart (ms epoch) — the directory and the
|
|
956
1003
|
* conversation carried over, the processes did not. Cleared once opened. */
|
|
957
|
-
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()
|
|
958
1007
|
});
|
|
959
1008
|
const ClosedTerminalRecordSchema = z$1.object({
|
|
960
1009
|
id: z$1.string(),
|
|
@@ -968,6 +1017,9 @@ const ClosedTerminalRecordSchema = z$1.object({
|
|
|
968
1017
|
/** B-149: 'daemon-gap' = died while no daemon was watching (restart/reboot);
|
|
969
1018
|
* absent means an ordinary observed close. */
|
|
970
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(),
|
|
971
1023
|
closedAt: z$1.number()
|
|
972
1024
|
});
|
|
973
1025
|
z$1.object({
|
|
@@ -1012,6 +1064,15 @@ z$1.object({
|
|
|
1012
1064
|
* ignore the field; old daemons never write it (web renders nothing).
|
|
1013
1065
|
*/
|
|
1014
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(),
|
|
1015
1076
|
/** Relay-owned CLI compatibility/update policy last checked by this daemon. */
|
|
1016
1077
|
cliUpdate: CliUpdateStateSchema.optional()
|
|
1017
1078
|
});
|
|
@@ -2149,6 +2210,69 @@ function shouldReconnect() {
|
|
|
2149
2210
|
return true;
|
|
2150
2211
|
}
|
|
2151
2212
|
|
|
2213
|
+
const SUBAGENT_RESULT_MAX_CHARS = 16384;
|
|
2214
|
+
const SUBAGENT_PROGRESS_MIN_INTERVAL_MS = 5e3;
|
|
2215
|
+
function capSubagentText(text, max = SUBAGENT_RESULT_MAX_CHARS) {
|
|
2216
|
+
const trimmed = text.trim();
|
|
2217
|
+
if (trimmed.length <= max) return { text: trimmed };
|
|
2218
|
+
return { text: trimmed.slice(0, max), truncated: true };
|
|
2219
|
+
}
|
|
2220
|
+
function toolUseResultToText(toolUseResult) {
|
|
2221
|
+
if (!toolUseResult || typeof toolUseResult !== "object") return null;
|
|
2222
|
+
const content = toolUseResult.content;
|
|
2223
|
+
if (typeof content === "string") return content;
|
|
2224
|
+
if (!Array.isArray(content)) return null;
|
|
2225
|
+
const parts = [];
|
|
2226
|
+
for (const block of content) {
|
|
2227
|
+
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
2228
|
+
parts.push(block.text);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
2232
|
+
}
|
|
2233
|
+
function toolUseResultStatus(toolUseResult) {
|
|
2234
|
+
if (!toolUseResult || typeof toolUseResult !== "object") return "unknown";
|
|
2235
|
+
const status = toolUseResult.status;
|
|
2236
|
+
if (status === "async_launched") return "async_launched";
|
|
2237
|
+
if (status === "completed") return "completed";
|
|
2238
|
+
return "unknown";
|
|
2239
|
+
}
|
|
2240
|
+
function toolUseResultStats(toolUseResult) {
|
|
2241
|
+
if (!toolUseResult || typeof toolUseResult !== "object") return void 0;
|
|
2242
|
+
const r = toolUseResult;
|
|
2243
|
+
const stats = {};
|
|
2244
|
+
if (typeof r.totalToolUseCount === "number") stats.toolUses = r.totalToolUseCount;
|
|
2245
|
+
if (typeof r.totalTokens === "number") stats.totalTokens = r.totalTokens;
|
|
2246
|
+
if (typeof r.totalDurationMs === "number") stats.durationMs = r.totalDurationMs;
|
|
2247
|
+
if (r.toolStats && typeof r.toolStats === "object") stats.toolStats = r.toolStats;
|
|
2248
|
+
return Object.keys(stats).length > 0 ? stats : void 0;
|
|
2249
|
+
}
|
|
2250
|
+
function parseTaskNotificationText(text) {
|
|
2251
|
+
if (!/<task-notification>/i.test(text)) return null;
|
|
2252
|
+
const pick = (tag) => text.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, "i"))?.[1] ?? null;
|
|
2253
|
+
const rawStatus = pick("status")?.trim().toLowerCase() ?? null;
|
|
2254
|
+
const status = rawStatus === "completed" || rawStatus === "failed" || rawStatus === "stopped" ? rawStatus : null;
|
|
2255
|
+
let resultText = pick("result");
|
|
2256
|
+
let result = null;
|
|
2257
|
+
if (resultText !== null) {
|
|
2258
|
+
resultText = resultText.replace(/^\s*\[harness:[^\]]*\]\s*/i, "").replace(/<usage>[\s\S]*?<\/usage>/gi, "").trim();
|
|
2259
|
+
if (resultText.length > 0) result = capSubagentText(resultText);
|
|
2260
|
+
}
|
|
2261
|
+
return {
|
|
2262
|
+
toolUseId: pick("tool-use-id")?.trim() || null,
|
|
2263
|
+
taskId: pick("task-id")?.trim() || null,
|
|
2264
|
+
status,
|
|
2265
|
+
summary: pick("summary")?.trim() || null,
|
|
2266
|
+
result
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
function shouldEmitProgress(previous, toolUses, now) {
|
|
2270
|
+
if (!previous) return true;
|
|
2271
|
+
if (previous.lastToolUses === toolUses) return false;
|
|
2272
|
+
return now - previous.lastAt >= SUBAGENT_PROGRESS_MIN_INTERVAL_MS;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
const SUBAGENT_BUFFER_MAX_MESSAGES = 100;
|
|
2152
2276
|
function isSubagentTool(name) {
|
|
2153
2277
|
return name === "Task" || name === "Agent";
|
|
2154
2278
|
}
|
|
@@ -2217,6 +2341,7 @@ function bufferSubagentMessage(state, subagent, message) {
|
|
|
2217
2341
|
const buffer = getBufferedSubagentMessages(state);
|
|
2218
2342
|
const queue = buffer.get(subagent) ?? [];
|
|
2219
2343
|
queue.push(message);
|
|
2344
|
+
while (queue.length > SUBAGENT_BUFFER_MAX_MESSAGES) queue.shift();
|
|
2220
2345
|
buffer.set(subagent, queue);
|
|
2221
2346
|
}
|
|
2222
2347
|
function consumeBufferedSubagentMessages(state, subagent) {
|
|
@@ -2237,6 +2362,31 @@ function getActiveSubagents(state) {
|
|
|
2237
2362
|
}
|
|
2238
2363
|
return state.activeSubagents;
|
|
2239
2364
|
}
|
|
2365
|
+
function getAgentToolCalls(state) {
|
|
2366
|
+
if (!state.agentToolCalls) state.agentToolCalls = /* @__PURE__ */ new Set();
|
|
2367
|
+
return state.agentToolCalls;
|
|
2368
|
+
}
|
|
2369
|
+
function getSubagentMeta(state) {
|
|
2370
|
+
if (!state.subagentMeta) state.subagentMeta = /* @__PURE__ */ new Map();
|
|
2371
|
+
return state.subagentMeta;
|
|
2372
|
+
}
|
|
2373
|
+
function getStubSubagents(state) {
|
|
2374
|
+
if (!state.stubSubagents) state.stubSubagents = /* @__PURE__ */ new Set();
|
|
2375
|
+
return state.stubSubagents;
|
|
2376
|
+
}
|
|
2377
|
+
function getProgressThrottle(state) {
|
|
2378
|
+
if (!state.progressThrottle) state.progressThrottle = /* @__PURE__ */ new Map();
|
|
2379
|
+
return state.progressThrottle;
|
|
2380
|
+
}
|
|
2381
|
+
function nowOf(state) {
|
|
2382
|
+
return state.now ? state.now() : Date.now();
|
|
2383
|
+
}
|
|
2384
|
+
function rememberSubagentMeta(state, subagent, meta) {
|
|
2385
|
+
const existing = getSubagentMeta(state).get(subagent) ?? {};
|
|
2386
|
+
const description = typeof meta.description === "string" && meta.description.trim() ? meta.description.trim() : existing.description;
|
|
2387
|
+
const subagentType = typeof meta.subagentType === "string" && meta.subagentType.trim() ? meta.subagentType.trim() : existing.subagentType;
|
|
2388
|
+
getSubagentMeta(state).set(subagent, { ...description ? { description } : {}, ...subagentType ? { subagentType } : {} });
|
|
2389
|
+
}
|
|
2240
2390
|
function pickAssistantUsage(message) {
|
|
2241
2391
|
if (message.type !== "assistant") return void 0;
|
|
2242
2392
|
const usage = message.message?.usage;
|
|
@@ -2413,13 +2563,26 @@ function maybeEmitSubagentStart(state, turn, subagent, envelopes) {
|
|
|
2413
2563
|
return;
|
|
2414
2564
|
}
|
|
2415
2565
|
const title = getSubagentTitles(state).get(subagent);
|
|
2566
|
+
const meta = getSubagentMeta(state).get(subagent);
|
|
2416
2567
|
envelopes.push(createEnvelope("agent", {
|
|
2417
2568
|
t: "start",
|
|
2418
|
-
...title ? { title } : {}
|
|
2569
|
+
...title ? { title } : {},
|
|
2570
|
+
...meta?.description ? { description: meta.description } : {},
|
|
2571
|
+
...meta?.subagentType ? { subagentType: meta.subagentType } : {}
|
|
2419
2572
|
}, { turn, subagent }));
|
|
2420
2573
|
started.add(subagent);
|
|
2421
2574
|
getActiveSubagents(state).add(subagent);
|
|
2422
2575
|
}
|
|
2576
|
+
function emitSubagentStopWithStatus(state, turn, subagent, envelopes, payload) {
|
|
2577
|
+
envelopes.push(createEnvelope("agent", {
|
|
2578
|
+
t: "stop",
|
|
2579
|
+
...payload.status ? { status: payload.status } : {},
|
|
2580
|
+
...payload.result ? { result: payload.result } : {},
|
|
2581
|
+
...payload.usage ? { usage: payload.usage } : {}
|
|
2582
|
+
}, { ...turn ? { turn } : {}, subagent }));
|
|
2583
|
+
getActiveSubagents(state).delete(subagent);
|
|
2584
|
+
getStartedSubagents(state).delete(subagent);
|
|
2585
|
+
}
|
|
2423
2586
|
function maybeEmitSubagentStop(state, turn, subagent, envelopes) {
|
|
2424
2587
|
const active = getActiveSubagents(state);
|
|
2425
2588
|
if (!active.has(subagent)) {
|
|
@@ -2431,8 +2594,6 @@ function maybeEmitSubagentStop(state, turn, subagent, envelopes) {
|
|
|
2431
2594
|
function clearSubagentTracking(state) {
|
|
2432
2595
|
getUuidToProviderSubagent(state).clear();
|
|
2433
2596
|
getTaskPromptToSubagents(state).clear();
|
|
2434
|
-
getProviderSubagentToSessionSubagent(state).clear();
|
|
2435
|
-
getSubagentTitles(state).clear();
|
|
2436
2597
|
getBufferedSubagentMessages(state).clear();
|
|
2437
2598
|
getHiddenParentToolCalls(state).clear();
|
|
2438
2599
|
getStartedSubagents(state).clear();
|
|
@@ -2493,6 +2654,58 @@ function closeClaudeTurnWithStatus(state, status, meta) {
|
|
|
2493
2654
|
function mapClaudeLogMessageToSessionEnvelopes(message, state) {
|
|
2494
2655
|
return mapClaudeLogMessageToSessionEnvelopesInternal(message, state);
|
|
2495
2656
|
}
|
|
2657
|
+
function mapTaskLifecycleSystemMessage(message, state, envelopes) {
|
|
2658
|
+
const raw = message;
|
|
2659
|
+
const subtype = typeof raw.subtype === "string" ? raw.subtype : "";
|
|
2660
|
+
if (!subtype.startsWith("task_")) return;
|
|
2661
|
+
if (raw.skip_transcript === true) return;
|
|
2662
|
+
const toolUseId = typeof raw.tool_use_id === "string" && raw.tool_use_id.length > 0 ? raw.tool_use_id : null;
|
|
2663
|
+
if (!toolUseId || !getAgentToolCalls(state).has(toolUseId)) return;
|
|
2664
|
+
const subagent = ensureSessionSubagentIdForProviderSubagent(state, toolUseId);
|
|
2665
|
+
const usage = raw.usage && typeof raw.usage === "object" ? {
|
|
2666
|
+
...typeof raw.usage.tool_uses === "number" ? { toolUses: raw.usage.tool_uses } : {},
|
|
2667
|
+
...typeof raw.usage.total_tokens === "number" ? { totalTokens: raw.usage.total_tokens } : {},
|
|
2668
|
+
...typeof raw.usage.duration_ms === "number" ? { durationMs: raw.usage.duration_ms } : {}
|
|
2669
|
+
} : void 0;
|
|
2670
|
+
if (subtype === "task_started") {
|
|
2671
|
+
rememberSubagentMeta(state, subagent, { description: raw.description, subagentType: raw.subagent_type });
|
|
2672
|
+
const turn = ensureTurn(state, envelopes);
|
|
2673
|
+
getStartedSubagents(state).delete(subagent);
|
|
2674
|
+
maybeEmitSubagentStart(state, turn, subagent, envelopes);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
if (subtype === "task_progress") {
|
|
2678
|
+
const toolUses = typeof raw.usage?.tool_uses === "number" ? raw.usage.tool_uses : 0;
|
|
2679
|
+
const throttle = getProgressThrottle(state);
|
|
2680
|
+
const now = nowOf(state);
|
|
2681
|
+
if (!shouldEmitProgress(throttle.get(subagent), toolUses, now)) return;
|
|
2682
|
+
throttle.set(subagent, { lastAt: now, lastToolUses: toolUses });
|
|
2683
|
+
const turn = ensureTurn(state, envelopes);
|
|
2684
|
+
maybeEmitSubagentStart(state, turn, subagent, envelopes);
|
|
2685
|
+
envelopes.push(createEnvelope("agent", {
|
|
2686
|
+
t: "progress",
|
|
2687
|
+
toolUses,
|
|
2688
|
+
...typeof raw.last_tool_name === "string" && raw.last_tool_name ? { lastTool: raw.last_tool_name } : {},
|
|
2689
|
+
...usage?.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
|
|
2690
|
+
...usage?.durationMs !== void 0 ? { durationMs: usage.durationMs } : {},
|
|
2691
|
+
...typeof raw.summary === "string" && raw.summary.trim() ? { summary: raw.summary.trim() } : {}
|
|
2692
|
+
}, { turn, subagent }));
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
if (subtype === "task_notification") {
|
|
2696
|
+
const status = raw.status === "completed" || raw.status === "failed" || raw.status === "stopped" ? raw.status : "completed";
|
|
2697
|
+
emitSubagentStopWithStatus(state, state.currentTurnId ?? void 0, subagent, envelopes, {
|
|
2698
|
+
status,
|
|
2699
|
+
...usage ? { usage } : {}
|
|
2700
|
+
});
|
|
2701
|
+
getStubSubagents(state).delete(subagent);
|
|
2702
|
+
getProgressThrottle(state).delete(subagent);
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
if (subtype === "task_updated" && raw.patch?.is_backgrounded === true) {
|
|
2706
|
+
getStubSubagents(state).add(subagent);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2496
2709
|
function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
|
|
2497
2710
|
const envelopes = [];
|
|
2498
2711
|
const claudeUuid = pickUuid(message);
|
|
@@ -2513,6 +2726,7 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
|
|
|
2513
2726
|
};
|
|
2514
2727
|
}
|
|
2515
2728
|
if (message.type === "system") {
|
|
2729
|
+
mapTaskLifecycleSystemMessage(message, state, envelopes);
|
|
2516
2730
|
return {
|
|
2517
2731
|
currentTurnId: state.currentTurnId,
|
|
2518
2732
|
envelopes
|
|
@@ -2554,6 +2768,9 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
|
|
|
2554
2768
|
queueTaskPromptSubagent(state, prompt, call);
|
|
2555
2769
|
}
|
|
2556
2770
|
setSubagentTitle(state, sessionSubagentForCall, pickTaskTitle(block.input) ?? prompt);
|
|
2771
|
+
getAgentToolCalls(state).add(call);
|
|
2772
|
+
const input = block.input ?? {};
|
|
2773
|
+
rememberSubagentMeta(state, sessionSubagentForCall, { description: input.description, subagentType: input.subagent_type });
|
|
2557
2774
|
}
|
|
2558
2775
|
if (shouldHideParentToolCall(name)) {
|
|
2559
2776
|
getHiddenParentToolCalls(state).add(call);
|
|
@@ -2598,6 +2815,18 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
|
|
|
2598
2815
|
maybeEmitSubagentStart(state, turnId2, subagent, envelopes);
|
|
2599
2816
|
envelopes.push(createEnvelope("agent", { t: "text", text: message.message.content }, { turn: turnId2, subagent, claudeUuid }));
|
|
2600
2817
|
} else {
|
|
2818
|
+
const origin = message.origin;
|
|
2819
|
+
if (origin?.kind === "task-notification" || /<task-notification>/i.test(message.message.content)) {
|
|
2820
|
+
const fields = parseTaskNotificationText(message.message.content);
|
|
2821
|
+
const providerId = fields?.toolUseId ?? null;
|
|
2822
|
+
const subagentForNotification = providerId && getAgentToolCalls(state).has(providerId) ? getSessionSubagentIdForProviderSubagent(state, providerId) : void 0;
|
|
2823
|
+
if (fields && subagentForNotification && (fields.result || fields.status)) {
|
|
2824
|
+
emitSubagentStopWithStatus(state, state.currentTurnId ?? void 0, subagentForNotification, envelopes, {
|
|
2825
|
+
...fields.status ? { status: fields.status } : {},
|
|
2826
|
+
...fields.result ? { result: fields.result } : {}
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2601
2830
|
closeTurn(state, "completed", envelopes);
|
|
2602
2831
|
envelopes.push(createEnvelope("user", { t: "text", text: message.message.content }, { claudeUuid }));
|
|
2603
2832
|
}
|
|
@@ -2628,6 +2857,31 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
|
|
|
2628
2857
|
getHiddenParentToolCalls(state).delete(block.tool_use_id);
|
|
2629
2858
|
continue;
|
|
2630
2859
|
}
|
|
2860
|
+
if (sessionSubagentForToolResult && getAgentToolCalls(state).has(block.tool_use_id)) {
|
|
2861
|
+
const toolUseResult = message.tool_use_result;
|
|
2862
|
+
const resultStatus = toolUseResultStatus(toolUseResult);
|
|
2863
|
+
if (resultStatus === "async_launched") {
|
|
2864
|
+
getStubSubagents(state).add(sessionSubagentForToolResult);
|
|
2865
|
+
envelopes.push(createEnvelope("agent", { t: "tool-call-end", call: block.tool_use_id }, { turn: turnId, subagent }));
|
|
2866
|
+
continue;
|
|
2867
|
+
}
|
|
2868
|
+
if (resultStatus === "completed") {
|
|
2869
|
+
const text = toolUseResultToText(toolUseResult);
|
|
2870
|
+
const stats = toolUseResultStats(toolUseResult);
|
|
2871
|
+
const result = text ? capSubagentText(text) : void 0;
|
|
2872
|
+
emitSubagentStopWithStatus(state, turnId, sessionSubagentForToolResult, envelopes, {
|
|
2873
|
+
status: "completed",
|
|
2874
|
+
...result ? { result } : {},
|
|
2875
|
+
...stats ? { usage: { toolUses: stats.toolUses, totalTokens: stats.totalTokens, durationMs: stats.durationMs } } : {}
|
|
2876
|
+
});
|
|
2877
|
+
envelopes.push(createEnvelope("agent", {
|
|
2878
|
+
t: "tool-call-end",
|
|
2879
|
+
call: block.tool_use_id,
|
|
2880
|
+
...result ? { result: { ...result, ...stats ? { stats } : {} } } : {}
|
|
2881
|
+
}, { turn: turnId, subagent }));
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2631
2885
|
if (sessionSubagentForToolResult) {
|
|
2632
2886
|
maybeEmitSubagentStop(state, turnId, sessionSubagentForToolResult, envelopes);
|
|
2633
2887
|
}
|
|
@@ -2845,10 +3099,19 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2845
3099
|
directInboundLocalIds = /* @__PURE__ */ new Set();
|
|
2846
3100
|
routedInboundLocalIds = /* @__PURE__ */ new Set();
|
|
2847
3101
|
handoverInFlight = null;
|
|
2848
|
-
|
|
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) {
|
|
2849
3108
|
super();
|
|
2850
3109
|
this.token = token;
|
|
2851
3110
|
this.sessionId = session.id;
|
|
3111
|
+
if (opts?.initialSeq !== void 0 && opts.initialSeq >= 0) {
|
|
3112
|
+
this.lastSeq = opts.initialSeq;
|
|
3113
|
+
this.awaitingInitialFetch = true;
|
|
3114
|
+
}
|
|
2852
3115
|
this.metadata = session.metadata;
|
|
2853
3116
|
this.metadataVersion = session.metadataVersion;
|
|
2854
3117
|
this.agentState = session.agentState;
|
|
@@ -2908,7 +3171,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2908
3171
|
}
|
|
2909
3172
|
if (data.body.t === "new-message") {
|
|
2910
3173
|
const messageSeq = data.body.message?.seq;
|
|
2911
|
-
if (this.lastSeq === 0) {
|
|
3174
|
+
if (this.lastSeq === 0 || this.awaitingInitialFetch) {
|
|
2912
3175
|
this.receiveSync.invalidate();
|
|
2913
3176
|
return;
|
|
2914
3177
|
}
|
|
@@ -3204,6 +3467,9 @@ class ApiSessionClient extends EventEmitter {
|
|
|
3204
3467
|
if (message.localId && this.directInboundLocalIds.delete(message.localId)) {
|
|
3205
3468
|
continue;
|
|
3206
3469
|
}
|
|
3470
|
+
if (message.localId && this.routedInboundLocalIds.has(message.localId)) {
|
|
3471
|
+
continue;
|
|
3472
|
+
}
|
|
3207
3473
|
if (message.content?.t !== "encrypted") {
|
|
3208
3474
|
continue;
|
|
3209
3475
|
}
|
|
@@ -3233,6 +3499,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
3233
3499
|
break;
|
|
3234
3500
|
}
|
|
3235
3501
|
}
|
|
3502
|
+
this.awaitingInitialFetch = false;
|
|
3236
3503
|
}
|
|
3237
3504
|
static MAX_OUTBOX_BATCH_SIZE = 50;
|
|
3238
3505
|
async flushOutbox() {
|
|
@@ -4480,6 +4747,101 @@ function registerTodoHandlers(manager) {
|
|
|
4480
4747
|
});
|
|
4481
4748
|
}
|
|
4482
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
|
+
|
|
4483
4845
|
var xtermHeadless = {};
|
|
4484
4846
|
|
|
4485
4847
|
var hasRequiredXtermHeadless;
|
|
@@ -9530,63 +9892,6 @@ function sendTerminalNotification(opts) {
|
|
|
9530
9892
|
});
|
|
9531
9893
|
}
|
|
9532
9894
|
|
|
9533
|
-
const LIVE_SNAPSHOT_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
9534
|
-
const LIVE_SNAPSHOT_MAX = 100;
|
|
9535
|
-
function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE_SNAPSHOT_MAX) {
|
|
9536
|
-
const out = /* @__PURE__ */ new Map();
|
|
9537
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
|
|
9538
|
-
const rows = [];
|
|
9539
|
-
for (const [id, value] of Object.entries(raw)) {
|
|
9540
|
-
if (!id || !value || typeof value !== "object") continue;
|
|
9541
|
-
const { title, cwd, seenAt } = value;
|
|
9542
|
-
if (typeof seenAt !== "number" || !Number.isFinite(seenAt)) continue;
|
|
9543
|
-
if (now - seenAt >= ttlMs) continue;
|
|
9544
|
-
rows.push([id, {
|
|
9545
|
-
title: typeof title === "string" && title.trim() ? title : void 0,
|
|
9546
|
-
cwd: typeof cwd === "string" && cwd ? cwd : void 0,
|
|
9547
|
-
seenAt
|
|
9548
|
-
}]);
|
|
9549
|
-
}
|
|
9550
|
-
rows.sort((a, b) => b[1].seenAt - a[1].seenAt);
|
|
9551
|
-
for (const [id, info] of rows.slice(0, max)) out.set(id, info);
|
|
9552
|
-
return out;
|
|
9553
|
-
}
|
|
9554
|
-
function serializeLiveSnapshot(map, max = LIVE_SNAPSHOT_MAX) {
|
|
9555
|
-
const rows = [...map.entries()].sort((a, b) => b[1].seenAt - a[1].seenAt).slice(0, max);
|
|
9556
|
-
const out = {};
|
|
9557
|
-
for (const [id, info] of rows) out[id] = info;
|
|
9558
|
-
return out;
|
|
9559
|
-
}
|
|
9560
|
-
function liveSnapshotChanged(prev, next) {
|
|
9561
|
-
if (prev.size !== next.size) return true;
|
|
9562
|
-
for (const [id, info] of next) {
|
|
9563
|
-
const before = prev.get(id);
|
|
9564
|
-
if (!before) return true;
|
|
9565
|
-
if (before.title !== info.title || before.cwd !== info.cwd) return true;
|
|
9566
|
-
}
|
|
9567
|
-
return false;
|
|
9568
|
-
}
|
|
9569
|
-
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}$/;
|
|
9570
|
-
function isClaudeSessionId(value) {
|
|
9571
|
-
return typeof value === "string" && CLAUDE_SESSION_ID_RE.test(value);
|
|
9572
|
-
}
|
|
9573
|
-
function pickMirrorForTerminal(sessions, terminalId) {
|
|
9574
|
-
if (!terminalId) return null;
|
|
9575
|
-
let best = null;
|
|
9576
|
-
for (const [id, session] of Object.entries(sessions || {})) {
|
|
9577
|
-
const meta = session?.metadata;
|
|
9578
|
-
if (!meta || meta.terminalId !== terminalId) continue;
|
|
9579
|
-
const savedAt = typeof session.savedAt === "number" ? session.savedAt : 0;
|
|
9580
|
-
if (best && savedAt <= best.savedAt) continue;
|
|
9581
|
-
best = {
|
|
9582
|
-
id,
|
|
9583
|
-
savedAt,
|
|
9584
|
-
claude: isClaudeSessionId(meta.claudeSessionId) ? meta.claudeSessionId : void 0
|
|
9585
|
-
};
|
|
9586
|
-
}
|
|
9587
|
-
return best ? { sessionId: best.id, claudeSessionId: best.claude } : null;
|
|
9588
|
-
}
|
|
9589
|
-
|
|
9590
9895
|
const AUTO_RESTORE_DEFAULT_MAX = 6;
|
|
9591
9896
|
const AUTO_RESTORE_DEFAULT_WINDOW_HOURS = 24;
|
|
9592
9897
|
const AUTO_RESTORE_HARD_MAX = 20;
|
|
@@ -9645,6 +9950,8 @@ function selectAutoRestore(candidates, opts) {
|
|
|
9645
9950
|
}
|
|
9646
9951
|
plans.push({
|
|
9647
9952
|
terminalId: c.id,
|
|
9953
|
+
...c.tags !== void 0 ? { tags: c.tags } : {},
|
|
9954
|
+
...c.manual ? { manual: true } : {},
|
|
9648
9955
|
cwd: c.cwd,
|
|
9649
9956
|
title: c.title,
|
|
9650
9957
|
claudeSessionId: c.claudeSessionId,
|
|
@@ -9693,10 +10000,11 @@ function sanitizeClosedTerminals(raw, max = CLOSED_TERMINALS_MAX) {
|
|
|
9693
10000
|
const out = [];
|
|
9694
10001
|
for (const item of raw) {
|
|
9695
10002
|
if (!item || typeof item !== "object") continue;
|
|
9696
|
-
const { id, title, cwd, mirrorSessionId, claudeSessionId, reason, closedAt } = item;
|
|
10003
|
+
const { id, title, cwd, mirrorSessionId, claudeSessionId, reason, closedAt, tags, manual } = item;
|
|
9697
10004
|
if (typeof id !== "string" || id.length === 0 || typeof closedAt !== "number") continue;
|
|
9698
10005
|
if (seen.has(id)) continue;
|
|
9699
10006
|
seen.add(id);
|
|
10007
|
+
const tagList = sanitizeTagList(tags);
|
|
9700
10008
|
out.push({
|
|
9701
10009
|
id,
|
|
9702
10010
|
title: typeof title === "string" ? title : void 0,
|
|
@@ -9704,6 +10012,8 @@ function sanitizeClosedTerminals(raw, max = CLOSED_TERMINALS_MAX) {
|
|
|
9704
10012
|
mirrorSessionId: typeof mirrorSessionId === "string" ? mirrorSessionId : void 0,
|
|
9705
10013
|
claudeSessionId: typeof claudeSessionId === "string" ? claudeSessionId : void 0,
|
|
9706
10014
|
reason: reason === "daemon-gap" || reason === "closed" ? reason : void 0,
|
|
10015
|
+
...tagList !== void 0 ? { tags: tagList } : {},
|
|
10016
|
+
...manual === true ? { manual: true } : {},
|
|
9707
10017
|
closedAt
|
|
9708
10018
|
});
|
|
9709
10019
|
}
|
|
@@ -9827,6 +10137,9 @@ function startupInjectionArgs(tmuxSession, command) {
|
|
|
9827
10137
|
["send-keys", "-t", `=${tmuxSession}:`, "Enter"]
|
|
9828
10138
|
];
|
|
9829
10139
|
}
|
|
10140
|
+
function seenInfoOf(t) {
|
|
10141
|
+
return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual };
|
|
10142
|
+
}
|
|
9830
10143
|
function terminalListSignature(items) {
|
|
9831
10144
|
const canon = [...items].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((t) => [
|
|
9832
10145
|
t.id,
|
|
@@ -10463,7 +10776,7 @@ class WebTerminalManager {
|
|
|
10463
10776
|
}
|
|
10464
10777
|
/** Refresh the per-id {title, cwd} cache from an observed live list. */
|
|
10465
10778
|
noteSeen(list) {
|
|
10466
|
-
this.lastSeenInfo = new Map(list.map((t) => [t.id,
|
|
10779
|
+
this.lastSeenInfo = new Map(list.map((t) => [t.id, seenInfoOf(t)]));
|
|
10467
10780
|
}
|
|
10468
10781
|
/** Record one terminal as closed (dedupe/cap in the pure module) and
|
|
10469
10782
|
* persist. The confirming daemonState push rides the list refresh that
|
|
@@ -10537,7 +10850,7 @@ class WebTerminalManager {
|
|
|
10537
10850
|
const liveIds = new Set(list.map((t) => t.id));
|
|
10538
10851
|
this.reconcileRestoredSnapshot(liveIds);
|
|
10539
10852
|
const next = new Map(
|
|
10540
|
-
list.map((t) => [t.id,
|
|
10853
|
+
list.map((t) => [t.id, seenInfoOf(t)])
|
|
10541
10854
|
);
|
|
10542
10855
|
let changed = false;
|
|
10543
10856
|
const now = Date.now();
|
|
@@ -10554,6 +10867,8 @@ class WebTerminalManager {
|
|
|
10554
10867
|
id,
|
|
10555
10868
|
title: info.title,
|
|
10556
10869
|
cwd: info.cwd,
|
|
10870
|
+
tags: info.tags,
|
|
10871
|
+
manual: info.manual,
|
|
10557
10872
|
// Live binding first (authoritative while it exists), persisted
|
|
10558
10873
|
// metadata as the fallback once the mirror has been torn down.
|
|
10559
10874
|
mirrorSessionId: this.mirrorResolver?.(id) ?? mirror?.sessionId,
|
|
@@ -10604,6 +10919,8 @@ class WebTerminalManager {
|
|
|
10604
10919
|
id,
|
|
10605
10920
|
title: info.title,
|
|
10606
10921
|
cwd: info.cwd,
|
|
10922
|
+
tags: info.tags,
|
|
10923
|
+
manual: info.manual,
|
|
10607
10924
|
mirrorSessionId: mirror?.sessionId,
|
|
10608
10925
|
claudeSessionId: mirror?.claudeSessionId,
|
|
10609
10926
|
reason: "daemon-gap",
|
|
@@ -10614,6 +10931,8 @@ class WebTerminalManager {
|
|
|
10614
10931
|
id,
|
|
10615
10932
|
title: info.title,
|
|
10616
10933
|
cwd: info.cwd,
|
|
10934
|
+
tags: info.tags,
|
|
10935
|
+
manual: info.manual,
|
|
10617
10936
|
seenAt: info.seenAt,
|
|
10618
10937
|
claudeSessionId: mirror?.claudeSessionId
|
|
10619
10938
|
});
|
|
@@ -10689,6 +11008,51 @@ class WebTerminalManager {
|
|
|
10689
11008
|
* exactly like a terminal whose pty was reaped.
|
|
10690
11009
|
*/
|
|
10691
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) {
|
|
10692
11056
|
const env = ptyEnv();
|
|
10693
11057
|
const name = `vh-${plan.terminalId}`;
|
|
10694
11058
|
const exists = spawnSync(
|
|
@@ -10725,18 +11089,35 @@ class WebTerminalManager {
|
|
|
10725
11089
|
tmuxArgs(["set-option", "-t", `=${name}:`, "@vh_title", plan.title]),
|
|
10726
11090
|
{ stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
|
|
10727
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
|
+
);
|
|
10728
11107
|
}
|
|
10729
|
-
|
|
10730
|
-
|
|
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
|
+
}
|
|
10731
11112
|
}
|
|
10732
|
-
logger.info(`[WEB TERMINAL]
|
|
11113
|
+
logger.info(`[WEB TERMINAL] cold-created ${plan.terminalId} in ${plan.cwd}`);
|
|
10733
11114
|
return true;
|
|
10734
11115
|
}
|
|
10735
11116
|
/** Mirror the in-memory cache to disk, but only when something a restart
|
|
10736
11117
|
* would care about changed (see liveSnapshotChanged). */
|
|
10737
11118
|
persistLiveSnapshot(live, now) {
|
|
10738
11119
|
const candidate = /* @__PURE__ */ new Map();
|
|
10739
|
-
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 });
|
|
10740
11121
|
if (!liveSnapshotChanged(this.lastPersistedSnapshot, candidate)) return;
|
|
10741
11122
|
this.lastPersistedSnapshot = candidate;
|
|
10742
11123
|
saveLiveSnapshot(candidate);
|
|
@@ -11311,6 +11692,8 @@ class WebTerminalManager {
|
|
|
11311
11692
|
id: terminalId,
|
|
11312
11693
|
title: info.title,
|
|
11313
11694
|
cwd: info.cwd,
|
|
11695
|
+
tags: info.tags,
|
|
11696
|
+
manual: info.manual,
|
|
11314
11697
|
mirrorSessionId: this.mirrorResolver?.(terminalId) ?? mirror?.sessionId,
|
|
11315
11698
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11316
11699
|
reason: "closed",
|
|
@@ -11394,7 +11777,8 @@ class WebTerminalManager {
|
|
|
11394
11777
|
// tmux last-activity (epoch s) → ms; optional so old daemons
|
|
11395
11778
|
// simply omit it and web clients fall back to createdAt.
|
|
11396
11779
|
activityAt: s.activity,
|
|
11397
|
-
agentState: this.probeAgentState(s.name, s.paneCurrentCommand)
|
|
11780
|
+
agentState: this.probeAgentState(s.name, s.paneCurrentCommand),
|
|
11781
|
+
manual: s.manual
|
|
11398
11782
|
});
|
|
11399
11783
|
}
|
|
11400
11784
|
return out;
|
|
@@ -11844,12 +12228,6 @@ function detectResumeSupport(happyHomeDir = configuration.happyHomeDir) {
|
|
|
11844
12228
|
};
|
|
11845
12229
|
}
|
|
11846
12230
|
|
|
11847
|
-
function getProjectPath(workingDirectory) {
|
|
11848
|
-
const projectId = resolve$1(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
11849
|
-
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
11850
|
-
return join(claudeConfigDir, "projects", projectId);
|
|
11851
|
-
}
|
|
11852
|
-
|
|
11853
12231
|
class ForkTruncateUuidNotFoundError extends Error {
|
|
11854
12232
|
constructor(cutAfterUuid, sourcePath) {
|
|
11855
12233
|
super(`Truncation UUID ${cutAfterUuid} not found in ${sourcePath}`);
|
|
@@ -13354,6 +13732,7 @@ class ApiMachineClient {
|
|
|
13354
13732
|
cliUpdatePushChain = Promise.resolve();
|
|
13355
13733
|
rpcHandlerManager;
|
|
13356
13734
|
resumeSessionHandler = null;
|
|
13735
|
+
restartSessionHandler = null;
|
|
13357
13736
|
stopSessionHandler = null;
|
|
13358
13737
|
listTrackedSessionIds = null;
|
|
13359
13738
|
reconnectInterval = null;
|
|
@@ -13420,11 +13799,13 @@ class ApiMachineClient {
|
|
|
13420
13799
|
setRPCHandlers({
|
|
13421
13800
|
spawnSession,
|
|
13422
13801
|
resumeSession,
|
|
13802
|
+
restartSession,
|
|
13423
13803
|
stopSession,
|
|
13424
13804
|
listTrackedSessionIds,
|
|
13425
13805
|
requestShutdown
|
|
13426
13806
|
}) {
|
|
13427
13807
|
this.resumeSessionHandler = resumeSession ?? null;
|
|
13808
|
+
this.restartSessionHandler = restartSession ?? null;
|
|
13428
13809
|
this.stopSessionHandler = stopSession;
|
|
13429
13810
|
this.listTrackedSessionIds = listTrackedSessionIds ?? null;
|
|
13430
13811
|
this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
|
|
@@ -13446,6 +13827,7 @@ class ApiMachineClient {
|
|
|
13446
13827
|
}
|
|
13447
13828
|
});
|
|
13448
13829
|
this.syncResumeSessionRpcRegistration();
|
|
13830
|
+
this.syncRestartSessionRpcRegistration();
|
|
13449
13831
|
this.rpcHandlerManager.registerHandler("open-terminal", async (params) => {
|
|
13450
13832
|
const { terminalId, cols, rows, cwd, fromSeq, encStream, startupCommand, resub, attachOnly, streamMode } = params || {};
|
|
13451
13833
|
const result = await this.webTerminal.open({
|
|
@@ -13521,6 +13903,14 @@ class ApiMachineClient {
|
|
|
13521
13903
|
this.encTerminals.delete(terminalId);
|
|
13522
13904
|
return { type: "success" };
|
|
13523
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
|
+
});
|
|
13524
13914
|
this.rpcHandlerManager.registerHandler("list-terminals", async () => {
|
|
13525
13915
|
return { type: "success", terminals: this.webTerminal.buildTerminalList() };
|
|
13526
13916
|
});
|
|
@@ -13672,6 +14062,40 @@ class ApiMachineClient {
|
|
|
13672
14062
|
return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
|
|
13673
14063
|
});
|
|
13674
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
|
+
}
|
|
13675
14099
|
syncResumeSessionRpcRegistration() {
|
|
13676
14100
|
const method = "resume-happy-session";
|
|
13677
14101
|
if (this.resumeSessionHandler) {
|
|
@@ -13879,6 +14303,9 @@ class ApiMachineClient {
|
|
|
13879
14303
|
httpPort: this.machine.daemonState?.httpPort,
|
|
13880
14304
|
startedAt: now,
|
|
13881
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 },
|
|
13882
14309
|
// B-084: closed records survive daemon restarts (persisted
|
|
13883
14310
|
// in closed-terminals.json), so the connect snapshot ships
|
|
13884
14311
|
// them too — not just the incremental pushes.
|
|
@@ -13888,6 +14315,7 @@ class ApiMachineClient {
|
|
|
13888
14315
|
this.webTerminal.startListTracking((terminals) => this.pushTerminalList(terminals));
|
|
13889
14316
|
if (!rpcAlreadyRegistered) this.rpcHandlerManager.onSocketConnect(socket);
|
|
13890
14317
|
this.syncResumeSessionRpcRegistration();
|
|
14318
|
+
this.syncRestartSessionRpcRegistration();
|
|
13891
14319
|
this.startKeepAlive();
|
|
13892
14320
|
void this.reconcileArchivedSessions();
|
|
13893
14321
|
void this.refreshRelayConnection();
|
|
@@ -14123,6 +14551,7 @@ class ApiMachineClient {
|
|
|
14123
14551
|
if (this.relaySocket !== relaySocket) return;
|
|
14124
14552
|
this.rpcHandlerManager.onSocketConnect(relaySocket);
|
|
14125
14553
|
this.syncResumeSessionRpcRegistration();
|
|
14554
|
+
this.syncRestartSessionRpcRegistration();
|
|
14126
14555
|
logger.debug(`[API MACHINE] Connected to regional relay ${selected.assignment.relayId}`);
|
|
14127
14556
|
});
|
|
14128
14557
|
relaySocket.on("disconnect", () => {
|
|
@@ -14897,8 +15326,8 @@ class ApiClient {
|
|
|
14897
15326
|
throw error;
|
|
14898
15327
|
}
|
|
14899
15328
|
}
|
|
14900
|
-
sessionSyncClient(session) {
|
|
14901
|
-
return new ApiSessionClient(this.credential.token, session);
|
|
15329
|
+
sessionSyncClient(session, opts) {
|
|
15330
|
+
return new ApiSessionClient(this.credential.token, session, opts);
|
|
14902
15331
|
}
|
|
14903
15332
|
machineSyncClient(machine) {
|
|
14904
15333
|
return new ApiMachineClient(this.credential.token, machine);
|
|
@@ -15074,6 +15503,56 @@ class ApiClient {
|
|
|
15074
15503
|
return false;
|
|
15075
15504
|
}
|
|
15076
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
|
+
}
|
|
15077
15556
|
/** Clear the server-owned archive tombstone before an intentional resume.
|
|
15078
15557
|
* Older servers return 404 because the endpoint does not exist; that is a
|
|
15079
15558
|
* compatible no-op because those servers also have no durable tombstone. */
|
|
@@ -15165,4 +15644,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
|
|
|
15165
15644
|
}).passthrough()
|
|
15166
15645
|
]);
|
|
15167
15646
|
|
|
15168
|
-
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 };
|