autopilot-ai 1.19.3 → 1.20.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
@@ -635,6 +635,15 @@ function readNumberEnv(name) {
635
635
  const parsed = parseInt(raw, 10);
636
636
  return Number.isNaN(parsed) ? void 0 : parsed;
637
637
  }
638
+ function resolveTraceConfig(persisted) {
639
+ const envEnabled = readBooleanEnv("KIMIFLARE_TRACE");
640
+ const envRoot = process.env.KIMIFLARE_TRACE_DIR?.trim() || void 0;
641
+ if (envEnabled === void 0 && envRoot === void 0 && persisted === void 0) return void 0;
642
+ const trace = { enabled: envEnabled ?? persisted?.enabled ?? false };
643
+ const root = envRoot ?? persisted?.root;
644
+ if (root) trace.root = root;
645
+ return trace;
646
+ }
638
647
  async function loadConfig() {
639
648
  let persisted = {};
640
649
  let hasFile = false;
@@ -726,7 +735,8 @@ async function loadConfig() {
726
735
  workerPreReadMaxChars: readNumberEnv("KIMIFLARE_WORKER_PRE_READ_MAX_CHARS") ?? persisted.workerPreReadMaxChars,
727
736
  permissions: persisted.permissions,
728
737
  preferPullRequests: readBooleanEnv("KIMIFLARE_PREFER_PULL_REQUESTS") ?? persisted.preferPullRequests ?? true,
729
- allowDirectPush: readBooleanEnv("KIMIFLARE_ALLOW_DIRECT_PUSH") ?? persisted.allowDirectPush ?? false
738
+ allowDirectPush: readBooleanEnv("KIMIFLARE_ALLOW_DIRECT_PUSH") ?? persisted.allowDirectPush ?? false,
739
+ trace: resolveTraceConfig(persisted.trace)
730
740
  };
731
741
  if (hasFile && hasLegacyFields(persisted)) {
732
742
  await rewriteLegacyConfig(persisted, openrouterApiKey).catch(() => void 0);
@@ -1468,9 +1478,108 @@ function stripOldImages(messages, keepLastTurns) {
1468
1478
  };
1469
1479
  });
1470
1480
  }
1481
+ function appendStoppedToolResults(messages, calls) {
1482
+ const answered = new Set(messages.filter((m) => m.role === "tool").map((m) => m.tool_call_id));
1483
+ let added = 0;
1484
+ for (const [id, name] of calls) {
1485
+ if (answered.has(id)) continue;
1486
+ messages.push({ role: "tool", tool_call_id: id, content: STOPPED_TOOL_RESULT, name });
1487
+ answered.add(id);
1488
+ added++;
1489
+ }
1490
+ return added;
1491
+ }
1492
+ function repairDanglingToolCalls(messages) {
1493
+ let inserted = 0;
1494
+ for (let i = 0; i < messages.length; i++) {
1495
+ const m = messages[i];
1496
+ if (m.role !== "assistant" || !m.tool_calls?.length) continue;
1497
+ let j = i + 1;
1498
+ const answered = /* @__PURE__ */ new Set();
1499
+ while (j < messages.length && messages[j].role === "tool") {
1500
+ const id = messages[j].tool_call_id;
1501
+ if (id) answered.add(id);
1502
+ j++;
1503
+ }
1504
+ const missing = m.tool_calls.filter((tc) => !answered.has(tc.id));
1505
+ if (missing.length === 0) continue;
1506
+ messages.splice(
1507
+ j,
1508
+ 0,
1509
+ ...missing.map((tc) => ({ role: "tool", tool_call_id: tc.id, content: STOPPED_TOOL_RESULT, name: tc.function.name }))
1510
+ );
1511
+ inserted += missing.length;
1512
+ i = j + missing.length - 1;
1513
+ }
1514
+ return inserted;
1515
+ }
1516
+ var STOPPED_TOOL_RESULT;
1471
1517
  var init_messages = __esm({
1472
1518
  "src/agent/messages.ts"() {
1473
1519
  "use strict";
1520
+ STOPPED_TOOL_RESULT = "(stopped)";
1521
+ }
1522
+ });
1523
+
1524
+ // src/trace/blobs.ts
1525
+ import { createHash } from "crypto";
1526
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
1527
+ import { join as join8 } from "path";
1528
+ function sha256Hex(content) {
1529
+ return createHash("sha256").update(content, "utf8").digest("hex");
1530
+ }
1531
+ function truncateUtf8(content, maxBytes) {
1532
+ const buf = Buffer.from(content, "utf8");
1533
+ if (buf.length <= maxBytes) return content;
1534
+ let end = maxBytes;
1535
+ while (end > 0 && (buf[end] & 192) === 128) end--;
1536
+ return buf.subarray(0, end).toString("utf8");
1537
+ }
1538
+ var DEFAULT_MAX_BLOB_BYTES, BlobStore;
1539
+ var init_blobs = __esm({
1540
+ "src/trace/blobs.ts"() {
1541
+ "use strict";
1542
+ DEFAULT_MAX_BLOB_BYTES = 2 * 1024 * 1024;
1543
+ BlobStore = class {
1544
+ constructor(dir, opts2 = {}) {
1545
+ this.dir = dir;
1546
+ this.maxBlobBytes = opts2.maxBlobBytes ?? DEFAULT_MAX_BLOB_BYTES;
1547
+ }
1548
+ dir;
1549
+ seen = /* @__PURE__ */ new Set();
1550
+ dirReady = false;
1551
+ maxBlobBytes;
1552
+ put(content) {
1553
+ const bytes = Buffer.byteLength(content, "utf8");
1554
+ let stored = content;
1555
+ let truncated = false;
1556
+ if (bytes > this.maxBlobBytes) {
1557
+ stored = `${truncateUtf8(content, this.maxBlobBytes)}
1558
+ [truncated: ${bytes} bytes total]`;
1559
+ truncated = true;
1560
+ }
1561
+ const sha2563 = sha256Hex(stored);
1562
+ if (this.seen.has(sha2563)) return { sha256: sha2563, bytes, truncated, written: 0 };
1563
+ if (!this.dirReady) {
1564
+ mkdirSync2(this.dir, { recursive: true });
1565
+ this.dirReady = true;
1566
+ }
1567
+ const path = join8(this.dir, sha2563);
1568
+ let written = 0;
1569
+ if (!existsSync2(path)) {
1570
+ writeFileSync(path, stored, "utf8");
1571
+ written = Buffer.byteLength(stored, "utf8");
1572
+ }
1573
+ this.seen.add(sha2563);
1574
+ return { sha256: sha2563, bytes, truncated, written };
1575
+ }
1576
+ has(sha2563) {
1577
+ return this.seen.has(sha2563) || existsSync2(join8(this.dir, sha2563));
1578
+ }
1579
+ pathFor(sha2563) {
1580
+ return join8(this.dir, sha2563);
1581
+ }
1582
+ };
1474
1583
  }
1475
1584
  });
1476
1585
 
