olakai-cli 0.1.15 → 0.2.1

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/index.js CHANGED
@@ -2183,9 +2183,197 @@ function registerActivityCommand(program2) {
2183
2183
  }
2184
2184
 
2185
2185
  // src/commands/monitor.ts
2186
+ import * as fs3 from "fs";
2187
+ import * as path3 from "path";
2188
+ import * as readline from "readline";
2189
+
2190
+ // src/commands/monitor-transcript.ts
2191
+ var SKILL_REGEX = /^\/([\w-]+)(?:\s|$)/;
2192
+ function detectSkill(userMessage) {
2193
+ if (typeof userMessage !== "string") return void 0;
2194
+ const trimmed = userMessage.trimStart();
2195
+ if (!trimmed) return void 0;
2196
+ const match = SKILL_REGEX.exec(trimmed);
2197
+ if (!match) return void 0;
2198
+ return match[1];
2199
+ }
2200
+ function extractTextContent(content) {
2201
+ if (typeof content === "string") return content;
2202
+ if (!Array.isArray(content)) return "";
2203
+ const parts = [];
2204
+ for (const block of content) {
2205
+ if (block?.type === "text" && typeof block.text === "string") {
2206
+ parts.push(block.text);
2207
+ }
2208
+ }
2209
+ return parts.join("\n").trim();
2210
+ }
2211
+ function isMetaUserMessage(line, text) {
2212
+ if (line.isMeta === true) return true;
2213
+ if (!text) return true;
2214
+ return text.includes("<command-name>") || text.includes("<local-command-caveat>") || text.includes("<command-message>");
2215
+ }
2216
+ function parseTimestamp(ts) {
2217
+ if (typeof ts !== "string" || !ts) return NaN;
2218
+ return Date.parse(ts);
2219
+ }
2220
+ function isCompactionEntry(line) {
2221
+ if (line.type !== "system") return false;
2222
+ const subtype = line.subtype ?? "";
2223
+ return subtype === "compact_boundary" || subtype === "pre_compact" || subtype === "post_compact" || /compact/i.test(subtype);
2224
+ }
2225
+ function parseTranscript(raw) {
2226
+ const empty = {
2227
+ prompt: "",
2228
+ response: "",
2229
+ tokens: 0,
2230
+ inputTokens: 0,
2231
+ outputTokens: 0,
2232
+ modelName: null,
2233
+ numTurns: 0
2234
+ };
2235
+ if (!raw) return empty;
2236
+ const lines = raw.split("\n");
2237
+ let lastUserText = "";
2238
+ let lastUserTimestamp = NaN;
2239
+ let lastUserTimestampRaw;
2240
+ let lastAssistantText = "";
2241
+ let lastAssistantTimestamp = NaN;
2242
+ let lastAssistantModel = null;
2243
+ let lastAssistantInputTokens = 0;
2244
+ let lastAssistantOutputTokens = 0;
2245
+ let numTurns = 0;
2246
+ let currentTurnUserTimestamp = NaN;
2247
+ for (const rawLine of lines) {
2248
+ if (!rawLine) continue;
2249
+ let parsed;
2250
+ try {
2251
+ parsed = JSON.parse(rawLine);
2252
+ } catch {
2253
+ continue;
2254
+ }
2255
+ if (isCompactionEntry(parsed)) continue;
2256
+ if (parsed.isSidechain === true) continue;
2257
+ if (parsed.type === "user" && parsed.message) {
2258
+ const text = extractTextContent(parsed.message.content);
2259
+ if (isMetaUserMessage(parsed, text)) continue;
2260
+ lastUserText = text;
2261
+ lastUserTimestamp = parseTimestamp(parsed.timestamp);
2262
+ currentTurnUserTimestamp = lastUserTimestamp;
2263
+ lastUserTimestampRaw = typeof parsed.timestamp === "string" && parsed.timestamp ? parsed.timestamp : void 0;
2264
+ } else if (parsed.type === "assistant" && parsed.message) {
2265
+ const text = extractTextContent(parsed.message.content);
2266
+ numTurns += 1;
2267
+ if (text) lastAssistantText = text;
2268
+ if (typeof parsed.message.model === "string") {
2269
+ lastAssistantModel = parsed.message.model;
2270
+ }
2271
+ const usage = parsed.message.usage;
2272
+ if (usage) {
2273
+ const input = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
2274
+ const output = usage.output_tokens ?? 0;
2275
+ lastAssistantInputTokens = input;
2276
+ lastAssistantOutputTokens = output;
2277
+ }
2278
+ const ts = parseTimestamp(parsed.timestamp);
2279
+ if (!Number.isNaN(ts)) {
2280
+ lastAssistantTimestamp = ts;
2281
+ }
2282
+ }
2283
+ }
2284
+ const result = {
2285
+ prompt: lastUserText,
2286
+ response: lastAssistantText,
2287
+ tokens: lastAssistantInputTokens + lastAssistantOutputTokens,
2288
+ inputTokens: lastAssistantInputTokens,
2289
+ outputTokens: lastAssistantOutputTokens,
2290
+ modelName: lastAssistantModel,
2291
+ numTurns
2292
+ };
2293
+ if (!Number.isNaN(currentTurnUserTimestamp) && !Number.isNaN(lastAssistantTimestamp) && lastAssistantTimestamp >= currentTurnUserTimestamp) {
2294
+ result.latencyMs = Math.round(
2295
+ lastAssistantTimestamp - currentTurnUserTimestamp
2296
+ );
2297
+ }
2298
+ const skill = detectSkill(lastUserText);
2299
+ if (skill) {
2300
+ result.skill = skill;
2301
+ }
2302
+ if (lastUserTimestampRaw) {
2303
+ result.userTurnTimestamp = lastUserTimestampRaw;
2304
+ }
2305
+ return result;
2306
+ }
2307
+
2308
+ // src/commands/monitor-state.ts
2186
2309
  import * as fs2 from "fs";
