very-happy-cli 0.2.102 → 0.2.104

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.
Files changed (33) hide show
  1. package/dist/{AcpBackend-CpKkdwIL.cjs → AcpBackend-DDGfk3HJ.cjs} +1 -1
  2. package/dist/{AcpBackend-C1nF06ZR.mjs → AcpBackend-De4iQ_JG.mjs} +1 -1
  3. package/dist/{AcpSessionManager-DjWIwGTR.cjs → AcpSessionManager-Cdv6Xgwy.cjs} +1 -1
  4. package/dist/{AcpSessionManager-BDad8lK3.mjs → AcpSessionManager-DRzAgHtT.mjs} +1 -1
  5. package/dist/{config-DSF29tTS.cjs → config-CqM5UxVd.cjs} +2 -2
  6. package/dist/{config-BlTfR0KA.mjs → config-E3KdWyaQ.mjs} +2 -2
  7. package/dist/{index-CF3rNFlZ.mjs → index--TiLl7-A.mjs} +22 -16
  8. package/dist/{index-DjYlt3fw.mjs → index-Bzj1XdPx.mjs} +2 -2
  9. package/dist/{index-D_2eCeep.cjs → index-CX_3uUem.cjs} +26 -20
  10. package/dist/{index-CPC-a5lH.mjs → index-C_dmtMJ6.mjs} +5 -5
  11. package/dist/{index-C8HuYyZq.cjs → index-De6H3U0N.cjs} +2 -2
  12. package/dist/{index-yLIv8JPD.cjs → index-fuavV8uc.cjs} +4 -4
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-BVMhXRZO.cjs → installTerminalHooks-BXMBwrvq.cjs} +2 -2
  16. package/dist/{installTerminalHooks-DAR3YEo3.mjs → installTerminalHooks-DV0e8-V3.mjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +17 -0
  19. package/dist/lib.d.mts +17 -0
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-LCZtdhF0.cjs → mcp-Cq3On_Qo.cjs} +2 -2
  22. package/dist/{mcp--I-Uz_wH.mjs → mcp-DFS9Zohz.mjs} +2 -2
  23. package/dist/{runGemini-cLR7rfj6.mjs → runGemini-BFjtlkZI.mjs} +4 -4
  24. package/dist/{runGemini-Crna_6uM.cjs → runGemini-DFvjh8cO.cjs} +4 -4
  25. package/dist/{runOpenClaw-CWjOCdWg.cjs → runOpenClaw-B5Og1FED.cjs} +3 -3
  26. package/dist/{runOpenClaw-C1u9xEH7.mjs → runOpenClaw-BFYj1F4u.mjs} +3 -3
  27. package/dist/{send-BtQ9qf_z.mjs → send-B666yos0.mjs} +2 -2
  28. package/dist/{send-CufsYMXq.cjs → send-Bog0YPxo.cjs} +2 -2
  29. package/dist/{spawn-DPllkoUN.cjs → spawn-BSdMkMPX.cjs} +2 -2
  30. package/dist/{spawn-rdb0uM9R.mjs → spawn-DrqvVoNF.mjs} +2 -2
  31. package/dist/{types-7-mfp-j5.cjs → types-14LATr9X.cjs} +349 -105
  32. package/dist/{types-B3Dnzv_q.mjs → types-D9LyZitb.mjs} +347 -105
  33. package/package.json +7 -7
@@ -51,7 +51,7 @@ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
51
51
  var pty__namespace = /*#__PURE__*/_interopNamespaceDefault(pty);
52
52
 
53
53
  var name = "very-happy-cli";
54
- var version = "0.2.102";
54
+ var version = "0.2.104";
55
55
  var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
56
56
  var author = "Kirill Dubovitskiy";