@@ -1927,8 +2036,8 @@ var init_artifact_compaction = __esm({
1927
2036
 
1928
2037
  // src/util/llm-dump.ts
1929
2038
  import { homedir as homedir5 } from "os";
1930
- import { join as join8 } from "path";
1931
- import { mkdirSync as mkdirSync2, appendFileSync, writeFileSync } from "fs";
2039
+ import { join as join9 } from "path";
2040
+ import { mkdirSync as mkdirSync3, appendFileSync, writeFileSync as writeFileSync2 } from "fs";
1932
2041
  function isLlmDumpEnabled() {
1933
2042
  const raw = process.env.KIMIFLARE_DUMP_LLM;
1934
2043
  if (raw === void 0) return false;
@@ -1938,11 +2047,11 @@ function isLlmDumpEnabled() {
1938
2047
  function defaultDumpRoot() {
1939
2048
  const override = process.env.KIMIFLARE_DUMP_LLM_DIR;
1940
2049
  if (override && override.trim()) return override;
1941
- const xdg = process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config");
1942
- return join8(xdg, "kimiflare", "llm-dumps");
2050
+ const xdg = process.env.XDG_CONFIG_HOME || join9(homedir5(), ".config");
2051
+ return join9(xdg, "kimiflare", "llm-dumps");
1943
2052
  }
1944
2053
  function dumpDir(sessionId) {
1945
- return join8(defaultDumpRoot(), sessionId && sessionId.trim() ? sessionId : "nosession");
2054
+ return join9(defaultDumpRoot(), sessionId && sessionId.trim() ? sessionId : "nosession");
1946
2055
  }
1947
2056
  function messageChars(m) {
1948
2057
  let chars = 0;
@@ -1999,12 +2108,12 @@ function writeLlmDump(record) {
1999
2108
  if (!isLlmDumpEnabled()) return;
2000
2109
  try {
2001
2110
  const dir = dumpDir(record.meta.sessionId);
2002
- mkdirSync2(dir, { recursive: true });
2111
+ mkdirSync3(dir, { recursive: true });
2003
2112
  const tsPart = safeSegment(record.meta.ts);
2004
2113
  const turnPart = safeSegment(record.meta.turnId ?? "noturn");
2005
2114
  const reqPart = safeSegment(record.meta.requestId);
2006
- const file = join8(dir, `${tsPart}-${turnPart}-${reqPart}.json`);
2007
- writeFileSync(file, JSON.stringify(record, null, 2));
2115
+ const file = join9(dir, `${tsPart}-${turnPart}-${reqPart}.json`);
2116
+ writeFileSync2(file, JSON.stringify(record, null, 2));
2008
2117
  const summary = {
2009
2118
  ...record.meta,
2010
2119
  ...record.breakdown,
@@ -2014,7 +2123,7 @@ function writeLlmDump(record) {
2014
2123
  usage: record.response.usage,
2015
2124
  file
2016
2125
  };
2017
- appendFileSync(join8(dir, "index.jsonl"), JSON.stringify(summary) + "\n");
2126
+ appendFileSync(join9(dir, "index.jsonl"), JSON.stringify(summary) + "\n");
2018
2127
  } catch {
2019
2128
  }
2020
2129
  }
@@ -2257,41 +2366,89 @@ async function* runKimi(opts2) {
2257
2366
  if (Object.keys(provider).length > 0) body.provider = provider;
2258
2367
  if (vendorOf(opts2.model) === "anthropic") body.cache_control = { type: "ephemeral" };
2259
2368
  }
2369
+ const dumpEnabled = isLlmDumpEnabled();
2370
+ const trace = opts2.trace?.enabled ? opts2.trace : null;
2371
+ const captured = dumpEnabled || trace ? captureRequest(body) : null;
2372
+ const response = {
2373
+ text: "",
2374
+ reasoning: "",
2375
+ reasoningDetails: [],
2376
+ toolCalls: [],
2377
+ finishReason: null,
2378
+ usage: null
2379
+ };
2260
2380
  let dumpRecord = null;
2261
- if (isLlmDumpEnabled()) {
2262
- const dumpMessages = body.messages;
2263
- const dumpTools = body.tools ?? [];
2264
- const { messages: _m, tools: _t, ...params } = body;
2265
- const dumpResponse = {
2266
- text: "",
2267
- reasoning: "",
2268
- toolCalls: [],
2269
- finishReason: null,
2270
- usage: null
2271
- };
2381
+ if (dumpEnabled && captured) {
2272
2382
  dumpRecord = {
2273
2383
  meta: {
2274
2384
  requestId,
2275
2385
  sessionId: opts2.sessionId ?? getLogSessionId(),
2276
- turnId: getLogTurnId(),
2386
+ turnId: opts2.turnId ?? getLogTurnId(),
2277
2387
  model: opts2.model,
2278
2388
  url,
2279
2389
  ts: (/* @__PURE__ */ new Date()).toISOString()
2280
2390
  },
2281
2391
  request: {
2282
- system: dumpMessages.filter((m) => m.role === "system"),
2283
- messages: dumpMessages,
2284
- tools: dumpTools,
2285
- params,
2286
- rawSerialized: stableStringify(body, jsonReplacer)
2392
+ system: captured.messages.filter((m) => m.role === "system"),
2393
+ messages: captured.messages,
2394
+ tools: captured.tools,
2395
+ params: captured.params,
2396
+ rawSerialized: captured.rawSerialized
2287
2397
  },
2288
- breakdown: computeBreakdown(dumpMessages, dumpTools),
2289
- response: dumpResponse
2398
+ breakdown: captured.breakdown,
2399
+ response
2290
2400
  };
2291
2401
  }
2402
+ const traceRequest = (attempt) => {
2403
+ if (!trace || !captured) return;
2404
+ trace.emit(
2405
+ "llm_request",
2406
+ {
2407
+ request_id: requestId,
2408
+ attempt,
2409
+ model: opts2.model,
2410
+ params: captured.params,
2411
+ request_blob: trace.blob(captured.rawSerialized) ?? "",
2412
+ messages_sha: captured.messagesSha,
2413
+ tools_sha: captured.toolsSha,
2414
+ estimated_tokens: captured.breakdown.estTokens
2415
+ },
2416
+ opts2.turnId ?? null
2417
+ );
2418
+ };
2419
+ const traceResponse = (startedAt2, error) => {
2420
+ if (!trace) return;
2421
+ const usage = response.usage;
2422
+ trace.emit(
2423
+ "llm_response",
2424
+ {
2425
+ request_id: requestId,
2426
+ text_blob: trace.blob(response.text) ?? "",
2427
+ ...response.reasoning ? { reasoning_blob: trace.blob(response.reasoning) ?? "" } : {},
2428
+ ...response.reasoningDetails.length ? { reasoning_details: response.reasoningDetails } : {},
2429
+ tool_calls: response.toolCalls.map((tc) => ({ id: tc.id, name: tc.name, arguments: tc.arguments })),
2430
+ finish_reason: response.finishReason,
2431
+ ...usage ? {
2432
+ usage: {
2433
+ prompt_tokens: usage.prompt_tokens,
2434
+ completion_tokens: usage.completion_tokens,
2435
+ ...usage.prompt_tokens_details?.cached_tokens !== void 0 ? { cached_tokens: usage.prompt_tokens_details.cached_tokens ?? void 0 } : {},
2436
+ ...usage.cost !== void 0 ? { cost: usage.cost } : {}
2437
+ }
2438
+ } : {},
2439
+ ...response.generationId ? { generation_id: response.generationId } : {},
2440
+ latency_ms: Math.round(performance.now() - startedAt2),
2441
+ aborted: opts2.signal?.aborted === true,
2442
+ ...error !== void 0 ? { error } : {}
2443
+ },
2444
+ opts2.turnId ?? null
2445
+ );
2446
+ };
2292
2447
  logger.debug("runKimi:request", { requestId, attempt: 0, model: opts2.model });
2293
2448
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
2294
2449
  let res;
2450
+ const attemptStartedAt = performance.now();
2451
+ traceRequest(attempt);
2295
2452
  try {
2296
2453
  const headers = {
2297
2454
  "Content-Type": "application/json",
@@ -2307,8 +2464,9 @@ async function* runKimi(opts2) {
2307
2464
  signal: opts2.signal
2308
2465
  });
2309
2466
  } catch (fetchErr) {
2310
- if (isAbortError(fetchErr)) throw fetchErr;
2311
2467
  const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
2468
+ traceResponse(attemptStartedAt, msg);
2469
+ if (isAbortError(fetchErr)) throw fetchErr;
2312
2470
  logger.warn("runKimi:fetch_error", { requestId, attempt, error: msg });
2313
2471
  if (attempt < MAX_ATTEMPTS - 1) {
2314
2472
  const delay = Math.random() * (500 * 2 ** attempt);
@@ -2340,6 +2498,7 @@ async function* runKimi(opts2) {
2340
2498
  err?.code,
2341
2499
  status
2342
2500
  );
2501
+ traceResponse(attemptStartedAt, apiErr.message);
2343
2502
  if (isRetryable(apiErr, attempt)) {
2344
2503
  const isRateLimit = apiErr.httpStatus === 429;
2345
2504
  const baseDelay = isRateLimit ? 2e3 : 500;
@@ -2351,18 +2510,27 @@ async function* runKimi(opts2) {
2351
2510
  }
2352
2511
  throw apiErr;
2353
2512
  }
2354
- if (!res.body) throw new KimiApiError("kimiflare: empty response body", void 0, res.status);
2513
+ if (!res.body) {
2514
+ const emptyErr = new KimiApiError("kimiflare: empty response body", void 0, res.status);
2515
+ traceResponse(attemptStartedAt, emptyErr.message);
2516
+ throw emptyErr;
2517
+ }
2355
2518
  logger.debug("runKimi:stream_start", { requestId });
2519
+ let streamError;
2356
2520
  try {
2357
2521
  for await (const ev of parseStream(res.body, opts2.signal, opts2.idleTimeoutMs, opts2.postFirstByteIdleTimeoutMs)) {
2358
- if (dumpRecord) accumulateDumpResponse(dumpRecord.response, ev);
2522
+ if (captured) accumulateDumpResponse(response, ev);
2359
2523
  yield ev;
2360
2524
  }
2525
+ } catch (streamErr) {
2526
+ streamError = streamErr instanceof Error ? streamErr.message : String(streamErr);
2527
+ throw streamErr;
2361
2528
  } finally {
2362
2529
  if (dumpRecord) {
2363
2530
  dumpRecord.meta.attempt = attempt;
2364
2531
  writeLlmDump(dumpRecord);
2365
2532
  }
2533
+ traceResponse(attemptStartedAt, streamError);
2366
2534
  }
2367
2535
  logger.debug("runKimi:stream_end", { requestId });
2368
2536
  return;
@@ -2439,16 +2607,36 @@ function describeRequestyHttpError(model, status, msg) {
2439
2607
  }
2440
2608
  return msg;
2441
2609
  }
2610
+ function captureRequest(body) {
2611
+ const messages = body.messages;
2612
+ const tools = body.tools ?? [];
2613
+ const { messages: _m, tools: _t, ...params } = body;
2614
+ return {
2615
+ messages,
2616
+ tools,
2617
+ params,
2618
+ rawSerialized: stableStringify(body, jsonReplacer),
2619
+ messagesSha: sha256Hex(stableStringify(messages, jsonReplacer)),
2620
+ toolsSha: sha256Hex(stableStringify(tools, jsonReplacer)),
2621
+ breakdown: computeBreakdown(messages, tools)
2622
+ };
2623
+ }
2442
2624
  function accumulateDumpResponse(resp, ev) {
2443
2625
  switch (ev.type) {
2626
+ case "response_meta":
2627
+ if (ev.meta.generationId) resp.generationId = ev.meta.generationId;
2628
+ break;
2444
2629
  case "text":
2445
2630
  resp.text += ev.delta;
2446
2631
  break;
2447
2632
  case "reasoning":
2448
2633
  resp.reasoning += ev.delta;
2449
2634
  break;
2635
+ case "reasoning_details":
2636
+ resp.reasoningDetails = mergeReasoningDetails(resp.reasoningDetails, ev.details);
2637
+ break;
2450
2638
  case "tool_call_complete":
2451
- resp.toolCalls.push({ name: ev.name, arguments: ev.arguments });
2639
+ resp.toolCalls.push({ id: ev.id, name: ev.name, arguments: ev.arguments });
2452
2640
  break;
2453
2641
  case "usage":
2454
2642
  resp.usage = ev.usage;
@@ -2674,6 +2862,7 @@ var init_client = __esm({
2674
2862
  init_errors();
2675
2863
  init_version();
2676
2864
  init_messages();
2865
+ init_blobs();
2677
2866
  init_logger();
2678
2867
  init_log_sink();
2679
2868
  init_llm_dump();
@@ -2746,7 +2935,7 @@ var init_recall_inject = __esm({
2746
2935
 
2747
2936
  // src/storage-limits.ts
2748
2937
  import { readdir as readdir2, stat as stat2, unlink } from "fs/promises";
2749
- import { join as join9 } from "path";
2938
+ import { join as join10 } from "path";
2750
2939
  async function listFilesByMtime(dir, pattern = /.*/) {
2751
2940
  let entries;
2752
2941
  try {
@@ -2757,7 +2946,7 @@ async function listFilesByMtime(dir, pattern = /.*/) {
2757
2946
  const files = [];
2758
2947
  for (const name of entries) {
2759
2948
  if (!pattern.test(name)) continue;
2760
- const p = join9(dir, name);
2949
+ const p = join10(dir, name);
2761
2950
  try {
2762
2951
  const s = await stat2(p);
2763
2952
  if (s.isFile()) files.push({ path: p, mtime: s.mtime });
@@ -2834,7 +3023,13 @@ var init_storage_limits = __esm({
2834
3023
  /** Memories older than this (days) are pruned. */
2835
3024
  memoryMaxAgeDays: 90,
2836
3025
  /** Max memories per repo. */
2837
- memoryMaxEntries: 1e3
3026
+ memoryMaxEntries: 1e3,
3027
+ /** Session trace directories older than this (days) are pruned. */
3028
+ traceMaxAgeDays: 90,
3029
+ /** Max total size of all session traces (bytes). Oldest sessions go first. */
3030
+ traceMaxTotalBytes: 2 * 1024 * 1024 * 1024,
3031
+ /** Max size of a single session trace (bytes); recording stops at the cap. */
3032
+ traceMaxSessionBytes: 200 * 1024 * 1024
2838
3033
  };
2839
3034
  }
2840
3035
  });
@@ -2842,13 +3037,13 @@ var init_storage_limits = __esm({
2842
3037
  // src/cost-debug.ts
2843
3038
  import { appendFile, mkdir as mkdir5 } from "fs/promises";
2844
3039
  import { homedir as homedir6 } from "os";
2845
- import { join as join10 } from "path";
3040
+ import { join as join11 } from "path";
2846
3041
  function debugDir() {
2847
- const xdg = process.env.XDG_DATA_HOME || join10(homedir6(), ".local", "share");
2848
- return join10(xdg, "kimiflare");
3042
+ const xdg = process.env.XDG_DATA_HOME || join11(homedir6(), ".local", "share");
3043
+ return join11(xdg, "kimiflare");
2849
3044
  }
2850
3045
  function debugPath() {
2851
- return join10(debugDir(), "cost-debug.jsonl");
3046
+ return join11(debugDir(), "cost-debug.jsonl");
2852
3047
  }
2853
3048
  function now() {
2854
3049
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -3562,7 +3757,7 @@ var require_isolated_vm = __commonJS({
3562
3757
  });
3563
3758
 
3564
3759
  // src/code-mode/sandbox.ts
3565
- import { join as join11, dirname as dirname5 } from "path";
3760
+ import { join as join12, dirname as dirname5 } from "path";
3566
3761
  import { pathToFileURL } from "url";
3567
3762
  function stripTypescript(code) {
3568
3763
  let js = code;
@@ -3589,7 +3784,7 @@ async function loadTypescript(cwd) {
3589
3784
  let dir = cwd;
3590
3785
  while (dir !== dirname5(dir)) {
3591
3786
  try {
3592
- const tsPath = join11(dir, "node_modules", "typescript", "lib", "typescript.js");
3787
+ const tsPath = join12(dir, "node_modules", "typescript", "lib", "typescript.js");
3593
3788
  return await import(pathToFileURL(tsPath).href);
3594
3789
  } catch {
3595
3790
  }
@@ -4433,11 +4628,11 @@ Prefer non-interactive flags when they exist (\`--yes\`, \`-m\`, \`--with-token\
4433
4628
 
4434
4629
  // src/agent/system-prompt.ts
4435
4630
  import { platform, release, homedir as homedir7 } from "os";
4436
- import { basename, join as join12 } from "path";
4631
+ import { basename, join as join13 } from "path";
4437
4632
  import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
4438
4633
  function loadContextFile(cwd) {
4439
4634
  for (const name of CONTEXT_FILENAMES) {
4440
- const path = join12(cwd, name);
4635
+ const path = join13(cwd, name);
4441
4636
  try {
4442
4637
  const s = statSync2(path);
4443
4638
  if (!s.isFile() || s.size > MAX_CONTEXT_BYTES) continue;
@@ -4964,6 +5159,403 @@ var init_completion_check = __esm({
4964
5159
  }
4965
5160
  });
4966
5161
 
5162
+ // src/trace/paths.ts
5163
+ import { homedir as homedir8 } from "os";
5164
+ import { join as join14 } from "path";
5165
+ function defaultTraceRoot() {
5166
+ const xdg = process.env.XDG_DATA_HOME || join14(homedir8(), ".local", "share");
5167
+ return join14(xdg, "kimiflare", "traces");
5168
+ }
5169
+ function traceRoot(configRoot) {
5170
+ const env2 = process.env.KIMIFLARE_TRACE_DIR;
5171
+ if (env2 && env2.trim()) return env2.trim();
5172
+ if (configRoot && configRoot.trim()) return configRoot.trim();
5173
+ return defaultTraceRoot();
5174
+ }
5175
+ function safeSessionSegment(sessionId) {
5176
+ const cleaned = sessionId.replace(/[\\/\0]/g, "_").replace(/^\.+$/, "_");
5177
+ return cleaned || "session";
5178
+ }
5179
+ function sessionTraceDir(sessionId, root) {
5180
+ return join14(traceRoot(root), safeSessionSegment(sessionId));
5181
+ }
5182
+ function traceFilePath(sessionDir) {
5183
+ return join14(sessionDir, TRACE_FILE_NAME);
5184
+ }
5185
+ function blobsDir(sessionDir) {
5186
+ return join14(sessionDir, BLOBS_DIR_NAME);
5187
+ }
5188
+ var TRACE_FILE_NAME, BLOBS_DIR_NAME;
5189
+ var init_paths = __esm({
5190
+ "src/trace/paths.ts"() {
5191
+ "use strict";
5192
+ TRACE_FILE_NAME = "trace.jsonl";
5193
+ BLOBS_DIR_NAME = "blobs";
5194
+ }
5195
+ });
5196
+
5197
+ // src/trace/redact.ts
5198
+ function redactLight(text) {
5199
+ if (!text) return text;
5200
+ let out = text.replace(PEM_BLOCK, "<redacted-key>");
5201
+ for (const re of TOKEN_SHAPES) out = out.replace(re, "<redacted>");
5202
+ out = out.replace(KEY_VALUE, "$1$2<redacted>");
5203
+ return out;
5204
+ }
5205
+ function isSecretConfigKey(key) {
5206
+ const lower = key.toLowerCase();
5207
+ return SECRET_KEY_SUFFIXES.some((suffix) => lower.endsWith(suffix));
5208
+ }
5209
+ function redactConfig(value) {
5210
+ return redactValue(value, true);
5211
+ }
5212
+ function redactDeep(value) {
5213
+ return redactValue(value, false);
5214
+ }
5215
+ function redactValue(value, redactSecretKeys) {
5216
+ if (typeof value === "string") return redactLight(value);
5217
+ if (Array.isArray(value)) return value.map((v) => redactValue(v, redactSecretKeys));
5218
+ if (value && typeof value === "object") {
5219
+ const out = {};
5220
+ for (const [k, v] of Object.entries(value)) {
5221
+ if (v === void 0) continue;
5222
+ if (redactSecretKeys && isSecretConfigKey(k) && v !== null && v !== "") {
5223
+ out[k] = "<redacted>";
5224
+ } else {
5225
+ out[k] = redactValue(v, redactSecretKeys);
5226
+ }
5227
+ }
5228
+ return out;
5229
+ }
5230
+ return value;
5231
+ }
5232
+ var PEM_BLOCK, TOKEN_SHAPES, KEY_VALUE, SECRET_KEY_SUFFIXES;
5233
+ var init_redact = __esm({
5234
+ "src/trace/redact.ts"() {
5235
+ "use strict";
5236
+ PEM_BLOCK = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
5237
+ TOKEN_SHAPES = [
5238
+ /sk-or-v1-[A-Za-z0-9]+/g,
5239
+ /ghp_[A-Za-z0-9]+/g,
5240
+ /github_pat_[A-Za-z0-9_]+/g,
5241
+ /Bearer [A-Za-z0-9._-]+/g,
5242
+ /Basic [A-Za-z0-9+/=]+/g
5243
+ ];
5244
+ KEY_VALUE = /(api[_-]?key|secret|token|password|authorization)(["']?\s*[:=]\s*["']?)[^\s"']+/gi;
5245
+ SECRET_KEY_SUFFIXES = ["apikey", "token", "secret", "password"];
5246
+ }
5247
+ });
5248
+
5249
+ // src/trace/types.ts
5250
+ var TRACE_SCHEMA_VERSION;
5251
+ var init_types = __esm({
5252
+ "src/trace/types.ts"() {
5253
+ "use strict";
5254
+ TRACE_SCHEMA_VERSION = 1;
5255
+ }
5256
+ });
5257
+
5258
+ // src/trace/recorder.ts
5259
+ import { randomUUID } from "crypto";
5260
+ import { appendFileSync as appendFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3 } from "fs";
5261
+ function createNoopRecorder(sessionId = "") {
5262
+ return new NoopRecorder(sessionId);
5263
+ }
5264
+ function makeTurnId() {
5265
+ return randomUUID();
5266
+ }
5267
+ function createRecorder(opts2) {
5268
+ if (!opts2.enabled) return new NoopRecorder(opts2.sessionId);
5269
+ const dir = sessionTraceDir(opts2.sessionId, opts2.root);
5270
+ return new FileTraceRecorder(opts2.sessionId, dir, {
5271
+ maxBlobBytes: opts2.maxBlobBytes,
5272
+ maxSessionBytes: opts2.maxSessionBytes
5273
+ });
5274
+ }
5275
+ var NoopRecorder, FileTraceRecorder;
5276
+ var init_recorder = __esm({
5277
+ "src/trace/recorder.ts"() {
5278
+ "use strict";
5279
+ init_logger();
5280
+ init_log_sink();
5281
+ init_storage_limits();
5282
+ init_blobs();
5283
+ init_paths();
5284
+ init_redact();
5285
+ init_types();
5286
+ NoopRecorder = class {
5287
+ constructor(sessionId) {
5288
+ this.sessionId = sessionId;
5289
+ }
5290
+ sessionId;
5291
+ enabled = false;
5292
+ dir = null;
5293
+ emit() {
5294
+ }
5295
+ blob() {
5296
+ return null;
5297
+ }
5298
+ flush() {
5299
+ return Promise.resolve();
5300
+ }
5301
+ };
5302
+ FileTraceRecorder = class {
5303
+ constructor(sessionId, dir, opts2) {
5304
+ this.sessionId = sessionId;
5305
+ this.dir = dir;
5306
+ this.file = traceFilePath(dir);
5307
+ this.blobs = new BlobStore(blobsDir(dir), { maxBlobBytes: opts2.maxBlobBytes });
5308
+ this.maxSessionBytes = opts2.maxSessionBytes ?? RETENTION.traceMaxSessionBytes;
5309
+ }
5310
+ sessionId;
5311
+ enabled = true;
5312
+ dir;
5313
+ file;
5314
+ blobs;
5315
+ maxSessionBytes;
5316
+ seq = 0;
5317
+ bytesWritten = 0;
5318
+ dirReady = false;
5319
+ emit(type, payload, turnId) {
5320
+ if (!this.enabled) return;
5321
+ try {
5322
+ const event = {
5323
+ v: TRACE_SCHEMA_VERSION,
5324
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5325
+ seq: this.seq,
5326
+ session_id: this.sessionId,
5327
+ turn_id: turnId === void 0 ? getLogTurnId() : turnId,
5328
+ type,
5329
+ payload: redactDeep(payload)
5330
+ };
5331
+ if (!this.dirReady) {
5332
+ mkdirSync4(this.dir, { recursive: true });
5333
+ this.dirReady = true;
5334
+ this.resumeSeq();
5335
+ event.seq = this.seq;
5336
+ }
5337
+ const line = JSON.stringify(event) + "\n";
5338
+ if (!this.ensureBudget(Buffer.byteLength(line, "utf8"))) return;
5339
+ appendFileSync2(this.file, line, "utf8");
5340
+ this.seq++;
5341
+ } catch (err) {
5342
+ this.fail(err);
5343
+ }
5344
+ }
5345
+ blob(content) {
5346
+ if (!this.enabled) return null;
5347
+ try {
5348
+ const result2 = this.blobs.put(redactLight(content));
5349
+ if (result2.written > 0) this.ensureBudget(result2.written);
5350
+ return result2.sha256;
5351
+ } catch (err) {
5352
+ this.fail(err);
5353
+ return null;
5354
+ }
5355
+ }
5356
+ flush() {
5357
+ return Promise.resolve();
5358
+ }
5359
+ /** Continue `seq` after the lines already in an existing trace file. */
5360
+ resumeSeq() {
5361
+ if (!existsSync3(this.file)) return;
5362
+ const existing = readFileSync3(this.file, "utf8");
5363
+ let lines = 0;
5364
+ for (let i = 0; i < existing.length; i++) if (existing.charCodeAt(i) === 10) lines++;
5365
+ this.seq = lines;
5366
+ this.bytesWritten += Buffer.byteLength(existing, "utf8");
5367
+ }
5368
+ /** Account for `bytes`; returns false (and disables) once the session cap is exceeded. */
5369
+ ensureBudget(bytes) {
5370
+ this.bytesWritten += bytes;
5371
+ if (this.bytesWritten > this.maxSessionBytes) {
5372
+ this.enabled = false;
5373
+ logger.warn("trace:session_cap", {
5374
+ session_id: this.sessionId,
5375
+ dir: this.dir,
5376
+ max_bytes: this.maxSessionBytes
5377
+ });
5378
+ return false;
5379
+ }
5380
+ return true;
5381
+ }
5382
+ fail(err) {
5383
+ this.enabled = false;
5384
+ logger.warn("trace:write_failed", {
5385
+ session_id: this.sessionId,
5386
+ dir: this.dir,
5387
+ error: err instanceof Error ? err.message : String(err)
5388
+ });
5389
+ }
5390
+ };
5391
+ }
5392
+ });
5393
+
5394
+ // src/cost-attribution/git-diff.ts
5395
+ import { execFile } from "child_process";
5396
+ import { promisify } from "util";
5397
+ function parseNumstat(raw) {
5398
+ const entries = [];
5399
+ for (const line of raw.split("\n")) {
5400
+ if (!line.trim()) continue;
5401
+ const parts = line.split(" ");
5402
+ if (parts.length < 3) continue;
5403
+ const ins = parseInt(parts[0], 10);
5404
+ const del = parseInt(parts[1], 10);
5405
+ entries.push({
5406
+ file: parts.slice(2).join(" "),
5407
+ insertions: Number.isNaN(ins) ? 0 : ins,
5408
+ deletions: Number.isNaN(del) ? 0 : del
5409
+ });
5410
+ }
5411
+ return entries;
5412
+ }
5413
+ async function gitNumstat(cwd, opts2 = {}) {
5414
+ let raw;
5415
+ try {
5416
+ const args = ["diff", "--numstat"];
5417
+ if (opts2.againstHead) args.push("HEAD");
5418
+ args.push("--", ".");
5419
+ try {
5420
+ raw = (await execFileAsync("git", args, { cwd, timeout: 5e3 })).stdout;
5421
+ } catch (err) {
5422
+ if (!opts2.againstHead) throw err;
5423
+ raw = (await execFileAsync("git", ["diff", "--numstat", "--", "."], { cwd, timeout: 5e3 })).stdout;
5424
+ }
5425
+ } catch {
5426
+ return null;
5427
+ }
5428
+ const entries = parseNumstat(raw);
5429
+ if (opts2.includeUntracked) {
5430
+ try {
5431
+ const { stdout } = await execFileAsync("git", ["ls-files", "--others", "--exclude-standard"], { cwd, timeout: 5e3 });
5432
+ const { readFile: readFile32, stat: stat11 } = await import("fs/promises");
5433
+ const { join: join54 } = await import("path");
5434
+ for (const file of stdout.split("\n").filter(Boolean)) {
5435
+ let lines = 0;
5436
+ try {
5437
+ const path = join54(cwd, file);
5438
+ const size = (await stat11(path)).size;
5439
+ if (size > 0 && size <= 1024 * 1024) {
5440
+ const text = await readFile32(path, "utf8");
5441
+ lines = text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
5442
+ }
5443
+ } catch {
5444
+ }
5445
+ entries.push({ file, insertions: lines, deletions: 0 });
5446
+ raw += `${lines} 0 ${file}
5447
+ `;
5448
+ }
5449
+ } catch {
5450
+ }
5451
+ }
5452
+ return { raw, entries };
5453
+ }
5454
+ async function gitPatch(cwd, maxBytes) {
5455
+ try {
5456
+ let stdout;
5457
+ try {
5458
+ stdout = (await execFileAsync("git", ["diff", "HEAD", "--", "."], { cwd, timeout: 1e4, maxBuffer: maxBytes * 4 })).stdout;
5459
+ } catch {
5460
+ stdout = (await execFileAsync("git", ["diff", "--", "."], { cwd, timeout: 1e4, maxBuffer: maxBytes * 4 })).stdout;
5461
+ }
5462
+ return Buffer.byteLength(stdout, "utf8") <= maxBytes ? stdout : null;
5463
+ } catch {
5464
+ return null;
5465
+ }
5466
+ }
5467
+ var execFileAsync;
5468
+ var init_git_diff = __esm({
5469
+ "src/cost-attribution/git-diff.ts"() {
5470
+ "use strict";
5471
+ execFileAsync = promisify(execFile);
5472
+ }
5473
+ });
5474
+
5475
+ // src/trace/git-delta.ts
5476
+ function toMap(entries) {
5477
+ return new Map(entries.map((e) => [e.file, e]));
5478
+ }
5479
+ function diffNumstat(before, after) {
5480
+ const b = toMap(before);
5481
+ const a = toMap(after);
5482
+ const files = /* @__PURE__ */ new Set([...b.keys(), ...a.keys()]);
5483
+ let insertions = 0;
5484
+ let deletions = 0;
5485
+ const changed = [];
5486
+ for (const file of files) {
5487
+ const x = b.get(file) ?? { file, insertions: 0, deletions: 0 };
5488
+ const y = a.get(file) ?? { file, insertions: 0, deletions: 0 };
5489
+ const di = y.insertions - x.insertions;
5490
+ const dd = y.deletions - x.deletions;
5491
+ if (di === 0 && dd === 0) continue;
5492
+ changed.push(file);
5493
+ insertions += Math.max(0, di);
5494
+ deletions += Math.max(0, dd);
5495
+ }
5496
+ changed.sort();
5497
+ return { files_changed: changed.length, insertions, deletions, files: changed };
5498
+ }
5499
+ function rememberGitDelta(sessionId, turnId, files) {
5500
+ if (files.length === 0) return;
5501
+ lastDeltaBySession.set(sessionId, { turnId, files });
5502
+ }
5503
+ function detectGitRevert(sessionId, command) {
5504
+ const last = lastDeltaBySession.get(sessionId);
5505
+ if (!last) return null;
5506
+ if (!REVERT_COMMAND.test(command)) return null;
5507
+ const isStash = /git\s+stash\b/.test(command) && !/git\s+stash\s+(pop|apply|list|show|drop)\b/.test(command);
5508
+ if (isStash) return { turnId: last.turnId };
5509
+ if (/git\s+(checkout\s+--|restore)\s*$/.test(command.trim())) return null;
5510
+ const hit = last.files.some((file) => {
5511
+ const base = file.slice(file.lastIndexOf("/") + 1);
5512
+ return command.includes(file) || base && new RegExp(`(^|[\\s/'"])${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(['"\\s]|$)`).test(command);
5513
+ });
5514
+ if (hit) return { turnId: last.turnId };
5515
+ if (/git\s+restore\s+(\.|--staged\s+\.)(\s|$)/.test(command) || /git\s+checkout\s+--\s+\.(\s|$)/.test(command)) return { turnId: last.turnId };
5516
+ return null;
5517
+ }
5518
+ async function beginGitDelta(opts2) {
5519
+ if (!opts2.trace.enabled) return null;
5520
+ const before = await gitNumstat(opts2.cwd, { againstHead: true, includeUntracked: true });
5521
+ if (!before) return null;
5522
+ let done = false;
5523
+ return {
5524
+ async finish() {
5525
+ if (done) return;
5526
+ done = true;
5527
+ const after = await gitNumstat(opts2.cwd, { againstHead: true, includeUntracked: true });
5528
+ if (!after || !opts2.trace.enabled) return;
5529
+ const delta = diffNumstat(before.entries, after.entries);
5530
+ const patch = delta.files_changed > 0 ? await gitPatch(opts2.cwd, MAX_PATCH_BYTES) : null;
5531
+ opts2.trace.emit(
5532
+ "outcome",
5533
+ {
5534
+ kind: "git_delta",
5535
+ turn_id: opts2.turnId,
5536
+ files_changed: delta.files_changed,
5537
+ insertions: delta.insertions,
5538
+ deletions: delta.deletions,
5539
+ numstat_blob: opts2.trace.blob(after.raw) ?? "",
5540
+ ...patch ? { patch_blob: opts2.trace.blob(patch) ?? "" } : {}
5541
+ },
5542
+ opts2.turnId
5543
+ );
5544
+ if (opts2.sessionId) rememberGitDelta(opts2.sessionId, opts2.turnId, delta.files);
5545
+ }
5546
+ };
5547
+ }
5548
+ var MAX_PATCH_BYTES, lastDeltaBySession, REVERT_COMMAND;
5549
+ var init_git_delta = __esm({
5550
+ "src/trace/git-delta.ts"() {
5551
+ "use strict";
5552
+ init_git_diff();
5553
+ MAX_PATCH_BYTES = 512 * 1024;
5554
+ lastDeltaBySession = /* @__PURE__ */ new Map();
5555
+ REVERT_COMMAND = /(^|[\s;&|(])git\s+(checkout\s+--|restore\b|stash\b)/;
5556
+ }
5557
+ });
5558
+
4967
5559
  // src/agent/loop.ts
4968
5560
  function getSessionWebFetchHistory(sessionId) {
4969
5561
  const key = sessionId ?? "default";
@@ -5002,7 +5594,90 @@ function raceWithSignal(promise, signal) {
5002
5594
  ]);
5003
5595
  }
5004
5596
  async function runAgentTurn(opts2) {
5005
- const turnStart = performance.now();
5597
+ const tr = {
5598
+ trace: opts2.trace ?? createNoopRecorder(opts2.sessionId ?? ""),
5599
+ turnId: opts2.turnId ?? makeTurnId(),
5600
+ startedAt: performance.now(),
5601
+ toolsUsed: [],
5602
+ partialText: "",
5603
+ finalText: null,
5604
+ costUsd: 0,
5605
+ endReason: null
5606
+ };
5607
+ const endTurn = (reason, finalText) => {
5608
+ if (!tr.trace.enabled) return;
5609
+ const finalBlob = finalText ? tr.trace.blob(finalText) : null;
5610
+ tr.trace.emit(
5611
+ "turn_end",
5612
+ {
5613
+ reason,
5614
+ tools_used: tr.toolsUsed,
5615
+ ...tr.costUsd > 0 ? { cost_usd: tr.costUsd } : {},
5616
+ duration_ms: Math.round(performance.now() - tr.startedAt),
5617
+ ...finalBlob ? { final_text_blob: finalBlob } : {}
5618
+ },
5619
+ tr.turnId
5620
+ );
5621
+ };
5622
+ const gitDelta = await beginGitDelta({ trace: tr.trace, turnId: tr.turnId, sessionId: opts2.sessionId, cwd: opts2.cwd }).catch(
5623
+ () => null
5624
+ );
5625
+ try {
5626
+ await runAgentTurnInner(opts2, tr);
5627
+ endTurn(tr.endReason ?? "complete", tr.finalText);
5628
+ } catch (err) {
5629
+ if (err instanceof DOMException && err.name === "AbortError") {
5630
+ endTurn("aborted", tr.partialText || tr.finalText);
5631
+ } else if (err instanceof AgentLoopError || err instanceof BudgetExhaustedError) {
5632
+ endTurn("guardrail", tr.finalText);
5633
+ } else {
5634
+ endTurn("error", tr.partialText || tr.finalText);
5635
+ }
5636
+ throw err;
5637
+ } finally {
5638
+ await gitDelta?.finish().catch(() => void 0);
5639
+ }
5640
+ }
5641
+ async function runAgentTurnInner(opts2, tr) {
5642
+ const turnStart = tr.startedAt;
5643
+ const { trace, turnId } = tr;
5644
+ const notifyGuardrail = (ev) => {
5645
+ if (ev.kind !== "completion_nudge") trace.emit("guardrail", { kind: ev.kind, message: ev.message }, turnId);
5646
+ opts2.callbacks.onGuardrail?.(ev);
5647
+ };
5648
+ const traceBlocked = (tc, result2, errorCode, redirected = false) => {
5649
+ if (!trace.enabled) return;
5650
+ const argsBlob = trace.blob(tc.function.arguments) ?? "";
5651
+ trace.emit(
5652
+ "tool_call",
5653
+ {
5654
+ tool_call_id: tc.id,
5655
+ name: tc.function.name,
5656
+ args_blob: argsBlob,
5657
+ ...tc.function.arguments.length <= 2048 ? { args_inline: parseArgsObject(tc.function.arguments) } : {},
5658
+ needs_permission: false,
5659
+ code_mode_redirected: redirected
5660
+ },
5661
+ turnId
5662
+ );
5663
+ const blob = trace.blob(result2.content) ?? "";
5664
+ const bytes = Buffer.byteLength(result2.content, "utf8");
5665
+ trace.emit(
5666
+ "tool_result",
5667
+ {
5668
+ tool_call_id: tc.id,
5669
+ name: tc.function.name,
5670
+ ok: false,
5671
+ error_code: errorCode,
5672
+ raw_blob: blob,
5673
+ raw_bytes: bytes,
5674
+ reduced_blob: blob,
5675
+ reduced_bytes: bytes,
5676
+ duration_ms: 0
5677
+ },
5678
+ turnId
5679
+ );
5680
+ };
5006
5681
  logger.info("turn:start", { sessionId: opts2.sessionId, codeMode: opts2.codeMode ?? false });
5007
5682
  const max = opts2.maxToolIterations ?? 200;
5008
5683
  const limitBehavior = opts2.toolLimitBehavior ?? (opts2.continueOnLimit ? "continue" : "throw");
@@ -5164,6 +5839,18 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5164
5839
  const webFetchHistory = getSessionWebFetchHistory(opts2.sessionId);
5165
5840
  let webFetchesThisTurn = 0;
5166
5841
  let codeModeRedirects = 0;
5842
+ const noteCodeModeRedirect = () => {
5843
+ if (codeModeRedirects === MAX_CODE_MODE_REDIRECTS) {
5844
+ trace.emit(
5845
+ "guardrail",
5846
+ {
5847
+ kind: "code_mode_redirect_cap",
5848
+ message: `Code Mode redirect cap (${MAX_CODE_MODE_REDIRECTS}) reached \u2014 direct tool calls now execute normally.`
5849
+ },
5850
+ turnId
5851
+ );
5852
+ }
5853
+ };
5167
5854
  const MAX_WEB_FETCH_PER_TURN = 5;
5168
5855
  let cumulativePromptTokens = 0;
5169
5856
  let iter = 0;
@@ -5201,7 +5888,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5201
5888
  role: "system",
5202
5889
  content: `You have reached the hard safety ceiling of ${hardCeiling} tool iterations for this turn. Do not call any more tools. Summarize what you accomplished, what remains unfinished, and the concrete next steps needed to complete the task.`
5203
5890
  });
5204
- opts2.callbacks.onGuardrail?.({
5891
+ notifyGuardrail({
5205
5892
  kind: "limit_ceiling",
5206
5893
  message: `Tool-iteration ceiling (${hardCeiling}) reached \u2014 wrapping up the turn with a summary.`,
5207
5894
  totalIterations: totalIter
@@ -5213,17 +5900,18 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5213
5900
  content: "You have reached the tool-call limit for this session. The counter has been reset so you can continue working. Please proceed with your task."
5214
5901
  });
5215
5902
  iter = 0;
5216
- opts2.callbacks.onGuardrail?.({
5903
+ notifyGuardrail({
5217
5904
  kind: "limit_reset",
5218
5905
  message: `Tool-call limit (${max}) reached \u2014 counter reset automatically (${totalIter}/${hardCeiling} total).`,
5219
5906
  totalIterations: totalIter
5220
5907
  });
5221
5908
  } else if (limitBehavior === "stop") {
5222
- opts2.callbacks.onGuardrail?.({
5909
+ notifyGuardrail({
5223
5910
  kind: "limit_stopped",
5224
5911
  message: `Tool-call limit (${max}) reached \u2014 turn stopped.`,
5225
5912
  totalIterations: totalIter
5226
5913
  });
5914
+ tr.endReason = "guardrail";
5227
5915
  await fireStopHook();
5228
5916
  return;
5229
5917
  } else {
@@ -5240,6 +5928,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5240
5928
  let reasoning = "";
5241
5929
  let responseMeta;
5242
5930
  let reasoningDetails = [];
5931
+ tr.partialText = "";
5243
5932
  opts2.callbacks.onAssistantStart?.();
5244
5933
  const stripReasoning = process.env.KIMIFLARE_STRIP_REASONING === "1";
5245
5934
  const shadowStrip = process.env.KIMIFLARE_SHADOW_STRIP === "1";
@@ -5286,6 +5975,17 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5286
5975
  budgetTokens: maxPromptTokens
5287
5976
  };
5288
5977
  logger.info("context:compacted", { sessionId: opts2.sessionId, ...info });
5978
+ trace.emit(
5979
+ "compaction",
5980
+ {
5981
+ method: "preflight",
5982
+ tokens_before: info.tokensBefore,
5983
+ tokens_after: info.tokensAfter,
5984
+ turns_removed: info.turnsRemoved,
5985
+ artifacts_archived: info.artifactsArchived
5986
+ },
5987
+ turnId
5988
+ );
5289
5989
  opts2.callbacks.onCompacted?.(info);
5290
5990
  }
5291
5991
  }
@@ -5305,7 +6005,9 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5305
6005
  reasoningEffort: opts2.reasoningEffort,
5306
6006
  sessionId: opts2.sessionId,
5307
6007
  idleTimeoutMs: opts2.idleTimeoutMs ?? 6e4,
5308
- postFirstByteIdleTimeoutMs: opts2.postFirstByteIdleTimeoutMs
6008
+ postFirstByteIdleTimeoutMs: opts2.postFirstByteIdleTimeoutMs,
6009
+ trace,
6010
+ turnId
5309
6011
  });
5310
6012
  let gotFirstChunk = false;
5311
6013
  for await (const ev of events) {
@@ -5327,6 +6029,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5327
6029
  break;
5328
6030
  case "text":
5329
6031
  content += ev.delta;
6032
+ tr.partialText = content;
5330
6033
  opts2.callbacks.onTextDelta?.(ev.delta);
5331
6034
  break;
5332
6035
  case "tool_call_start":
@@ -5343,6 +6046,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5343
6046
  function: { name: normalizeToolName(ev.name, opts2.tools), arguments: safeArgs }
5344
6047
  };
5345
6048
  toolCalls.push(call);
6049
+ tr.toolsUsed.push(call.function.name);
5346
6050
  opts2.callbacks.onToolCallFinalized?.(call);
5347
6051
  break;
5348
6052
  }
@@ -5356,6 +6060,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5356
6060
  }
5357
6061
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
5358
6062
  if (lastUsage) {
6063
+ if (typeof lastUsage.cost === "number") tr.costUsd += lastUsage.cost;
5359
6064
  opts2.callbacks.onUsageFinal?.(lastUsage, responseMeta);
5360
6065
  cumulativePromptTokens += lastUsage.prompt_tokens;
5361
6066
  if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens) {
@@ -5384,8 +6089,10 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5384
6089
  }
5385
6090
  }
5386
6091
  opts2.messages.push(assistantMsg);
6092
+ tr.finalText = typeof assistantMsg.content === "string" ? assistantMsg.content : null;
5387
6093
  opts2.callbacks.onAssistantFinal?.(assistantMsg);
5388
6094
  if (finalizeReason !== null) {
6095
+ tr.endReason = "guardrail";
5389
6096
  logger.info("turn:finalized", { sessionId: opts2.sessionId, reason: finalizeReason, totalIter });
5390
6097
  if (budgetExhausted) throw new BudgetExhaustedError();
5391
6098
  await fireStopHook();
@@ -5424,11 +6131,12 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5424
6131
  if (shouldCheckCompletion(input, opts2.intentClassification?.tier)) {
5425
6132
  const p = await opts2.completionCheck(input, opts2.signal).catch(() => null);
5426
6133
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
6134
+ if (p !== null) trace.emit("completion_check", { probability: p, nudged: p < INCOMPLETE_THRESHOLD }, turnId);
5427
6135
  if (p !== null && p < INCOMPLETE_THRESHOLD) {
5428
6136
  completionNudges++;
5429
6137
  logger.info("turn:completion_nudge", { sessionId: opts2.sessionId, probability: p });
5430
6138
  opts2.messages.push({ role: "system", content: CONTINUE_NUDGE });
5431
- opts2.callbacks.onGuardrail?.({
6139
+ notifyGuardrail({
5432
6140
  kind: "completion_nudge",
5433
6141
  message: "The agent stopped before finishing \u2014 asked it to continue.",
5434
6142
  probability: p
@@ -5468,6 +6176,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5468
6176
  kind: "blocked",
5469
6177
  tc,
5470
6178
  loopSignature,
6179
+ errorCode: "loop_guardrail",
5471
6180
  result: {
5472
6181
  tool_call_id: tc.id,
5473
6182
  name: tc.function.name,
@@ -5480,10 +6189,13 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5480
6189
  }
5481
6190
  if (codeMode && CODE_MODE_REDIRECT_TOOLS.has(tc.function.name) && codeModeRedirects < MAX_CODE_MODE_REDIRECTS) {
5482
6191
  codeModeRedirects++;
6192
+ noteCodeModeRedirect();
5483
6193
  items.push({
5484
6194
  kind: "blocked",
5485
6195
  tc,
5486
6196
  loopSignature,
6197
+ errorCode: "code_mode_redirect",
6198
+ redirected: true,
5487
6199
  result: { tool_call_id: tc.id, name: tc.function.name, content: codeModeRedirectMessage(tc.function.name), ok: false }
5488
6200
  });
5489
6201
  continue;
@@ -5500,6 +6212,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5500
6212
  kind: "blocked",
5501
6213
  tc,
5502
6214
  loopSignature,
6215
+ errorCode: "web_fetch_budget",
5503
6216
  result: { tool_call_id: tc.id, name: "web_fetch", content: warning, ok: false, guardrail: true }
5504
6217
  });
5505
6218
  continue;
@@ -5520,6 +6233,8 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5520
6233
  { id: it.tc.id, name: it.tc.function.name, arguments: it.tc.function.arguments },
5521
6234
  batchAskPermission,
5522
6235
  {
6236
+ trace,
6237
+ turnId,
5523
6238
  cwd: opts2.cwd,
5524
6239
  signal: opts2.signal,
5525
6240
  onTasks: wrappedOnTasks,
@@ -5562,6 +6277,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5562
6277
  }
5563
6278
  for (const it of items) {
5564
6279
  if (it.kind === "blocked") {
6280
+ traceBlocked(it.tc, it.result, it.errorCode, it.redirected === true);
5565
6281
  toolResults.push(it.result);
5566
6282
  opts2.messages.push({
5567
6283
  role: "tool",
@@ -5697,6 +6413,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5697
6413
  content: `Tool ${tc.function.name} is not available under this turn's policy. Continue without it or ask the user to enable it.`,
5698
6414
  ok: false
5699
6415
  };
6416
+ traceBlocked(tc, unavailableResult, "unavailable");
5700
6417
  toolResults.push(unavailableResult);
5701
6418
  opts2.messages.push({
5702
6419
  role: "tool",
@@ -5720,6 +6437,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5720
6437
  ok: false,
5721
6438
  guardrail: true
5722
6439
  };
6440
+ traceBlocked(tc, loopResult, "loop_guardrail");
5723
6441
  toolResults.push(loopResult);
5724
6442
  opts2.messages.push({
5725
6443
  role: "tool",
@@ -5748,6 +6466,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5748
6466
  ok: false,
5749
6467
  guardrail: true
5750
6468
  };
6469
+ traceBlocked(tc, budgetResult, "web_fetch_budget");
5751
6470
  toolResults.push(budgetResult);
5752
6471
  opts2.messages.push({
5753
6472
  role: "tool",
@@ -5770,6 +6489,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5770
6489
  ok: false,
5771
6490
  guardrail: true
5772
6491
  };
6492
+ traceBlocked(tc, sessionCapResult, "web_fetch_budget");
5773
6493
  toolResults.push(sessionCapResult);
5774
6494
  opts2.messages.push({
5775
6495
  role: "tool",
@@ -5791,12 +6511,39 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5791
6511
  if (codeMode && tc.function.name === "execute_code") {
5792
6512
  const args = JSON.parse(tc.function.arguments || "{}");
5793
6513
  const code = args.code || "";
6514
+ const sandboxStartedAt = performance.now();
6515
+ if (trace.enabled) {
6516
+ trace.emit(
6517
+ "tool_call",
6518
+ {
6519
+ tool_call_id: tc.id,
6520
+ name: "execute_code",
6521
+ args_blob: trace.blob(tc.function.arguments) ?? "",
6522
+ ...tc.function.arguments.length <= 2048 ? { args_inline: parseArgsObject(tc.function.arguments) } : {},
6523
+ needs_permission: false,
6524
+ code_mode_redirected: false
6525
+ },
6526
+ turnId
6527
+ );
6528
+ }
5794
6529
  const sandboxResult = await runInSandbox({
5795
6530
  code,
5796
6531
  tools: sandboxTools,
5797
6532
  executor: opts2.executor,
5798
6533
  askPermission: opts2.callbacks.askPermission,
5799
- ctx: { cwd: opts2.cwd, signal: opts2.signal, onTasks: wrappedOnTasks, onPlanOptions: opts2.callbacks.onPlanOptions, coauthor: opts2.coauthor, memoryManager: opts2.memoryManager, sessionId: opts2.sessionId, githubToken: opts2.githubToken },
6534
+ ctx: {
6535
+ cwd: opts2.cwd,
6536
+ signal: opts2.signal,
6537
+ onTasks: wrappedOnTasks,
6538
+ onPlanOptions: opts2.callbacks.onPlanOptions,
6539
+ coauthor: opts2.coauthor,
6540
+ memoryManager: opts2.memoryManager,
6541
+ sessionId: opts2.sessionId,
6542
+ githubToken: opts2.githubToken,
6543
+ trace,
6544
+ turnId,
6545
+ codeModeParentToolCallId: tc.id
6546
+ },
5800
6547
  timeoutMs: 3e4,
5801
6548
  memoryLimitMB: 128
5802
6549
  });
@@ -5819,6 +6566,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5819
6566
 
5820
6567
  Output:
5821
6568
  ${sandboxResult.output}` : sandboxResult.output;
6569
+ const sandboxRawContent = resultContent;
5822
6570
  if (resultContent.length > MAX_TOOL_CONTENT_CHARS) {
5823
6571
  const rawBytes = resultContent.length;
5824
6572
  resultContent = resultContent.slice(0, MAX_TOOL_CONTENT_CHARS) + `
@@ -5837,6 +6585,24 @@ ${sandboxResult.output}` : sandboxResult.output;
5837
6585
  content: resultContent,
5838
6586
  ok: !sandboxResult.error
5839
6587
  };
6588
+ if (trace.enabled) {
6589
+ const rawBlob = trace.blob(sandboxRawContent) ?? "";
6590
+ trace.emit(
6591
+ "tool_result",
6592
+ {
6593
+ tool_call_id: tc.id,
6594
+ name: "execute_code",
6595
+ ok: result2.ok,
6596
+ ...sandboxResult.error ? { error_code: "sandbox_error" } : {},
6597
+ raw_blob: rawBlob,
6598
+ raw_bytes: Buffer.byteLength(sandboxRawContent, "utf8"),
6599
+ reduced_blob: sandboxRawContent === resultContent ? rawBlob : trace.blob(resultContent) ?? "",
6600
+ reduced_bytes: Buffer.byteLength(resultContent, "utf8"),
6601
+ duration_ms: Math.round(performance.now() - sandboxStartedAt)
6602
+ },
6603
+ turnId
6604
+ );
6605
+ }
5840
6606
  toolResults.push(result2);
5841
6607
  opts2.messages.push({
5842
6608
  role: "tool",
@@ -5849,8 +6615,10 @@ ${sandboxResult.output}` : sandboxResult.output;
5849
6615
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
5850
6616
  } else if (codeMode && CODE_MODE_REDIRECT_TOOLS.has(tc.function.name) && codeModeRedirects < MAX_CODE_MODE_REDIRECTS) {
5851
6617
  codeModeRedirects++;
6618
+ noteCodeModeRedirect();
5852
6619
  const msg = codeModeRedirectMessage(tc.function.name);
5853
6620
  const redirectResult = { tool_call_id: tc.id, name: tc.function.name, content: msg, ok: false };
6621
+ traceBlocked(tc, redirectResult, "code_mode_redirect", true);
5854
6622
  toolResults.push(redirectResult);
5855
6623
  opts2.messages.push({ role: "tool", tool_call_id: tc.id, content: msg, name: tc.function.name });
5856
6624
  opts2.callbacks.onToolResult?.(redirectResult);
@@ -5861,6 +6629,8 @@ ${sandboxResult.output}` : sandboxResult.output;
5861
6629
  { id: tc.id, name: tc.function.name, arguments: tc.function.arguments },
5862
6630
  opts2.callbacks.askPermission,
5863
6631
  {
6632
+ trace,
6633
+ turnId,
5864
6634
  cwd: opts2.cwd,
5865
6635
  signal: opts2.signal,
5866
6636
  onTasks: wrappedOnTasks,
@@ -5929,6 +6699,7 @@ ${sandboxResult.output}` : sandboxResult.output;
5929
6699
  condition: result2.waitRequest.condition,
5930
6700
  wakeAt: result2.waitRequest.wakeAt
5931
6701
  });
6702
+ tr.endReason = "needs_input";
5932
6703
  try {
5933
6704
  opts2.callbacks.onRunYield?.(result2.waitRequest);
5934
6705
  } catch (error) {
@@ -6087,7 +6858,7 @@ ${sandboxResult.output}` : sandboxResult.output;
6087
6858
  role: "system",
6088
6859
  content: "Every tool call in your last step was blocked because you kept repeating the same calls with identical arguments. Do not repeat them. Either take a genuinely different approach (different tools, arguments, or files), or, if you already have enough information, stop calling tools and give your final answer."
6089
6860
  });
6090
- opts2.callbacks.onGuardrail?.({
6861
+ notifyGuardrail({
6091
6862
  kind: "loop_recovery",
6092
6863
  message: "Loop guardrail: repeated tool calls were blocked \u2014 asked the agent to change approach."
6093
6864
  });
@@ -6098,7 +6869,7 @@ ${sandboxResult.output}` : sandboxResult.output;
6098
6869
  role: "system",
6099
6870
  content: "You are still repeating blocked tool calls after being asked to change approach, so this turn is ending. Do not call any more tools. Summarize what you accomplished, what is blocking you, and what the user could do next."
6100
6871
  });
6101
- opts2.callbacks.onGuardrail?.({
6872
+ notifyGuardrail({
6102
6873
  kind: "loop_stopped",
6103
6874
  message: "Loop guardrail: the agent kept repeating blocked tool calls \u2014 ending the turn with a summary."
6104
6875
  });
@@ -6159,6 +6930,8 @@ var init_loop = __esm({
6159
6930
  init_context_budget();
6160
6931
  init_turn_directive();
6161
6932
  init_completion_check();
6933
+ init_recorder();
6934
+ init_git_delta();
6162
6935
  MAX_LOOP_RECOVERIES = 1;
6163
6936
  BudgetExhaustedError = class extends Error {
6164
6937
  constructor(message2 = "Cumulative input token budget exhausted") {
@@ -6214,10 +6987,10 @@ var init_pricing = __esm({
6214
6987
  // src/util/atomic-file.ts
6215
6988
  import { mkdir as mkdir6, rename, rm, stat as stat3, writeFile as writeFile5 } from "fs/promises";
6216
6989
  import { dirname as dirname6 } from "path";
6217
- import { randomUUID } from "crypto";
6990
+ import { randomUUID as randomUUID2 } from "crypto";
6218
6991
  async function writeFileAtomic(path, data) {
6219
6992
  await mkdir6(dirname6(path), { recursive: true });
6220
- const tmp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
6993
+ const tmp = `${path}.${process.pid}.${randomUUID2().slice(0, 8)}.tmp`;
6221
6994
  try {
6222
6995
  await writeFile5(tmp, data, "utf8");
6223
6996
  await rename(tmp, path);
@@ -6272,19 +7045,19 @@ var init_atomic_file = __esm({
6272
7045
 
6273
7046
  // src/usage-tracker.ts
6274
7047
  import { readFile as readFile5, rename as rename2 } from "fs/promises";
6275
- import { homedir as homedir8 } from "os";
6276
- import { join as join13 } from "path";
7048
+ import { homedir as homedir9 } from "os";
7049
+ import { join as join15 } from "path";
6277
7050
  import { EventEmitter } from "events";
6278
- import { randomUUID as randomUUID2 } from "crypto";
7051
+ import { randomUUID as randomUUID3 } from "crypto";
6279
7052
  function usageDir() {
6280
- const xdg = process.env.XDG_DATA_HOME || join13(homedir8(), ".local", "share");
6281
- return join13(xdg, "kimiflare");
7053
+ const xdg = process.env.XDG_DATA_HOME || join15(homedir9(), ".local", "share");
7054
+ return join15(xdg, "kimiflare");
6282
7055
  }
6283
7056
  function usagePath() {
6284
- return join13(usageDir(), "usage.json");
7057
+ return join15(usageDir(), "usage.json");
6285
7058
  }
6286
7059
  function historyPath() {
6287
- return join13(usageDir(), "history.jsonl");
7060
+ return join15(usageDir(), "history.jsonl");
6288
7061
  }
6289
7062
  function today() {
6290
7063
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -6404,7 +7177,7 @@ async function recordUsage(sessionId, usage, lookup, model) {
6404
7177
  );
6405
7178
  const estimatedCost = cost.total;
6406
7179
  const cachedTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
6407
- const turnId = randomUUID2();
7180
+ const turnId = randomUUID3();
6408
7181
  const generationId = lookup?.meta.generationId;
6409
7182
  const inlineCost = typeof usage.cost === "number" && Number.isFinite(usage.cost) ? usage.cost : void 0;
6410
7183
  await withLock(async () => {
@@ -6705,12 +7478,41 @@ var init_tool_error = __esm({
6705
7478
  }
6706
7479
  });
6707
7480
 
7481
+ // src/trace/verification.ts
7482
+ function segment(alternatives) {
7483
+ return new RegExp(`${SEG}(?:${alternatives})(?:\\s|$)`);
7484
+ }
7485
+ function classifyVerification(command) {
7486
+ const trimmed = command.trim();
7487
+ if (!trimmed) return null;
7488
+ for (const [category, re] of TABLE) {
7489
+ if (re.test(trimmed)) return category;
7490
+ }
7491
+ return null;
7492
+ }
7493
+ var SEG, TABLE;
7494
+ var init_verification = __esm({
7495
+ "src/trace/verification.ts"() {
7496
+ "use strict";
7497
+ SEG = String.raw`(?:^|[;|(]\s*|&&\s*|\|\|\s*)(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:npx\s+|pnpm\s+exec\s+|pnpm\s+dlx\s+|yarn\s+|bunx\s+|bun\s+run\s+|(?:\.\/)?node_modules\/\.bin\/)?`;
7498
+ TABLE = [
7499
+ [
7500
+ "test",
7501
+ segment(String.raw`npm\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|yarn\s+(?:run\s+)?test|tsx\s+--test|node\s+--test|vitest|jest|pytest|go\s+test|cargo\s+test`)
7502
+ ],
7503
+ ["lint", segment(String.raw`eslint|ruff|golangci-lint|npm\s+run\s+lint|pnpm\s+(?:run\s+)?lint|yarn\s+(?:run\s+)?lint`)],
7504
+ ["typecheck", segment(String.raw`tsc|mypy|pyright|npm\s+run\s+typecheck|pnpm\s+(?:run\s+)?typecheck|yarn\s+(?:run\s+)?typecheck`)],
7505
+ ["build", segment(String.raw`npm\s+run\s+build|tsup|cargo\s+build|go\s+build|pnpm\s+(?:run\s+)?build|yarn\s+(?:run\s+)?build`)]
7506
+ ];
7507
+ }
7508
+ });
7509
+
6708
7510
  // src/util/paths.ts
6709
7511
  import { resolve, isAbsolute, relative, sep } from "path";
6710
- import { homedir as homedir9 } from "os";
7512
+ import { homedir as homedir10 } from "os";
6711
7513
  function resolvePath(cwd, input) {
6712
7514
  if (input.startsWith("~/") || input === "~") {
6713
- return resolve(homedir9(), input === "~" ? "." : input.slice(2));
7515
+ return resolve(homedir10(), input === "~" ? "." : input.slice(2));
6714
7516
  }
6715
7517
  return isAbsolute(input) ? input : resolve(cwd, input);
6716
7518
  }
@@ -6737,7 +7539,7 @@ function collapsePath(input, cwd, maxLen = 40) {
6737
7539
  function collapsePathsInText(s, cwd, maxLen = 40) {
6738
7540
  return s.replace(/([~/][^\s"',)}\]]+)/g, (match) => collapsePath(match, cwd, maxLen));
6739
7541
  }
6740
- var init_paths = __esm({
7542
+ var init_paths2 = __esm({
6741
7543
  "src/util/paths.ts"() {
6742
7544
  "use strict";
6743
7545
  }
@@ -6794,7 +7596,7 @@ var MAX_BYTES, MAX_STREAM_BYTES, readTool;
6794
7596
  var init_read = __esm({
6795
7597
  "src/tools/read.ts"() {
6796
7598
  "use strict";
6797
- init_paths();
7599
+ init_paths2();
6798
7600
  MAX_BYTES = 2 * 1024 * 1024;
6799
7601
  MAX_STREAM_BYTES = 50 * 1024 * 1024;
6800
7602
  readTool = {
@@ -6845,7 +7647,7 @@ var writeTool;
6845
7647
  var init_write = __esm({
6846
7648
  "src/tools/write.ts"() {
6847
7649
  "use strict";
6848
- init_paths();
7650
+ init_paths2();
6849
7651
  writeTool = {
6850
7652
  name: "write",
6851
7653
  description: "Create a file or overwrite an existing one with the given contents. Prompts the user for permission first and shows a diff preview.",
@@ -6896,7 +7698,7 @@ var editTool;
6896
7698
  var init_edit = __esm({
6897
7699
  "src/tools/edit.ts"() {
6898
7700
  "use strict";
6899
- init_paths();
7701
+ init_paths2();
6900
7702
  editTool = {
6901
7703
  name: "edit",
6902
7704
  description: "Replace an exact string in a file. If replace_all is false (default), the old_string must appear exactly once or the call fails. Prompts the user for permission first and shows a diff preview.",
@@ -6937,7 +7739,7 @@ var init_edit = __esm({
6937
7739
  // src/tools/bash.ts
6938
7740
  import { spawn } from "child_process";
6939
7741
  import { tmpdir, platform as platform2 } from "os";
6940
- import { join as join14 } from "path";
7742
+ import { join as join16 } from "path";
6941
7743
  function getShellCommand(override) {
6942
7744
  const raw = override?.trim();
6943
7745
  if (raw && raw !== "auto") {
@@ -7139,7 +7941,7 @@ function injectCoauthor(command, coauthor) {
7139
7941
  if (movesHeadOnly) return unchanged;
7140
7942
  const env2 = {
7141
7943
  [COAUTHOR_TRAILER_ENV]: trailer,
7142
- [COAUTHOR_MSG_ENV]: join14(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`)
7944
+ [COAUTHOR_MSG_ENV]: join16(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`)
7143
7945
  };
7144
7946
  if (createsCommit || isRebaseContinue) {
7145
7947
  return { command: `(${command}) && { ${AMEND_BLOCK}
@@ -7232,7 +8034,8 @@ ${stderr}`
7232
8034
  resolve15({
7233
8035
  content: raw,
7234
8036
  rawBytes: Buffer.byteLength(raw, "utf8"),
7235
- reducedBytes: Buffer.byteLength(raw, "utf8")
8037
+ reducedBytes: Buffer.byteLength(raw, "utf8"),
8038
+ meta: { exitCode: code, timedOut: killedByTimeout }
7236
8039
  });
7237
8040
  });
7238
8041
  });
@@ -7280,7 +8083,7 @@ var init_bash = __esm({
7280
8083
 
7281
8084
  // src/util/glob.ts
7282
8085
  import { readdir as readdir3, lstat, realpath } from "fs/promises";
7283
- import { join as join15, relative as relative2, resolve as resolve2 } from "path";
8086
+ import { join as join17, relative as relative2, resolve as resolve2 } from "path";
7284
8087
  function parsePattern(pattern) {
7285
8088
  const parts = pattern.split(/\//g);
7286
8089
  return parts.map((p) => {
@@ -7321,8 +8124,8 @@ function globToRegex(glob2) {
7321
8124
  re += "$";
7322
8125
  return new RegExp(re);
7323
8126
  }
7324
- function matchSegment(segment, pattern) {
7325
- return globToRegex(pattern).test(segment);
8127
+ function matchSegment(segment2, pattern) {
8128
+ return globToRegex(pattern).test(segment2);
7326
8129
  }
7327
8130
  function shouldIgnore(relativePath, ignorePatterns) {
7328
8131
  for (const pat of ignorePatterns) {
@@ -7389,7 +8192,7 @@ async function* walk(root, pattern, options) {
7389
8192
  if (!dot && name.startsWith(".")) continue;
7390
8193
  const matched = seg.type === "literal" ? seg.value === name : matchSegment(name, seg.value);
7391
8194
  if (!matched) continue;
7392
- const childPath = join15(dirPath, name);
8195
+ const childPath = join17(dirPath, name);
7393
8196
  const childRel = [...relativeParts, name];
7394
8197
  const childRelStr = childRel.join("/");
7395
8198
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -7414,7 +8217,7 @@ async function* walk(root, pattern, options) {
7414
8217
  if (!dot && name.startsWith(".")) continue;
7415
8218
  const matched = seg.type === "literal" ? seg.value === name : matchSegment(name, seg.value);
7416
8219
  if (!matched) continue;
7417
- const childPath = join15(dirPath, name);
8220
+ const childPath = join17(dirPath, name);
7418
8221
  const childRel = [...relativeParts, name];
7419
8222
  const childRelStr = childRel.join("/");
7420
8223
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -7456,7 +8259,7 @@ async function* walk(root, pattern, options) {
7456
8259
  const name = String(ent.name);
7457
8260
  if (name === "." || name === "..") continue;
7458
8261
  if (!dot && name.startsWith(".")) continue;
7459
- const childPath = join15(dirPath, name);
8262
+ const childPath = join17(dirPath, name);
7460
8263
  const childRel = [...relativeParts, name];
7461
8264
  const childRelStr = childRel.join("/");
7462
8265
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -7550,7 +8353,7 @@ var globTool;
7550
8353
  var init_glob2 = __esm({
7551
8354
  "src/tools/glob.ts"() {
7552
8355
  "use strict";
7553
- init_paths();
8356
+ init_paths2();
7554
8357
  init_glob();
7555
8358
  globTool = {
7556
8359
  name: "glob",
@@ -7601,8 +8404,8 @@ var init_glob2 = __esm({
7601
8404
  });
7602
8405
 
7603
8406
  // src/tools/grep.ts
7604
- import { execFile } from "child_process";
7605
- import { promisify } from "util";
8407
+ import { execFile as execFile2 } from "child_process";
8408
+ import { promisify as promisify2 } from "util";
7606
8409
  import { readFile as readFile9 } from "fs/promises";
7607
8410
  async function hasRipgrep() {
7608
8411
  if (cachedHasRg !== null) return cachedHasRg;
@@ -7678,9 +8481,9 @@ var pExecFile, cachedHasRg, grepTool;
7678
8481
  var init_grep = __esm({
7679
8482
  "src/tools/grep.ts"() {
7680
8483
  "use strict";
7681
- init_paths();
8484
+ init_paths2();
7682
8485
  init_glob();
7683
- pExecFile = promisify(execFile);
8486
+ pExecFile = promisify2(execFile2);
7684
8487
  cachedHasRg = null;
7685
8488
  grepTool = {
7686
8489
  name: "grep",
@@ -8224,7 +9027,7 @@ __export(changelog_image_exports, {
8224
9027
  });
8225
9028
  import { readFile as readFile10 } from "fs/promises";
8226
9029
  import { writeFile as writeFile8 } from "fs/promises";
8227
- import { join as join16 } from "path";
9030
+ import { join as join18 } from "path";
8228
9031
  async function githubFetch2(path, token) {
8229
9032
  const controller = new AbortController();
8230
9033
  const timer3 = setTimeout(() => controller.abort(), TIMEOUT_MS4);
@@ -8255,7 +9058,7 @@ function escapeXml(str) {
8255
9058
  }
8256
9059
  async function loadLogoBase64() {
8257
9060
  try {
8258
- const buf = await readFile10(join16(process.cwd(), "docs", "logo.png"));
9061
+ const buf = await readFile10(join18(process.cwd(), "docs", "logo.png"));
8259
9062
  return `data:image/png;base64,${buf.toString("base64")}`;
8260
9063
  } catch {
8261
9064
  return null;
@@ -8563,7 +9366,7 @@ Format your response as plain text bullet points, one per line, starting with "\
8563
9366
  // src/tools/browser.ts
8564
9367
  import { mkdir as mkdir8 } from "fs/promises";
8565
9368
  import { tmpdir as tmpdir2 } from "os";
8566
- import { join as join17, dirname as dirname8 } from "path";
9369
+ import { join as join19, dirname as dirname8 } from "path";
8567
9370
  async function autoScroll(page) {
8568
9371
  await page.evaluate(async () => {
8569
9372
  await new Promise((resolve15) => {
@@ -8639,7 +9442,7 @@ var init_browser = __esm({
8639
9442
  }
8640
9443
  let screenshotPath;
8641
9444
  if (args.screenshot) {
8642
- screenshotPath = join17(tmpdir2(), `kimiflare-browser-${Date.now()}.png`);
9445
+ screenshotPath = join19(tmpdir2(), `kimiflare-browser-${Date.now()}.png`);
8643
9446
  await mkdir8(dirname8(screenshotPath), { recursive: true });
8644
9447
  await page.screenshot({ path: screenshotPath, fullPage: true });
8645
9448
  }
@@ -8893,13 +9696,13 @@ var init_memory = __esm({
8893
9696
  });
8894
9697
 
8895
9698
  // src/tools/hotcell-snapshot.ts
8896
- import { execFile as execFile2 } from "child_process";
9699
+ import { execFile as execFile3 } from "child_process";
8897
9700
  import { mkdtemp, rm as rm2 } from "fs/promises";
8898
9701
  import { tmpdir as tmpdir3 } from "os";
8899
- import { join as join18 } from "path";
8900
- import { promisify as promisify2 } from "util";
9702
+ import { join as join20 } from "path";
9703
+ import { promisify as promisify3 } from "util";
8901
9704
  async function git(args, cwd, env2) {
8902
- const { stdout } = await execFileAsync("git", args, {
9705
+ const { stdout } = await execFileAsync2("git", args, {
8903
9706
  cwd,
8904
9707
  env: env2 ? { ...process.env, ...env2 } : process.env,
8905
9708
  maxBuffer: 64 * 1024 * 1024,
@@ -8908,7 +9711,7 @@ async function git(args, cwd, env2) {
8908
9711
  return stdout.toString("utf8");
8909
9712
  }
8910
9713
  async function gitBuffer(args, cwd) {
8911
- const { stdout } = await execFileAsync("git", args, { cwd, maxBuffer: 64 * 1024 * 1024, encoding: "buffer" });
9714
+ const { stdout } = await execFileAsync2("git", args, { cwd, maxBuffer: 64 * 1024 * 1024, encoding: "buffer" });
8912
9715
  return stdout;
8913
9716
  }
8914
9717
  async function originBranchesContaining(commit, cwd) {
@@ -8944,9 +9747,9 @@ async function getRepositorySnapshot(cwd) {
8944
9747
  if (!ref) {
8945
9748
  throw new Error("Could not find an origin branch containing the base commit. Run git fetch, then retry.");
8946
9749
  }
8947
- const scratch = await mkdtemp(join18(tmpdir3(), "autopilot-snapshot-"));
9750
+ const scratch = await mkdtemp(join20(tmpdir3(), "autopilot-snapshot-"));
8948
9751
  try {
8949
- const env2 = { GIT_INDEX_FILE: join18(scratch, "index") };
9752
+ const env2 = { GIT_INDEX_FILE: join20(scratch, "index") };
8950
9753
  await git(["read-tree", "HEAD"], root, env2);
8951
9754
  await git(["add", "-A", "--", ".", ...SECRET_EXCLUDES], root, env2);
8952
9755
  const tree = (await git(["write-tree"], root, env2)).trim();
@@ -8971,9 +9774,9 @@ async function getRepositorySnapshot(cwd) {
8971
9774
  async function getWorkingTreeArchive(cwd, limitBytes = MAX_ARCHIVE_BYTES) {
8972
9775
  const root = (await git(["rev-parse", "--show-toplevel"], cwd).catch(() => "")).trim();
8973
9776
  if (!root) throw new Error("Subagents need to run inside a Git repository.");
8974
- const scratch = await mkdtemp(join18(tmpdir3(), "autopilot-archive-"));
9777
+ const scratch = await mkdtemp(join20(tmpdir3(), "autopilot-archive-"));
8975
9778
  try {
8976
- const env2 = { GIT_INDEX_FILE: join18(scratch, "index") };
9779
+ const env2 = { GIT_INDEX_FILE: join20(scratch, "index") };
8977
9780
  const hasHead = await git(["rev-parse", "--verify", "-q", "HEAD"], root).then(() => true, () => false);
8978
9781
  await git(hasHead ? ["read-tree", "HEAD"] : ["read-tree", "--empty"], root, env2);
8979
9782
  await git(["add", "-A", "--", ".", ...SECRET_EXCLUDES], root, env2);
@@ -8995,11 +9798,11 @@ function chunkPayload(base64, chunkSize = PAYLOAD_CHUNK_CHARS) {
8995
9798
  for (let i = 0; i < base64.length; i += chunkSize) chunks.push(base64.slice(i, i + chunkSize));
8996
9799
  return chunks;
8997
9800
  }
8998
- var execFileAsync, MAX_SNAPSHOT_PATCH_BYTES, MAX_ARCHIVE_BYTES, SECRET_EXCLUDES, PAYLOAD_CHUNK_CHARS;
9801
+ var execFileAsync2, MAX_SNAPSHOT_PATCH_BYTES, MAX_ARCHIVE_BYTES, SECRET_EXCLUDES, PAYLOAD_CHUNK_CHARS;
8999
9802
  var init_hotcell_snapshot = __esm({
9000
9803
  "src/tools/hotcell-snapshot.ts"() {
9001
9804
  "use strict";
9002
- execFileAsync = promisify2(execFile2);
9805
+ execFileAsync2 = promisify3(execFile3);
9003
9806
  MAX_SNAPSHOT_PATCH_BYTES = 4 * 1024 * 1024;
9004
9807
  MAX_ARCHIVE_BYTES = 24 * 1024 * 1024;
9005
9808
  SECRET_EXCLUDES = [
@@ -9022,11 +9825,11 @@ var init_hotcell_snapshot = __esm({
9022
9825
 
9023
9826
  // src/tools/hotcell-worker-cache.ts
9024
9827
  import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile10 } from "fs/promises";
9025
- import { homedir as homedir10 } from "os";
9026
- import { dirname as dirname9, join as join19 } from "path";
9828
+ import { homedir as homedir11 } from "os";
9829
+ import { dirname as dirname9, join as join21 } from "path";
9027
9830
  function workerCachePath() {
9028
- const xdg = process.env.XDG_CONFIG_HOME || join19(homedir10(), ".config");
9029
- return join19(xdg, "kimiflare", "hotcell-worker-cache.json");
9831
+ const xdg = process.env.XDG_CONFIG_HOME || join21(homedir11(), ".config");
9832
+ return join21(xdg, "kimiflare", "hotcell-worker-cache.json");
9030
9833
  }
9031
9834
  async function readCache2() {
9032
9835
  try {
@@ -9098,8 +9901,8 @@ var init_hotcell_worker_cache = __esm({
9098
9901
 
9099
9902
  // src/tools/hotcell-worker.ts
9100
9903
  import { spawn as spawn2 } from "child_process";
9101
- import { promisify as promisify3 } from "util";
9102
- import { execFile as execFile3 } from "child_process";
9904
+ import { promisify as promisify4 } from "util";
9905
+ import { execFile as execFile4 } from "child_process";
9103
9906
  import { resolve as resolve3 } from "path";
9104
9907
  async function runHotcellWorker(options) {
9105
9908
  const workerId = `hotcell-${crypto.randomUUID().slice(0, 8)}`;
@@ -9505,7 +10308,7 @@ function releaseSlot() {
9505
10308
  runningWorkers = Math.max(0, runningWorkers - 1);
9506
10309
  for (const wake of workerQueue.slice()) wake();
9507
10310
  }
9508
- var execFileAsync2, MAX_OUTPUT_BYTES, MAX_PROMPT_CHARS, DEFAULT_CELL_TIMEOUT_MS, DEFAULT_SETUP_TIMEOUT_MS, WORKER_BIN, PACKAGE_SPEC, LOCATE_REPO_ROOT, READ_REPO_ROOT, REPO_ASIDE, SNAPSHOT_PREFIX, ARCHIVE_ROOT, MAX_PARALLEL_WORKERS, MIN_WORKER_INPUT_TOKENS, MAX_WORKER_INPUT_TOKENS, DEFAULT_WORKER_INPUT_TOKENS, INPUT_SHARE_OF_BUDGET, MAX_REPORT_FINDINGS, MAX_REPORT_SUMMARY_CHARS, MAX_REPORT_LIST_ITEMS, runningWorkers, workerQueue, WORKER_REPORT_INSTRUCTIONS;
10311
+ var execFileAsync3, MAX_OUTPUT_BYTES, MAX_PROMPT_CHARS, DEFAULT_CELL_TIMEOUT_MS, DEFAULT_SETUP_TIMEOUT_MS, WORKER_BIN, PACKAGE_SPEC, LOCATE_REPO_ROOT, READ_REPO_ROOT, REPO_ASIDE, SNAPSHOT_PREFIX, ARCHIVE_ROOT, MAX_PARALLEL_WORKERS, MIN_WORKER_INPUT_TOKENS, MAX_WORKER_INPUT_TOKENS, DEFAULT_WORKER_INPUT_TOKENS, INPUT_SHARE_OF_BUDGET, MAX_REPORT_FINDINGS, MAX_REPORT_SUMMARY_CHARS, MAX_REPORT_LIST_ITEMS, runningWorkers, workerQueue, WORKER_REPORT_INSTRUCTIONS;
9509
10312
  var init_hotcell_worker = __esm({
9510
10313
  "src/tools/hotcell-worker.ts"() {
9511
10314
  "use strict";
@@ -9514,7 +10317,7 @@ var init_hotcell_worker = __esm({
9514
10317
  init_registry();
9515
10318
  init_hotcell_snapshot();
9516
10319
  init_hotcell_worker_cache();
9517
- execFileAsync2 = promisify3(execFile3);
10320
+ execFileAsync3 = promisify4(execFile4);
9518
10321
  MAX_OUTPUT_BYTES = 1e6;
9519
10322
  MAX_PROMPT_CHARS = 4e4;
9520
10323
  DEFAULT_CELL_TIMEOUT_MS = 3e5;
@@ -9636,11 +10439,11 @@ var init_worker_registry = __esm({
9636
10439
 
9637
10440
  // src/tools/subagent-stats.ts
9638
10441
  import { appendFile as appendFile2, mkdir as mkdir10, readFile as readFile12 } from "fs/promises";
9639
- import { homedir as homedir11 } from "os";
9640
- import { dirname as dirname10, join as join20 } from "path";
10442
+ import { homedir as homedir12 } from "os";
10443
+ import { dirname as dirname10, join as join22 } from "path";
9641
10444
  function subagentStatsPath() {
9642
- const xdg = process.env.XDG_DATA_HOME || join20(homedir11(), ".local", "share");
9643
- return join20(xdg, "kimiflare", "subagents.jsonl");
10445
+ const xdg = process.env.XDG_DATA_HOME || join22(homedir12(), ".local", "share");
10446
+ return join22(xdg, "kimiflare", "subagents.jsonl");
9644
10447
  }
9645
10448
  async function recordSubagentEvent(event) {
9646
10449
  const path = subagentStatsPath();
@@ -9866,16 +10669,29 @@ var init_subagent = __esm({
9866
10669
  }
9867
10670
  const handle = workerRegistry.start(args.mission, model, ctx.signal);
9868
10671
  const startedAt2 = Date.now();
9869
- const record = (status, extra = {}) => void recordSubagentEvent({
9870
- kind: "subagent",
9871
- ts: Date.now(),
9872
- sessionId: ctx.sessionId ?? "unknown",
9873
- status,
9874
- durationMs: Date.now() - startedAt2,
9875
- costUsd: extra.costUsd ?? 0,
9876
- ...failureReason(extra.error) ? { reason: failureReason(extra.error) } : {},
9877
- ...extra.localChanges ? { localChanges: extra.localChanges } : {}
9878
- });
10672
+ const record = (status, extra = {}) => {
10673
+ void recordSubagentEvent({
10674
+ kind: "subagent",
10675
+ ts: Date.now(),
10676
+ sessionId: ctx.sessionId ?? "unknown",
10677
+ status,
10678
+ durationMs: Date.now() - startedAt2,
10679
+ costUsd: extra.costUsd ?? 0,
10680
+ ...failureReason(extra.error) ? { reason: failureReason(extra.error) } : {},
10681
+ ...extra.localChanges ? { localChanges: extra.localChanges } : {}
10682
+ });
10683
+ ctx.trace?.emit(
10684
+ "subagent",
10685
+ {
10686
+ worker_id: handle.id,
10687
+ mode: "hotcell",
10688
+ status,
10689
+ ...extra.costUsd !== void 0 ? { cost_usd: extra.costUsd } : {},
10690
+ duration_ms: Date.now() - startedAt2
10691
+ },
10692
+ ctx.turnId ?? null
10693
+ );
10694
+ };
9879
10695
  let result2;
9880
10696
  try {
9881
10697
  result2 = await runHotcellWorker({
@@ -10547,13 +11363,13 @@ var init_expand_artifact = __esm({
10547
11363
  // src/jobs/manager.ts
10548
11364
  import Database from "better-sqlite3";
10549
11365
  import { spawn as spawn3, execFileSync } from "child_process";
10550
- import { mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync3, readSync, existsSync as existsSync2, statSync as statSync3, closeSync, rmSync } from "fs";
10551
- import { homedir as homedir12 } from "os";
10552
- import { dirname as dirname11, join as join21 } from "path";
10553
- import { randomUUID as randomUUID3 } from "crypto";
11366
+ import { mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync4, readSync, existsSync as existsSync4, statSync as statSync3, closeSync, rmSync } from "fs";
11367
+ import { homedir as homedir13 } from "os";
11368
+ import { dirname as dirname11, join as join23 } from "path";
11369
+ import { randomUUID as randomUUID4 } from "crypto";
10554
11370
  function defaultJobDbPath() {
10555
- const root = process.env.XDG_STATE_HOME || join21(homedir12(), ".local", "state");
10556
- return process.env.AUTOPILOT_JOBS_DB || join21(root, "autopilot", "jobs.db");
11371
+ const root = process.env.XDG_STATE_HOME || join23(homedir13(), ".local", "state");
11372
+ return process.env.AUTOPILOT_JOBS_DB || join23(root, "autopilot", "jobs.db");
10557
11373
  }
10558
11374
  function isProcessGroupAlive(pgid) {
10559
11375
  try {
@@ -10571,7 +11387,7 @@ function waitForProcessStartTime(pid, jobId, exitPath) {
10571
11387
  let previous = null;
10572
11388
  const title = `autopilot-job-${jobId}`;
10573
11389
  while (Date.now() < deadline) {
10574
- if (existsSync2(exitPath)) break;
11390
+ if (existsSync4(exitPath)) break;
10575
11391
  const current = readProcessStartTime(pid);
10576
11392
  const hasUniqueTitle = process.platform !== "darwin" || current?.includes(title) === true;
10577
11393
  if (current && current === previous && hasUniqueTitle && !isZombie(pid)) return current;
@@ -10583,8 +11399,8 @@ function waitForProcessStartTime(pid, jobId, exitPath) {
10583
11399
  function readProcessStartTime(pid) {
10584
11400
  if (process.platform === "linux") {
10585
11401
  try {
10586
- const stat10 = readFileSync3(`/proc/${pid}/stat`, "utf8");
10587
- const fieldsAfterCommand = stat10.slice(stat10.lastIndexOf(")") + 1).trim().split(/\s+/);
11402
+ const stat11 = readFileSync4(`/proc/${pid}/stat`, "utf8");
11403
+ const fieldsAfterCommand = stat11.slice(stat11.lastIndexOf(")") + 1).trim().split(/\s+/);
10588
11404
  return fieldsAfterCommand[19] ? `linux:${fieldsAfterCommand[19]}` : null;
10589
11405
  } catch {
10590
11406
  return null;
@@ -10696,9 +11512,9 @@ child.on('close', (code, signal) => {
10696
11512
  db;
10697
11513
  jobsDir;
10698
11514
  constructor(dbPath = defaultJobDbPath()) {
10699
- mkdirSync3(dirname11(dbPath), { recursive: true, mode: 448 });
10700
- this.jobsDir = join21(dirname11(dbPath), "jobs");
10701
- mkdirSync3(this.jobsDir, { recursive: true, mode: 448 });
11515
+ mkdirSync5(dirname11(dbPath), { recursive: true, mode: 448 });
11516
+ this.jobsDir = join23(dirname11(dbPath), "jobs");
11517
+ mkdirSync5(this.jobsDir, { recursive: true, mode: 448 });
10702
11518
  this.db = new Database(dbPath);
10703
11519
  this.db.pragma("journal_mode = WAL");
10704
11520
  this.db.pragma("busy_timeout = 5000");
@@ -10747,14 +11563,14 @@ child.on('close', (code, signal) => {
10747
11563
  const existing = this.db.prepare("SELECT * FROM jobs WHERE cwd = ? AND idempotency_key = ?").get(options.cwd, options.idempotencyKey);
10748
11564
  if (existing) return toJobRecord(existing);
10749
11565
  }
10750
- const id = randomUUID3();
10751
- const jobDir = join21(this.jobsDir, id);
10752
- mkdirSync3(jobDir, { recursive: true, mode: 448 });
11566
+ const id = randomUUID4();
11567
+ const jobDir = join23(this.jobsDir, id);
11568
+ mkdirSync5(jobDir, { recursive: true, mode: 448 });
10753
11569
  const paths = {
10754
- stdoutPath: join21(jobDir, "stdout.log"),
10755
- stderrPath: join21(jobDir, "stderr.log"),
10756
- exitPath: join21(jobDir, "exit.json"),
10757
- pidPath: join21(jobDir, "pid")
11570
+ stdoutPath: join23(jobDir, "stdout.log"),
11571
+ stderrPath: join23(jobDir, "stderr.log"),
11572
+ exitPath: join23(jobDir, "exit.json"),
11573
+ pidPath: join23(jobDir, "pid")
10758
11574
  };
10759
11575
  const now2 = Date.now();
10760
11576
  try {
@@ -10807,7 +11623,7 @@ child.on('close', (code, signal) => {
10807
11623
  const row = this.getRow(id);
10808
11624
  if (!row) throw new Error(`Job not found: ${id}`);
10809
11625
  const path = stream === "stdout" ? row.stdout_path : row.stderr_path;
10810
- if (!existsSync2(path)) return { content: "", offset: 0, size: 0, truncated: false };
11626
+ if (!existsSync4(path)) return { content: "", offset: 0, size: 0, truncated: false };
10811
11627
  const size = statSync3(path).size;
10812
11628
  if (options.offset !== void 0) {
10813
11629
  const offset = Math.max(0, Math.min(size, Math.trunc(options.offset)));
@@ -10875,9 +11691,9 @@ child.on('close', (code, signal) => {
10875
11691
  reconcile() {
10876
11692
  const rows = this.db.prepare("SELECT * FROM jobs WHERE status IN ('starting', 'running', 'cancelling', 'unknown')").all();
10877
11693
  for (const row of rows) {
10878
- if (existsSync2(row.exit_path)) {
11694
+ if (existsSync4(row.exit_path)) {
10879
11695
  try {
10880
- const result2 = JSON.parse(readFileSync3(row.exit_path, "utf8"));
11696
+ const result2 = JSON.parse(readFileSync4(row.exit_path, "utf8"));
10881
11697
  const status = result2.timedOut ? "timed_out" : row.status === "cancelling" ? "cancelled" : result2.code === 0 ? "completed" : "failed";
10882
11698
  this.db.prepare("UPDATE jobs SET status = ?, exit_code = ?, finished_at = COALESCE(finished_at, ?) WHERE id = ?").run(status, result2.code, Date.now(), row.id);
10883
11699
  continue;
@@ -10885,8 +11701,8 @@ child.on('close', (code, signal) => {
10885
11701
  }
10886
11702
  }
10887
11703
  let pid = row.pid;
10888
- if (!pid && existsSync2(row.pid_path)) {
10889
- const parsed = Number(readFileSync3(row.pid_path, "utf8").trim());
11704
+ if (!pid && existsSync4(row.pid_path)) {
11705
+ const parsed = Number(readFileSync4(row.pid_path, "utf8").trim());
10890
11706
  if (Number.isInteger(parsed) && parsed > 0) pid = parsed;
10891
11707
  }
10892
11708
  if (pid && row.process_start_time) {
@@ -11032,13 +11848,13 @@ ${result2.content}`;
11032
11848
 
11033
11849
  // src/runs/store.ts
11034
11850
  import Database2 from "better-sqlite3";
11035
- import { createHash, randomUUID as randomUUID4 } from "crypto";
11036
- import { chmodSync, mkdirSync as mkdirSync4 } from "fs";
11037
- import { homedir as homedir13 } from "os";
11038
- import { dirname as dirname12, join as join22 } from "path";
11851
+ import { createHash as createHash2, randomUUID as randomUUID5 } from "crypto";
11852
+ import { chmodSync, mkdirSync as mkdirSync6 } from "fs";
11853
+ import { homedir as homedir14 } from "os";
11854
+ import { dirname as dirname12, join as join24 } from "path";
11039
11855
  function defaultRunsDbPath() {
11040
- const root = process.env.XDG_STATE_HOME || join22(homedir13(), ".local", "state");
11041
- return process.env.AUTOPILOT_RUNS_DB || join22(root, "autopilot", "runs.db");
11856
+ const root = process.env.XDG_STATE_HOME || join24(homedir14(), ".local", "state");
11857
+ return process.env.AUTOPILOT_RUNS_DB || join24(root, "autopilot", "runs.db");
11042
11858
  }
11043
11859
  function validateOptionalPositiveInteger(name, value, minimum = 1) {
11044
11860
  if (value !== null && (!Number.isSafeInteger(value) || value < minimum)) {
@@ -11054,7 +11870,7 @@ function stableJson(value) {
11054
11870
  }
11055
11871
  }
11056
11872
  function sha256(value) {
11057
- return createHash("sha256").update(value).digest("hex");
11873
+ return createHash2("sha256").update(value).digest("hex");
11058
11874
  }
11059
11875
  function rowToRun(row) {
11060
11876
  return {
@@ -11126,7 +11942,7 @@ var init_store = __esm({
11126
11942
  RunStore = class {
11127
11943
  db;
11128
11944
  constructor(dbPath = defaultRunsDbPath()) {
11129
- mkdirSync4(dirname12(dbPath), { recursive: true, mode: 448 });
11945
+ mkdirSync6(dirname12(dbPath), { recursive: true, mode: 448 });
11130
11946
  this.db = new Database2(dbPath);
11131
11947
  this.db.pragma("journal_mode = WAL");
11132
11948
  this.db.pragma("foreign_keys = ON");
@@ -11231,7 +12047,7 @@ var init_store = __esm({
11231
12047
  if (maxCostUsd !== null && (!Number.isFinite(maxCostUsd) || maxCostUsd < 0.01)) {
11232
12048
  throw new Error("maxCostUsd must be null or a finite number greater than or equal to 0.01");
11233
12049
  }
11234
- const id = input.id ?? input.worktree?.runId ?? randomUUID4();
12050
+ const id = input.id ?? input.worktree?.runId ?? randomUUID5();
11235
12051
  if (!id.trim()) throw new Error("id must not be empty");
11236
12052
  if (input.worktree && input.worktree.runId !== id) throw new Error("worktree runId must match run id");
11237
12053
  const now2 = Date.now();
@@ -11425,7 +12241,7 @@ var init_store = __esm({
11425
12241
  throw new Error("intervalMs must be an integer from 1000 through 604800000");
11426
12242
  }
11427
12243
  this.assertRunExists(input.runId);
11428
- const id = randomUUID4();
12244
+ const id = randomUUID5();
11429
12245
  const now2 = Date.now();
11430
12246
  this.db.prepare(`INSERT INTO run_timers
11431
12247
  (id, run_id, condition_type, job_id, wake_at, interval_ms, status, max_attempts, created_at)
@@ -11721,6 +12537,12 @@ function capContentForHook(s) {
11721
12537
  return `${cut}
11722
12538
  [\u2026truncated for hook payload]`;
11723
12539
  }
12540
+ function tracePermissionDecision(result2) {
12541
+ if (result2.decision === "deny") return "deny";
12542
+ if (result2.scope === "session") return "allow_session";
12543
+ if (result2.scope === "pattern") return "auto";
12544
+ return "allow";
12545
+ }
11724
12546
  function isDiffCommand(cmd) {
11725
12547
  const trimmed = cmd.trim();
11726
12548
  if (/^git\s+show(?:\s|$)/.test(trimmed)) return true;
@@ -11741,11 +12563,13 @@ function normalizeToolOutput(result2) {
11741
12563
  function truncateForError(s) {
11742
12564
  return s.length <= 200 ? s : `${s.slice(0, 200)}... [${s.length - 200} more chars]`;
11743
12565
  }
11744
- var ALL_TOOLS, RESEARCH_WORKER_TOOL_NAMES, SENSITIVE_RESEARCH_PATHS, HOOK_RESULT_CONTENT_CAP_BYTES, ToolExecutor;
12566
+ var ALL_TOOLS, RESEARCH_WORKER_TOOL_NAMES, SENSITIVE_RESEARCH_PATHS, HOOK_RESULT_CONTENT_CAP_BYTES, ToolExecutor, TRACE_ARGS_INLINE_MAX, ToolTrace;
11745
12567
  var init_executor = __esm({
11746
12568
  "src/tools/executor.ts"() {
11747
12569
  "use strict";
11748
12570
  init_tool_error();
12571
+ init_verification();
12572
+ init_git_delta();
11749
12573
  init_read();
11750
12574
  init_write();
11751
12575
  init_edit();
@@ -11825,17 +12649,25 @@ var init_executor = __esm({
11825
12649
  * every `run` call regardless of caller (standard agent loop,
11826
12650
  * code-mode sandbox, init turn, SDK, CLI print mode). */
11827
12651
  hooks;
12652
+ /** Default session trace recorder; `ctx.trace` (set by the agent loop)
12653
+ * takes precedence per call. */
12654
+ trace;
11828
12655
  constructor(tools = ALL_TOOLS, opts2) {
11829
12656
  this.tools = new Map(tools.map((t) => [t.name, t]));
11830
12657
  this.artifactStore = new ToolArtifactStore();
11831
12658
  this.tools.set("expand_artifact", makeExpandArtifactTool(this.artifactStore));
11832
12659
  this.hooks = opts2?.hooks ?? null;
12660
+ this.trace = opts2?.trace ?? null;
11833
12661
  }
11834
12662
  /** Swap or detach the hooks manager. Used by app startup so the
11835
12663
  * executor created before the manager exists can pick it up later. */
11836
12664
  setHooks(hooks) {
11837
12665
  this.hooks = hooks;
11838
12666
  }
12667
+ /** Swap or detach the default trace recorder (e.g. when the TUI starts a new session). */
12668
+ setTrace(trace) {
12669
+ this.trace = trace;
12670
+ }
11839
12671
  list() {
11840
12672
  return [...this.tools.values()];
11841
12673
  }
@@ -11852,8 +12684,17 @@ var init_executor = __esm({
11852
12684
  this.artifactStore.clear();
11853
12685
  }
11854
12686
  async run(call, askPermission, ctx, onFileChange) {
12687
+ const trace = ctx.trace ?? this.trace;
12688
+ if (!trace?.enabled) return this.runInner(call, askPermission, ctx, onFileChange, null);
12689
+ const tr = new ToolTrace(trace, ctx, call);
12690
+ const result2 = await this.runInner(call, askPermission, ctx, onFileChange, tr);
12691
+ tr.finish(result2);
12692
+ return result2;
12693
+ }
12694
+ async runInner(call, askPermission, ctx, onFileChange, tr) {
11855
12695
  const tool = this.tools.get(call.name) ?? (call.name === "spawn_worker" ? this.tools.get("subagent") : void 0);
11856
12696
  if (!tool) {
12697
+ tr?.call(null, false);
11857
12698
  return {
11858
12699
  tool_call_id: call.id,
11859
12700
  name: call.name,
@@ -11867,6 +12708,7 @@ var init_executor = __esm({
11867
12708
  try {
11868
12709
  args = call.arguments.trim() ? JSON.parse(call.arguments) : {};
11869
12710
  } catch (e) {
12711
+ tr?.call(null, tool.needsPermission);
11870
12712
  return {
11871
12713
  tool_call_id: call.id,
11872
12714
  name: call.name,
@@ -11877,6 +12719,7 @@ var init_executor = __esm({
11877
12719
  suggestion: "reformulate the tool call with valid JSON arguments"
11878
12720
  };
11879
12721
  }
12722
+ tr?.call(args, tool.needsPermission);
11880
12723
  if (this.hooks?.hasEnabledHooks("PreToolUse")) {
11881
12724
  const preOutcome = await this.hooks.fire(
11882
12725
  "PreToolUse",
@@ -11908,8 +12751,10 @@ var init_executor = __esm({
11908
12751
  if (tool.needsPermission) {
11909
12752
  const sessionKey = this.permissionKey(tool, args);
11910
12753
  if (!this.sessionAllowed.has(sessionKey)) {
12754
+ const askedAt = performance.now();
11911
12755
  const raw = await askPermission({ tool, args, sessionKey });
11912
12756
  const result2 = toPermissionResult(raw);
12757
+ tr?.permission(tracePermissionDecision(result2), Math.round(performance.now() - askedAt), result2.feedback);
11913
12758
  if (result2.decision === "deny") {
11914
12759
  const denied = {
11915
12760
  tool_call_id: call.id,
@@ -11924,7 +12769,11 @@ var init_executor = __esm({
11924
12769
  return denied;
11925
12770
  }
11926
12771
  if (result2.scope === "session") this.sessionAllowed.add(sessionKey);
12772
+ } else {
12773
+ tr?.permission("auto", 0);
11927
12774
  }
12775
+ } else {
12776
+ tr?.permission("auto", 0);
11928
12777
  }
11929
12778
  let runStore = null;
11930
12779
  let intentRecorded = false;
@@ -11938,6 +12787,7 @@ var init_executor = __esm({
11938
12787
  const result2 = await tool.run(args, ctx);
11939
12788
  toolCompleted = true;
11940
12789
  const normalized = normalizeToolOutput(result2);
12790
+ tr?.raw(normalized);
11941
12791
  if (runStore && ctx.runId) {
11942
12792
  runStore.recordToolResult(ctx.runId, call.id, call.name, { ok: true, content: normalized.content });
11943
12793
  }
@@ -11991,6 +12841,7 @@ var init_executor = __esm({
11991
12841
  if (e instanceof RunToolBudgetExceededError) ctx.onRunBudgetExceeded?.(e.reason);
11992
12842
  const err = wrapAsToolError(e);
11993
12843
  const msg = `Error running ${call.name}: ${err.message}`;
12844
+ tr?.raw({ content: msg, rawBytes: Buffer.byteLength(msg, "utf8"), reducedBytes: Buffer.byteLength(msg, "utf8") });
11994
12845
  const failure = {
11995
12846
  tool_call_id: call.id,
11996
12847
  name: call.name,
@@ -12051,6 +12902,121 @@ var init_executor = __esm({
12051
12902
  return tool.name;
12052
12903
  }
12053
12904
  };
12905
+ TRACE_ARGS_INLINE_MAX = 2 * 1024;
12906
+ ToolTrace = class {
12907
+ constructor(trace, ctx, invocation) {
12908
+ this.trace = trace;
12909
+ this.invocation = invocation;
12910
+ this.turnId = ctx.turnId ?? null;
12911
+ this.parentId = ctx.codeModeParentToolCallId;
12912
+ this.sessionId = ctx.sessionId;
12913
+ }
12914
+ trace;
12915
+ invocation;
12916
+ startedAt = performance.now();
12917
+ turnId;
12918
+ parentId;
12919
+ sessionId;
12920
+ rawOutput = null;
12921
+ argsBlob = "";
12922
+ args = null;
12923
+ /** The call was parsed (args null when the arguments were not valid JSON). */
12924
+ call(args, needsPermission) {
12925
+ this.args = args;
12926
+ this.argsBlob = this.trace.blob(this.invocation.arguments) ?? "";
12927
+ if (this.parentId) return;
12928
+ const payload = {
12929
+ tool_call_id: this.invocation.id,
12930
+ name: this.invocation.name,
12931
+ args_blob: this.argsBlob,
12932
+ ...args && this.invocation.arguments.length <= TRACE_ARGS_INLINE_MAX ? { args_inline: args } : {},
12933
+ needs_permission: needsPermission,
12934
+ code_mode_redirected: false
12935
+ };
12936
+ this.trace.emit("tool_call", payload, this.turnId);
12937
+ }
12938
+ permission(decision, latencyMs, feedback) {
12939
+ this.trace.emit(
12940
+ "permission_decision",
12941
+ {
12942
+ tool_call_id: this.invocation.id,
12943
+ decision,
12944
+ ...feedback ? { feedback_text: feedback } : {},
12945
+ latency_ms: latencyMs
12946
+ },
12947
+ this.turnId
12948
+ );
12949
+ }
12950
+ /** The tool's output before reduction. */
12951
+ raw(output) {
12952
+ this.rawOutput = output;
12953
+ }
12954
+ finish(result2) {
12955
+ const durationMs = Math.round(performance.now() - this.startedAt);
12956
+ const rawContent = this.rawOutput?.content ?? result2.content;
12957
+ const rawBlob = this.trace.blob(rawContent) ?? "";
12958
+ this.outcomes(result2);
12959
+ if (this.parentId) {
12960
+ this.trace.emit(
12961
+ "code_mode_call",
12962
+ {
12963
+ parent_tool_call_id: this.parentId,
12964
+ name: this.invocation.name,
12965
+ args_blob: this.argsBlob,
12966
+ raw_blob: rawBlob,
12967
+ ok: result2.ok,
12968
+ duration_ms: durationMs
12969
+ },
12970
+ this.turnId
12971
+ );
12972
+ return;
12973
+ }
12974
+ const meta = this.rawOutput?.meta;
12975
+ const payload = {
12976
+ tool_call_id: this.invocation.id,
12977
+ name: this.invocation.name,
12978
+ ok: result2.ok,
12979
+ ...result2.errorCode ? { error_code: result2.errorCode } : {},
12980
+ raw_blob: rawBlob,
12981
+ raw_bytes: Buffer.byteLength(rawContent, "utf8"),
12982
+ reduced_blob: rawContent === result2.content ? rawBlob : this.trace.blob(result2.content) ?? "",
12983
+ reduced_bytes: Buffer.byteLength(result2.content, "utf8"),
12984
+ ...result2.artifactId ? { artifact_id: result2.artifactId } : {},
12985
+ ...meta && meta.exitCode !== void 0 && meta.exitCode !== null ? { exit_code: meta.exitCode } : {},
12986
+ ...meta?.timedOut !== void 0 ? { timed_out: meta.timedOut } : {},
12987
+ duration_ms: durationMs
12988
+ };
12989
+ this.trace.emit("tool_result", payload, this.turnId);
12990
+ }
12991
+ /** Outcome signals a bash call carries: a verification exit code, or a git revert of the last delta. */
12992
+ outcomes(result2) {
12993
+ if (this.invocation.name !== "bash") return;
12994
+ const command = this.args?.command;
12995
+ if (typeof command !== "string" || !command.trim()) return;
12996
+ const exitCode = this.rawOutput?.meta?.exitCode;
12997
+ const category = classifyVerification(command);
12998
+ if (category && typeof exitCode === "number") {
12999
+ this.trace.emit(
13000
+ "outcome",
13001
+ {
13002
+ kind: "verification",
13003
+ turn_id: this.turnId ?? "",
13004
+ tool_call_id: this.invocation.id,
13005
+ command,
13006
+ category,
13007
+ exit_code: exitCode
13008
+ },
13009
+ this.turnId
13010
+ );
13011
+ }
13012
+ if (result2.ok && this.sessionId) {
13013
+ const reverted = detectGitRevert(this.sessionId, command);
13014
+ if (reverted) {
13015
+ this.trace.emit("outcome", { kind: "reverted", turn_id: reverted.turnId, how: "git_checkout" }, this.turnId);
13016
+ }
13017
+ }
13018
+ }
13019
+ };
12054
13020
  }
12055
13021
  });
12056
13022
 
@@ -12068,11 +13034,11 @@ __export(sessions_exports, {
12068
13034
  sessionsDir: () => sessionsDir
12069
13035
  });
12070
13036
  import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir11, readdir as readdir4, stat as stat5 } from "fs/promises";
12071
- import { homedir as homedir14 } from "os";
12072
- import { join as join23 } from "path";
13037
+ import { homedir as homedir15 } from "os";
13038
+ import { join as join25 } from "path";
12073
13039
  function sessionsDir() {
12074
- const xdg = process.env.XDG_DATA_HOME || join23(homedir14(), ".local", "share");
12075
- return join23(xdg, "kimiflare", "sessions");
13040
+ const xdg = process.env.XDG_DATA_HOME || join25(homedir15(), ".local", "share");
13041
+ return join25(xdg, "kimiflare", "sessions");
12076
13042
  }
12077
13043
  function sanitize(text) {
12078
13044
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -12092,7 +13058,7 @@ function makeSessionId(firstPrompt) {
12092
13058
  async function saveSession(file) {
12093
13059
  const dir = sessionsDir();
12094
13060
  await mkdir11(dir, { recursive: true });
12095
- const path = join23(dir, `${file.id}.json`);
13061
+ const path = join25(dir, `${file.id}.json`);
12096
13062
  await writeFile11(path, JSON.stringify(file, null, 2), "utf8");
12097
13063
  return path;
12098
13064
  }
@@ -12112,7 +13078,7 @@ async function listSessions(limit = 30, cwd) {
12112
13078
  const summaries = [];
12113
13079
  for (const name of entries) {
12114
13080
  if (!name.endsWith(".json")) continue;
12115
- const path = join23(dir, name);
13081
+ const path = join25(dir, name);
12116
13082
  try {
12117
13083
  const [s, raw] = await Promise.all([stat5(path), readFile13(path, "utf8")]);
12118
13084
  const parsed = JSON.parse(raw);
@@ -12137,7 +13103,9 @@ async function listSessions(limit = 30, cwd) {
12137
13103
  }
12138
13104
  async function loadSession(filePath) {
12139
13105
  const raw = await readFile13(filePath, "utf8");
12140
- return JSON.parse(raw);
13106
+ const file = JSON.parse(raw);
13107
+ if (Array.isArray(file.messages)) repairDanglingToolCalls(file.messages);
13108
+ return file;
12141
13109
  }
12142
13110
  async function addCheckpoint(filePath, checkpoint) {
12143
13111
  const file = await loadSession(filePath);
@@ -12166,6 +13134,7 @@ var INTENT_PREFIX_MAP;
12166
13134
  var init_sessions = __esm({
12167
13135
  "src/sessions.ts"() {
12168
13136
  "use strict";
13137
+ init_messages();
12169
13138
  init_storage_limits();
12170
13139
  INTENT_PREFIX_MAP = {
12171
13140
  diagnose: "Bug:",
@@ -12223,7 +13192,7 @@ var init_image = __esm({
12223
13192
 
12224
13193
  // src/permissions-evaluator.ts
12225
13194
  import { resolve as resolve5, relative as relative4 } from "path";
12226
- import { homedir as homedir15 } from "os";
13195
+ import { homedir as homedir16 } from "os";
12227
13196
  function evaluatePermissionRules(req, rules) {
12228
13197
  const toolRules = rules[req.tool];
12229
13198
  if (!toolRules) return "ask";
@@ -12239,7 +13208,7 @@ function evaluatePermissionRules(req, rules) {
12239
13208
  }
12240
13209
  function expandHome(path) {
12241
13210
  if (path.startsWith("~/")) {
12242
- return resolve5(homedir15(), path.slice(2));
13211
+ return resolve5(homedir16(), path.slice(2));
12243
13212
  }
12244
13213
  return path;
12245
13214
  }
@@ -12265,7 +13234,7 @@ function extractTarget(tool, args, cwd) {
12265
13234
  function matchPattern(target, pattern, cwd) {
12266
13235
  let expandedPattern = pattern;
12267
13236
  if (pattern.startsWith("~/")) {
12268
- expandedPattern = resolve5(homedir15(), pattern.slice(2));
13237
+ expandedPattern = resolve5(homedir16(), pattern.slice(2));
12269
13238
  }
12270
13239
  if (target.startsWith(expandedPattern)) {
12271
13240
  return true;
@@ -12342,9 +13311,237 @@ var init_classify = __esm({
12342
13311
  }
12343
13312
  });
12344
13313
 
13314
+ // src/trace/retention.ts
13315
+ var retention_exports = {};
13316
+ __export(retention_exports, {
13317
+ listTraceSessions: () => listTraceSessions,
13318
+ pruneTraces: () => pruneTraces
13319
+ });
13320
+ import { readdir as readdir5, rm as rm3, stat as stat6 } from "fs/promises";
13321
+ import { join as join26 } from "path";
13322
+ async function dirSize(path) {
13323
+ let total = 0;
13324
+ const entries = await readdir5(path, { withFileTypes: true });
13325
+ for (const entry of entries) {
13326
+ const p = join26(path, entry.name);
13327
+ if (entry.isDirectory()) total += await dirSize(p);
13328
+ else if (entry.isFile()) {
13329
+ try {
13330
+ total += (await stat6(p)).size;
13331
+ } catch {
13332
+ }
13333
+ }
13334
+ }
13335
+ return total;
13336
+ }
13337
+ async function listTraceSessions(root) {
13338
+ const base = traceRoot(root);
13339
+ let names;
13340
+ try {
13341
+ names = await readdir5(base);
13342
+ } catch {
13343
+ return [];
13344
+ }
13345
+ const sessions = [];
13346
+ for (const name of names) {
13347
+ const path = join26(base, name);
13348
+ try {
13349
+ const s = await stat6(path);
13350
+ if (!s.isDirectory()) continue;
13351
+ let mtime = s.mtime;
13352
+ try {
13353
+ mtime = (await stat6(join26(path, TRACE_FILE_NAME))).mtime;
13354
+ } catch {
13355
+ }
13356
+ sessions.push({ sessionId: name, path, mtime, bytes: await dirSize(path) });
13357
+ } catch {
13358
+ }
13359
+ }
13360
+ sessions.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
13361
+ return sessions;
13362
+ }
13363
+ async function pruneTraces(opts2 = {}) {
13364
+ const maxAgeDays = opts2.maxAgeDays ?? RETENTION.traceMaxAgeDays;
13365
+ const maxTotalBytes = opts2.maxTotalBytes ?? RETENTION.traceMaxTotalBytes;
13366
+ const maxSessionBytes = opts2.maxSessionBytes ?? RETENTION.traceMaxSessionBytes;
13367
+ const now2 = opts2.now ?? Date.now();
13368
+ const cutoff = now2 - maxAgeDays * 24 * 60 * 60 * 1e3;
13369
+ const removed = [];
13370
+ let sessions;
13371
+ try {
13372
+ sessions = await listTraceSessions(opts2.root);
13373
+ } catch {
13374
+ return { removed, keptBytes: 0 };
13375
+ }
13376
+ const remove = async (s) => {
13377
+ try {
13378
+ await rm3(s.path, { recursive: true, force: true });
13379
+ removed.push(s.sessionId);
13380
+ } catch {
13381
+ }
13382
+ };
13383
+ const kept = [];
13384
+ for (const s of sessions) {
13385
+ if (s.mtime.getTime() < cutoff || s.bytes > maxSessionBytes) await remove(s);
13386
+ else kept.push(s);
13387
+ }
13388
+ let total = kept.reduce((sum, s) => sum + s.bytes, 0);
13389
+ while (total > maxTotalBytes && kept.length > 0) {
13390
+ const oldest = kept.pop();
13391
+ await remove(oldest);
13392
+ total -= oldest.bytes;
13393
+ }
13394
+ return { removed, keptBytes: total };
13395
+ }
13396
+ var init_retention = __esm({
13397
+ "src/trace/retention.ts"() {
13398
+ "use strict";
13399
+ init_storage_limits();
13400
+ init_paths();
13401
+ }
13402
+ });
13403
+
13404
+ // src/trace/session-start.ts
13405
+ import { execFile as execFile5 } from "child_process";
13406
+ import { existsSync as existsSync5 } from "fs";
13407
+ import { dirname as dirname13, join as join27 } from "path";
13408
+ import { fileURLToPath as fileURLToPath4 } from "url";
13409
+ import { promisify as promisify5 } from "util";
13410
+ async function git2(args, cwd) {
13411
+ try {
13412
+ const { stdout } = await execFileAsync4("git", args, { cwd, timeout: 3e3, windowsHide: true });
13413
+ const out = stdout.trim();
13414
+ return out || null;
13415
+ } catch {
13416
+ return null;
13417
+ }
13418
+ }
13419
+ function harnessCheckoutDir() {
13420
+ const here = dirname13(fileURLToPath4(import.meta.url));
13421
+ for (const candidate of [join27(here, "..", ".."), join27(here, "..")]) {
13422
+ if (existsSync5(join27(candidate, "package.json")) && existsSync5(join27(candidate, ".git"))) return candidate;
13423
+ }
13424
+ return null;
13425
+ }
13426
+ async function resolveHarnessCommit() {
13427
+ const dir = harnessCheckoutDir();
13428
+ if (!dir) return null;
13429
+ return git2(["rev-parse", "HEAD"], dir);
13430
+ }
13431
+ async function collectSessionStart(opts2, blob) {
13432
+ const toolsJson = JSON.stringify(opts2.tools);
13433
+ const [harnessCommit, repoRemote, repoHead] = await Promise.all([
13434
+ resolveHarnessCommit(),
13435
+ git2(["remote", "get-url", "origin"], opts2.cwd),
13436
+ git2(["rev-parse", "HEAD"], opts2.cwd)
13437
+ ]);
13438
+ return {
13439
+ autopilot_version: getAppVersion(),
13440
+ harness_commit: harnessCommit,
13441
+ cwd: opts2.cwd,
13442
+ repo_remote: repoRemote,
13443
+ repo_head: repoHead,
13444
+ model: opts2.model,
13445
+ mode: opts2.mode,
13446
+ config: redactConfig(opts2.config ?? {}),
13447
+ tool_profile: opts2.toolProfile ?? "full",
13448
+ tool_schemas_sha: sha256Hex(toolsJson),
13449
+ tools_blob: blob(toolsJson) ?? "",
13450
+ system_prompt_sha: sha256Hex(opts2.systemPrompt)
13451
+ };
13452
+ }
13453
+ async function emitSessionStart(recorder, opts2) {
13454
+ if (!recorder.enabled) return;
13455
+ const payload = await collectSessionStart(opts2, (content) => recorder.blob(content));
13456
+ recorder.emit("session_start", payload, null);
13457
+ }
13458
+ var execFileAsync4;
13459
+ var init_session_start = __esm({
13460
+ "src/trace/session-start.ts"() {
13461
+ "use strict";
13462
+ init_version();
13463
+ init_blobs();
13464
+ init_redact();
13465
+ execFileAsync4 = promisify5(execFile5);
13466
+ }
13467
+ });
13468
+
13469
+ // src/trace/compaction.ts
13470
+ function traceCompiledCompaction(trace, result2, turnId = null) {
13471
+ if (!trace?.enabled || result2.metrics.rawTurnsRemoved === 0) return;
13472
+ trace.emit(
13473
+ "compaction",
13474
+ {
13475
+ method: "compiled",
13476
+ tokens_before: result2.metrics.estimatedTokensBefore,
13477
+ tokens_after: result2.metrics.estimatedTokensAfter,
13478
+ turns_removed: result2.metrics.rawTurnsRemoved,
13479
+ artifacts_archived: result2.metrics.archivedArtifacts
13480
+ },
13481
+ turnId
13482
+ );
13483
+ }
13484
+ function traceLlmCompaction(trace, before, result2, turnId = null) {
13485
+ if (!trace?.enabled || result2.replacedCount === 0) return;
13486
+ trace.emit(
13487
+ "compaction",
13488
+ {
13489
+ method: "llm",
13490
+ tokens_before: estimatePromptTokens(before),
13491
+ tokens_after: estimatePromptTokens(result2.newMessages),
13492
+ turns_removed: result2.replacedCount,
13493
+ ...result2.summary ? { summary_blob: trace.blob(result2.summary) ?? "" } : {}
13494
+ },
13495
+ turnId
13496
+ );
13497
+ }
13498
+ var init_compaction = __esm({
13499
+ "src/trace/compaction.ts"() {
13500
+ "use strict";
13501
+ init_artifact_compaction();
13502
+ }
13503
+ });
13504
+
13505
+ // src/trace/next-message.ts
13506
+ function classifyNextMessage(text) {
13507
+ const trimmed = text.trim();
13508
+ if (!trimmed) return "other";
13509
+ if (trimmed.startsWith("/")) return "other";
13510
+ if (CORRECTION.test(trimmed)) return "correction";
13511
+ if (CONTINUE.test(trimmed)) return "continue";
13512
+ if (trimmed.endsWith("?")) return "question";
13513
+ return "new_task";
13514
+ }
13515
+ var CORRECTION, CONTINUE;
13516
+ var init_next_message = __esm({
13517
+ "src/trace/next-message.ts"() {
13518
+ "use strict";
13519
+ CORRECTION = /^(no\b|nope\b|wrong\b|that's not\b|that is not\b|thats not\b|not what\b|undo\b|revert\b|stop\b|don't\b|dont\b)/i;
13520
+ CONTINUE = /^(go on\b|continue\b|yes\b|yep\b|yeah\b|ok\b|okay\b|do it\b|proceed\b|sure\b|sounds good\b|lgtm\b|looks good\b|next\b)/i;
13521
+ }
13522
+ });
13523
+
13524
+ // src/trace/index.ts
13525
+ var init_trace = __esm({
13526
+ "src/trace/index.ts"() {
13527
+ "use strict";
13528
+ init_types();
13529
+ init_recorder();
13530
+ init_blobs();
13531
+ init_redact();
13532
+ init_paths();
13533
+ init_retention();
13534
+ init_session_start();
13535
+ init_compaction();
13536
+ init_verification();
13537
+ init_next_message();
13538
+ init_git_delta();
13539
+ }
13540
+ });
13541
+
12345
13542
  // src/hooks/types.ts
12346
13543
  var HOOK_EVENTS;
12347
- var init_types = __esm({
13544
+ var init_types2 = __esm({
12348
13545
  "src/hooks/types.ts"() {
12349
13546
  "use strict";
12350
13547
  HOOK_EVENTS = [
@@ -12358,21 +13555,21 @@ var init_types = __esm({
12358
13555
  });
12359
13556
 
12360
13557
  // src/hooks/settings.ts
12361
- import { existsSync as existsSync3, readFileSync as readFileSync4, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
12362
- import { homedir as homedir16 } from "os";
12363
- import { join as join24, dirname as dirname13 } from "path";
12364
- import { createHash as createHash2 } from "crypto";
13558
+ import { existsSync as existsSync6, readFileSync as readFileSync5, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
13559
+ import { homedir as homedir17 } from "os";
13560
+ import { join as join28, dirname as dirname14 } from "path";
13561
+ import { createHash as createHash3 } from "crypto";
12365
13562
  function globalSettingsPath() {
12366
- const xdg = process.env.XDG_CONFIG_HOME || join24(homedir16(), ".config");
12367
- return join24(xdg, "kimiflare", "settings.json");
13563
+ const xdg = process.env.XDG_CONFIG_HOME || join28(homedir17(), ".config");
13564
+ return join28(xdg, "kimiflare", "settings.json");
12368
13565
  }
12369
13566
  function projectSettingsPath(cwd) {
12370
- return join24(cwd, ".kimiflare", "settings.json");
13567
+ return join28(cwd, ".kimiflare", "settings.json");
12371
13568
  }
12372
13569
  function readSettingsFile(path) {
12373
- if (!existsSync3(path)) return null;
13570
+ if (!existsSync6(path)) return null;
12374
13571
  try {
12375
- const raw = readFileSync4(path, "utf8");
13572
+ const raw = readFileSync5(path, "utf8");
12376
13573
  const parsed = JSON.parse(raw);
12377
13574
  return parsed && typeof parsed === "object" ? parsed : null;
12378
13575
  } catch {
@@ -12380,7 +13577,7 @@ function readSettingsFile(path) {
12380
13577
  }
12381
13578
  }
12382
13579
  function deriveHookId(event, command) {
12383
- const h = createHash2("sha256").update(`${event}\0${command}`).digest("hex");
13580
+ const h = createHash3("sha256").update(`${event}\0${command}`).digest("hex");
12384
13581
  return h.slice(0, 8);
12385
13582
  }
12386
13583
  function normalizeHook(event, raw, source) {
@@ -12443,8 +13640,8 @@ function appendHook(scope, cwd, event, hook) {
12443
13640
  return rest;
12444
13641
  });
12445
13642
  existing.hooks = hooks;
12446
- mkdirSync5(dirname13(path), { recursive: true });
12447
- writeFileSync3(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
13643
+ mkdirSync7(dirname14(path), { recursive: true });
13644
+ writeFileSync4(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
12448
13645
  return path;
12449
13646
  }
12450
13647
  function setHookEnabled(cwd, id, enabled2) {
@@ -12469,8 +13666,8 @@ function setHookEnabled(cwd, id, enabled2) {
12469
13666
  }
12470
13667
  }
12471
13668
  if (changed) {
12472
- mkdirSync5(dirname13(path), { recursive: true });
12473
- writeFileSync3(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
13669
+ mkdirSync7(dirname14(path), { recursive: true });
13670
+ writeFileSync4(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
12474
13671
  return path;
12475
13672
  }
12476
13673
  void scope;
@@ -12480,7 +13677,7 @@ function setHookEnabled(cwd, id, enabled2) {
12480
13677
  var init_settings = __esm({
12481
13678
  "src/hooks/settings.ts"() {
12482
13679
  "use strict";
12483
- init_types();
13680
+ init_types2();
12484
13681
  }
12485
13682
  });
12486
13683
 
@@ -12698,11 +13895,11 @@ __export(cost_verify_exports, {
12698
13895
  verifyTurn: () => verifyTurn
12699
13896
  });
12700
13897
  import { readFile as readFile16 } from "fs/promises";
12701
- import { homedir as homedir17 } from "os";
12702
- import { join as join25 } from "path";
13898
+ import { homedir as homedir18 } from "os";
13899
+ import { join as join29 } from "path";
12703
13900
  function usagePath2() {
12704
- const xdg = process.env.XDG_DATA_HOME || join25(homedir17(), ".local", "share");
12705
- return join25(xdg, "kimiflare", "usage.json");
13901
+ const xdg = process.env.XDG_DATA_HOME || join29(homedir18(), ".local", "share");
13902
+ return join29(xdg, "kimiflare", "usage.json");
12706
13903
  }
12707
13904
  async function loadSessions() {
12708
13905
  try {
@@ -12795,7 +13992,7 @@ var init_cost_verify = __esm({
12795
13992
 
12796
13993
  // src/cost-attribution/types.ts
12797
13994
  var ALL_CATEGORIES;
12798
- var init_types2 = __esm({
13995
+ var init_types3 = __esm({
12799
13996
  "src/cost-attribution/types.ts"() {
12800
13997
  "use strict";
12801
13998
  ALL_CATEGORIES = [
@@ -12874,7 +14071,7 @@ function buildReport(opts2) {
12874
14071
  var init_report = __esm({
12875
14072
  "src/cost-attribution/report.ts"() {
12876
14073
  "use strict";
12877
- init_types2();
14074
+ init_types3();
12878
14075
  }
12879
14076
  });
12880
14077
 
@@ -13238,11 +14435,11 @@ var init_heuristic = __esm({
13238
14435
 
13239
14436
  // src/cost-attribution/classify-from-session.ts
13240
14437
  import { readFile as readFile17 } from "fs/promises";
13241
- import { join as join26 } from "path";
13242
- import { homedir as homedir18 } from "os";
14438
+ import { join as join30 } from "path";
14439
+ import { homedir as homedir19 } from "os";
13243
14440
  function sessionsDir2() {
13244
- const xdg = process.env.XDG_DATA_HOME || join26(homedir18(), ".local", "share");
13245
- return join26(xdg, "kimiflare", "sessions");
14441
+ const xdg = process.env.XDG_DATA_HOME || join30(homedir19(), ".local", "share");
14442
+ return join30(xdg, "kimiflare", "sessions");
13246
14443
  }
13247
14444
  function parseToolCalls(calls) {
13248
14445
  return calls.map((c) => {
@@ -13256,7 +14453,7 @@ function parseToolCalls(calls) {
13256
14453
  }
13257
14454
  async function classifyFromSessionFile(sessionId) {
13258
14455
  try {
13259
- const raw = await readFile17(join26(sessionsDir2(), `${sessionId}.json`), "utf8");
14456
+ const raw = await readFile17(join30(sessionsDir2(), `${sessionId}.json`), "utf8");
13260
14457
  const session = JSON.parse(raw);
13261
14458
  const messages = session.messages ?? [];
13262
14459
  const turns = [];
@@ -13290,14 +14487,14 @@ __export(cli_exports, {
13290
14487
  runCostCommand: () => runCostCommand
13291
14488
  });
13292
14489
  import { readFile as readFile18 } from "fs/promises";
13293
- import { join as join27 } from "path";
13294
- import { homedir as homedir19 } from "os";
14490
+ import { join as join31 } from "path";
14491
+ import { homedir as homedir20 } from "os";
13295
14492
  function usageDir2() {
13296
- const xdg = process.env.XDG_DATA_HOME || join27(homedir19(), ".local", "share");
13297
- return join27(xdg, "kimiflare");
14493
+ const xdg = process.env.XDG_DATA_HOME || join31(homedir20(), ".local", "share");
14494
+ return join31(xdg, "kimiflare");
13298
14495
  }
13299
14496
  function usagePath3() {
13300
- return join27(usageDir2(), "usage.json");
14497
+ return join31(usageDir2(), "usage.json");
13301
14498
  }
13302
14499
  function today2() {
13303
14500
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -13399,7 +14596,7 @@ __export(aster_workspaces_exports, {
13399
14596
  findAsterWorkspace: () => findAsterWorkspace,
13400
14597
  loadAsterServerConfig: () => loadAsterServerConfig
13401
14598
  });
13402
- import { readFile as readFile19, realpath as realpath3, stat as stat6 } from "fs/promises";
14599
+ import { readFile as readFile19, realpath as realpath3, stat as stat7 } from "fs/promises";
13403
14600
  import { isAbsolute as isAbsolute3, relative as relative5, sep as sep3 } from "path";
13404
14601
  async function loadAsterServerConfig(configPath3 = process.env.AUTOPILOT_ASTER_CONFIG || "/etc/autopilot/aster.json") {
13405
14602
  let raw;
@@ -13441,7 +14638,7 @@ async function loadAsterServerConfig(configPath3 = process.env.AUTOPILOT_ASTER_C
13441
14638
  let rootPath;
13442
14639
  try {
13443
14640
  rootPath = await realpath3(workspace.rootPath);
13444
- if (!(await stat6(rootPath)).isDirectory()) throw new Error("not a directory");
14641
+ if (!(await stat7(rootPath)).isDirectory()) throw new Error("not a directory");
13445
14642
  } catch {
13446
14643
  throw new AsterConfigError();
13447
14644
  }
@@ -13481,16 +14678,16 @@ __export(aster_store_exports, {
13481
14678
  defaultAsterDbPath: () => defaultAsterDbPath
13482
14679
  });
13483
14680
  import Database3 from "better-sqlite3";
13484
- import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID5 } from "crypto";
13485
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync6 } from "fs";
13486
- import { homedir as homedir20 } from "os";
13487
- import { dirname as dirname14, join as join28 } from "path";
14681
+ import { createHash as createHash4, randomBytes as randomBytes2, randomUUID as randomUUID6 } from "crypto";
14682
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync8 } from "fs";
14683
+ import { homedir as homedir21 } from "os";
14684
+ import { dirname as dirname15, join as join32 } from "path";
13488
14685
  function defaultAsterDbPath() {
13489
- const root = process.env.XDG_STATE_HOME || join28(homedir20(), ".local", "state");
13490
- return process.env.AUTOPILOT_ASTER_DB || join28(root, "autopilot", "aster.db");
14686
+ const root = process.env.XDG_STATE_HOME || join32(homedir21(), ".local", "state");
14687
+ return process.env.AUTOPILOT_ASTER_DB || join32(root, "autopilot", "aster.db");
13491
14688
  }
13492
14689
  function hashToken(token) {
13493
- return createHash3("sha256").update(token).digest("hex");
14690
+ return createHash4("sha256").update(token).digest("hex");
13494
14691
  }
13495
14692
  function cellMappingFromRow(row) {
13496
14693
  return {
@@ -13554,9 +14751,9 @@ var init_aster_store = __esm({
13554
14751
  AsterStore = class {
13555
14752
  db;
13556
14753
  constructor(dbPath = defaultAsterDbPath()) {
13557
- mkdirSync6(dirname14(dbPath), { recursive: true, mode: 448 });
14754
+ mkdirSync8(dirname15(dbPath), { recursive: true, mode: 448 });
13558
14755
  try {
13559
- chmodSync2(dirname14(dbPath), 448);
14756
+ chmodSync2(dirname15(dbPath), 448);
13560
14757
  } catch {
13561
14758
  }
13562
14759
  this.db = new Database3(dbPath);
@@ -13667,8 +14864,8 @@ var init_aster_store = __esm({
13667
14864
  this.db.close();
13668
14865
  }
13669
14866
  reserveCellConversation(input) {
13670
- const keyHash = createHash3("sha256").update(input.idempotencyKey).digest("hex");
13671
- const fingerprint = createHash3("sha256").update(JSON.stringify([input.workspaceId, input.model])).digest("hex");
14867
+ const keyHash = createHash4("sha256").update(input.idempotencyKey).digest("hex");
14868
+ const fingerprint = createHash4("sha256").update(JSON.stringify([input.workspaceId, input.model])).digest("hex");
13672
14869
  const reserve = this.db.transaction(() => {
13673
14870
  const existing = this.db.prepare("SELECT * FROM aster_cell_mappings WHERE credential_id = ? AND create_key_hash = ?").get(input.credentialId, keyHash);
13674
14871
  if (existing) {
@@ -13719,7 +14916,7 @@ var init_aster_store = __esm({
13719
14916
  const scopes = [...new Set(input.scopes)];
13720
14917
  if (scopes.length === 0 || scopes.some((scope) => !VALID_SCOPES.has(scope))) throw new Error("credential scopes are invalid");
13721
14918
  if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= Date.now()) throw new Error("credential expiry must be in the future");
13722
- const id = randomUUID5();
14919
+ const id = randomUUID6();
13723
14920
  const token = `aster_${randomBytes2(32).toString("base64url")}`;
13724
14921
  const now2 = Date.now();
13725
14922
  this.db.prepare(`INSERT INTO aster_credentials
@@ -13892,7 +15089,7 @@ var init_aster_store = __esm({
13892
15089
  }));
13893
15090
  }
13894
15091
  createApproval(input) {
13895
- const id = randomUUID5();
15092
+ const id = randomUUID6();
13896
15093
  const now2 = Date.now();
13897
15094
  this.db.prepare(`INSERT INTO aster_approvals
13898
15095
  (id, conversation_id, run_id, tool_name, explanation, arguments_json, status, expires_at, created_at)
@@ -14553,13 +15750,13 @@ var init_openapi = __esm({
14553
15750
  });
14554
15751
 
14555
15752
  // src/runs/worktrees.ts
14556
- import { execFile as execFile4 } from "child_process";
15753
+ import { execFile as execFile6 } from "child_process";
14557
15754
  import { access as access2, chmod as chmod2, mkdir as mkdir12, realpath as realpath4 } from "fs/promises";
14558
- import { homedir as homedir21 } from "os";
14559
- import { isAbsolute as isAbsolute4, join as join29, parse, relative as relative6, resolve as resolve7, sep as sep4 } from "path";
15755
+ import { homedir as homedir22 } from "os";
15756
+ import { isAbsolute as isAbsolute4, join as join33, parse, relative as relative6, resolve as resolve7, sep as sep4 } from "path";
14560
15757
  function defaultWorktreesDir() {
14561
- const dataHome = process.env.XDG_DATA_HOME || join29(homedir21(), ".local", "share");
14562
- return join29(dataHome, "autopilot", "worktrees");
15758
+ const dataHome = process.env.XDG_DATA_HOME || join33(homedir22(), ".local", "share");
15759
+ return join33(dataHome, "autopilot", "worktrees");
14563
15760
  }
14564
15761
  function isOutside(path) {
14565
15762
  return path === ".." || path.startsWith(`..${sep4}`) || isAbsolute4(path);
@@ -14570,7 +15767,7 @@ function isWithin2(parent, candidate) {
14570
15767
  }
14571
15768
  function runGit(args, cwd) {
14572
15769
  return new Promise((resolveOutput, reject) => {
14573
- execFile4("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
15770
+ execFile6("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
14574
15771
  if (error) {
14575
15772
  const detail = stderr.trim();
14576
15773
  reject(new Error(`git ${args[0]} failed${detail ? `: ${detail}` : ""}`, { cause: error }));
@@ -14610,7 +15807,7 @@ var init_worktrees = __esm({
14610
15807
  throw new Error("worktree root must be outside the source repository");
14611
15808
  }
14612
15809
  await chmod2(worktreesRoot, 448);
14613
- const worktreePath = join29(worktreesRoot, runId);
15810
+ const worktreePath = join33(worktreesRoot, runId);
14614
15811
  try {
14615
15812
  await access2(worktreePath);
14616
15813
  throw new Error(`worktree path already exists for run ${runId}`);
@@ -14787,11 +15984,11 @@ __export(openrouter_catalog_exports, {
14787
15984
  mapOpenRouterModel: () => mapOpenRouterModel
14788
15985
  });
14789
15986
  import { readFile as readFile20, writeFile as writeFile12, mkdir as mkdir13 } from "fs/promises";
14790
- import { homedir as homedir22 } from "os";
14791
- import { join as join30, dirname as dirname15 } from "path";
15987
+ import { homedir as homedir23 } from "os";
15988
+ import { join as join34, dirname as dirname16 } from "path";
14792
15989
  function catalogCachePath() {
14793
- const xdg = process.env.XDG_CONFIG_HOME || join30(homedir22(), ".config");
14794
- return join30(xdg, "kimiflare", "openrouter-models.json");
15990
+ const xdg = process.env.XDG_CONFIG_HOME || join34(homedir23(), ".config");
15991
+ return join34(xdg, "kimiflare", "openrouter-models.json");
14795
15992
  }
14796
15993
  function perMtok(perToken) {
14797
15994
  const n = Number(perToken);
@@ -14852,7 +16049,7 @@ async function readCache3() {
14852
16049
  async function writeCache3(models) {
14853
16050
  const path = catalogCachePath();
14854
16051
  try {
14855
- await mkdir13(dirname15(path), { recursive: true });
16052
+ await mkdir13(dirname16(path), { recursive: true });
14856
16053
  const cache = { version: CACHE_VERSION, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), models };
14857
16054
  await writeFile12(path, JSON.stringify(cache, null, 2), "utf8");
14858
16055
  } catch {
@@ -15083,7 +16280,7 @@ var init_aster_tools = __esm({
15083
16280
  init_edit();
15084
16281
  init_read();
15085
16282
  init_write();
15086
- init_paths();
16283
+ init_paths2();
15087
16284
  ASTR_TOOLS = [readTool, writeTool, editTool];
15088
16285
  SENSITIVE_NAMES = /* @__PURE__ */ new Set([
15089
16286
  ".git",
@@ -15105,9 +16302,9 @@ var init_aster_tools = __esm({
15105
16302
 
15106
16303
  // src/server/aster-memory.ts
15107
16304
  import Database4 from "better-sqlite3";
15108
- import { randomUUID as randomUUID6 } from "crypto";
15109
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync7 } from "fs";
15110
- import { dirname as dirname16 } from "path";
16305
+ import { randomUUID as randomUUID7 } from "crypto";
16306
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync9 } from "fs";
16307
+ import { dirname as dirname17 } from "path";
15111
16308
  function rowToItem(row) {
15112
16309
  return {
15113
16310
  id: row.id,
@@ -15160,7 +16357,7 @@ var init_aster_memory = __esm({
15160
16357
  AsterMemory = class {
15161
16358
  constructor(dbPath = defaultAsterDbPath(), options = {}) {
15162
16359
  this.options = options;
15163
- mkdirSync7(dirname16(dbPath), { recursive: true, mode: 448 });
16360
+ mkdirSync9(dirname17(dbPath), { recursive: true, mode: 448 });
15164
16361
  this.db = new Database4(dbPath);
15165
16362
  this.db.pragma("journal_mode = WAL");
15166
16363
  this.db.pragma("busy_timeout = 5000");
@@ -15224,7 +16421,7 @@ var init_aster_memory = __esm({
15224
16421
  this.db.prepare("UPDATE aster_memories SET title = ?, content = ?, pinned = ?, updated_at = ? WHERE id = ?").run(title, content, existing.pinned || input.pinned ? 1 : 0, now2, existing.id);
15225
16422
  return { item: this.get(existing.id), change: "updated" };
15226
16423
  }
15227
- const id = randomUUID6();
16424
+ const id = randomUUID7();
15228
16425
  this.db.prepare(`INSERT INTO aster_memories (id, kind, key, title, content, pinned, source_conversation_id, created_at, updated_at)
15229
16426
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.kind, input.key ?? null, title, content, input.pinned ? 1 : 0, input.sourceConversationId ?? null, now2, now2);
15230
16427
  return { item: this.get(id), change: "added" };
@@ -15436,9 +16633,9 @@ var init_aster_github_proxy = __esm({
15436
16633
  }
15437
16634
  handle(req, res) {
15438
16635
  const url = new URL(req.url ?? "/", "http://proxy.local");
15439
- const git2 = url.pathname.match(GIT_PATH_RE);
15440
- const api = git2 ? null : url.pathname.match(API_PATH_RE);
15441
- if (!git2 && !api) return deny(res, 404, "Only this conversation's repository is reachable");
16636
+ const git3 = url.pathname.match(GIT_PATH_RE);
16637
+ const api = git3 ? null : url.pathname.match(API_PATH_RE);
16638
+ if (!git3 && !api) return deny(res, 404, "Only this conversation's repository is reachable");
15442
16639
  const grantToken = extractToken(req);
15443
16640
  if (!grantToken) {
15444
16641
  res.writeHead(401, { "www-authenticate": 'Basic realm="aster-github"', "content-type": "text/plain" });
@@ -15447,18 +16644,18 @@ var init_aster_github_proxy = __esm({
15447
16644
  }
15448
16645
  const grant = this.options.resolveGrant(grantToken);
15449
16646
  if (!grant) return deny(res, 403, "This repository grant is invalid or was revoked");
15450
- const [, owner, name] = git2 ?? api;
16647
+ const [, owner, name] = git3 ?? api;
15451
16648
  if (`${owner}/${name}`.toLowerCase() !== grant.repository.toLowerCase()) {
15452
16649
  return deny(res, 403, `This conversation can only access ${grant.repository}`);
15453
16650
  }
15454
- const upstream = git2 ? new URL(`/${owner}/${name}.git/${git2[3]}${url.search}`, this.gitUpstream) : new URL(`/repos/${owner}/${name}${api[3] ?? ""}${url.search}`, this.apiUpstream);
16651
+ const upstream = git3 ? new URL(`/${owner}/${name}.git/${git3[3]}${url.search}`, this.gitUpstream) : new URL(`/repos/${owner}/${name}${api[3] ?? ""}${url.search}`, this.apiUpstream);
15455
16652
  const headers = {};
15456
16653
  for (const name2 of FORWARDED_REQUEST_HEADERS) {
15457
16654
  const value = req.headers[name2];
15458
16655
  if (typeof value === "string") headers[name2] = value;
15459
16656
  }
15460
16657
  headers["user-agent"] ??= "autopilot-aster";
15461
- headers.authorization = git2 ? "Basic " + Buffer.from(`x-access-token:${this.options.token}`).toString("base64") : `Bearer ${this.options.token}`;
16658
+ headers.authorization = git3 ? "Basic " + Buffer.from(`x-access-token:${this.options.token}`).toString("base64") : `Bearer ${this.options.token}`;
15462
16659
  const send = upstream.protocol === "https:" ? httpsRequest : httpRequest;
15463
16660
  const forward = send(upstream, { method: req.method, headers }, (upstreamRes) => {
15464
16661
  const responseHeaders = {};
@@ -15481,7 +16678,7 @@ var init_aster_github_proxy = __esm({
15481
16678
  // src/server/aster-push.ts
15482
16679
  import Database5 from "better-sqlite3";
15483
16680
  import { createSign } from "crypto";
15484
- import { readFileSync as readFileSync5 } from "fs";
16681
+ import { readFileSync as readFileSync6 } from "fs";
15485
16682
  import { connect } from "http2";
15486
16683
  function truncate2(value, limit) {
15487
16684
  const clean = value.replace(/\s+/g, " ").trim();
@@ -15599,7 +16796,7 @@ var init_aster_push = __esm({
15599
16796
  token() {
15600
16797
  const now2 = Math.floor(Date.now() / 1e3);
15601
16798
  if (this.jwt && now2 - this.jwt.issuedAt < 50 * 60) return this.jwt.value;
15602
- this.privateKey ??= readFileSync5(this.options.keyPath, "utf8");
16799
+ this.privateKey ??= readFileSync6(this.options.keyPath, "utf8");
15603
16800
  const encode = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
15604
16801
  const unsigned = `${encode({ alg: "ES256", kid: this.options.keyId })}.${encode({ iss: this.options.teamId, iat: now2 })}`;
15605
16802
  const signature = createSign("SHA256").update(unsigned).sign({ key: this.privateKey, dsaEncoding: "ieee-p1363" }).toString("base64url");
@@ -15611,7 +16808,7 @@ var init_aster_push = __esm({
15611
16808
  });
15612
16809
 
15613
16810
  // src/server/aster-api.ts
15614
- import { randomUUID as randomUUID7 } from "crypto";
16811
+ import { randomUUID as randomUUID8 } from "crypto";
15615
16812
  import { unlink as unlink2 } from "fs/promises";
15616
16813
  import { resolve as resolve8 } from "path";
15617
16814
  function parseCursor(header, query) {
@@ -16168,8 +17365,8 @@ var init_aster_api = __esm({
16168
17365
  };
16169
17366
  }
16170
17367
  async createConversation(res, store, principal, workspace, model, idempotencyKey, kind, repository) {
16171
- const candidateId = randomUUID7();
16172
- const candidateSessionId = randomUUID7();
17368
+ const candidateId = randomUUID8();
17369
+ const candidateSessionId = randomUUID8();
16173
17370
  const reservation = store.reserveCellConversation({
16174
17371
  conversationId: candidateId,
16175
17372
  credentialId: principal.credentialId,
@@ -16299,7 +17496,7 @@ var init_aster_api = __esm({
16299
17496
  sendError(res, 409, "conversation_cell_unavailable", "Conversation cell is not available");
16300
17497
  return;
16301
17498
  }
16302
- const runId = randomUUID7();
17499
+ const runId = randomUUID8();
16303
17500
  if (!idempotent && !store.beginTurn(conversation.id, runId)) {
16304
17501
  sendError(res, 409, "conversation_busy", "A turn is already active for this conversation");
16305
17502
  return;
@@ -16747,10 +17944,10 @@ data: ${JSON.stringify(event.data)}
16747
17944
  });
16748
17945
 
16749
17946
  // src/server/aster-cell-runtime.ts
16750
- import { execFile as execFile5 } from "child_process";
17947
+ import { execFile as execFile7 } from "child_process";
16751
17948
  import { readFile as readFile21 } from "fs/promises";
16752
- import { promisify as promisify4 } from "util";
16753
- import { randomUUID as randomUUID8 } from "crypto";
17949
+ import { promisify as promisify6 } from "util";
17950
+ import { randomUUID as randomUUID9 } from "crypto";
16754
17951
  async function writeCellFileChunked(cell, path, content) {
16755
17952
  if (Buffer.byteLength(content, "utf8") <= CELL_FILE_WRITE_CHUNK_CHARS) {
16756
17953
  await cell.writeFile(path, content, { mode: "0600" });
@@ -16785,7 +17982,7 @@ async function readBundledCellSdkBundle() {
16785
17982
  throw new Error("cell_runtime_bundle_unavailable");
16786
17983
  }
16787
17984
  async function defaultArchiveWorkspace(workspaceRoot) {
16788
- const { stdout } = await execFileAsync3("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
17985
+ const { stdout } = await execFileAsync5("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
16789
17986
  encoding: "buffer",
16790
17987
  maxBuffer: 512 * 1024 * 1024
16791
17988
  });
@@ -16833,13 +18030,13 @@ ${redacted.slice(-half)}`;
16833
18030
  function sleep4(ms) {
16834
18031
  return new Promise((resolve15) => setTimeout(resolve15, ms));
16835
18032
  }
16836
- var execFileAsync3, CELL_WORKSPACE, CELL_STATE_DIR, CHECKPOINT_MESSAGE_FILE, CELL_RUNTIME_DIR, INSTALLED_RUNTIME_PACKAGE, CELL_RUNTIME_SDK_ENTRY, NOTES_FILE, MEMORY_FILE, MEMORY_EXTRACTION_TIMEOUT_MS, LIVENESS_CHECK_MS, ARTIFACTS_DIR, MAX_ARTIFACTS, MAX_ARTIFACT_BYTES, BRANCH_RE, REPOSITORY_RE, GIT, COMMIT_SHA_RE, MAX_TOOL_OUTPUT_CHARS, MAX_TOOL_INPUT_CHARS, MAX_CHECKPOINTS, CELL_CONTROL_DIR, BRIDGE_MARKER, CONTROL_RUNNER, BRIDGE_RUNNER, HEALTH_TIMEOUT_MS, EVENT_POLL_MS, AsterHotcellRuntime, CELL_FILE_WRITE_CHUNK_CHARS, ARTIFACT_MIME_TYPES;
18033
+ var execFileAsync5, CELL_WORKSPACE, CELL_STATE_DIR, CHECKPOINT_MESSAGE_FILE, CELL_RUNTIME_DIR, INSTALLED_RUNTIME_PACKAGE, CELL_RUNTIME_SDK_ENTRY, NOTES_FILE, MEMORY_FILE, MEMORY_EXTRACTION_TIMEOUT_MS, LIVENESS_CHECK_MS, ARTIFACTS_DIR, MAX_ARTIFACTS, MAX_ARTIFACT_BYTES, BRANCH_RE, REPOSITORY_RE, GIT, COMMIT_SHA_RE, MAX_TOOL_OUTPUT_CHARS, MAX_TOOL_INPUT_CHARS, MAX_CHECKPOINTS, CELL_CONTROL_DIR, BRIDGE_MARKER, CONTROL_RUNNER, BRIDGE_RUNNER, HEALTH_TIMEOUT_MS, EVENT_POLL_MS, AsterHotcellRuntime, CELL_FILE_WRITE_CHUNK_CHARS, ARTIFACT_MIME_TYPES;
16837
18034
  var init_aster_cell_runtime = __esm({
16838
18035
  "src/server/aster-cell-runtime.ts"() {
16839
18036
  "use strict";
16840
18037
  init_version();
16841
18038
  init_aster_tools();
16842
- execFileAsync3 = promisify4(execFile5);
18039
+ execFileAsync5 = promisify6(execFile7);
16843
18040
  CELL_WORKSPACE = "/workspace";
16844
18041
  CELL_STATE_DIR = "/workspace/.aster";
16845
18042
  CHECKPOINT_MESSAGE_FILE = `${CELL_STATE_DIR}/checkpoint-message`;
@@ -16946,7 +18143,7 @@ var init_aster_cell_runtime = __esm({
16946
18143
  await this.installRuntime(cell);
16947
18144
  await this.ensureBridge(cell);
16948
18145
  await this.control(cell, "POST", "/rpc", {
16949
- id: randomUUID8(),
18146
+ id: randomUUID9(),
16950
18147
  type: "new_session",
16951
18148
  sessionId: input.sessionId,
16952
18149
  config: { model: input.model, cwd: CELL_WORKSPACE }
@@ -16965,7 +18162,7 @@ var init_aster_cell_runtime = __esm({
16965
18162
  /** Switch the cell session's model between turns. The bridge persists it for session restore. */
16966
18163
  async setModel(cellId, model) {
16967
18164
  await this.ensureReady(cellId);
16968
- await this.controlById(cellId, "POST", "/rpc", { id: randomUUID8(), type: "set_model", modelId: model });
18165
+ await this.controlById(cellId, "POST", "/rpc", { id: randomUUID9(), type: "set_model", modelId: model });
16969
18166
  }
16970
18167
  /** Reinstall the runtime if the cell's image lost it and restart the bridge if it isn't running. */
16971
18168
  async ensureReady(cellId) {
@@ -17028,7 +18225,7 @@ var init_aster_cell_runtime = __esm({
17028
18225
  const active = this.activeRuns.get(runId);
17029
18226
  if (!active) return;
17030
18227
  active.stop();
17031
- void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID8(), type: "abort" }).catch(() => {
18228
+ void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID9(), type: "abort" }).catch(() => {
17032
18229
  });
17033
18230
  }
17034
18231
  async runTurn(turn, options = {}) {
@@ -17127,7 +18324,7 @@ ${turn.userText}` : turn.userText;
17127
18324
  args: event.args ?? {}
17128
18325
  });
17129
18326
  await this.controlById(cellId, "POST", "/rpc", {
17130
- id: randomUUID8(),
18327
+ id: randomUUID9(),
17131
18328
  type: "resolve_permission",
17132
18329
  requestId: event.requestId,
17133
18330
  decision
@@ -17255,9 +18452,9 @@ ${turn.userText}` : turn.userText;
17255
18452
  if (!isSafeArtifactPath(path)) throw new Error("invalid_artifact_path");
17256
18453
  const cell = await this.provider.getCell(cellId);
17257
18454
  const file = shellQuote2(`${ARTIFACTS_DIR}/${path}`);
17258
- const stat10 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
17259
- if (stat10.exitCode !== 0) throw new Error("artifact_not_found");
17260
- if (Number(stat10.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
18455
+ const stat11 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
18456
+ if (stat11.exitCode !== 0) throw new Error("artifact_not_found");
18457
+ if (Number(stat11.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
17261
18458
  const encoded = await cell.exec(`base64 -w0 -- ${file}`);
17262
18459
  if (encoded.exitCode !== 0) throw new Error("artifact_not_found");
17263
18460
  return { data: Buffer.from(encoded.stdout.trim(), "base64"), mimeType: artifactMimeType(path), name: path.split("/").pop() ?? path };
@@ -17336,7 +18533,7 @@ ${text}` : text, { mode: "0600" });
17336
18533
  }
17337
18534
  }
17338
18535
  async control(cell, method, path, body) {
17339
- const requestId = randomUUID8();
18536
+ const requestId = randomUUID9();
17340
18537
  await cell.mkdir(CELL_CONTROL_DIR, { parents: true });
17341
18538
  await writeCellFileChunked(cell, `${CELL_CONTROL_DIR}/${requestId}.json`, JSON.stringify({ method, path, body }));
17342
18539
  const result2 = await cell.exec(`${CONTROL_RUNNER} ${requestId}`);
@@ -17552,7 +18749,7 @@ var init_hotcell_provider = __esm({
17552
18749
  });
17553
18750
 
17554
18751
  // src/server/routes.ts
17555
- import { randomUUID as randomUUID9 } from "crypto";
18752
+ import { randomUUID as randomUUID10 } from "crypto";
17556
18753
  import { URL as URL2 } from "url";
17557
18754
  import { readFile as readFile22, unlink as unlink3 } from "fs/promises";
17558
18755
  import { resolve as resolve9, basename as basename5 } from "path";
@@ -17567,6 +18764,27 @@ function createHotcellAsterRuntime() {
17567
18764
  throw error;
17568
18765
  }
17569
18766
  }
18767
+ async function startSessionTrace(config2, sessionFile, tools, messages) {
18768
+ const trace = createRecorder({
18769
+ enabled: config2.trace?.enabled === true,
18770
+ sessionId: sessionFile.id,
18771
+ root: config2.trace?.root
18772
+ });
18773
+ const systemPrompt = messages.filter((m) => m.role === "system" && typeof m.content === "string").map((m) => m.content).join("\n\n");
18774
+ await emitSessionStart(trace, {
18775
+ cwd: sessionFile.cwd,
18776
+ model: sessionFile.model,
18777
+ mode: "server",
18778
+ config: config2,
18779
+ tools: toOpenAIToolDefs(tools),
18780
+ systemPrompt
18781
+ });
18782
+ return trace;
18783
+ }
18784
+ function traceUserMessage(trace, text, attachments, turnId) {
18785
+ if (!trace?.enabled) return;
18786
+ trace.emit("user_message", { content_blob: trace.blob(text) ?? "", source: "server", attachments }, turnId);
18787
+ }
17570
18788
  function parseOptionalBudgetInteger(body, name, minimum = 1) {
17571
18789
  if (!Object.hasOwn(body, name)) return void 0;
17572
18790
  const value = body[name];
@@ -17617,8 +18835,8 @@ async function resolveFiles2(filePatterns, cwd) {
17617
18835
  const resolved = /* @__PURE__ */ new Set();
17618
18836
  for (const pattern of filePatterns) {
17619
18837
  try {
17620
- const stat10 = await import("fs/promises").then((m) => m.stat(resolve9(cwd, pattern)));
17621
- if (stat10.isFile()) {
18838
+ const stat11 = await import("fs/promises").then((m) => m.stat(resolve9(cwd, pattern)));
18839
+ if (stat11.isFile()) {
17622
18840
  resolved.add(resolve9(cwd, pattern));
17623
18841
  continue;
17624
18842
  }
@@ -17686,7 +18904,8 @@ function setupRoutes(config2, options = {}) {
17686
18904
  executor: new ToolExecutor(tools),
17687
18905
  sseClients: /* @__PURE__ */ new Set(),
17688
18906
  runId: run.id,
17689
- allowedTools
18907
+ allowedTools,
18908
+ trace: await startSessionTrace(config2, sessionFile, tools, sessionFile.messages)
17690
18909
  };
17691
18910
  activeSessions.set(run.sessionId, active);
17692
18911
  activeRuns.set(run.id, active);
@@ -17874,7 +19093,7 @@ function setupRoutes(config2, options = {}) {
17874
19093
  const executor = new ToolExecutor(tools);
17875
19094
  const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
17876
19095
  const sessionId = makeSessionId2(task);
17877
- const runId = randomUUID9();
19096
+ const runId = randomUUID10();
17878
19097
  const worktreeManager = new RunWorktreeManager();
17879
19098
  let worktree;
17880
19099
  if (useWorktree) {
@@ -17932,7 +19151,10 @@ function setupRoutes(config2, options = {}) {
17932
19151
  }
17933
19152
  throw error;
17934
19153
  }
19154
+ const runTrace = await startSessionTrace(config2, sessionFile, tools, messages);
19155
+ traceUserMessage(runTrace, task, 0, makeTurnId());
17935
19156
  const active = {
19157
+ trace: runTrace,
17936
19158
  sessionFile,
17937
19159
  messages,
17938
19160
  executor,
@@ -18114,11 +19336,16 @@ function setupRoutes(config2, options = {}) {
18114
19336
  const resolvedFiles = await resolveFiles2(files, cwd);
18115
19337
  const userContent = await buildUserMessage2(prompt, resolvedFiles, cwd);
18116
19338
  messages.push({ role: "user", content: userContent });
19339
+ const trace = await startSessionTrace(config2, sessionFile, ALL_TOOLS, messages);
19340
+ const firstTurnId = makeTurnId();
19341
+ traceUserMessage(trace, prompt, resolvedFiles.length, firstTurnId);
18117
19342
  const active = {
18118
19343
  sessionFile,
18119
19344
  messages,
18120
19345
  executor,
18121
- sseClients: /* @__PURE__ */ new Set()
19346
+ sseClients: /* @__PURE__ */ new Set(),
19347
+ trace,
19348
+ nextTurnId: firstTurnId
18122
19349
  };
18123
19350
  activeSessions.set(sessionId, active);
18124
19351
  launchAgentTurnForSession(active, config2, allowAll);
@@ -18146,6 +19373,15 @@ function setupRoutes(config2, options = {}) {
18146
19373
  const resolvedFiles = await resolveFiles2(files, active.sessionFile.cwd);
18147
19374
  const userContent = await buildUserMessage2(prompt, resolvedFiles, active.sessionFile.cwd);
18148
19375
  active.messages.push({ role: "user", content: userContent });
19376
+ active.nextTurnId = makeTurnId();
19377
+ if (active.trace?.enabled && active.lastTurnId) {
19378
+ active.trace.emit(
19379
+ "outcome",
19380
+ { kind: "next_message", turn_id: active.lastTurnId, classification: classifyNextMessage(prompt), classifier: "regex" },
19381
+ active.lastTurnId
19382
+ );
19383
+ }
19384
+ traceUserMessage(active.trace, prompt, resolvedFiles.length, active.nextTurnId);
18149
19385
  launchAgentTurnForSession(active, config2, allowAll);
18150
19386
  json2(res, 202, { sessionId, status: "started" });
18151
19387
  return;
@@ -18160,6 +19396,7 @@ function setupRoutes(config2, options = {}) {
18160
19396
  wakeScheduler.dispose();
18161
19397
  for (const [, active] of activeSessions) {
18162
19398
  active.shuttingDown = true;
19399
+ if (active.trace?.enabled) active.trace.emit("session_end", { reason: "exit" }, null);
18163
19400
  active.controller?.abort(new Error("server_shutdown"));
18164
19401
  for (const client of active.sseClients) {
18165
19402
  client.close();
@@ -18328,6 +19565,9 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18328
19565
  }
18329
19566
  };
18330
19567
  const compaction = sessionFileCompactionTarget(sessionFile);
19568
+ const turnId = active.nextTurnId ?? makeTurnId();
19569
+ active.nextTurnId = void 0;
19570
+ active.lastTurnId = turnId;
18331
19571
  try {
18332
19572
  if (run && runStore) {
18333
19573
  const tools = ALL_TOOLS.filter((tool) => active.allowedTools?.has(tool.name));
@@ -18349,6 +19589,8 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18349
19589
  preferPullRequests: config2.preferPullRequests,
18350
19590
  compaction,
18351
19591
  callbacks,
19592
+ trace: active.trace,
19593
+ turnId,
18352
19594
  onIterationEnd: async (updatedMessages) => {
18353
19595
  sessionFile.messages = updatedMessages;
18354
19596
  sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -18375,7 +19617,9 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18375
19617
  allowDirectPush: config2.allowDirectPush,
18376
19618
  preferPullRequests: config2.preferPullRequests,
18377
19619
  compaction,
18378
- callbacks
19620
+ callbacks,
19621
+ trace: active.trace,
19622
+ turnId
18379
19623
  });
18380
19624
  }
18381
19625
  sessionFile.messages = messages;
@@ -18427,6 +19671,8 @@ var init_routes = __esm({
18427
19671
  "use strict";
18428
19672
  init_config();
18429
19673
  init_llm_auth();
19674
+ init_trace();
19675
+ init_registry2();
18430
19676
  init_loop();
18431
19677
  init_system_prompt();
18432
19678
  init_executor();
@@ -18559,8 +19805,8 @@ __export(db_exports, {
18559
19805
  updateMemoryEmbedding: () => updateMemoryEmbedding
18560
19806
  });
18561
19807
  import Database6 from "better-sqlite3";
18562
- import { dirname as dirname17 } from "path";
18563
- import { mkdirSync as mkdirSync8, statSync as statSync4 } from "fs";
19808
+ import { dirname as dirname18 } from "path";
19809
+ import { mkdirSync as mkdirSync10, statSync as statSync4 } from "fs";
18564
19810
  function initSchema(db) {
18565
19811
  db.exec(`
18566
19812
  CREATE TABLE IF NOT EXISTS memories (
@@ -18644,7 +19890,7 @@ function openMemoryDb(dbPath) {
18644
19890
  if (dbInstance) {
18645
19891
  dbInstance.close();
18646
19892
  }
18647
- mkdirSync8(dirname17(dbPath), { recursive: true });
19893
+ mkdirSync10(dirname18(dbPath), { recursive: true });
18648
19894
  dbInstance = new Database6(dbPath);
18649
19895
  dbInstance.pragma("journal_mode = WAL");
18650
19896
  dbInstance.pragma("foreign_keys = ON");
@@ -19778,12 +21024,12 @@ var init_connection = __esm({
19778
21024
  });
19779
21025
 
19780
21026
  // src/lsp/protocol.ts
19781
- import { pathToFileURL as pathToFileURL2, fileURLToPath as fileURLToPath4 } from "url";
21027
+ import { pathToFileURL as pathToFileURL2, fileURLToPath as fileURLToPath5 } from "url";
19782
21028
  function toUri(path) {
19783
21029
  return pathToFileURL2(path).href;
19784
21030
  }
19785
21031
  function fromUri(uri) {
19786
- return fileURLToPath4(uri);
21032
+ return fileURLToPath5(uri);
19787
21033
  }
19788
21034
  function symbolKindName(kind) {
19789
21035
  const names = {
@@ -20594,7 +21840,7 @@ function makeLspTools(manager) {
20594
21840
  var init_lsp = __esm({
20595
21841
  "src/tools/lsp.ts"() {
20596
21842
  "use strict";
20597
- init_paths();
21843
+ init_paths2();
20598
21844
  init_adapter();
20599
21845
  }
20600
21846
  });
@@ -20610,11 +21856,11 @@ __export(requesty_catalog_exports, {
20610
21856
  mapRequestyModel: () => mapRequestyModel
20611
21857
  });
20612
21858
  import { readFile as readFile23, writeFile as writeFile13, mkdir as mkdir14 } from "fs/promises";
20613
- import { homedir as homedir23 } from "os";
20614
- import { join as join32, dirname as dirname18 } from "path";
21859
+ import { homedir as homedir24 } from "os";
21860
+ import { join as join36, dirname as dirname19 } from "path";
20615
21861
  function catalogCachePath2() {
20616
- const xdg = process.env.XDG_CONFIG_HOME || join32(homedir23(), ".config");
20617
- return join32(xdg, "kimiflare", "requesty-models.json");
21862
+ const xdg = process.env.XDG_CONFIG_HOME || join36(homedir24(), ".config");
21863
+ return join36(xdg, "kimiflare", "requesty-models.json");
20618
21864
  }
20619
21865
  function isSafeModelId(id) {
20620
21866
  return typeof id === "string" && id.length > 0 && !CONTROL_CHARS.test(id);
@@ -20685,7 +21931,7 @@ async function readCache4() {
20685
21931
  async function writeCache4(catalog) {
20686
21932
  const path = catalogCachePath2();
20687
21933
  try {
20688
- await mkdir14(dirname18(path), { recursive: true });
21934
+ await mkdir14(dirname19(path), { recursive: true });
20689
21935
  const cache = { version: CACHE_VERSION2, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), ...catalog };
20690
21936
  await writeFile13(path, JSON.stringify(cache, null, 2), "utf8");
20691
21937
  } catch {
@@ -20777,9 +22023,9 @@ var init_permissions = __esm({
20777
22023
 
20778
22024
  // src/sdk/session.ts
20779
22025
  import { resolve as resolve10 } from "path";
20780
- import { homedir as homedir24 } from "os";
20781
- import { join as join33 } from "path";
20782
- import { existsSync as existsSync4 } from "fs";
22026
+ import { homedir as homedir25 } from "os";
22027
+ import { join as join37 } from "path";
22028
+ import { existsSync as existsSync7 } from "fs";
20783
22029
  async function createAgentSession(opts2) {
20784
22030
  const config2 = await resolveSdkConfig(opts2);
20785
22031
  if (usesRequesty(llmAuthFromConfig(config2))) await ensureRequestyCatalog();
@@ -20795,7 +22041,7 @@ async function createAgentSession(opts2) {
20795
22041
  let memoryManager = null;
20796
22042
  const memoryEnabled = opts2.memoryEnabled ?? config2.memoryEnabled ?? false;
20797
22043
  if (memoryEnabled) {
20798
- const dbPath = config2.memoryDbPath ?? join33(homedir24(), ".local", "share", "kimiflare", "memory.db");
22044
+ const dbPath = config2.memoryDbPath ?? join37(homedir25(), ".local", "share", "kimiflare", "memory.db");
20799
22045
  memoryManager = new MemoryManager({
20800
22046
  dbPath,
20801
22047
  ...llmAuthFromConfig(config2),
@@ -20825,7 +22071,7 @@ async function createAgentSession(opts2) {
20825
22071
  }
20826
22072
  let sessionFile;
20827
22073
  if (opts2.sessionId) {
20828
- const filePath = join33(sessionsDir(), `${opts2.sessionId}.json`);
22074
+ const filePath = join37(sessionsDir(), `${opts2.sessionId}.json`);
20829
22075
  try {
20830
22076
  sessionFile = await loadSession(filePath);
20831
22077
  } catch {
@@ -20860,6 +22106,19 @@ async function createAgentSession(opts2) {
20860
22106
  ...sessionFile.messages.filter((m) => m.role !== "system")
20861
22107
  ];
20862
22108
  const isNewSession = sessionFile.messages.length === 0;
22109
+ const trace = createRecorder({
22110
+ enabled: config2.trace?.enabled === true,
22111
+ sessionId: sessionFile.id,
22112
+ root: config2.trace?.root
22113
+ });
22114
+ await emitSessionStart(trace, {
22115
+ cwd,
22116
+ model: config2.model,
22117
+ mode: "sdk",
22118
+ config: config2,
22119
+ tools: toOpenAIToolDefs(allTools),
22120
+ systemPrompt
22121
+ });
20863
22122
  const session = new InternalSession({
20864
22123
  sessionFile,
20865
22124
  cwd,
@@ -20873,7 +22132,8 @@ async function createAgentSession(opts2) {
20873
22132
  permissionHandler: opts2.permissionHandler,
20874
22133
  onKimiMdStale: opts2.onKimiMdStale,
20875
22134
  hooks,
20876
- provider: opts2.provider
22135
+ provider: opts2.provider,
22136
+ trace
20877
22137
  });
20878
22138
  if (isNewSession) {
20879
22139
  await session.save().catch((err) => {
@@ -20906,6 +22166,8 @@ var init_session = __esm({
20906
22166
  init_context_budget();
20907
22167
  init_config2();
20908
22168
  init_permissions();
22169
+ init_trace();
22170
+ init_registry2();
20909
22171
  InternalSession = class {
20910
22172
  sessionId;
20911
22173
  cwd;
@@ -20921,6 +22183,10 @@ var init_session = __esm({
20921
22183
  onKimiMdStale;
20922
22184
  provider;
20923
22185
  hooks;
22186
+ trace;
22187
+ traceCostUsd = 0;
22188
+ /** Turn id of the last turn that ran, for next-message outcome signals. */
22189
+ lastTurnId = null;
20924
22190
  listeners = /* @__PURE__ */ new Set();
20925
22191
  steerQueue = [];
20926
22192
  followUpQueue = [];
@@ -20961,6 +22227,7 @@ var init_session = __esm({
20961
22227
  this.onKimiMdStale = opts2.onKimiMdStale;
20962
22228
  this.provider = opts2.provider;
20963
22229
  this.hooks = opts2.hooks;
22230
+ this.trace = opts2.trace;
20964
22231
  this.permissionHandler = opts2.permissionHandler ?? createDefaultPermissionHandler({
20965
22232
  mode: this.currentMode,
20966
22233
  onRequest: (req) => {
@@ -21014,6 +22281,22 @@ var init_session = __esm({
21014
22281
  }
21015
22282
  const userMessage = { role: "user", content: userContent };
21016
22283
  this.messages.push(userMessage);
22284
+ const turnId = makeTurnId();
22285
+ if (this.trace.enabled) {
22286
+ if (this.lastTurnId) {
22287
+ this.trace.emit(
22288
+ "outcome",
22289
+ { kind: "next_message", turn_id: this.lastTurnId, classification: classifyNextMessage(text), classifier: "regex" },
22290
+ this.lastTurnId
22291
+ );
22292
+ }
22293
+ this.trace.emit(
22294
+ "user_message",
22295
+ { content_blob: this.trace.blob(text) ?? "", source: "sdk", attachments: options?.images?.length ?? 0 },
22296
+ turnId
22297
+ );
22298
+ }
22299
+ this.lastTurnId = turnId;
21017
22300
  this.emit({ type: "message.start", messageId, role: "user" });
21018
22301
  this.emit({ type: "message.end", messageId });
21019
22302
  this.isStreaming = true;
@@ -21021,7 +22304,7 @@ var init_session = __esm({
21021
22304
  this.abortController = new AbortController();
21022
22305
  let succeeded = false;
21023
22306
  try {
21024
- await this.runTurn(mode, options?.maxToolIterations);
22307
+ await this.runTurn(mode, options?.maxToolIterations, turnId);
21025
22308
  succeeded = true;
21026
22309
  this.isStreaming = false;
21027
22310
  this.emit({ type: "status", status: "idle" });
@@ -21116,6 +22399,9 @@ var init_session = __esm({
21116
22399
  }
21117
22400
  dispose() {
21118
22401
  this.disposed = true;
22402
+ if (this.trace.enabled) {
22403
+ this.trace.emit("session_end", { reason: "exit", ...this.traceCostUsd > 0 ? { total_cost_usd: this.traceCostUsd } : {} }, null);
22404
+ }
21119
22405
  this.denyPendingPermissions();
21120
22406
  this.abortController?.abort();
21121
22407
  this.lspManager?.stopAll().catch(() => {
@@ -21123,7 +22409,7 @@ var init_session = __esm({
21123
22409
  this.memoryManager?.close();
21124
22410
  this.listeners.clear();
21125
22411
  }
21126
- async runTurn(mode, maxToolIterations) {
22412
+ async runTurn(mode, maxToolIterations, turnId = makeTurnId()) {
21127
22413
  const signal = this.abortController.signal;
21128
22414
  const coauthor = this.config.coauthor !== false ? {
21129
22415
  name: this.config.coauthorName || "autopilot",
@@ -21196,6 +22482,7 @@ var init_session = __esm({
21196
22482
  });
21197
22483
  },
21198
22484
  onUsageFinal: (usage, gatewayMeta) => {
22485
+ if (typeof usage.cost === "number") this.traceCostUsd += usage.cost;
21199
22486
  this.usage.totalInputTokens += usage.prompt_tokens;
21200
22487
  this.usage.totalOutputTokens += usage.completion_tokens;
21201
22488
  this.usage.turnCount += 1;
@@ -21273,6 +22560,8 @@ var init_session = __esm({
21273
22560
  compaction: this.compaction,
21274
22561
  allowDirectPush: this.config.allowDirectPush,
21275
22562
  preferPullRequests: this.config.preferPullRequests,
22563
+ trace: this.trace,
22564
+ turnId,
21276
22565
  // Steering lands at the next tool boundary, or before the final answer
21277
22566
  // ends the turn (the loop continues so the model can respond to it).
21278
22567
  drainPendingInput: () => {
@@ -21281,6 +22570,9 @@ var init_session = __esm({
21281
22570
  const messageId = `msg_${this.nextMessageId++}`;
21282
22571
  this.emit({ type: "message.start", messageId, role: "user" });
21283
22572
  this.emit({ type: "message.end", messageId });
22573
+ if (this.trace.enabled) {
22574
+ this.trace.emit("user_message", { content_blob: this.trace.blob(text) ?? "", source: "continuation", attachments: 0 }, turnId);
22575
+ }
21284
22576
  return { role: "user", content: text };
21285
22577
  });
21286
22578
  },
@@ -21296,7 +22588,7 @@ var init_session = __esm({
21296
22588
  }
21297
22589
  }
21298
22590
  });
21299
- if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync4(join33(this.cwd, name)))) {
22591
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync7(join37(this.cwd, name)))) {
21300
22592
  this.messages[0] = {
21301
22593
  role: "system",
21302
22594
  content: buildSystemPrompt({
@@ -22108,6 +23400,8 @@ var init_builtins = __esm({
22108
23400
  { name: "checkpoint", argHint: "[label]", description: "Save current point in session", source: "builtin" },
22109
23401
  { name: "checkpoints", description: "List checkpoints in current session", source: "builtin" },
22110
23402
  { name: "compact", description: "Summarize old turns to free context", source: "builtin" },
23403
+ { name: "good", description: "Rate the last turn as good (recorded in the local session trace)", source: "builtin" },
23404
+ { name: "bad", argHint: "[reason]", description: "Rate the last turn as bad (recorded in the local session trace)", source: "builtin" },
22111
23405
  { name: "clear", description: "Clear current conversation", source: "builtin" },
22112
23406
  { name: "fresh", description: "Start a fresh session with a summarized plan or continuation context", source: "builtin" },
22113
23407
  { name: "init", description: "Scan repo and write AGENTS.md", source: "builtin" },
@@ -22574,7 +23868,9 @@ var init_help_menu = __esm({
22574
23868
  { command: "/resume", description: "pick a past conversation" },
22575
23869
  { command: "/compact", description: "summarize old turns to free context" },
22576
23870
  { command: "/clear", description: "clear current conversation" },
22577
- { command: "/fresh", description: "reset session and start fresh with the last plan" }
23871
+ { command: "/fresh", description: "reset session and start fresh with the last plan" },
23872
+ { command: "/good", description: "rate the last turn good (local session trace)" },
23873
+ { command: "/bad [reason]", description: "rate the last turn bad (local session trace)", selectable: false }
22578
23874
  ]
22579
23875
  },
22580
23876
  {
@@ -22733,8 +24029,8 @@ __export(app_helpers_exports, {
22733
24029
  trackRecentFile: () => trackRecentFile
22734
24030
  });
22735
24031
  import { execSync as execSync3, spawn as spawn6 } from "child_process";
22736
- import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
22737
- import { join as join34 } from "path";
24032
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
24033
+ import { join as join38 } from "path";
22738
24034
  import { platform as platform3 } from "os";
22739
24035
  function replaceQueuedPrompt(queue2, key, full, display) {
22740
24036
  const index = queue2.findIndex((item) => item.key === key);
@@ -22842,12 +24138,12 @@ function buildFilePickerIgnoreList(cwd) {
22842
24138
  ];
22843
24139
  const gitignorePatterns = [];
22844
24140
  try {
22845
- const gitignorePath = join34(cwd, ".gitignore");
24141
+ const gitignorePath = join38(cwd, ".gitignore");
22846
24142
  const stats = statSync5(gitignorePath);
22847
24143
  if (stats.size > MAX_GITIGNORE_SIZE) {
22848
24144
  return hardcoded;
22849
24145
  }
22850
- const content = readFileSync6(gitignorePath, "utf-8");
24146
+ const content = readFileSync7(gitignorePath, "utf-8");
22851
24147
  for (const line of content.split(/\r?\n/)) {
22852
24148
  const trimmed = line.trim();
22853
24149
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -22981,7 +24277,7 @@ function findImagePaths(text) {
22981
24277
  let match;
22982
24278
  while ((match = quotedRegex.exec(text)) !== null) {
22983
24279
  const path = match[1] ?? match[2];
22984
- if (path && isImagePath(path) && existsSync5(path)) {
24280
+ if (path && isImagePath(path) && existsSync8(path)) {
22985
24281
  paths.push(path);
22986
24282
  }
22987
24283
  }
@@ -22990,7 +24286,7 @@ function findImagePaths(text) {
22990
24286
  const processed = remaining.replace(/\\ /g, ESCAPED_SPACE);
22991
24287
  for (const token of processed.split(/\s+/)) {
22992
24288
  const clean = token.replace(new RegExp(ESCAPED_SPACE, "g"), " ").replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
22993
- if (clean && isImagePath(clean) && existsSync5(clean) && !paths.includes(clean)) {
24289
+ if (clean && isImagePath(clean) && existsSync8(clean) && !paths.includes(clean)) {
22994
24290
  paths.push(clean);
22995
24291
  }
22996
24292
  }
@@ -23020,9 +24316,9 @@ var init_app_helpers = __esm({
23020
24316
 
23021
24317
  // src/ui/bang-command.ts
23022
24318
  import { spawn as spawn7 } from "child_process";
23023
- import { mkdtemp as mkdtemp2, readFile as readFile24, rm as rm3 } from "fs/promises";
24319
+ import { mkdtemp as mkdtemp2, readFile as readFile24, rm as rm4 } from "fs/promises";
23024
24320
  import { tmpdir as tmpdir4, platform as platform4 } from "os";
23025
- import { join as join35 } from "path";
24321
+ import { join as join39 } from "path";
23026
24322
  function registerInkInstance(instance) {
23027
24323
  inkInstance = instance;
23028
24324
  }
@@ -23125,8 +24421,8 @@ async function runBangCommand(command, cwd) {
23125
24421
  const origErr = stderr.write;
23126
24422
  const readable = stdin.listeners("readable");
23127
24423
  const wasRaw = stdin.isTTY ? stdin.isRaw : false;
23128
- const logDir2 = isWindows ? null : await mkdtemp2(join35(tmpdir4(), "autopilot-bang-"));
23129
- const logPath = logDir2 ? join35(logDir2, "out.log") : null;
24424
+ const logDir2 = isWindows ? null : await mkdtemp2(join39(tmpdir4(), "autopilot-bang-"));
24425
+ const logPath = logDir2 ? join39(logDir2, "out.log") : null;
23130
24426
  const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
23131
24427
  inkInstance?.clear();
23132
24428
  origOut.call(stdout, `
@@ -23170,7 +24466,7 @@ $ ${command}
23170
24466
  if (captured && logPath) {
23171
24467
  output = cleanTerminalOutput(await readFile24(logPath, "utf8").catch(() => ""));
23172
24468
  }
23173
- if (logDir2) await rm3(logDir2, { recursive: true, force: true }).catch(() => {
24469
+ if (logDir2) await rm4(logDir2, { recursive: true, force: true }).catch(() => {
23174
24470
  });
23175
24471
  return { command, exitCode: result2.code, signal: result2.signal, output };
23176
24472
  }
@@ -25357,9 +26653,9 @@ __export(camouflage_view_exports, {
25357
26653
  validateCommandName: () => validateCommandName
25358
26654
  });
25359
26655
  import { execFileSync as execFileSync2, spawn as spawn9 } from "child_process";
25360
- import { readdir as readdir5 } from "fs/promises";
25361
- import { homedir as homedir25 } from "os";
25362
- import { join as join36, resolve as resolve11 } from "path";
26656
+ import { readdir as readdir6 } from "fs/promises";
26657
+ import { homedir as homedir26 } from "os";
26658
+ import { join as join40, resolve as resolve11 } from "path";
25363
26659
  async function loadSdk() {
25364
26660
  const sdk = await import("camouflage-tui").catch((err) => {
25365
26661
  throw new CamouflageUnavailable(`camouflage-tui is not installed (${err.message})`);
@@ -25538,10 +26834,10 @@ function truncateText(s, max) {
25538
26834
  async function listMentionDir(cwd, query) {
25539
26835
  const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
25540
26836
  const dir = exact ? `${query}/` : query.slice(0, query.lastIndexOf("/") + 1);
25541
- const expanded = dir.startsWith("~") ? join36(homedir25(), dir.slice(1)) : dir;
26837
+ const expanded = dir.startsWith("~") ? join40(homedir26(), dir.slice(1)) : dir;
25542
26838
  const target = resolve11(cwd, expanded || ".");
25543
26839
  try {
25544
- const items = await readdir5(target, { withFileTypes: true });
26840
+ const items = await readdir6(target, { withFileTypes: true });
25545
26841
  const entries = items.filter((d) => !d.name.startsWith(".")).map((d) => ({ name: d.name, isDir: d.isDirectory() })).sort((a, b) => a.isDir !== b.isDir ? a.isDir ? -1 : 1 : a.name.localeCompare(b.name)).slice(0, 300).map((d) => `${dir}${d.name}${d.isDir ? "/" : ""}`);
25546
26842
  return { dir, entries };
25547
26843
  } catch {
@@ -25619,7 +26915,7 @@ function shortModel(id) {
25619
26915
  return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
25620
26916
  }
25621
26917
  function shortPath(p) {
25622
- const home = homedir25();
26918
+ const home = homedir26();
25623
26919
  return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
25624
26920
  }
25625
26921
  function gitBranch(cwd) {
@@ -27626,7 +28922,7 @@ var init_tool_view = __esm({
27626
28922
  "use strict";
27627
28923
  init_spinner();
27628
28924
  init_diff_view();
27629
- init_paths();
28925
+ init_paths2();
27630
28926
  init_theme_context();
27631
28927
  init_narrator();
27632
28928
  ToolView = React7.memo(function ToolView2({ evt, verbose, isRepeated, intentTier }) {
@@ -28373,7 +29669,7 @@ function PermissionModal({ tool, args, onDecide, onFeedback }) {
28373
29669
  );
28374
29670
  const handleFeedbackSubmit = useCallback(
28375
29671
  (text) => {
28376
- onDecide(DENY);
29672
+ onDecide(text.trim() ? { ...DENY, feedback: text.trim() } : DENY);
28377
29673
  if (text.trim()) {
28378
29674
  onFeedback?.(text);
28379
29675
  }
@@ -28889,7 +30185,7 @@ var init_queue_plan_picker = __esm({
28889
30185
  // src/agent/jev-context.ts
28890
30186
  import { execFileSync as execFileSync3 } from "child_process";
28891
30187
  import { readFile as readFile25 } from "fs/promises";
28892
- import { basename as basename6, join as join37, resolve as resolve12 } from "path";
30188
+ import { basename as basename6, join as join41, resolve as resolve12 } from "path";
28893
30189
  function messageText(message2) {
28894
30190
  if (typeof message2.content === "string") return message2.content.trim();
28895
30191
  if (!Array.isArray(message2.content)) return "";
@@ -28911,7 +30207,7 @@ function projectRoot(cwd) {
28911
30207
  }
28912
30208
  async function readPackageMetadata(root) {
28913
30209
  try {
28914
- const parsed = JSON.parse(await readFile25(join37(root, "package.json"), "utf8"));
30210
+ const parsed = JSON.parse(await readFile25(join41(root, "package.json"), "utf8"));
28915
30211
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
28916
30212
  const pkg = parsed;
28917
30213
  const metadata = {};
@@ -28928,7 +30224,7 @@ async function readPackageMetadata(root) {
28928
30224
  async function readLicenseEvidence(root) {
28929
30225
  for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
28930
30226
  try {
28931
- const content = (await readFile25(join37(root, name), "utf8")).trim();
30227
+ const content = (await readFile25(join41(root, name), "utf8")).trim();
28932
30228
  if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
28933
30229
  } catch {
28934
30230
  }
@@ -30010,8 +31306,8 @@ var init_frontmatter = __esm({
30010
31306
  });
30011
31307
 
30012
31308
  // src/skills/loader.ts
30013
- import { readFile as readFile26, readdir as readdir6, stat as stat7 } from "fs/promises";
30014
- import { join as join38, extname } from "path";
31309
+ import { readFile as readFile26, readdir as readdir7, stat as stat8 } from "fs/promises";
31310
+ import { join as join42, extname } from "path";
30015
31311
  function normalizeManifest(raw, filePath) {
30016
31312
  const name = typeof raw.name === "string" ? raw.name : "";
30017
31313
  const description = typeof raw.description === "string" ? raw.description : "";
@@ -30044,11 +31340,11 @@ async function loadSkillFile(filePath) {
30044
31340
  }
30045
31341
  async function loadSkillsFromDir(dirPath) {
30046
31342
  try {
30047
- const entries = await readdir6(dirPath);
31343
+ const entries = await readdir7(dirPath);
30048
31344
  const files = [];
30049
31345
  for (const entry of entries) {
30050
- const full = join38(dirPath, entry);
30051
- const s = await stat7(full);
31346
+ const full = join42(dirPath, entry);
31347
+ const s = await stat8(full);
30052
31348
  if (s.isFile() && extname(entry) === ".md") {
30053
31349
  files.push(full);
30054
31350
  }
@@ -30076,11 +31372,11 @@ var init_loader = __esm({
30076
31372
 
30077
31373
  // src/skills/manager.ts
30078
31374
  import { mkdir as mkdir15, writeFile as writeFile14, unlink as unlink4, readFile as readFile27 } from "fs/promises";
30079
- import { join as join39 } from "path";
31375
+ import { join as join43 } from "path";
30080
31376
  function getSkillDirs(cwd) {
30081
31377
  return {
30082
- projectDir: join39(cwd, ".kimiflare", "skills"),
30083
- globalDir: join39(process.env.HOME ?? "", ".config", "kimiflare", "skills")
31378
+ projectDir: join43(cwd, ".kimiflare", "skills"),
31379
+ globalDir: join43(process.env.HOME ?? "", ".config", "kimiflare", "skills")
30084
31380
  };
30085
31381
  }
30086
31382
  async function listAllSkills(cwd) {
@@ -30094,7 +31390,7 @@ async function listAllSkills(cwd) {
30094
31390
  async function createSkill(opts2) {
30095
31391
  const dirs = getSkillDirs(opts2.cwd);
30096
31392
  const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
30097
- const filepath = join39(dir, `${opts2.name}.md`);
31393
+ const filepath = join43(dir, `${opts2.name}.md`);
30098
31394
  const frontmatter = {
30099
31395
  name: opts2.name,
30100
31396
  enabled: true,
@@ -30403,14 +31699,14 @@ __export(tui_report_exports, {
30403
31699
  getCategoryReportText: () => getCategoryReportText
30404
31700
  });
30405
31701
  import { readFile as readFile28 } from "fs/promises";
30406
- import { join as join40 } from "path";
30407
- import { homedir as homedir26 } from "os";
31702
+ import { join as join44 } from "path";
31703
+ import { homedir as homedir27 } from "os";
30408
31704
  function usageDir3() {
30409
- const xdg = process.env.XDG_DATA_HOME || join40(homedir26(), ".local", "share");
30410
- return join40(xdg, "kimiflare");
31705
+ const xdg = process.env.XDG_DATA_HOME || join44(homedir27(), ".local", "share");
31706
+ return join44(xdg, "kimiflare");
30411
31707
  }
30412
31708
  function usagePath4() {
30413
- return join40(usageDir3(), "usage.json");
31709
+ return join44(usageDir3(), "usage.json");
30414
31710
  }
30415
31711
  function today3() {
30416
31712
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -30479,7 +31775,7 @@ var init_tui_report = __esm({
30479
31775
  });
30480
31776
 
30481
31777
  // src/ui/slash-commands.ts
30482
- import { join as join41 } from "path";
31778
+ import { join as join45 } from "path";
30483
31779
  import QRCode from "qrcode";
30484
31780
  function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
30485
31781
  const oldSessionId = ctx.sessionIdRef.current;
@@ -30570,6 +31866,28 @@ function modesGate(ctx) {
30570
31866
  ]);
30571
31867
  return false;
30572
31868
  }
31869
+ function rateLastTurn(ctx, value, reason) {
31870
+ const { mkKey: mkKey2, setEvents } = ctx;
31871
+ const last = ctx.turnHistoryRef.current.at(-1);
31872
+ if (!last) {
31873
+ setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to rate yet \u2014 finish a turn first" }]);
31874
+ return true;
31875
+ }
31876
+ const trace = ctx.traceRef.current;
31877
+ if (!trace.enabled) {
31878
+ setEvents((e) => [
31879
+ ...e,
31880
+ { kind: "info", key: mkKey2(), text: "session tracing is off, so the rating was not recorded (enable with KIMIFLARE_TRACE=1)" }
31881
+ ]);
31882
+ return true;
31883
+ }
31884
+ trace.emit("outcome", { kind: "rating", turn_id: last.turnId, value, ...reason ? { reason } : {} }, last.turnId);
31885
+ setEvents((e) => [
31886
+ ...e,
31887
+ { kind: "info", key: mkKey2(), text: `recorded ${value === "good" ? "\u{1F44D} good" : "\u{1F44E} bad"} for the last turn${reason ? `: ${reason}` : ""}` }
31888
+ ]);
31889
+ return true;
31890
+ }
30573
31891
  function dispatchSlashCommand(ctx, cmd) {
30574
31892
  const raw = cmd.trim();
30575
31893
  const [head, ...rest] = raw.split(/\s+/);
@@ -30579,7 +31897,7 @@ function dispatchSlashCommand(ctx, cmd) {
30579
31897
  if (!handler) return false;
30580
31898
  return handler(ctx, rest, arg);
30581
31899
  }
30582
- var handleExit, handleClear, handleFresh, handleReasoning, handleCost, handleShell, handleSubagents, handleJev, handleModel, SETTINGS, handleSettings, handleKey, handleMode, handleMultiAgent, handleTheme, handlePlan, handleAuto, handleEdit, handleSkills, handleMemory, handleResume, handleCheckpoint, handleCompact, handleInit, handleUpdate, handleMcp, handleLsp, handleHooks, handleHello, handleInbox, handleLogout, handleCommand, handleRemote, handleHelp, handleChangelogImage, handlers;
31900
+ var handleExit, handleClear, handleFresh, handleReasoning, handleCost, handleShell, handleSubagents, handleJev, handleModel, SETTINGS, handleSettings, handleKey, handleMode, handleMultiAgent, handleTheme, handlePlan, handleAuto, handleEdit, handleSkills, handleMemory, handleResume, handleCheckpoint, handleCompact, handleInit, handleUpdate, handleMcp, handleLsp, handleHooks, handleHello, handleInbox, handleLogout, handleCommand, handleRemote, handleHelp, handleChangelogImage, handleGood, handleBad, handlers;
30583
31901
  var init_slash_commands = __esm({
30584
31902
  "src/ui/slash-commands.ts"() {
30585
31903
  "use strict";
@@ -30602,7 +31920,7 @@ var init_slash_commands = __esm({
30602
31920
  init_executor();
30603
31921
  init_recommended();
30604
31922
  init_settings();
30605
- init_types();
31923
+ init_types2();
30606
31924
  init_update_check();
30607
31925
  init_version();
30608
31926
  init_app_helpers();
@@ -31389,7 +32707,7 @@ ${lines.join("\n")}` }]);
31389
32707
  void (async () => {
31390
32708
  try {
31391
32709
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
31392
- const file = await loadSession(join41(sessionsDir3(), `${currentId}.json`));
32710
+ const file = await loadSession(join45(sessionsDir3(), `${currentId}.json`));
31393
32711
  const cps = file.checkpoints ?? [];
31394
32712
  if (cps.length === 0) {
31395
32713
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
@@ -31434,7 +32752,7 @@ ${lines.join("\n")}` }]);
31434
32752
  try {
31435
32753
  ctx.ensureSessionId();
31436
32754
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
31437
- const filePath = join41(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
32755
+ const filePath = join45(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
31438
32756
  await addCheckpoint(filePath, cp);
31439
32757
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
31440
32758
  } catch (e) {
@@ -32025,6 +33343,8 @@ project: ${projectSettingsPath(cwd)}`
32025
33343
  });
32026
33344
  return true;
32027
33345
  };
33346
+ handleGood = (ctx, rest) => rateLastTurn(ctx, "good", rest.join(" ").trim());
33347
+ handleBad = (ctx, rest) => rateLastTurn(ctx, "bad", rest.join(" ").trim());
32028
33348
  handlers = {
32029
33349
  "/exit": handleExit,
32030
33350
  "/clear": handleClear,
@@ -32048,6 +33368,8 @@ project: ${projectSettingsPath(cwd)}`
32048
33368
  "/resume": handleResume,
32049
33369
  "/checkpoint": handleCheckpoint,
32050
33370
  "/compact": handleCompact,
33371
+ "/good": handleGood,
33372
+ "/bad": handleBad,
32051
33373
  "/init": handleInit,
32052
33374
  "/update": handleUpdate,
32053
33375
  "/mcp": handleMcp,
@@ -32379,7 +33701,7 @@ __export(openrouter_oauth_exports, {
32379
33701
  pkceChallenge: () => pkceChallenge,
32380
33702
  startLoopbackListener: () => startLoopbackListener
32381
33703
  });
32382
- import { createHash as createHash4, randomBytes as randomBytes3 } from "crypto";
33704
+ import { createHash as createHash5, randomBytes as randomBytes3 } from "crypto";
32383
33705
  import { createServer as createServer3 } from "http";
32384
33706
  import { hostname } from "os";
32385
33707
  function createPkce() {
@@ -32387,7 +33709,7 @@ function createPkce() {
32387
33709
  return { verifier, challenge: pkceChallenge(verifier) };
32388
33710
  }
32389
33711
  function pkceChallenge(verifier) {
32390
- return createHash4("sha256").update(verifier).digest("base64url");
33712
+ return createHash5("sha256").update(verifier).digest("base64url");
32391
33713
  }
32392
33714
  function openRouterAuthPageUrl() {
32393
33715
  return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
@@ -32899,14 +34221,14 @@ var init_frontmatter2 = __esm({
32899
34221
 
32900
34222
  // src/commands/loader.ts
32901
34223
  import { open, realpath as realpath6 } from "fs/promises";
32902
- import { homedir as homedir27 } from "os";
32903
- import { join as join42, relative as relative10, sep as sep6 } from "path";
34224
+ import { homedir as homedir28 } from "os";
34225
+ import { join as join46, relative as relative10, sep as sep6 } from "path";
32904
34226
  function projectCommandsDir(cwd = process.cwd()) {
32905
- return join42(cwd, ".kimiflare", "commands");
34227
+ return join46(cwd, ".kimiflare", "commands");
32906
34228
  }
32907
34229
  function globalCommandsDir() {
32908
- const xdg = process.env.XDG_CONFIG_HOME || join42(homedir27(), ".config");
32909
- return join42(xdg, "kimiflare", "commands");
34230
+ const xdg = process.env.XDG_CONFIG_HOME || join46(homedir28(), ".config");
34231
+ return join46(xdg, "kimiflare", "commands");
32910
34232
  }
32911
34233
  async function loadCustomCommands(cwd = process.cwd()) {
32912
34234
  const warnings = [];
@@ -33050,7 +34372,7 @@ var init_loader2 = __esm({
33050
34372
  init_mode();
33051
34373
  init_glob();
33052
34374
  init_config();
33053
- init_paths();
34375
+ init_paths2();
33054
34376
  init_frontmatter2();
33055
34377
  MAX_COMMAND_FILE_BYTES = 256 * 1024;
33056
34378
  }
@@ -33060,7 +34382,7 @@ var init_loader2 = __esm({
33060
34382
  import { exec } from "child_process";
33061
34383
  import { open as open2, realpath as realpath7 } from "fs/promises";
33062
34384
  import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolvePathJoin, basename as pathBasename } from "path";
33063
- import { promisify as promisify5 } from "util";
34385
+ import { promisify as promisify7 } from "util";
33064
34386
  function tokenizeArgs(s) {
33065
34387
  return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
33066
34388
  const token = match[0];
@@ -33212,8 +34534,8 @@ var execAsync, ARG_TOKEN_RE, POSITIONAL_RE, HAS_POSITIONAL, SHELL_RE, FILE_RE, D
33212
34534
  var init_renderer2 = __esm({
33213
34535
  "src/commands/renderer.ts"() {
33214
34536
  "use strict";
33215
- init_paths();
33216
- execAsync = promisify5(exec);
34537
+ init_paths2();
34538
+ execAsync = promisify7(exec);
33217
34539
  ARG_TOKEN_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
33218
34540
  POSITIONAL_RE = /\$(\d+)/g;
33219
34541
  HAS_POSITIONAL = /\$\d+/;
@@ -33288,11 +34610,11 @@ var init_wcag = __esm({
33288
34610
  });
33289
34611
 
33290
34612
  // src/ui/theme-loader.ts
33291
- import { readFile as readFile29, readdir as readdir7 } from "fs/promises";
33292
- import { join as join43 } from "path";
33293
- import { homedir as homedir28 } from "os";
34613
+ import { readFile as readFile29, readdir as readdir8 } from "fs/promises";
34614
+ import { join as join47 } from "path";
34615
+ import { homedir as homedir29 } from "os";
33294
34616
  function projectThemesDir(cwd = process.cwd()) {
33295
- return join43(cwd, ".kimiflare", "themes");
34617
+ return join47(cwd, ".kimiflare", "themes");
33296
34618
  }
33297
34619
  function isHexColor(c) {
33298
34620
  return /^#[0-9a-fA-F]{6}$/.test(c);
@@ -33376,12 +34698,12 @@ async function loadThemesFromDir(dir, source) {
33376
34698
  const errors = [];
33377
34699
  let files;
33378
34700
  try {
33379
- files = await readdir7(dir);
34701
+ files = await readdir8(dir);
33380
34702
  } catch {
33381
34703
  return { themes, errors };
33382
34704
  }
33383
34705
  for (const file of files.filter((f) => f.endsWith(".json"))) {
33384
- const path = join43(dir, file);
34706
+ const path = join47(dir, file);
33385
34707
  let raw;
33386
34708
  try {
33387
34709
  raw = await readFile29(path, "utf-8");
@@ -33530,8 +34852,8 @@ var init_theme_loader = __esm({
33530
34852
  "use strict";
33531
34853
  init_wcag();
33532
34854
  init_theme();
33533
- USER_THEMES_DIR = join43(
33534
- process.env.XDG_CONFIG_HOME || join43(homedir28(), ".config"),
34855
+ USER_THEMES_DIR = join47(
34856
+ process.env.XDG_CONFIG_HOME || join47(homedir29(), ".config"),
33535
34857
  "kimiflare",
33536
34858
  "themes"
33537
34859
  );
@@ -35689,6 +37011,61 @@ function useSessionManager(deps) {
35689
37011
  setLogSessionId(sessionIdRef.current);
35690
37012
  return sessionIdRef.current;
35691
37013
  }, []);
37014
+ const traceRef = useRef7(createNoopRecorder());
37015
+ const traceStartedRef = useRef7(false);
37016
+ const turnHistoryRef = useRef7([]);
37017
+ const noteTurnEnded = useCallback6((turnId) => {
37018
+ turnHistoryRef.current.push({ turnId, endedAt: Date.now() });
37019
+ if (turnHistoryRef.current.length > 100) turnHistoryRef.current.shift();
37020
+ }, []);
37021
+ const endTrace = useCallback6((reason) => {
37022
+ const trace = traceRef.current;
37023
+ if (trace.enabled && traceStartedRef.current) trace.emit("session_end", { reason }, null);
37024
+ traceStartedRef.current = false;
37025
+ traceRef.current = createNoopRecorder();
37026
+ }, []);
37027
+ const traceReverted = useCallback6((how, turns) => {
37028
+ const trace = traceRef.current;
37029
+ if (!trace.enabled) return;
37030
+ for (const turn of turns) trace.emit("outcome", { kind: "reverted", turn_id: turn.turnId, how }, turn.turnId);
37031
+ }, []);
37032
+ const ensureTrace = useCallback6(() => {
37033
+ const id = ensureSessionId();
37034
+ const cfgTrace = depsRef.current.cfg?.trace;
37035
+ const wantEnabled = cfgTrace?.enabled === true;
37036
+ const current = traceRef.current;
37037
+ if (current.sessionId === id && current.enabled === wantEnabled) return current;
37038
+ if (current.enabled && traceStartedRef.current) current.emit("session_end", { reason: "exit" }, null);
37039
+ traceStartedRef.current = false;
37040
+ traceRef.current = createRecorder({ enabled: wantEnabled, sessionId: id, root: cfgTrace?.root });
37041
+ return traceRef.current;
37042
+ }, [ensureSessionId]);
37043
+ const ensureTraceStarted = useCallback6(
37044
+ async (info) => {
37045
+ const trace = ensureTrace();
37046
+ if (!trace.enabled || traceStartedRef.current) return trace;
37047
+ traceStartedRef.current = true;
37048
+ await emitSessionStart(trace, {
37049
+ cwd: process.cwd(),
37050
+ model: info.model,
37051
+ mode: info.mode,
37052
+ config: info.config,
37053
+ tools: info.tools,
37054
+ systemPrompt: info.systemPrompt,
37055
+ toolProfile: info.toolProfile
37056
+ });
37057
+ return trace;
37058
+ },
37059
+ [ensureTrace]
37060
+ );
37061
+ React23.useEffect(() => {
37062
+ const onExit = () => endTrace("exit");
37063
+ process.on("exit", onExit);
37064
+ return () => {
37065
+ process.off("exit", onExit);
37066
+ endTrace("exit");
37067
+ };
37068
+ }, [endTrace]);
35692
37069
  const saveSessionSafe = useCallback6(async () => {
35693
37070
  const d = depsRef.current;
35694
37071
  if (!d.cfg) return;
@@ -35727,11 +37104,18 @@ function useSessionManager(deps) {
35727
37104
  setResumeProgress(0);
35728
37105
  setResumeStage("Loading session file\u2026");
35729
37106
  startSmoothProgress(0, 10, 400);
35730
- const file = checkpointId ? (await loadSessionFromCheckpoint(filePath, checkpointId)).file : await loadSession(filePath);
37107
+ const loaded = checkpointId ? await loadSessionFromCheckpoint(filePath, checkpointId) : null;
37108
+ const file = loaded ? loaded.file : await loadSession(filePath);
35731
37109
  setResumeProgress(10);
35732
37110
  setResumeStage("Restoring artifact store\u2026");
35733
37111
  startSmoothProgress(10, 20, 400);
35734
37112
  d.messagesRef.current = file.messages;
37113
+ if (loaded && file.id === sessionIdRef.current) {
37114
+ const since = Date.parse(loaded.checkpoint.timestamp);
37115
+ traceReverted("restore_checkpoint", turnHistoryRef.current.filter((t) => Number.isNaN(since) || t.endedAt > since));
37116
+ }
37117
+ endTrace(checkpointId ? "restore_checkpoint" : "exit");
37118
+ turnHistoryRef.current = [];
35735
37119
  sessionIdRef.current = file.id;
35736
37120
  setLogSessionId(file.id);
35737
37121
  sessionCreatedAtRef.current = file.createdAt;
@@ -35907,6 +37291,9 @@ ${summary}`
35907
37291
  [checkpointSession, doResumeSession]
35908
37292
  );
35909
37293
  const resetSession = useCallback6(() => {
37294
+ traceReverted("clear", turnHistoryRef.current.slice(-2));
37295
+ turnHistoryRef.current = [];
37296
+ endTrace("clear");
35910
37297
  sessionIdRef.current = null;
35911
37298
  setLogSessionId(null);
35912
37299
  sessionCreatedAtRef.current = null;
@@ -35914,7 +37301,7 @@ ${summary}`
35914
37301
  const d = depsRef.current;
35915
37302
  d.sessionStateRef.current = emptySessionState();
35916
37303
  d.artifactStoreRef.current = new ArtifactStore();
35917
- }, []);
37304
+ }, [endTrace, traceReverted]);
35918
37305
  return {
35919
37306
  sessionIdRef,
35920
37307
  sessionCreatedAtRef,
@@ -35936,7 +37323,13 @@ ${summary}`
35936
37323
  doResumeSession,
35937
37324
  handleResumePick,
35938
37325
  handleCheckpointPick,
35939
- resetSession
37326
+ resetSession,
37327
+ traceRef,
37328
+ ensureTrace,
37329
+ ensureTraceStarted,
37330
+ endTrace,
37331
+ turnHistoryRef,
37332
+ noteTurnEnded
35940
37333
  };
35941
37334
  }
35942
37335
  var init_use_session_manager = __esm({
@@ -35948,6 +37341,7 @@ var init_use_session_manager = __esm({
35948
37341
  init_recall_inject();
35949
37342
  init_usage_tracker();
35950
37343
  init_log_sink();
37344
+ init_trace();
35951
37345
  init_app_helpers();
35952
37346
  init_continuation_summary();
35953
37347
  init_config();
@@ -35986,9 +37380,9 @@ var init_resume_progress = __esm({
35986
37380
  });
35987
37381
 
35988
37382
  // src/agent/supervisor.ts
35989
- import { readdir as readdir8, readFile as readFile30, stat as stat8 } from "fs/promises";
35990
- import { createHash as createHash5 } from "crypto";
35991
- import { resolve as resolve13, join as join44 } from "path";
37383
+ import { readdir as readdir9, readFile as readFile30, stat as stat9 } from "fs/promises";
37384
+ import { createHash as createHash6 } from "crypto";
37385
+ import { resolve as resolve13, join as join48 } from "path";
35992
37386
  var TurnSupervisor;
35993
37387
  var init_supervisor = __esm({
35994
37388
  "src/agent/supervisor.ts"() {
@@ -36346,6 +37740,7 @@ function interruptTurn(deps) {
36346
37740
  ...e,
36347
37741
  { kind: "info", key: deps.mkKey(), text: "(interrupted)" }
36348
37742
  ]);
37743
+ appendStoppedToolResults(deps.messagesRef.current, deps.pendingToolCallsRef.current);
36349
37744
  if (!deps.skipPendingToolCleanup) {
36350
37745
  for (const [toolId] of deps.pendingToolCallsRef.current) {
36351
37746
  deps.updateTool(toolId, { status: "cancelled" });
@@ -36371,17 +37766,18 @@ function interruptOrExit(deps) {
36371
37766
  var init_input_handlers = __esm({
36372
37767
  "src/ui/input-handlers.ts"() {
36373
37768
  "use strict";
37769
+ init_messages();
36374
37770
  }
36375
37771
  });
36376
37772
 
36377
37773
  // src/init/context-generator.ts
36378
- import { existsSync as existsSync6, statSync as statSync6 } from "fs";
36379
- import { join as join45 } from "path";
37774
+ import { existsSync as existsSync9, statSync as statSync6 } from "fs";
37775
+ import { join as join49 } from "path";
36380
37776
  function detectFlavor(cwd) {
36381
37777
  for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
36382
37778
  if (flavor === "generic") continue;
36383
37779
  for (const sig of signatures) {
36384
- const path = join45(cwd, sig);
37780
+ const path = join49(cwd, sig);
36385
37781
  if (sig.includes("*")) {
36386
37782
  try {
36387
37783
  const parts = sig.split("*");
@@ -36393,7 +37789,7 @@ function detectFlavor(cwd) {
36393
37789
  }
36394
37790
  } catch {
36395
37791
  }
36396
- } else if (existsSync6(path)) {
37792
+ } else if (existsSync9(path)) {
36397
37793
  return flavor;
36398
37794
  }
36399
37795
  }
@@ -36402,14 +37798,14 @@ function detectFlavor(cwd) {
36402
37798
  }
36403
37799
  function findFile(cwd, candidates) {
36404
37800
  for (const c of candidates) {
36405
- if (existsSync6(join45(cwd, c))) return c;
37801
+ if (existsSync9(join49(cwd, c))) return c;
36406
37802
  }
36407
37803
  return null;
36408
37804
  }
36409
37805
  function findSourceRoots(cwd) {
36410
37806
  const roots = [];
36411
37807
  for (const r of SOURCE_ROOT_CANDIDATES) {
36412
- const p = join45(cwd, r);
37808
+ const p = join49(cwd, r);
36413
37809
  try {
36414
37810
  const s = statSync6(p);
36415
37811
  if (s.isDirectory()) roots.push(r);
@@ -36420,9 +37816,9 @@ function findSourceRoots(cwd) {
36420
37816
  }
36421
37817
  function findCiConfig(cwd) {
36422
37818
  for (const c of CI_PATHS) {
36423
- if (existsSync6(join45(cwd, c))) {
37819
+ if (existsSync9(join49(cwd, c))) {
36424
37820
  try {
36425
- const s = statSync6(join45(cwd, c));
37821
+ const s = statSync6(join49(cwd, c));
36426
37822
  return s.isDirectory() ? c : c;
36427
37823
  } catch {
36428
37824
  }
@@ -36559,7 +37955,7 @@ function analyzeProject(cwd) {
36559
37955
  ciConfig: findCiConfig(cwd),
36560
37956
  readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
36561
37957
  sourceRoots: findSourceRoots(cwd),
36562
- hasGit: existsSync6(join45(cwd, ".git"))
37958
+ hasGit: existsSync9(join49(cwd, ".git"))
36563
37959
  };
36564
37960
  }
36565
37961
  function bashDiscoveryCommands(profile) {
@@ -36724,7 +38120,7 @@ Aim for 100\u2013200 lines total. Use markdown tables where they save space.
36724
38120
  }
36725
38121
  function buildInitPrompt(cwd) {
36726
38122
  const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
36727
- (n) => existsSync6(join45(cwd, n))
38123
+ (n) => existsSync9(join49(cwd, n))
36728
38124
  );
36729
38125
  const isRefresh = existingName !== void 0;
36730
38126
  const targetFilename = existingName ?? "AGENTS.md";
@@ -36807,8 +38203,8 @@ var init_context_generator = __esm({
36807
38203
  });
36808
38204
 
36809
38205
  // src/init/run-init.ts
36810
- import { existsSync as existsSync7 } from "fs";
36811
- import { join as join46 } from "path";
38206
+ import { existsSync as existsSync10 } from "fs";
38207
+ import { join as join50 } from "path";
36812
38208
  async function runInit(deps) {
36813
38209
  const {
36814
38210
  cfg,
@@ -37006,7 +38402,7 @@ async function runInit(deps) {
37006
38402
  }
37007
38403
  }
37008
38404
  });
37009
- if (existsSync7(join46(cwd, targetFilename))) {
38405
+ if (existsSync10(join50(cwd, targetFilename))) {
37010
38406
  if (cacheStableRef.current) {
37011
38407
  messagesRef.current[1] = {
37012
38408
  role: "system",
@@ -37098,11 +38494,11 @@ var init_run_init = __esm({
37098
38494
  });
37099
38495
 
37100
38496
  // src/skills/discovery.ts
37101
- import { readdir as readdir9, stat as stat9, readFile as readFile31 } from "fs/promises";
37102
- import { join as join47, extname as extname2 } from "path";
38497
+ import { readdir as readdir10, stat as stat10, readFile as readFile31 } from "fs/promises";
38498
+ import { join as join51, extname as extname2 } from "path";
37103
38499
  async function dirExists(path) {
37104
38500
  try {
37105
- const s = await stat9(path);
38501
+ const s = await stat10(path);
37106
38502
  return s.isDirectory();
37107
38503
  } catch {
37108
38504
  return false;
@@ -37110,7 +38506,7 @@ async function dirExists(path) {
37110
38506
  }
37111
38507
  async function fileExists(path) {
37112
38508
  try {
37113
- const s = await stat9(path);
38509
+ const s = await stat10(path);
37114
38510
  return s.isFile();
37115
38511
  } catch {
37116
38512
  return false;
@@ -37118,20 +38514,20 @@ async function fileExists(path) {
37118
38514
  }
37119
38515
  async function scanSkillDir(dirPath, source) {
37120
38516
  if (!await dirExists(dirPath)) return [];
37121
- const entries = await readdir9(dirPath, { withFileTypes: true });
38517
+ const entries = await readdir10(dirPath, { withFileTypes: true });
37122
38518
  const files = [];
37123
38519
  for (const entry of entries) {
37124
38520
  if (!entry.isFile()) continue;
37125
38521
  if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
37126
- files.push({ filePath: join47(dirPath, entry.name), source });
38522
+ files.push({ filePath: join51(dirPath, entry.name), source });
37127
38523
  }
37128
38524
  return files;
37129
38525
  }
37130
38526
  async function discoverSkills(cwd) {
37131
- const agentsSkills = await scanSkillDir(join47(cwd, ".agents", "skills"), "agents");
37132
- const agentsMd = await fileExists(join47(cwd, "AGENTS.md")) ? [{ filePath: join47(cwd, "AGENTS.md"), source: "agents-md" }] : [];
37133
- const githubSkills = await scanSkillDir(join47(cwd, ".github", "skills"), "github");
37134
- const kimiflareSkills = await scanSkillDir(join47(cwd, ".kimiflare", "skills"), "kimiflare");
38527
+ const agentsSkills = await scanSkillDir(join51(cwd, ".agents", "skills"), "agents");
38528
+ const agentsMd = await fileExists(join51(cwd, "AGENTS.md")) ? [{ filePath: join51(cwd, "AGENTS.md"), source: "agents-md" }] : [];
38529
+ const githubSkills = await scanSkillDir(join51(cwd, ".github", "skills"), "github");
38530
+ const kimiflareSkills = await scanSkillDir(join51(cwd, ".kimiflare", "skills"), "kimiflare");
37135
38531
  const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
37136
38532
  return ordered;
37137
38533
  }
@@ -37148,9 +38544,9 @@ var init_discovery = __esm({
37148
38544
  });
37149
38545
 
37150
38546
  // src/skills/parser.ts
37151
- import { createHash as createHash6 } from "crypto";
38547
+ import { createHash as createHash7 } from "crypto";
37152
38548
  function sha2562(input) {
37153
- return createHash6("sha256").update(input).digest("hex");
38549
+ return createHash7("sha256").update(input).digest("hex");
37154
38550
  }
37155
38551
  function computeContentHash(rawText, parserVersion) {
37156
38552
  return sha2562(`${rawText}
@@ -37341,8 +38737,8 @@ var init_skills = __esm({
37341
38737
  });
37342
38738
 
37343
38739
  // src/ui/run-startup-tasks.ts
37344
- import { existsSync as existsSync8 } from "fs";
37345
- import { join as join48 } from "path";
38740
+ import { existsSync as existsSync11 } from "fs";
38741
+ import { join as join52 } from "path";
37346
38742
  function runStartupTasks(deps) {
37347
38743
  const {
37348
38744
  cfg,
@@ -37356,6 +38752,7 @@ function runStartupTasks(deps) {
37356
38752
  } = deps;
37357
38753
  const auth = llmAuthFromConfig(cfg);
37358
38754
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(({ pruneSessions: pruneSessions2 }) => pruneSessions2());
38755
+ void Promise.resolve().then(() => (init_retention(), retention_exports)).then(({ pruneTraces: pruneTraces2 }) => pruneTraces2({ root: cfg.trace?.root }));
37359
38756
  void Promise.resolve().then(() => (init_log_sink(), log_sink_exports)).then(({ pruneOldLogs: pruneOldLogs2, isLogSinkEnabled: isLogSinkEnabled2 }) => {
37360
38757
  if (!isLogSinkEnabled2()) return;
37361
38758
  try {
@@ -37364,7 +38761,7 @@ function runStartupTasks(deps) {
37364
38761
  }
37365
38762
  });
37366
38763
  if (cfg.memoryEnabled) {
37367
- const dbPath = cfg.memoryDbPath ?? join48(process.cwd(), ".kimiflare", "memory.db");
38764
+ const dbPath = cfg.memoryDbPath ?? join52(process.cwd(), ".kimiflare", "memory.db");
37368
38765
  const manager = new MemoryManager({
37369
38766
  dbPath,
37370
38767
  ...auth,
@@ -37396,7 +38793,7 @@ function runStartupTasks(deps) {
37396
38793
  });
37397
38794
  const cwd = process.cwd();
37398
38795
  sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
37399
- if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(join48(cwd, name)))) {
38796
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync11(join52(cwd, name)))) {
37400
38797
  const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
37401
38798
  const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
37402
38799
  if (driftCount >= 5) {
@@ -37407,7 +38804,7 @@ function runStartupTasks(deps) {
37407
38804
  memoryManagerRef.current?.close();
37408
38805
  memoryManagerRef.current = null;
37409
38806
  }
37410
- const skillDbPath = cfg.memoryDbPath ?? join48(process.cwd(), ".kimiflare", "memory.db");
38807
+ const skillDbPath = cfg.memoryDbPath ?? join52(process.cwd(), ".kimiflare", "memory.db");
37411
38808
  const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
37412
38809
  initSkillsSchema(skillDb);
37413
38810
  void indexSkills({
@@ -37652,7 +39049,8 @@ async function runCompact(deps) {
37652
39049
  pendingToolCallsRef,
37653
39050
  hooks,
37654
39051
  sessionId,
37655
- supervisorRef
39052
+ supervisorRef,
39053
+ trace
37656
39054
  } = deps;
37657
39055
  if (busy || supervisorRef.current.isRunning) {
37658
39056
  setEvents((e) => [
@@ -37688,6 +39086,7 @@ async function runCompact(deps) {
37688
39086
  if (result2.metrics.rawTurnsRemoved === 0) {
37689
39087
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to compact yet" }]);
37690
39088
  } else {
39089
+ traceCompiledCompaction(trace, result2);
37691
39090
  messagesRef.current = result2.newMessages;
37692
39091
  sessionStateRef.current = result2.newState;
37693
39092
  setEvents(
@@ -37706,6 +39105,7 @@ async function runCompact(deps) {
37706
39105
  await saveSessionSafe();
37707
39106
  }
37708
39107
  } else {
39108
+ const before = messagesRef.current;
37709
39109
  const result2 = await summarizeMessagesViaLlm({
37710
39110
  ...llmAuthFromConfig(cfg),
37711
39111
  model: cfg.model,
@@ -37715,6 +39115,7 @@ async function runCompact(deps) {
37715
39115
  if (result2.replacedCount === 0) {
37716
39116
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to compact yet" }]);
37717
39117
  } else {
39118
+ traceLlmCompaction(trace, before, result2);
37718
39119
  messagesRef.current = result2.newMessages;
37719
39120
  setEvents(
37720
39121
  (e) => compactEventsVisual(
@@ -37755,12 +39156,13 @@ var init_run_compact = __esm({
37755
39156
  init_logger();
37756
39157
  init_app_helpers();
37757
39158
  init_llm_auth();
39159
+ init_compaction();
37758
39160
  }
37759
39161
  });
37760
39162
 
37761
39163
  // src/commands/save.ts
37762
39164
  import { mkdir as mkdir16, writeFile as writeFile15, unlink as unlink5 } from "fs/promises";
37763
- import { dirname as dirname20 } from "path";
39165
+ import { dirname as dirname21 } from "path";
37764
39166
  async function saveCustomCommand(opts2) {
37765
39167
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
37766
39168
  const filepath = `${dir}/${opts2.name}.md`;
@@ -37771,7 +39173,7 @@ async function saveCustomCommand(opts2) {
37771
39173
  if (opts2.effort) data.effort = opts2.effort;
37772
39174
  const frontmatter = serializeFrontmatter(data);
37773
39175
  const content = frontmatter + opts2.template;
37774
- await mkdir16(dirname20(filepath), { recursive: true });
39176
+ await mkdir16(dirname21(filepath), { recursive: true });
37775
39177
  await writeFile15(filepath, content, "utf8");
37776
39178
  return { filepath };
37777
39179
  }
@@ -37894,9 +39296,9 @@ __export(app_exports, {
37894
39296
  });
37895
39297
  import React25, { useState as useState27, useRef as useRef9, useEffect as useEffect11, useCallback as useCallback8, useMemo as useMemo6 } from "react";
37896
39298
  import { Box as Box40, Text as Text41, useApp, useInput as useInput19, useWindowSize as useWindowSize2, render } from "ink";
37897
- import { existsSync as existsSync9 } from "fs";
37898
- import { join as join49, resolve as resolve14 } from "path";
37899
- import { homedir as homedir29 } from "os";
39299
+ import { existsSync as existsSync12 } from "fs";
39300
+ import { join as join53, resolve as resolve14 } from "path";
39301
+ import { homedir as homedir30 } from "os";
37900
39302
  import { jsx as jsx42, jsxs as jsxs39 } from "react/jsx-runtime";
37901
39303
  function App({
37902
39304
  initialCfg,
@@ -38153,6 +39555,8 @@ ${wcagWarnings.join("\n")}` }
38153
39555
  }, []);
38154
39556
  const sessionStartRecallRef = useRef9(null);
38155
39557
  const kimiMdStaleNudgedRef = useRef9(false);
39558
+ const currentTurnIdRef = useRef9(null);
39559
+ const denyFeedbackRef = useRef9(false);
38156
39560
  const sessionPlanRef = useRef9(null);
38157
39561
  const planCompletePlanRef = useRef9(null);
38158
39562
  const sessionMgr = useSessionManager({
@@ -38190,7 +39594,11 @@ ${wcagWarnings.join("\n")}` }
38190
39594
  doResumeSession,
38191
39595
  handleResumePick,
38192
39596
  handleCheckpointPick,
38193
- resetSession
39597
+ resetSession,
39598
+ traceRef,
39599
+ ensureTraceStarted,
39600
+ turnHistoryRef,
39601
+ noteTurnEnded
38194
39602
  } = sessionMgr;
38195
39603
  const pendingTextRef = useRef9(/* @__PURE__ */ new Map());
38196
39604
  const flushTimeoutRef = useRef9(null);
@@ -38216,7 +39624,7 @@ ${wcagWarnings.join("\n")}` }
38216
39624
  const exactDirectory = query === "~" || query === "." || query === ".." || query.endsWith("/..");
38217
39625
  const prefix = exactDirectory ? `${query.replace(/\/$/, "")}/` : slash >= 0 ? query.slice(0, slash + 1) : "";
38218
39626
  const directory = exactDirectory ? query : slash >= 0 ? query.slice(0, slash + 1) : query;
38219
- const expanded = directory.startsWith("~") ? join49(homedir29(), directory.slice(1)) : directory;
39627
+ const expanded = directory.startsWith("~") ? join53(homedir30(), directory.slice(1)) : directory;
38220
39628
  const target = resolve14(cwd, expanded || ".");
38221
39629
  const entries2 = await glob("*", {
38222
39630
  cwd: target,
@@ -38477,6 +39885,7 @@ ${wcagWarnings.join("\n")}` }
38477
39885
  store
38478
39886
  });
38479
39887
  if (result2.metrics.rawTurnsRemoved > 0) {
39888
+ traceCompiledCompaction(traceRef.current, result2, currentTurnIdRef.current);
38480
39889
  sessionStateRef.current = result2.newState;
38481
39890
  setEvents((e) => [
38482
39891
  ...e,
@@ -38522,6 +39931,7 @@ ${wcagWarnings.join("\n")}` }
38522
39931
  signal
38523
39932
  });
38524
39933
  if (result2.replacedCount > 0) {
39934
+ traceLlmCompaction(traceRef.current, messages, result2, currentTurnIdRef.current);
38525
39935
  setEvents((e) => [
38526
39936
  ...e,
38527
39937
  {
@@ -38668,6 +40078,7 @@ ${wcagWarnings.join("\n")}` }
38668
40078
  denyPendingPermission,
38669
40079
  pendingToolCallsRef,
38670
40080
  updateTool,
40081
+ messagesRef,
38671
40082
  setEvents,
38672
40083
  mkKey,
38673
40084
  saveSessionSafe,
@@ -38695,7 +40106,8 @@ ${wcagWarnings.join("\n")}` }
38695
40106
  pendingToolCallsRef,
38696
40107
  hooks: hooksManagerRef.current,
38697
40108
  sessionId: sessionIdRef.current,
38698
- supervisorRef
40109
+ supervisorRef,
40110
+ trace: traceRef.current
38699
40111
  });
38700
40112
  }, [cfg, busy, saveSessionSafe]);
38701
40113
  const runInit2 = useCallback8(async () => {
@@ -38875,7 +40287,9 @@ ${wcagWarnings.join("\n")}` }
38875
40287
  compiledContextRef,
38876
40288
  lastApiErrorRef,
38877
40289
  activeScopeRef,
38878
- sessionPlanRef
40290
+ sessionPlanRef,
40291
+ traceRef,
40292
+ turnHistoryRef
38879
40293
  }), [
38880
40294
  exit,
38881
40295
  busy,
@@ -39178,6 +40592,8 @@ ${result2.output ?? "(output not captured)"}`,
39178
40592
  }
39179
40593
  }
39180
40594
  messagesRef.current.push({ role: "user", content });
40595
+ const traceUserSource = denyFeedbackRef.current ? "deny_feedback" : "typed";
40596
+ denyFeedbackRef.current = false;
39181
40597
  await saveSessionSafe();
39182
40598
  if (compiledContextRef.current) {
39183
40599
  const { ids, recalled } = recallArtifacts(messagesRef.current, artifactStoreRef.current, sessionStateRef.current);
@@ -39191,7 +40607,7 @@ ${result2.output ?? "(output not captured)"}`,
39191
40607
  }
39192
40608
  }
39193
40609
  turnCounterRef.current += 1;
39194
- if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(join49(process.cwd(), name))) && !kimiMdStale) {
40610
+ if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync12(join53(process.cwd(), name))) && !kimiMdStale) {
39195
40611
  setEvents((e) => [
39196
40612
  ...e,
39197
40613
  { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps autopilot (formerly kimiflare) stay accurate as your project evolves." }
@@ -39215,7 +40631,7 @@ ${result2.output ?? "(output not captured)"}`,
39215
40631
  };
39216
40632
  ensureSessionId();
39217
40633
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39218
- const filePath = join49(sessionsDir3(), `${sessionIdRef.current}.json`);
40634
+ const filePath = join53(sessionsDir3(), `${sessionIdRef.current}.json`);
39219
40635
  await addCheckpoint(filePath, cp);
39220
40636
  }
39221
40637
  const summary = await generateContinuationSummary({
@@ -39464,6 +40880,34 @@ ${result2.output ?? "(output not captured)"}`,
39464
40880
  offered: turnTools.some((tool) => tool.name === "subagent"),
39465
40881
  guidance: delegationGuidance.kind
39466
40882
  });
40883
+ const turnId = makeTurnId();
40884
+ currentTurnIdRef.current = turnId;
40885
+ const trace = await ensureTraceStarted({
40886
+ tools: toOpenAIToolDefs(turnTools),
40887
+ systemPrompt: messagesRef.current.filter((m) => m.role === "system" && typeof m.content === "string").map((m) => m.content).join("\n\n"),
40888
+ model: overrideModel ?? cfg.model,
40889
+ mode: "tui",
40890
+ config: cfg
40891
+ });
40892
+ if (trace.enabled) {
40893
+ const previousTurn = turnHistoryRef.current.at(-1);
40894
+ if (previousTurn) {
40895
+ trace.emit(
40896
+ "outcome",
40897
+ { kind: "next_message", turn_id: previousTurn.turnId, classification: classifyNextMessage(trimmed), classifier: "regex" },
40898
+ previousTurn.turnId
40899
+ );
40900
+ }
40901
+ trace.emit(
40902
+ "user_message",
40903
+ {
40904
+ content_blob: trace.blob(typeof content === "string" ? content : trimmed) ?? "",
40905
+ source: traceUserSource,
40906
+ attachments: Array.isArray(content) ? content.filter((p) => p.type !== "text").length : 0
40907
+ },
40908
+ turnId
40909
+ );
40910
+ }
39467
40911
  supervisorRef.current.startTurn(
39468
40912
  {
39469
40913
  ...llmAuthFromConfig(cfg),
@@ -39509,6 +40953,8 @@ ${result2.output ?? "(output not captured)"}`,
39509
40953
  },
39510
40954
  mode: modeRef.current,
39511
40955
  cacheStable: cacheStableRef.current,
40956
+ trace,
40957
+ turnId,
39512
40958
  onFileChange: (path, content2) => {
39513
40959
  if (content2) {
39514
40960
  lspManagerRef.current.notifyChange(path, content2);
@@ -39523,6 +40969,7 @@ ${result2.output ?? "(output not captured)"}`,
39523
40969
  },
39524
40970
  {
39525
40971
  onDone: () => {
40972
+ noteTurnEnded(turnId);
39526
40973
  void (async () => {
39527
40974
  await saveSessionSafe();
39528
40975
  if (turnScope.signal.aborted) {
@@ -39550,6 +40997,7 @@ ${result2.output ?? "(output not captured)"}`,
39550
40997
  store
39551
40998
  });
39552
40999
  if (result2.metrics.rawTurnsRemoved > 0) {
41000
+ traceCompiledCompaction(traceRef.current, result2, turnId);
39553
41001
  messagesRef.current = result2.newMessages;
39554
41002
  sessionStateRef.current = result2.newState;
39555
41003
  setEvents((e) => [
@@ -39565,6 +41013,7 @@ ${result2.output ?? "(output not captured)"}`,
39565
41013
  }
39566
41014
  } else {
39567
41015
  try {
41016
+ const before = messagesRef.current;
39568
41017
  const result2 = await summarizeMessagesViaLlm({
39569
41018
  ...llmAuthFromConfig(cfg),
39570
41019
  model: cfg.model,
@@ -39572,6 +41021,7 @@ ${result2.output ?? "(output not captured)"}`,
39572
41021
  signal: turnScope.signal
39573
41022
  });
39574
41023
  if (result2.replacedCount > 0) {
41024
+ traceLlmCompaction(traceRef.current, before, result2, turnId);
39575
41025
  messagesRef.current = result2.newMessages;
39576
41026
  setEvents((e) => [
39577
41027
  ...e,
@@ -39637,6 +41087,7 @@ ${result2.output ?? "(output not captured)"}`,
39637
41087
  keepLastTurns: 1
39638
41088
  });
39639
41089
  if (result2.metrics.rawTurnsRemoved > 0) {
41090
+ traceCompiledCompaction(traceRef.current, result2, turnId);
39640
41091
  messagesRef.current = result2.newMessages;
39641
41092
  sessionStateRef.current = result2.newState;
39642
41093
  setEvents((e) => [
@@ -39652,6 +41103,7 @@ ${result2.output ?? "(output not captured)"}`,
39652
41103
  }
39653
41104
  } else {
39654
41105
  try {
41106
+ const before = messagesRef.current;
39655
41107
  const result2 = await summarizeMessagesViaLlm({
39656
41108
  ...llmAuthFromConfig(cfg),
39657
41109
  model: cfg.model,
@@ -39660,6 +41112,7 @@ ${result2.output ?? "(output not captured)"}`,
39660
41112
  signal: turnScope.signal
39661
41113
  });
39662
41114
  if (result2.replacedCount > 0) {
41115
+ traceLlmCompaction(traceRef.current, before, result2, turnId);
39663
41116
  messagesRef.current = result2.newMessages;
39664
41117
  setEvents((e) => [
39665
41118
  ...e,
@@ -39700,7 +41153,7 @@ ${result2.output ?? "(output not captured)"}`,
39700
41153
  };
39701
41154
  ensureSessionId();
39702
41155
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39703
- const filePath = join49(sessionsDir3(), `${sessionIdRef.current}.json`);
41156
+ const filePath = join53(sessionsDir3(), `${sessionIdRef.current}.json`);
39704
41157
  await addCheckpoint(filePath, cp);
39705
41158
  }
39706
41159
  const summary = await generateContinuationSummary({
@@ -39751,15 +41204,9 @@ ${result2.output ?? "(output not captured)"}`,
39751
41204
  }
39752
41205
  },
39753
41206
  onError: async (e) => {
41207
+ noteTurnEnded(turnId);
39754
41208
  if (e.name === "AbortError") {
39755
- for (const [tcId, tcName] of pendingToolCallsRef.current) {
39756
- messagesRef.current.push({
39757
- role: "tool",
39758
- tool_call_id: tcId,
39759
- content: "(stopped)",
39760
- name: tcName
39761
- });
39762
- }
41209
+ appendStoppedToolResults(messagesRef.current, pendingToolCallsRef.current);
39763
41210
  setEvents(
39764
41211
  (evts) => evts.map((e2) => e2.kind === "tool" && e2.status === "running" ? { ...e2, status: "error", result: "(stopped)" } : e2)
39765
41212
  );
@@ -39782,7 +41229,7 @@ ${result2.output ?? "(output not captured)"}`,
39782
41229
  }
39783
41230
  );
39784
41231
  },
39785
- [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand]
41232
+ [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand, ensureTraceStarted]
39786
41233
  );
39787
41234
  useEffect11(() => {
39788
41235
  if (!busy && queuePlanDraft === null && supervisorRef.current.phase === "idle") {
@@ -40283,7 +41730,7 @@ ${result2.output ?? "(output not captured)"}`,
40283
41730
  lsp: {
40284
41731
  servers: cfg?.lspServers ?? {},
40285
41732
  scope: lspScope,
40286
- hasProjectDir: existsSync9(join49(process.cwd(), ".kimiflare"))
41733
+ hasProjectDir: existsSync12(join53(process.cwd(), ".kimiflare"))
40287
41734
  }
40288
41735
  });
40289
41736
  });
@@ -40340,7 +41787,7 @@ ${result2.output ?? "(output not captured)"}`,
40340
41787
  onCommandDelete: handleCommandDelete2,
40341
41788
  lspServers: cfg?.lspServers ?? {},
40342
41789
  lspScope,
40343
- hasProjectDir: existsSync9(join49(process.cwd(), ".kimiflare")),
41790
+ hasProjectDir: existsSync12(join53(process.cwd(), ".kimiflare")),
40344
41791
  onLspSave: handleLspSave2,
40345
41792
  themes: themeList(),
40346
41793
  onPickTheme: handleThemePick,
@@ -40411,6 +41858,7 @@ ${result2.output ?? "(output not captured)"}`,
40411
41858
  args: perm.args,
40412
41859
  onDecide: decidePermission2,
40413
41860
  onFeedback: (text) => {
41861
+ denyFeedbackRef.current = true;
40414
41862
  submitRef.current(text);
40415
41863
  }
40416
41864
  }
@@ -40676,6 +42124,8 @@ var init_app = __esm({
40676
42124
  init_run_startup_tasks();
40677
42125
  init_manager_init();
40678
42126
  init_run_compact();
42127
+ init_trace();
42128
+ init_registry2();
40679
42129
  init_distill();
40680
42130
  init_plan_resolver();
40681
42131
  init_user_shell();
@@ -40821,6 +42271,8 @@ init_glob();
40821
42271
  init_permissions_evaluator();
40822
42272
  init_classify();
40823
42273
  init_subagent_policy();
42274
+ init_trace();
42275
+ init_registry2();
40824
42276
  import { readFile as readFile15 } from "fs/promises";
40825
42277
  import { resolve as resolve6, basename as basename3 } from "path";
40826
42278
  async function resolveSession(opts2) {
@@ -40860,8 +42312,8 @@ async function resolveFiles(filePatterns, cwd) {
40860
42312
  const resolved = /* @__PURE__ */ new Set();
40861
42313
  for (const pattern of filePatterns) {
40862
42314
  try {
40863
- const stat10 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
40864
- if (stat10.isFile()) {
42315
+ const stat11 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
42316
+ if (stat11.isFile()) {
40865
42317
  resolved.add(resolve6(cwd, pattern));
40866
42318
  continue;
40867
42319
  }
@@ -40955,24 +42407,43 @@ async function runPrintMode(opts2) {
40955
42407
  });
40956
42408
  }
40957
42409
  const messages = [];
42410
+ const systemPrompt = buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests });
40958
42411
  if (isNew || sessionFile.messages.length === 0) {
40959
- messages.push({
40960
- role: "system",
40961
- content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
40962
- });
42412
+ messages.push({ role: "system", content: systemPrompt });
40963
42413
  } else {
40964
42414
  const nonSystem = sessionFile.messages.filter(
40965
42415
  (m) => m.role !== "system" || typeof m.content === "string" && m.content.startsWith("[compiled session state]")
40966
42416
  );
40967
- messages.push({
40968
- role: "system",
40969
- content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
40970
- });
42417
+ messages.push({ role: "system", content: systemPrompt });
40971
42418
  messages.push(...nonSystem);
40972
42419
  }
42420
+ const trace = createRecorder({
42421
+ enabled: opts2.trace?.enabled === true,
42422
+ sessionId: sessionFile.id,
42423
+ root: opts2.trace?.root
42424
+ });
42425
+ await emitSessionStart(trace, {
42426
+ cwd,
42427
+ model: opts2.model,
42428
+ mode: "print",
42429
+ config: opts2.traceConfigSnapshot,
42430
+ tools: toOpenAIToolDefs(workerTools),
42431
+ systemPrompt,
42432
+ toolProfile: opts2.workerProfile ?? "full"
42433
+ });
42434
+ let traceCostUsd = 0;
42435
+ let traceEndReason = "exit";
40973
42436
  const files = opts2.files ? await resolveFiles(opts2.files, cwd) : [];
40974
42437
  const { content: userContent } = await buildUserMessage(opts2.prompt, files, cwd);
40975
42438
  messages.push({ role: "user", content: userContent });
42439
+ const turnId = makeTurnId();
42440
+ if (trace.enabled) {
42441
+ trace.emit(
42442
+ "user_message",
42443
+ { content_blob: trace.blob(opts2.prompt) ?? "", source: "print", attachments: files.length },
42444
+ turnId
42445
+ );
42446
+ }
40976
42447
  const controller = new AbortController();
40977
42448
  process.on("SIGINT", () => controller.abort());
40978
42449
  const format = opts2.format ?? "text";
@@ -40996,6 +42467,7 @@ async function runPrintMode(opts2) {
40996
42467
  onUsageFinal: (u, meta) => {
40997
42468
  const lookup = opts2.openrouterApiKey && !auth.customEndpoint && meta?.generationId ? { apiKey: opts2.openrouterApiKey, meta } : void 0;
40998
42469
  usageWrites.push(recordUsage(sessionFile.id, u, lookup, opts2.model).catch(() => void 0));
42470
+ if (typeof u.cost === "number") traceCostUsd += u.cost;
40999
42471
  },
41000
42472
  onReasoningDelta: opts2.showReasoning ? (delta) => {
41001
42473
  if (format === "text") {
@@ -41137,7 +42609,9 @@ async function runPrintMode(opts2) {
41137
42609
  // --continue sessions grow across runs; archive older turns onto the
41138
42610
  // session file (saved below) instead of failing on context size.
41139
42611
  compaction: sessionFileCompactionTarget(sessionFile),
41140
- callbacks
42612
+ callbacks,
42613
+ trace,
42614
+ turnId
41141
42615
  });
41142
42616
  } catch (err) {
41143
42617
  if (err instanceof BudgetExhaustedError) {
@@ -41167,9 +42641,12 @@ async function runPrintMode(opts2) {
41167
42641
  process.exitCode = 1;
41168
42642
  return;
41169
42643
  }
42644
+ traceEndReason = "crash";
41170
42645
  throw err;
41171
42646
  } finally {
41172
42647
  await Promise.all(usageWrites);
42648
+ trace.emit("session_end", { reason: traceEndReason, total_cost_usd: traceCostUsd || void 0 }, null);
42649
+ await trace.flush();
41173
42650
  }
41174
42651
  sessionFile.messages = messages;
41175
42652
  sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -41483,7 +42960,9 @@ To use Requesty instead, set REQUESTY_API_KEY or run autopilot auth requesty`
41483
42960
  dir: opts.dir,
41484
42961
  title: opts.title,
41485
42962
  permissions: cfg.permissions,
41486
- workerProfile: opts.workerProfile
42963
+ workerProfile: opts.workerProfile,
42964
+ trace: cfg.trace,
42965
+ traceConfigSnapshot: cfg
41487
42966
  });
41488
42967
  return;
41489
42968
  }