2310
+ import * as os2 from "os";
2187
2311
  import * as path2 from "path";
2188
- import * as readline from "readline";
2312
+ var STATE_DIR_SEGMENTS = [".olakai", "monitor-state"];
2313
+ function getStateDir(homeDir) {
2314
+ return path2.join(homeDir, ...STATE_DIR_SEGMENTS);
2315
+ }
2316
+ function getStateFile(sessionId, homeDir) {
2317
+ return path2.join(getStateDir(homeDir), `${sessionId}.json`);
2318
+ }
2319
+ var debugLogger = null;
2320
+ function setDebugLogger(logger) {
2321
+ debugLogger = logger;
2322
+ }
2323
+ function log(label, data) {
2324
+ if (debugLogger) {
2325
+ try {
2326
+ debugLogger(label, data);
2327
+ } catch {
2328
+ }
2329
+ }
2330
+ }
2331
+ function loadSessionState(sessionId, homeDir = os2.homedir()) {
2332
+ if (!sessionId) return null;
2333
+ const filePath = getStateFile(sessionId, homeDir);
2334
+ try {
2335
+ if (!fs2.existsSync(filePath)) return null;
2336
+ const raw = fs2.readFileSync(filePath, "utf-8");
2337
+ const parsed = JSON.parse(raw);
2338
+ if (typeof parsed?.lastUserTimestamp !== "string" || typeof parsed?.lastReportedAt !== "string" || typeof parsed?.numTurnsAtLastReport !== "number") {
2339
+ return null;
2340
+ }
2341
+ return {
2342
+ lastUserTimestamp: parsed.lastUserTimestamp,
2343
+ lastReportedAt: parsed.lastReportedAt,
2344
+ numTurnsAtLastReport: parsed.numTurnsAtLastReport
2345
+ };
2346
+ } catch (err) {
2347
+ log("state-load-failed", {
2348
+ sessionId,
2349
+ error: err.message
2350
+ });
2351
+ return null;
2352
+ }
2353
+ }
2354
+ function saveSessionState(sessionId, state, homeDir = os2.homedir()) {
2355
+ if (!sessionId) return;
2356
+ const dir = getStateDir(homeDir);
2357
+ const filePath = getStateFile(sessionId, homeDir);
2358
+ try {
2359
+ if (!fs2.existsSync(dir)) {
2360
+ fs2.mkdirSync(dir, { recursive: true });
2361
+ }
2362
+ fs2.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
2363
+ } catch (err) {
2364
+ log("state-save-failed", {
2365
+ sessionId,
2366
+ error: err.message
2367
+ });
2368
+ }
2369
+ }
2370
+ function shouldReportTurn(existing, currentUserTimestamp) {
2371
+ if (!currentUserTimestamp) return true;
2372
+ if (!existing) return true;
2373
+ return existing.lastUserTimestamp !== currentUserTimestamp;
2374
+ }
2375
+
2376
+ // src/commands/monitor.ts
2189
2377
  var CLAUDE_DIR = ".claude";