57
57
  var contributors = [
@@ -164,12 +164,12 @@ var dependencies = {
164
164
  zod: "^4.0.0"
165
165
  };
166
166
  var optionalDependencies = {
167
- "very-happy-tools-arm64-darwin": "0.2.102",
168
- "very-happy-tools-x64-darwin": "0.2.102",
169
- "very-happy-tools-arm64-linux": "0.2.102",
170
- "very-happy-tools-x64-linux": "0.2.102",
171
- "very-happy-tools-arm64-win32": "0.2.102",
172
- "very-happy-tools-x64-win32": "0.2.102"
167
+ "very-happy-tools-arm64-darwin": "0.2.104",
168
+ "very-happy-tools-x64-darwin": "0.2.104",
169
+ "very-happy-tools-arm64-linux": "0.2.104",
170
+ "very-happy-tools-x64-linux": "0.2.104",
171
+ "very-happy-tools-arm64-win32": "0.2.104",
172
+ "very-happy-tools-x64-win32": "0.2.104"
173
173
  };
174
174
  var devDependencies = {
175
175
  "@slopus/happy-wire": "workspace:*",
@@ -1127,6 +1127,15 @@ z.z.object({
1127
1127
  /** B-282: `kill-terminal` honours `alsoAttached`. */
1128
1128
  killAttached: z.z.boolean().optional()
1129
1129
  }).optional(),
1130
+ /**
1131
+ * B-290 capability flag: this daemon answers `claude-list-history` (import a
1132
+ * Claude Code conversation that was never started through very-happy).
1133
+ * Same trust rule (`detectedAt >= startedAt`).
1134
+ */
1135
+ claudeHistory: z.z.object({
1136
+ rpcAvailable: z.z.boolean(),
1137
+ detectedAt: z.z.number()
1138
+ }).optional(),
1130
1139
  /** Relay-owned CLI compatibility/update policy last checked by this daemon. */
1131
1140
  cliUpdate: CliUpdateStateSchema.optional(),
1132
1141
  /**
@@ -1585,7 +1594,7 @@ class RpcHandlerManager {
1585
1594
  }
1586
1595
  }
1587
1596
 
1588
- const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-7-mfp-j5.cjs', document.baseURI).href))));
1597
+ const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-14LATr9X.cjs', document.baseURI).href))));
1589
1598
  function projectPath() {
1590
1599
  const path = path$1.resolve(__dirname$1, "..");
1591
1600
  return path;
@@ -2116,102 +2125,66 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2116
2125
  }
2117
2126
 
2118
2127
  const PRICING = {
2119
- // --- Claude 4 & Future Models ---
2120
- "claude-4.5-opus": {
2121
- input: 5,
2122
- output: 25,
2123
- cache_write: 6.25,
2124
- cache_read: 0.5
2125
- },
2126
- "claude-4.1-opus": {
2127
- input: 15,
2128
- output: 75,
2129
- cache_write: 18.75,
2130
- cache_read: 1.5
2131
- },
2132
- "claude-4-opus": {
2133
- input: 15,
2134
- output: 75,
2135
- cache_write: 18.75,
2136
- cache_read: 1.5
2137
- },
2138
- "claude-4.5-sonnet": {
2139
- input: 3,
2140
- output: 15,
2141
- cache_write: 3.75,
2142
- cache_read: 0.3
2143
- },
2144
- "claude-4-sonnet": {
2145
- input: 3,
2146
- output: 15,
2147
- cache_write: 3.75,
2148
- cache_read: 0.3
2149
- },
2150
- "claude-4.5-haiku": {
2151
- input: 1,
2152
- output: 5,
2153
- cache_write: 1.25,
2154
- cache_read: 0.1
2155
- },
2128
+ // --- Claude Fable / Mythos (same tier, same price) ---
2129
+ "claude-fable-5-1": { input: 10, output: 50, cache_write: 12.5, cache_read: 0.25 },
2130
+ "claude-fable-5": { input: 10, output: 50, cache_write: 12.5, cache_read: 1 },
2131
+ "claude-mythos-5-1": { input: 10, output: 50, cache_write: 12.5, cache_read: 0.25 },
2132
+ "claude-mythos-5": { input: 10, output: 50, cache_write: 12.5, cache_read: 1 },
2133
+ // --- Claude 5 ---
2134
+ "claude-opus-5": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
2135
+ "claude-sonnet-5": { input: 2, output: 10, cache_write: 2.5, cache_read: 0.2 },
2136
+ // --- Claude 4.x ---
2137
+ "claude-opus-4-8": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
2138
+ "claude-opus-4-7": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
2139
+ "claude-opus-4-6": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
2140
+ "claude-opus-4-5": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
2141
+ "claude-opus-4-1": { input: 15, output: 75, cache_write: 18.75, cache_read: 1.5 },
2142
+ "claude-opus-4": { input: 15, output: 75, cache_write: 18.75, cache_read: 1.5 },
2143
+ "claude-sonnet-4-6": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
2144
+ "claude-sonnet-4-5": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
2145
+ "claude-sonnet-4": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
2146
+ "claude-haiku-4-5": { input: 1, output: 5, cache_write: 1.25, cache_read: 0.1 },
2156
2147
  // --- Legacy / Claude 3 ---
2157
- "claude-3-opus-20240229": {
2158
- input: 15,
2159
- output: 75,
2160
- cache_write: 18.75,
2161
- cache_read: 1.5
2162
- },
2163
- "claude-3-sonnet-20240229": {
2164
- input: 3,
2165
- output: 15,
2166
- cache_write: 3.75,
2167
- cache_read: 0.3
2168
- },
2169
- "claude-3-5-sonnet-20240620": {
2170
- input: 3,
2171
- output: 15,
2172
- cache_write: 3.75,
2173
- cache_read: 0.3
2174
- },
2175
- // New Sonnet 3.5 updated model
2176
- "claude-3-5-sonnet-20241022": {
2177
- input: 3,
2178
- output: 15,
2179
- cache_write: 3.75,
2180
- cache_read: 0.3
2181
- },
2182
- "claude-3-haiku-20240307": {
2183
- input: 0.25,
2184
- output: 1.25,
2185
- cache_write: 0.3125,
2186
- cache_read: 0.025
2187
- },
2188
- "claude-3-5-haiku-20241022": {
2189
- input: 0.8,
2190
- output: 4,
2191
- cache_write: 1,
2192
- // Approx based on 1.25x rule usually or custom
2193
- cache_read: 0.08
2194
- }
2148
+ "claude-3-opus-20240229": { input: 15, output: 75, cache_write: 18.75, cache_read: 1.5 },
2149
+ "claude-3-sonnet-20240229": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
2150
+ "claude-3-5-sonnet-20240620": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
2151
+ "claude-3-5-sonnet-20241022": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
2152
+ "claude-3-7-sonnet-20250219": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
2153
+ "claude-3-haiku-20240307": { input: 0.25, output: 1.25, cache_write: 0.3125, cache_read: 0.025 },
2154
+ "claude-3-5-haiku-20241022": { input: 0.8, output: 4, cache_write: 1, cache_read: 0.08 }
2155
+ };
2156
+ const DEFAULT_MODEL = "claude-opus-5";
2157
+ const FAMILY_DEFAULTS = {
2158
+ fable: "claude-fable-5-1",
2159
+ // Project Glasswing counterpart of Fable: same tier, same published rates.
2160
+ mythos: "claude-mythos-5-1",
2161
+ opus: "claude-opus-5",
2162
+ sonnet: "claude-sonnet-5",
2163
+ haiku: "claude-haiku-4-5"
2195
2164
  };
2196
- const DEFAULT_MODEL = "claude-3-5-sonnet-20241022";
2165
+ function resolvePricingKey(modelId) {
2166
+ if (!modelId) return DEFAULT_MODEL;
2167
+ const raw = modelId.trim().toLowerCase();
2168
+ if (raw in PRICING) return raw;
2169
+ const id = raw.replace(/\[1m\]$/, "");
2170
+ if (id in PRICING) return id;
2171
+ const family = ["fable", "mythos", "opus", "sonnet", "haiku"].find((f) => id.includes(f));
2172
+ if (!family) return DEFAULT_MODEL;
2173
+ const version = id.match(/(?:^|[^0-9])(\d)(?:[-._](\d))?(?:[^0-9]|$)/);
2174
+ const major = version ? version[1] : null;
2175
+ const minor = version && version[2] ? version[2] : null;
2176
+ const key = major ? `claude-${family}-${major}${minor ? `-${minor}` : ""}` : null;
2177
+ if (key && key in PRICING) return key;
2178
+ if (major === "3" && family !== "fable" && family !== "mythos") {
2179
+ if (family === "opus") return "claude-3-opus-20240229";
2180
+ if (family === "haiku") return minor === "5" ? "claude-3-5-haiku-20241022" : "claude-3-haiku-20240307";
2181
+ if (minor === "7") return "claude-3-7-sonnet-20250219";
2182
+ return minor === "5" ? "claude-3-5-sonnet-20241022" : "claude-3-sonnet-20240229";
2183
+ }
2184
+ return FAMILY_DEFAULTS[family];
2185
+ }
2197
2186
  function calculateCost(usage, modelId) {
2198
- let pricing = PRICING[modelId];
2199
- if (!pricing) {
2200
- if (modelId?.includes("opus")) {
2201
- if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-opus"];
2202
- else if (modelId.includes("4.1")) pricing = PRICING["claude-4.1-opus"];
2203
- else if (modelId.includes("4")) pricing = PRICING["claude-4-opus"];
2204
- else pricing = PRICING["claude-3-opus-20240229"];
2205
- } else if (modelId?.includes("sonnet")) {
2206
- if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-sonnet"];
2207
- else if (modelId.includes("4")) pricing = PRICING["claude-4-sonnet"];
2208
- else pricing = PRICING["claude-3-5-sonnet-20241022"];
2209
- } else if (modelId?.includes("haiku")) {
2210
- if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-haiku"];
2211
- else if (modelId.includes("3.5")) pricing = PRICING["claude-3-5-haiku-20241022"];
2212
- else pricing = PRICING["claude-3-haiku-20240307"];
2213
- } else pricing = PRICING[DEFAULT_MODEL];
2214
- }
2187
+ const pricing = PRICING[resolvePricingKey(modelId)];
2215
2188
  const inputCost = usage.input_tokens / 1e6 * pricing.input;
2216
2189
  const outputCost = usage.output_tokens / 1e6 * pricing.output;
2217
2190
  const cacheWriteCost = (usage.cache_creation_input_tokens || 0) / 1e6 * pricing.cache_write;
@@ -4056,6 +4029,7 @@ function summarizeSpawnSessionForLog(value) {
4056
4029
  environmentVariableNames: Object.keys(environmentVariables).sort(),
4057
4030
  resumesClaude: typeof input.resumeClaudeSessionId === "string" && input.resumeClaudeSessionId.length > 0,
4058
4031
  resumesCodex: typeof input.resumeCodexThreadId === "string" && input.resumeCodexThreadId.length > 0,
4032
+ importsClaude: typeof input.importedFromClaudeSessionId === "string" && input.importedFromClaudeSessionId.length > 0,
4059
4033
  forceNew: input.forceNew === true,
4060
4034
  ...typeof input.permissionMode === "string" ? { permissionMode: input.permissionMode } : {}
4061
4035
  };
@@ -4593,6 +4567,23 @@ function readPersistedSessions() {
4593
4567
  return {};
4594
4568
  }
4595
4569
  }
4570
+ function readTrackedClaudeSessionIds() {
4571
+ try {
4572
+ if (!node_fs.existsSync(configuration.sessionsFile)) return [];
4573
+ const data = JSON.parse(node_fs.readFileSync(configuration.sessionsFile, "utf-8"));
4574
+ if (!data?.sessions || typeof data.sessions !== "object") return [];
4575
+ const ids = /* @__PURE__ */ new Set();
4576
+ for (const session of Object.values(data.sessions)) {
4577
+ const metadata = (session ?? {}).metadata;
4578
+ for (const id of [metadata?.claudeSessionId, metadata?.importedFromClaudeSessionId]) {
4579
+ if (typeof id === "string" && id.length > 0) ids.add(id.toLowerCase());
4580
+ }
4581
+ }
4582
+ return [...ids];
4583
+ } catch {
4584
+ return [];
4585
+ }
4586
+ }
4596
4587
  function persistSession(sessionId, session) {
4597
4588
  try {
4598
4589
  const existing = readPersistedSessions();
@@ -4640,6 +4631,7 @@ var persistence = /*#__PURE__*/Object.freeze({
4640
4631
  readDaemonState: readDaemonState,
4641
4632
  readPersistedSessions: readPersistedSessions,
4642
4633
  readSettings: readSettings,
4634
+ readTrackedClaudeSessionIds: readTrackedClaudeSessionIds,
4643
4635
  releaseDaemonLock: releaseDaemonLock,
4644
4636
  updateSettings: updateSettings,
4645
4637
  writeCredentialsDataKey: writeCredentialsDataKey,
@@ -5005,10 +4997,15 @@ function scrubTmuxClientEnv(env) {
5005
4997
  return env;
5006
4998
  }
5007
4999
 
5000
+ function getClaudeConfigDir() {
5001
+ return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
5002
+ }
5003
+ function getClaudeProjectsRoot() {
5004
+ return path.join(getClaudeConfigDir(), "projects");
5005
+ }
5008
5006
  function getProjectPath(workingDirectory) {
5009
5007
  const projectId = path.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
5010
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
5011
- return path.join(claudeConfigDir, "projects", projectId);
5008
+ return path.join(getClaudeProjectsRoot(), projectId);
5012
5009
  }
5013
5010
 
5014
5011
  var xtermHeadless = {};
@@ -12591,6 +12588,182 @@ function detectResumeSupport(happyHomeDir = configuration.happyHomeDir) {
12591
12588
  };
12592
12589
  }
12593
12590
 
12591
+ const TITLE_PROMPT_PREFIX = "Summarize the SPECIFIC topic/request of this message into a 3-6 word title";
12592
+ const BOARD_ANALYZER_PROMPT_PREFIX = "You are a status analyzer for a task board of coding-agent sessions.";
12593
+ const VERY_HAPPY_ONE_SHOT_PROMPT_PREFIXES = [
12594
+ TITLE_PROMPT_PREFIX,
12595
+ BOARD_ANALYZER_PROMPT_PREFIX
12596
+ ];
12597
+ function isVeryHappyOneShotPrompt(firstPrompt) {
12598
+ const text = firstPrompt.trimStart();
12599
+ return VERY_HAPPY_ONE_SHOT_PROMPT_PREFIXES.some((prefix) => text.startsWith(prefix));
12600
+ }
12601
+
12602
+ const UUID_RE$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12603
+ async function listClaudeProjectDirs(projectsRoot) {
12604
+ let names;
12605
+ try {
12606
+ names = await promises.readdir(projectsRoot);
12607
+ } catch (error) {
12608
+ if (error.code === "ENOENT") return [];
12609
+ throw error;
12610
+ }
12611
+ const dirs = [];
12612
+ for (const name of names) {
12613
+ const full = path.join(projectsRoot, name);
12614
+ try {
12615
+ if ((await promises.stat(full)).isDirectory()) dirs.push(full);
12616
+ } catch {
12617
+ }
12618
+ }
12619
+ return dirs;
12620
+ }
12621
+ async function collectCandidates(projectDirs, exclude) {
12622
+ const out = [];
12623
+ for (const dir of projectDirs) {
12624
+ let names;
12625
+ try {
12626
+ names = await promises.readdir(dir);
12627
+ } catch {
12628
+ continue;
12629
+ }
12630
+ for (const name of names) {
12631
+ if (!name.endsWith(".jsonl")) continue;
12632
+ const id = name.slice(0, -".jsonl".length);
12633
+ if (!UUID_RE$1.test(id) || exclude.has(id.toLowerCase())) continue;
12634
+ const path$1 = path.join(dir, name);
12635
+ try {
12636
+ const s = await promises.stat(path$1);
12637
+ if (!s.isFile() || s.size === 0) continue;
12638
+ out.push({ path: path$1, id, updatedAt: s.mtimeMs, sizeBytes: s.size });
12639
+ } catch {
12640
+ }
12641
+ }
12642
+ }
12643
+ out.sort((a, b) => b.updatedAt - a.updatedAt);
12644
+ const seen = /* @__PURE__ */ new Set();
12645
+ return out.filter((c) => {
12646
+ const key = c.id.toLowerCase();
12647
+ if (seen.has(key)) return false;
12648
+ seen.add(key);
12649
+ return true;
12650
+ });
12651
+ }
12652
+ async function readHead(path, bytes) {
12653
+ const fh = await promises.open(path, "r");
12654
+ try {
12655
+ const buffer = Buffer.alloc(bytes);
12656
+ const { bytesRead } = await fh.read(buffer, 0, bytes, 0);
12657
+ return buffer.subarray(0, bytesRead).toString("utf-8");
12658
+ } finally {
12659
+ await fh.close();
12660
+ }
12661
+ }
12662
+ function oneLine(text, max) {
12663
+ const collapsed = text.replace(/\s+/g, " ").trim();
12664
+ return collapsed.length > max ? `${collapsed.slice(0, max - 1)}\u2026` : collapsed;
12665
+ }
12666
+ function parseTimestamp(raw) {
12667
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
12668
+ if (typeof raw === "string") {
12669
+ const t = Date.parse(raw);
12670
+ if (Number.isFinite(t)) return t;
12671
+ }
12672
+ return null;
12673
+ }
12674
+ function parseClaudeHistoryHead(head, promptChars) {
12675
+ let cwd;
12676
+ let firstPrompt;
12677
+ let summary;
12678
+ let startedAt = null;
12679
+ let entrypoint;
12680
+ let gitBranch;
12681
+ let version;
12682
+ const lines = head.split("\n");
12683
+ for (const line of lines) {
12684
+ if (line.length === 0) continue;
12685
+ let parsed;
12686
+ try {
12687
+ parsed = JSON.parse(line);
12688
+ } catch {
12689
+ continue;
12690
+ }
12691
+ if (!parsed || typeof parsed !== "object") continue;
12692
+ if (parsed.type === "summary" && typeof parsed.summary === "string" && !summary) {
12693
+ const s = oneLine(parsed.summary, promptChars);
12694
+ if (s) summary = s;
12695
+ continue;
12696
+ }
12697
+ if (typeof parsed.cwd === "string" && parsed.cwd && !cwd) cwd = parsed.cwd;
12698
+ if (typeof parsed.entrypoint === "string" && parsed.entrypoint && !entrypoint) entrypoint = parsed.entrypoint;
12699
+ if (typeof parsed.gitBranch === "string" && parsed.gitBranch && !gitBranch) gitBranch = parsed.gitBranch;
12700
+ if (typeof parsed.version === "string" && parsed.version && !version) version = parsed.version;
12701
+ if (startedAt === null) startedAt = parseTimestamp(parsed.timestamp);
12702
+ if (!firstPrompt && parsed.type === "user" && !parsed.isSidechain) {
12703
+ const content = parsed.message?.content;
12704
+ let text = null;
12705
+ if (typeof content === "string") {
12706
+ text = content;
12707
+ } else if (Array.isArray(content)) {
12708
+ const block = content.find((b) => b && b.type === "text" && typeof b.text === "string");
12709
+ if (block) text = block.text;
12710
+ }
12711
+ if (text) {
12712
+ if (isVeryHappyOneShotPrompt(text)) return null;
12713
+ const cleaned = oneLine(stripHarnessTags(text), promptChars);
12714
+ if (cleaned) firstPrompt = cleaned;
12715
+ }
12716
+ }
12717
+ if (cwd && firstPrompt && summary !== void 0 && startedAt !== null) break;
12718
+ }
12719
+ if (!cwd) return null;
12720
+ if (!firstPrompt && !summary) return null;
12721
+ return {
12722
+ cwd,
12723
+ firstPrompt: firstPrompt ?? summary ?? "",
12724
+ ...summary ? { summary } : {},
12725
+ startedAt: startedAt ?? 0,
12726
+ ...entrypoint ? { entrypoint } : {},
12727
+ ...gitBranch ? { gitBranch } : {},
12728
+ ...version ? { version } : {}
12729
+ };
12730
+ }
12731
+ function stripHarnessTags(text) {
12732
+ return text.replace(/<(system-reminder|task-notification|command-message|command-name|command-args|local-command-stdout|local-command-caveat)>[\s\S]*?<\/\1>/g, " ").replace(/<\/?(system-reminder|task-notification)>/g, " ");
12733
+ }
12734
+ async function listClaudeSessionHistory(options) {
12735
+ const limit = Math.max(1, Math.min(options.limit ?? 60, 500));
12736
+ const headBytes = Math.max(4096, options.headBytes ?? 64 * 1024);
12737
+ const promptChars = Math.max(20, options.promptChars ?? 200);
12738
+ const exclude = new Set(Array.from(options.exclude ?? [], (id) => id.toLowerCase()));
12739
+ const candidates = await collectCandidates(options.projectDirs, exclude);
12740
+ const entries = [];
12741
+ let scanned = 0;
12742
+ let truncated = false;
12743
+ for (const candidate of candidates) {
12744
+ if (entries.length >= limit) {
12745
+ truncated = true;
12746
+ break;
12747
+ }
12748
+ scanned += 1;
12749
+ let head;
12750
+ try {
12751
+ head = await readHead(candidate.path, headBytes);
12752
+ } catch {
12753
+ continue;
12754
+ }
12755
+ const parsed = parseClaudeHistoryHead(head, promptChars);
12756
+ if (!parsed) continue;
12757
+ entries.push({
12758
+ claudeSessionId: candidate.id.toLowerCase(),
12759
+ updatedAt: Math.round(candidate.updatedAt),
12760
+ sizeBytes: candidate.sizeBytes,
12761
+ ...parsed
12762
+ });
12763
+ }
12764
+ return { entries, truncated, scanned };
12765
+ }
12766
+
12594
12767
  class ForkTruncateUuidNotFoundError extends Error {
12595
12768
  constructor(cutAfterUuid, sourcePath) {
12596
12769
  super(`Truncation UUID ${cutAfterUuid} not found in ${sourcePath}`);
@@ -12615,6 +12788,16 @@ function isUserPrompt(parsed) {
12615
12788
  const content = parsed.message?.content;
12616
12789
  return typeof content === "string";
12617
12790
  }
12791
+ async function discardForkedSession(projectDir, claudeSessionId) {
12792
+ try {
12793
+ await promises.unlink(jsonlPath(projectDir, claudeSessionId));
12794
+ logger.debug(`[CLAUDE FORK] Discarded unused fork ${claudeSessionId}`);
12795
+ } catch (error) {
12796
+ if (error.code !== "ENOENT") {
12797
+ logger.debug(`[CLAUDE FORK] Failed to discard fork ${claudeSessionId}:`, error);
12798
+ }
12799
+ }
12800
+ }
12618
12801
  async function forkSession(projectDir, sourceClaudeSessionId) {
12619
12802
  const newId = node_crypto.randomUUID();
12620
12803
  const src = jsonlPath(projectDir, sourceClaudeSessionId);
@@ -14211,12 +14394,12 @@ class ApiMachineClient {
14211
14394
  this.stopSessionHandler = stopSession;
14212
14395
  this.listTrackedSessionIds = listTrackedSessionIds ?? null;
14213
14396
  this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
14214
- const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, variant, forceNew, permissionMode } = params || {};
14397
+ const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, importedFromClaudeSessionId, variant, forceNew, permissionMode } = params || {};
14215
14398
  logger.debug("[API MACHINE] Spawning session:", summarizeSpawnSessionForLog(params));
14216
14399
  if (!directory && variant !== "assistant") {
14217
14400
  throw new Error("Directory is required");
14218
14401
  }
14219
- const result = await spawnSession({ directory: directory || "", sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, variant: variant === "assistant" ? "assistant" : void 0, forceNew: forceNew === true, permissionMode: typeof permissionMode === "string" ? permissionMode : void 0 });
14402
+ const result = await spawnSession({ directory: directory || "", sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, importedFromClaudeSessionId: typeof importedFromClaudeSessionId === "string" ? importedFromClaudeSessionId : void 0, variant: variant === "assistant" ? "assistant" : void 0, forceNew: forceNew === true, permissionMode: typeof permissionMode === "string" ? permissionMode : void 0 });
14220
14403
  switch (result.type) {
14221
14404
  case "success":
14222
14405
  logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
@@ -14228,6 +14411,46 @@ class ApiMachineClient {
14228
14411
  throw new Error(result.errorMessage);
14229
14412
  }
14230
14413
  });
14414
+ this.rpcHandlerManager.registerHandler("claude-import-session", async (params) => {
14415
+ const { directory, claudeSessionId, approvedNewDirectoryCreation, permissionMode } = params || {};
14416
+ if (typeof directory !== "string" || directory.length === 0) {
14417
+ throw new Error("directory is required");
14418
+ }
14419
+ if (typeof claudeSessionId !== "string" || !UUID_RE.test(claudeSessionId)) {
14420
+ throw new Error("claudeSessionId must be a valid UUID");
14421
+ }
14422
+ const projectDir = getProjectPath(directory);
14423
+ let newClaudeSessionId;
14424
+ try {
14425
+ newClaudeSessionId = await forkSession(projectDir, claudeSessionId);
14426
+ } catch (error) {
14427
+ if (error instanceof ForkSourceMissingError) {
14428
+ throw new Error("Claude session file not found on this machine");
14429
+ }
14430
+ throw error;
14431
+ }
14432
+ let result;
14433
+ try {
14434
+ result = await spawnSession({
14435
+ directory,
14436
+ machineId: this.machine.id,
14437
+ approvedNewDirectoryCreation: approvedNewDirectoryCreation === true,
14438
+ agent: "claude",
14439
+ resumeClaudeSessionId: newClaudeSessionId,
14440
+ importedFromClaudeSessionId: claudeSessionId,
14441
+ permissionMode: typeof permissionMode === "string" ? permissionMode : void 0
14442
+ });
14443
+ } catch (error) {
14444
+ await discardForkedSession(projectDir, newClaudeSessionId);
14445
+ throw error;
14446
+ }
14447
+ if (result.type !== "success") {
14448
+ await discardForkedSession(projectDir, newClaudeSessionId);
14449
+ return result;
14450
+ }
14451
+ logger.debug(`[API MACHINE] Imported Claude conversation ${claudeSessionId} as session ${result.sessionId}`);
14452
+ return { type: "success", sessionId: result.sessionId, newClaudeSessionId };
14453
+ });
14231
14454
  this.syncResumeSessionRpcRegistration();
14232
14455
  this.syncRestartSessionRpcRegistration();
14233
14456
  this.rpcHandlerManager.registerHandler("open-terminal", async (params) => {
@@ -14419,6 +14642,23 @@ class ApiMachineClient {
14419
14642
  throw error;
14420
14643
  }
14421
14644
  });
14645
+ this.rpcHandlerManager.registerHandler("claude-list-history", async (params) => {
14646
+ const { directory, limit, exclude } = params || {};
14647
+ if (directory !== void 0 && (typeof directory !== "string" || directory.length === 0)) {
14648
+ throw new Error("directory must be a non-empty string when provided");
14649
+ }
14650
+ const projectDirs = typeof directory === "string" ? [getProjectPath(directory)] : await listClaudeProjectDirs(getClaudeProjectsRoot());
14651
+ const excludeIds = [
14652
+ ...Array.isArray(exclude) ? exclude.filter((id) => typeof id === "string" && UUID_RE.test(id)) : [],
14653
+ ...readTrackedClaudeSessionIds()
14654
+ ];
14655
+ const result = await listClaudeSessionHistory({
14656
+ projectDirs,
14657
+ limit: typeof limit === "number" && Number.isFinite(limit) ? Math.max(1, Math.min(Math.floor(limit), 200)) : 60,
14658
+ exclude: excludeIds
14659
+ });
14660
+ return { type: "success", ...result };
14661
+ });
14422
14662
  this.rpcHandlerManager.registerHandler("codex-fork-thread", async (params) => {
14423
14663
  const directory = requireNonEmptyString(params?.directory, "directory");
14424
14664
  const codexThreadId = requireNonEmptyString(params?.codexThreadId, "codexThreadId");
@@ -14716,6 +14956,8 @@ class ApiMachineClient {
14716
14956
  terminalRestore: { rpcAvailable: true, detectedAt: now },
14717
14957
  // B-273 capability flag (same restamp discipline).
14718
14958
  tmuxSessions: { rpcAvailable: true, detectedAt: now, killAttached: true },
14959
+ // B-290 capability flag: `claude-list-history` (same restamp discipline).
14960
+ claudeHistory: { rpcAvailable: true, detectedAt: now },
14719
14961
  // B-084: closed records survive daemon restarts (persisted
14720
14962
  // in closed-terminals.json), so the connect snapshot ships
14721
14963
  // them too — not just the incremental pushes.
@@ -16060,6 +16302,7 @@ const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
16060
16302
  exports.ApiClient = ApiClient;
16061
16303
  exports.ApiSessionClient = ApiSessionClient;
16062
16304
  exports.AsyncLock = AsyncLock;
16305
+ exports.BOARD_ANALYZER_PROMPT_PREFIX = BOARD_ANALYZER_PROMPT_PREFIX;
16063
16306
  exports.CodexAppServerClient = CodexAppServerClient;
16064
16307
  exports.DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS = DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS;
16065
16308
  exports.InvalidateSync = InvalidateSync;
@@ -16067,6 +16310,7 @@ exports.MAX_CHAT_ATTACHMENT_SOURCE_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES;
16067
16310
  exports.MIRROR_BACKFILL_LINES_DEFAULT = MIRROR_BACKFILL_LINES_DEFAULT;
16068
16311
  exports.RawJSONLinesSchema = RawJSONLinesSchema;
16069
16312
  exports.SandboxConfigSchema = SandboxConfigSchema;
16313
+ exports.TITLE_PROMPT_PREFIX = TITLE_PROMPT_PREFIX;
16070
16314
  exports.acquireDaemonLock = acquireDaemonLock;
16071
16315
  exports.api = api;
16072
16316
  exports.clearCredentials = clearCredentials;