2190
2378
  var SETTINGS_FILE = "settings.json";
2191
2379
  var MONITOR_CONFIG_FILE = "olakai-monitor.json";
@@ -2201,8 +2389,36 @@ var HOOK_DEFINITIONS = {
2201
2389
  }
2202
2390
  ]
2203
2391
  }
2392
+ ],
2393
+ SubagentStop: [
2394
+ {
2395
+ matcher: "",
2396
+ hooks: [
2397
+ {
2398
+ type: "command",
2399
+ command: "olakai monitor hook subagent-stop"
2400
+ }
2401
+ ]
2402
+ }
2204
2403
  ]
2205
2404
  };
2405
+ function mergeHooksSettings(existing, definitions = HOOK_DEFINITIONS) {
2406
+ const merged = {
2407
+ ...existing ?? {}
2408
+ };
2409
+ for (const [event, defaultEntries] of Object.entries(definitions)) {
2410
+ const existingEntries = merged[event] ?? [];
2411
+ const hasOlakaiHook = existingEntries.some(
2412
+ (e) => e.hooks.some((h) => h.command.includes(OLAKAI_HOOK_MARKER))
2413
+ );
2414
+ if (hasOlakaiHook) {
2415
+ merged[event] = existingEntries;
2416
+ } else {
2417
+ merged[event] = [...existingEntries, ...defaultEntries];
2418
+ }
2419
+ }
2420
+ return merged;
2421
+ }
2206
2422
  function prompt(question) {
2207
2423
  const rl = readline.createInterface({
2208
2424
  input: process.stdin,
@@ -2218,39 +2434,55 @@ function prompt(question) {
2218
2434
  function findProjectRoot(startDir) {
2219
2435
  let dir = startDir ?? process.cwd();
2220
2436
  while (true) {
2221
- if (fs2.existsSync(path2.join(dir, CLAUDE_DIR))) {
2437
+ if (fs3.existsSync(path3.join(dir, CLAUDE_DIR))) {
2222
2438
  return dir;
2223
2439
  }
2224
- const parent = path2.dirname(dir);
2440
+ const parent = path3.dirname(dir);
2225
2441
  if (parent === dir) break;
2226
2442
  dir = parent;
2227
2443
  }
2228
2444
  return startDir ?? process.cwd();
2229
2445
  }
2446
+ function findConfiguredProjectRoot(startDir) {
2447
+ let dir = startDir ?? process.cwd();
2448
+ while (true) {
2449
+ if (fs3.existsSync(path3.join(dir, CLAUDE_DIR, MONITOR_CONFIG_FILE))) {
2450
+ return dir;
2451
+ }
2452
+ const parent = path3.dirname(dir);
2453
+ if (parent === dir) break;
2454
+ dir = parent;
2455
+ }
2456
+ return null;
2457
+ }
2458
+ function resolveProjectRootFromPayload(eventData, fallbackCwd) {
2459
+ const payloadCwd = typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : fallbackCwd;
2460
+ return findConfiguredProjectRoot(payloadCwd);
2461
+ }
2230
2462
  function getClaudeDir(projectRoot) {
2231
- return path2.join(projectRoot, CLAUDE_DIR);
2463
+ return path3.join(projectRoot, CLAUDE_DIR);
2232
2464
  }
2233
2465
  function getSettingsPath(projectRoot) {
2234
- return path2.join(getClaudeDir(projectRoot), SETTINGS_FILE);
2466
+ return path3.join(getClaudeDir(projectRoot), SETTINGS_FILE);
2235
2467
  }
2236
2468
  function getMonitorConfigPath(projectRoot) {
2237
- return path2.join(getClaudeDir(projectRoot), MONITOR_CONFIG_FILE);
2469
+ return path3.join(getClaudeDir(projectRoot), MONITOR_CONFIG_FILE);
2238
2470
  }
2239
2471
  function readJsonFile(filePath) {
2240
2472
  try {
2241
- if (!fs2.existsSync(filePath)) return null;
2242
- const content = fs2.readFileSync(filePath, "utf-8");
2473
+ if (!fs3.existsSync(filePath)) return null;
2474
+ const content = fs3.readFileSync(filePath, "utf-8");
2243
2475
  return JSON.parse(content);
2244
2476
  } catch {
2245
2477
  return null;
2246
2478
  }
2247
2479
  }
2248
2480
  function writeJsonFile(filePath, data) {
2249
- const dir = path2.dirname(filePath);
2250
- if (!fs2.existsSync(dir)) {
2251
- fs2.mkdirSync(dir, { recursive: true });
2481
+ const dir = path3.dirname(filePath);
2482
+ if (!fs3.existsSync(dir)) {
2483
+ fs3.mkdirSync(dir, { recursive: true });
2252
2484
  }
2253
- fs2.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
2485
+ fs3.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
2254
2486
  }
2255
2487
  function loadMonitorConfig(projectRoot) {
2256
2488
  const root = projectRoot ?? findProjectRoot();
@@ -2290,7 +2522,7 @@ async function initCommand() {
2290
2522
  }
2291
2523
  console.log("Setting up Claude Code monitoring for this workspace...\n");
2292
2524
  const projectRoot = process.cwd();
2293
- const dirName = path2.basename(projectRoot);
2525
+ const dirName = path3.basename(projectRoot);
2294
2526
  let agent;
2295
2527
  const choice = await prompt(
2296
2528
  "Create a new agent or use an existing one? (new/existing) [new]: "
@@ -2341,21 +2573,12 @@ Select agent (1-${agents.length}): `);
2341
2573
  }
2342
2574
  }
2343
2575
  const claudeDir = getClaudeDir(projectRoot);
2344
- if (!fs2.existsSync(claudeDir)) {
2345
- fs2.mkdirSync(claudeDir, { recursive: true });
2576
+ if (!fs3.existsSync(claudeDir)) {
2577
+ fs3.mkdirSync(claudeDir, { recursive: true });
2346
2578
  }
2347
2579
  const settingsPath = getSettingsPath(projectRoot);
2348
2580
  const existingSettings = readJsonFile(settingsPath) ?? {};
2349
- const mergedHooks = {
2350
- ...existingSettings.hooks ?? {}
2351
- };
2352
- for (const [event, entries] of Object.entries(HOOK_DEFINITIONS)) {
2353
- const existing = mergedHooks[event] ?? [];
2354
- const filtered = existing.filter(
2355
- (e) => !e.hooks.some((h) => h.command.includes(OLAKAI_HOOK_MARKER))
2356
- );
2357
- mergedHooks[event] = [...filtered, ...entries];
2358
- }
2581
+ const mergedHooks = mergeHooksSettings(existingSettings.hooks);
2359
2582
  const updatedSettings = {
2360
2583
  ...existingSettings,
2361
2584
  hooks: mergedHooks
@@ -2372,7 +2595,7 @@ Select agent (1-${agents.length}): `);
2372
2595
  };
2373
2596
  const monitorConfigPath = getMonitorConfigPath(projectRoot);
2374
2597
  writeJsonFile(monitorConfigPath, monitorConfig);
2375
- fs2.chmodSync(monitorConfigPath, 384);
2598
+ fs3.chmodSync(monitorConfigPath, 384);
2376
2599
  console.log("");
2377
2600
  console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
2378
2601
  if (agent.apiKey?.key) {
@@ -2416,26 +2639,10 @@ function debugLog(label, data) {
2416
2639
  const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;
2417
2640
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${label}: ${typeof data === "string" ? data : JSON.stringify(data, null, 2)}
2418
2641
  `;
2419
- fs2.appendFileSync(logPath, line, "utf-8");
2642
+ fs3.appendFileSync(logPath, line, "utf-8");
2420
2643
  } catch {
2421
2644
  }
2422
2645
  }
2423
- function extractTextContent(content) {
2424
- if (typeof content === "string") return content;
2425
- if (!Array.isArray(content)) return "";
2426
- const parts = [];
2427
- for (const block of content) {
2428
- if (block?.type === "text" && typeof block.text === "string") {
2429
- parts.push(block.text);
2430
- }
2431
- }
2432
- return parts.join("\n").trim();
2433
- }
2434
- function isMetaUserMessage(line, text) {
2435
- if (line.isMeta === true) return true;
2436
- if (!text) return true;
2437
- return text.includes("<command-name>") || text.includes("<local-command-caveat>") || text.includes("<command-message>");
2438
- }
2439
2646
  function extractFromTranscript(transcriptPath) {
2440
2647
  const empty = {
2441
2648
  prompt: "",
@@ -2449,7 +2656,7 @@ function extractFromTranscript(transcriptPath) {
2449
2656
  if (!transcriptPath) return empty;
2450
2657
  let raw;
2451
2658
  try {
2452
- raw = fs2.readFileSync(transcriptPath, "utf-8");
2659
+ raw = fs3.readFileSync(transcriptPath, "utf-8");
2453
2660
  } catch (err) {
2454
2661
  debugLog("transcript-read-failed", {
2455
2662
  transcriptPath,
@@ -2457,58 +2664,25 @@ function extractFromTranscript(transcriptPath) {
2457
2664
  });
2458
2665
  return empty;
2459
2666
  }
2460
- const lines = raw.split("\n");
2461
- let lastUserText = "";
2462
- let lastAssistantText = "";
2463
- let lastAssistantModel = null;
2464
- let lastAssistantInputTokens = 0;
2465
- let lastAssistantOutputTokens = 0;
2466
- let numTurns = 0;
2467
- for (const rawLine of lines) {
2468
- if (!rawLine) continue;
2469
- let parsed;
2470
- try {
2471
- parsed = JSON.parse(rawLine);
2472
- } catch {
2473
- continue;
2474
- }
2475
- if (parsed.isSidechain === true) continue;
2476
- if (parsed.type === "user" && parsed.message) {
2477
- const text = extractTextContent(parsed.message.content);
2478
- if (isMetaUserMessage(parsed, text)) continue;
2479
- lastUserText = text;
2480
- } else if (parsed.type === "assistant" && parsed.message) {
2481
- const text = extractTextContent(parsed.message.content);
2482
- numTurns += 1;
2483
- if (text) lastAssistantText = text;
2484
- if (typeof parsed.message.model === "string") {
2485
- lastAssistantModel = parsed.message.model;
2486
- }
2487
- const usage = parsed.message.usage;
2488
- if (usage) {
2489
- const input = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
2490
- const output = usage.output_tokens ?? 0;
2491
- lastAssistantInputTokens = input;
2492
- lastAssistantOutputTokens = output;
2493
- }
2667
+ return parseTranscript(raw);
2668
+ }
2669
+ function extractSubagentName(event) {
2670
+ const candidates = [
2671
+ event.agent_name,
2672
+ event.subagent_type,
2673
+ event.agent_type,
2674
+ event.tool_input?.subagent_type
2675
+ ];
2676
+ for (const value of candidates) {
2677
+ if (typeof value === "string" && value.trim()) {
2678
+ return value.trim();
2494
2679
  }
2495
2680
  }
2496
- return {
2497
- prompt: lastUserText,
2498
- response: lastAssistantText,
2499
- tokens: lastAssistantInputTokens + lastAssistantOutputTokens,
2500
- inputTokens: lastAssistantInputTokens,
2501
- outputTokens: lastAssistantOutputTokens,
2502
- modelName: lastAssistantModel,
2503
- numTurns
2504
- };
2681
+ return void 0;
2505
2682
  }
2506
2683
  async function hookCommand(event) {
2684
+ setDebugLogger(debugLog);
2507
2685
  try {
2508
- const config = loadMonitorConfig();
2509
- if (!config) {
2510
- return;
2511
- }
2512
2686
  const stdinData = await readStdin(3e3);
2513
2687
  debugLog("stdin-raw", stdinData);
2514
2688
  let eventData = {};
@@ -2517,15 +2691,42 @@ async function hookCommand(event) {
2517
2691
  eventData = JSON.parse(stdinData);
2518
2692
  } catch {
2519
2693
  debugLog("stdin-parse-failed", stdinData);
2520
- return;
2521
2694
  }
2522
2695
  }
2523
2696
  debugLog("event-parsed", { event, eventData });
2697
+ const projectRoot = resolveProjectRootFromPayload(
2698
+ eventData,
2699
+ process.cwd()
2700
+ );
2701
+ if (!projectRoot) {
2702
+ debugLog("config-not-found", {
2703
+ startDir: typeof eventData.cwd === "string" && eventData.cwd.trim() ? eventData.cwd : process.cwd()
2704
+ });
2705
+ return;
2706
+ }
2707
+ const config = loadMonitorConfig(projectRoot);
2708
+ if (!config) {
2709
+ debugLog("config-load-failed", { projectRoot });
2710
+ return;
2711
+ }
2524
2712
  const payload = buildPayload(event, eventData, config);
2525
2713
  if (!payload) {
2526
2714
  return;
2527
2715
  }
2528
2716
  debugLog("payload-built", payload);
2717
+ const sessionId = typeof eventData.session_id === "string" && eventData.session_id || void 0;
2718
+ const extracted = extractFromTranscript(eventData.transcript_path);
2719
+ const userTurnTimestamp = extracted.userTurnTimestamp;
2720
+ if (sessionId) {
2721
+ const existingState = loadSessionState(sessionId);
2722
+ if (!shouldReportTurn(existingState, userTurnTimestamp)) {
2723
+ debugLog("turn-dedup-skip", {
2724
+ sessionId,
2725
+ userTurnTimestamp
2726
+ });
2727
+ return;
2728
+ }
2729
+ }
2529
2730
  const controller = new AbortController();
2530
2731
  const timeoutId = setTimeout(() => controller.abort(), 5e3);
2531
2732
  try {
@@ -2545,6 +2746,18 @@ async function hookCommand(event) {
2545
2746
  } finally {
2546
2747
  clearTimeout(timeoutId);
2547
2748
  }
2749
+ if (sessionId && userTurnTimestamp) {
2750
+ saveSessionState(sessionId, {
2751
+ lastUserTimestamp: userTurnTimestamp,
2752
+ lastReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
2753
+ numTurnsAtLastReport: extracted.numTurns
2754
+ });
2755
+ debugLog("state-saved", {
2756
+ sessionId,
2757
+ userTurnTimestamp,
2758
+ numTurns: extracted.numTurns
2759
+ });
2760
+ }
2548
2761
  } catch (err) {
2549
2762
  debugLog("hook-exception", err.message);
2550
2763
  }
@@ -2552,27 +2765,43 @@ async function hookCommand(event) {
2552
2765
  function buildPayload(event, eventData, config) {
2553
2766
  const sessionId = eventData.session_id ?? `claude-code-${Date.now()}`;
2554
2767
  switch (event) {
2555
- case "stop": {
2768
+ case "stop":
2769
+ case "subagent-stop": {
2556
2770
  const extracted = extractFromTranscript(eventData.transcript_path);
2771
+ const isSubagent = event === "subagent-stop";
2772
+ const customData = {
2773
+ hookEvent: eventData.hook_event_name ?? (isSubagent ? "SubagentStop" : "Stop"),
2774
+ sessionId,
2775
+ transcriptPath: eventData.transcript_path ?? "",
2776
+ cwd: eventData.cwd ?? "",
2777
+ stopHookActive: eventData.stop_hook_active ?? false,
2778
+ inputTokens: extracted.inputTokens,
2779
+ outputTokens: extracted.outputTokens,
2780
+ numTurns: extracted.numTurns
2781
+ };
2782
+ if (typeof extracted.latencyMs === "number") {
2783
+ customData.latencyMs = extracted.latencyMs;
2784
+ }
2785
+ if (isSubagent) {
2786
+ const subagent = extractSubagentName(eventData);
2787
+ if (subagent) {
2788
+ customData.subagent = subagent;
2789
+ }
2790
+ } else if (extracted.skill) {
2791
+ customData.skill = extracted.skill;
2792
+ }
2793
+ const payloadAssistant = eventData.last_assistant_message;
2794
+ const response = typeof payloadAssistant === "string" && payloadAssistant.trim() ? payloadAssistant : extracted.response;
2557
2795
  return {
2558
2796
  prompt: extracted.prompt,
2559
- response: extracted.response,
2797
+ response,
2560
2798
  chatId: sessionId,
2561
2799
  // Top-level source so the backend ExternalPromptRequestSchema
2562
2800
  // picks it up (matches regex ^[a-z0-9_-]+$).
2563
2801
  source: config.source,
2564
2802
  modelName: extracted.modelName ?? void 0,
2565
2803
  tokens: extracted.tokens,
2566
- customData: {
2567
- hookEvent: eventData.hook_event_name ?? "Stop",
2568
- sessionId,
2569
- transcriptPath: eventData.transcript_path ?? "",
2570
- cwd: eventData.cwd ?? "",
2571
- stopHookActive: eventData.stop_hook_active ?? false,
2572
- inputTokens: extracted.inputTokens,
2573
- outputTokens: extracted.outputTokens,
2574
- numTurns: extracted.numTurns
2575
- }
2804
+ customData
2576
2805
  };
2577
2806
  }
2578
2807
  default:
@@ -2672,8 +2901,8 @@ async function disableCommand(options) {
2672
2901
  }
2673
2902
  if (!options.keepConfig) {
2674
2903
  const configPath = getMonitorConfigPath(projectRoot);
2675
- if (fs2.existsSync(configPath)) {
2676
- fs2.unlinkSync(configPath);
2904
+ if (fs3.existsSync(configPath)) {
2905
+ fs3.unlinkSync(configPath);
2677
2906
  console.log(`\u2713 Monitor config removed (${CLAUDE_DIR}/${MONITOR_CONFIG_FILE})`);
2678
2907
  }
2679
2908
  } else {