autopilot-ai 1.19.2 → 1.20.0

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);
@@ -1474,6 +1484,68 @@ var init_messages = __esm({
1474
1484
  }
1475
1485
  });
1476
1486
 
1487
+ // src/trace/blobs.ts
1488
+ import { createHash } from "crypto";
1489
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
1490
+ import { join as join8 } from "path";
1491
+ function sha256Hex(content) {
1492
+ return createHash("sha256").update(content, "utf8").digest("hex");
1493
+ }
1494
+ function truncateUtf8(content, maxBytes) {
1495
+ const buf = Buffer.from(content, "utf8");
1496
+ if (buf.length <= maxBytes) return content;
1497
+ let end = maxBytes;
1498
+ while (end > 0 && (buf[end] & 192) === 128) end--;
1499
+ return buf.subarray(0, end).toString("utf8");
1500
+ }
1501
+ var DEFAULT_MAX_BLOB_BYTES, BlobStore;
1502
+ var init_blobs = __esm({
1503
+ "src/trace/blobs.ts"() {
1504
+ "use strict";
1505
+ DEFAULT_MAX_BLOB_BYTES = 2 * 1024 * 1024;
1506
+ BlobStore = class {
1507
+ constructor(dir, opts2 = {}) {
1508
+ this.dir = dir;
1509
+ this.maxBlobBytes = opts2.maxBlobBytes ?? DEFAULT_MAX_BLOB_BYTES;
1510
+ }
1511
+ dir;
1512
+ seen = /* @__PURE__ */ new Set();
1513
+ dirReady = false;
1514
+ maxBlobBytes;
1515
+ put(content) {
1516
+ const bytes = Buffer.byteLength(content, "utf8");
1517
+ let stored = content;
1518
+ let truncated = false;
1519
+ if (bytes > this.maxBlobBytes) {
1520
+ stored = `${truncateUtf8(content, this.maxBlobBytes)}
1521
+ [truncated: ${bytes} bytes total]`;
1522
+ truncated = true;
1523
+ }
1524
+ const sha2563 = sha256Hex(stored);
1525
+ if (this.seen.has(sha2563)) return { sha256: sha2563, bytes, truncated, written: 0 };
1526
+ if (!this.dirReady) {
1527
+ mkdirSync2(this.dir, { recursive: true });
1528
+ this.dirReady = true;
1529
+ }
1530
+ const path = join8(this.dir, sha2563);
1531
+ let written = 0;
1532
+ if (!existsSync2(path)) {
1533
+ writeFileSync(path, stored, "utf8");
1534
+ written = Buffer.byteLength(stored, "utf8");
1535
+ }
1536
+ this.seen.add(sha2563);
1537
+ return { sha256: sha2563, bytes, truncated, written };
1538
+ }
1539
+ has(sha2563) {
1540
+ return this.seen.has(sha2563) || existsSync2(join8(this.dir, sha2563));
1541
+ }
1542
+ pathFor(sha2563) {
1543
+ return join8(this.dir, sha2563);
1544
+ }
1545
+ };
1546
+ }
1547
+ });
1548
+
1477
1549
  // src/agent/session-state.ts
1478
1550
  function emptySessionState(task = "") {
1479
1551
  return {
@@ -1927,8 +1999,8 @@ var init_artifact_compaction = __esm({
1927
1999
 
1928
2000
  // src/util/llm-dump.ts
1929
2001
  import { homedir as homedir5 } from "os";
1930
- import { join as join8 } from "path";
1931
- import { mkdirSync as mkdirSync2, appendFileSync, writeFileSync } from "fs";
2002
+ import { join as join9 } from "path";
2003
+ import { mkdirSync as mkdirSync3, appendFileSync, writeFileSync as writeFileSync2 } from "fs";
1932
2004
  function isLlmDumpEnabled() {
1933
2005
  const raw = process.env.KIMIFLARE_DUMP_LLM;
1934
2006
  if (raw === void 0) return false;
@@ -1938,11 +2010,11 @@ function isLlmDumpEnabled() {
1938
2010
  function defaultDumpRoot() {
1939
2011
  const override = process.env.KIMIFLARE_DUMP_LLM_DIR;
1940
2012
  if (override && override.trim()) return override;
1941
- const xdg = process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config");
1942
- return join8(xdg, "kimiflare", "llm-dumps");
2013
+ const xdg = process.env.XDG_CONFIG_HOME || join9(homedir5(), ".config");
2014
+ return join9(xdg, "kimiflare", "llm-dumps");
1943
2015
  }
1944
2016
  function dumpDir(sessionId) {
1945
- return join8(defaultDumpRoot(), sessionId && sessionId.trim() ? sessionId : "nosession");
2017
+ return join9(defaultDumpRoot(), sessionId && sessionId.trim() ? sessionId : "nosession");
1946
2018
  }
1947
2019
  function messageChars(m) {
1948
2020
  let chars = 0;
@@ -1999,12 +2071,12 @@ function writeLlmDump(record) {
1999
2071
  if (!isLlmDumpEnabled()) return;
2000
2072
  try {
2001
2073
  const dir = dumpDir(record.meta.sessionId);
2002
- mkdirSync2(dir, { recursive: true });
2074
+ mkdirSync3(dir, { recursive: true });
2003
2075
  const tsPart = safeSegment(record.meta.ts);
2004
2076
  const turnPart = safeSegment(record.meta.turnId ?? "noturn");
2005
2077
  const reqPart = safeSegment(record.meta.requestId);
2006
- const file = join8(dir, `${tsPart}-${turnPart}-${reqPart}.json`);
2007
- writeFileSync(file, JSON.stringify(record, null, 2));
2078
+ const file = join9(dir, `${tsPart}-${turnPart}-${reqPart}.json`);
2079
+ writeFileSync2(file, JSON.stringify(record, null, 2));
2008
2080
  const summary = {
2009
2081
  ...record.meta,
2010
2082
  ...record.breakdown,
@@ -2014,7 +2086,7 @@ function writeLlmDump(record) {
2014
2086
  usage: record.response.usage,
2015
2087
  file
2016
2088
  };
2017
- appendFileSync(join8(dir, "index.jsonl"), JSON.stringify(summary) + "\n");
2089
+ appendFileSync(join9(dir, "index.jsonl"), JSON.stringify(summary) + "\n");
2018
2090
  } catch {
2019
2091
  }
2020
2092
  }
@@ -2257,41 +2329,89 @@ async function* runKimi(opts2) {
2257
2329
  if (Object.keys(provider).length > 0) body.provider = provider;
2258
2330
  if (vendorOf(opts2.model) === "anthropic") body.cache_control = { type: "ephemeral" };
2259
2331
  }
2332
+ const dumpEnabled = isLlmDumpEnabled();
2333
+ const trace = opts2.trace?.enabled ? opts2.trace : null;
2334
+ const captured = dumpEnabled || trace ? captureRequest(body) : null;
2335
+ const response = {
2336
+ text: "",
2337
+ reasoning: "",
2338
+ reasoningDetails: [],
2339
+ toolCalls: [],
2340
+ finishReason: null,
2341
+ usage: null
2342
+ };
2260
2343
  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
- };
2344
+ if (dumpEnabled && captured) {
2272
2345
  dumpRecord = {
2273
2346
  meta: {
2274
2347
  requestId,
2275
2348
  sessionId: opts2.sessionId ?? getLogSessionId(),
2276
- turnId: getLogTurnId(),
2349
+ turnId: opts2.turnId ?? getLogTurnId(),
2277
2350
  model: opts2.model,
2278
2351
  url,
2279
2352
  ts: (/* @__PURE__ */ new Date()).toISOString()
2280
2353
  },
2281
2354
  request: {
2282
- system: dumpMessages.filter((m) => m.role === "system"),
2283
- messages: dumpMessages,
2284
- tools: dumpTools,
2285
- params,
2286
- rawSerialized: stableStringify(body, jsonReplacer)
2355
+ system: captured.messages.filter((m) => m.role === "system"),
2356
+ messages: captured.messages,
2357
+ tools: captured.tools,
2358
+ params: captured.params,
2359
+ rawSerialized: captured.rawSerialized
2287
2360
  },
2288
- breakdown: computeBreakdown(dumpMessages, dumpTools),
2289
- response: dumpResponse
2361
+ breakdown: captured.breakdown,
2362
+ response
2290
2363
  };
2291
2364
  }
2365
+ const traceRequest = (attempt) => {
2366
+ if (!trace || !captured) return;
2367
+ trace.emit(
2368
+ "llm_request",
2369
+ {
2370
+ request_id: requestId,
2371
+ attempt,
2372
+ model: opts2.model,
2373
+ params: captured.params,
2374
+ request_blob: trace.blob(captured.rawSerialized) ?? "",
2375
+ messages_sha: captured.messagesSha,
2376
+ tools_sha: captured.toolsSha,
2377
+ estimated_tokens: captured.breakdown.estTokens
2378
+ },
2379
+ opts2.turnId ?? null
2380
+ );
2381
+ };
2382
+ const traceResponse = (startedAt2, error) => {
2383
+ if (!trace) return;
2384
+ const usage = response.usage;
2385
+ trace.emit(
2386
+ "llm_response",
2387
+ {
2388
+ request_id: requestId,
2389
+ text_blob: trace.blob(response.text) ?? "",
2390
+ ...response.reasoning ? { reasoning_blob: trace.blob(response.reasoning) ?? "" } : {},
2391
+ ...response.reasoningDetails.length ? { reasoning_details: response.reasoningDetails } : {},
2392
+ tool_calls: response.toolCalls.map((tc) => ({ id: tc.id, name: tc.name, arguments: tc.arguments })),
2393
+ finish_reason: response.finishReason,
2394
+ ...usage ? {
2395
+ usage: {
2396
+ prompt_tokens: usage.prompt_tokens,
2397
+ completion_tokens: usage.completion_tokens,
2398
+ ...usage.prompt_tokens_details?.cached_tokens !== void 0 ? { cached_tokens: usage.prompt_tokens_details.cached_tokens ?? void 0 } : {},
2399
+ ...usage.cost !== void 0 ? { cost: usage.cost } : {}
2400
+ }
2401
+ } : {},
2402
+ ...response.generationId ? { generation_id: response.generationId } : {},
2403
+ latency_ms: Math.round(performance.now() - startedAt2),
2404
+ aborted: opts2.signal?.aborted === true,
2405
+ ...error !== void 0 ? { error } : {}
2406
+ },
2407
+ opts2.turnId ?? null
2408
+ );
2409
+ };
2292
2410
  logger.debug("runKimi:request", { requestId, attempt: 0, model: opts2.model });
2293
2411
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
2294
2412
  let res;
2413
+ const attemptStartedAt = performance.now();
2414
+ traceRequest(attempt);
2295
2415
  try {
2296
2416
  const headers = {
2297
2417
  "Content-Type": "application/json",
@@ -2307,8 +2427,9 @@ async function* runKimi(opts2) {
2307
2427
  signal: opts2.signal
2308
2428
  });
2309
2429
  } catch (fetchErr) {
2310
- if (isAbortError(fetchErr)) throw fetchErr;
2311
2430
  const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
2431
+ traceResponse(attemptStartedAt, msg);
2432
+ if (isAbortError(fetchErr)) throw fetchErr;
2312
2433
  logger.warn("runKimi:fetch_error", { requestId, attempt, error: msg });
2313
2434
  if (attempt < MAX_ATTEMPTS - 1) {
2314
2435
  const delay = Math.random() * (500 * 2 ** attempt);
@@ -2340,6 +2461,7 @@ async function* runKimi(opts2) {
2340
2461
  err?.code,
2341
2462
  status
2342
2463
  );
2464
+ traceResponse(attemptStartedAt, apiErr.message);
2343
2465
  if (isRetryable(apiErr, attempt)) {
2344
2466
  const isRateLimit = apiErr.httpStatus === 429;
2345
2467
  const baseDelay = isRateLimit ? 2e3 : 500;
@@ -2351,18 +2473,27 @@ async function* runKimi(opts2) {
2351
2473
  }
2352
2474
  throw apiErr;
2353
2475
  }
2354
- if (!res.body) throw new KimiApiError("kimiflare: empty response body", void 0, res.status);
2476
+ if (!res.body) {
2477
+ const emptyErr = new KimiApiError("kimiflare: empty response body", void 0, res.status);
2478
+ traceResponse(attemptStartedAt, emptyErr.message);
2479
+ throw emptyErr;
2480
+ }
2355
2481
  logger.debug("runKimi:stream_start", { requestId });
2482
+ let streamError;
2356
2483
  try {
2357
2484
  for await (const ev of parseStream(res.body, opts2.signal, opts2.idleTimeoutMs, opts2.postFirstByteIdleTimeoutMs)) {
2358
- if (dumpRecord) accumulateDumpResponse(dumpRecord.response, ev);
2485
+ if (captured) accumulateDumpResponse(response, ev);
2359
2486
  yield ev;
2360
2487
  }
2488
+ } catch (streamErr) {
2489
+ streamError = streamErr instanceof Error ? streamErr.message : String(streamErr);
2490
+ throw streamErr;
2361
2491
  } finally {
2362
2492
  if (dumpRecord) {
2363
2493
  dumpRecord.meta.attempt = attempt;
2364
2494
  writeLlmDump(dumpRecord);
2365
2495
  }
2496
+ traceResponse(attemptStartedAt, streamError);
2366
2497
  }
2367
2498
  logger.debug("runKimi:stream_end", { requestId });
2368
2499
  return;
@@ -2439,16 +2570,36 @@ function describeRequestyHttpError(model, status, msg) {
2439
2570
  }
2440
2571
  return msg;
2441
2572
  }
2573
+ function captureRequest(body) {
2574
+ const messages = body.messages;
2575
+ const tools = body.tools ?? [];
2576
+ const { messages: _m, tools: _t, ...params } = body;
2577
+ return {
2578
+ messages,
2579
+ tools,
2580
+ params,
2581
+ rawSerialized: stableStringify(body, jsonReplacer),
2582
+ messagesSha: sha256Hex(stableStringify(messages, jsonReplacer)),
2583
+ toolsSha: sha256Hex(stableStringify(tools, jsonReplacer)),
2584
+ breakdown: computeBreakdown(messages, tools)
2585
+ };
2586
+ }
2442
2587
  function accumulateDumpResponse(resp, ev) {
2443
2588
  switch (ev.type) {
2589
+ case "response_meta":
2590
+ if (ev.meta.generationId) resp.generationId = ev.meta.generationId;
2591
+ break;
2444
2592
  case "text":
2445
2593
  resp.text += ev.delta;
2446
2594
  break;
2447
2595
  case "reasoning":
2448
2596
  resp.reasoning += ev.delta;
2449
2597
  break;
2598
+ case "reasoning_details":
2599
+ resp.reasoningDetails = mergeReasoningDetails(resp.reasoningDetails, ev.details);
2600
+ break;
2450
2601
  case "tool_call_complete":
2451
- resp.toolCalls.push({ name: ev.name, arguments: ev.arguments });
2602
+ resp.toolCalls.push({ id: ev.id, name: ev.name, arguments: ev.arguments });
2452
2603
  break;
2453
2604
  case "usage":
2454
2605
  resp.usage = ev.usage;
@@ -2674,6 +2825,7 @@ var init_client = __esm({
2674
2825
  init_errors();
2675
2826
  init_version();
2676
2827
  init_messages();
2828
+ init_blobs();
2677
2829
  init_logger();
2678
2830
  init_log_sink();
2679
2831
  init_llm_dump();
@@ -2746,7 +2898,7 @@ var init_recall_inject = __esm({
2746
2898
 
2747
2899
  // src/storage-limits.ts
2748
2900
  import { readdir as readdir2, stat as stat2, unlink } from "fs/promises";
2749
- import { join as join9 } from "path";
2901
+ import { join as join10 } from "path";
2750
2902
  async function listFilesByMtime(dir, pattern = /.*/) {
2751
2903
  let entries;
2752
2904
  try {
@@ -2757,7 +2909,7 @@ async function listFilesByMtime(dir, pattern = /.*/) {
2757
2909
  const files = [];
2758
2910
  for (const name of entries) {
2759
2911
  if (!pattern.test(name)) continue;
2760
- const p = join9(dir, name);
2912
+ const p = join10(dir, name);
2761
2913
  try {
2762
2914
  const s = await stat2(p);
2763
2915
  if (s.isFile()) files.push({ path: p, mtime: s.mtime });
@@ -2834,7 +2986,13 @@ var init_storage_limits = __esm({
2834
2986
  /** Memories older than this (days) are pruned. */
2835
2987
  memoryMaxAgeDays: 90,
2836
2988
  /** Max memories per repo. */
2837
- memoryMaxEntries: 1e3
2989
+ memoryMaxEntries: 1e3,
2990
+ /** Session trace directories older than this (days) are pruned. */
2991
+ traceMaxAgeDays: 90,
2992
+ /** Max total size of all session traces (bytes). Oldest sessions go first. */
2993
+ traceMaxTotalBytes: 2 * 1024 * 1024 * 1024,
2994
+ /** Max size of a single session trace (bytes); recording stops at the cap. */
2995
+ traceMaxSessionBytes: 200 * 1024 * 1024
2838
2996
  };
2839
2997
  }
2840
2998
  });
@@ -2842,13 +3000,13 @@ var init_storage_limits = __esm({
2842
3000
  // src/cost-debug.ts
2843
3001
  import { appendFile, mkdir as mkdir5 } from "fs/promises";
2844
3002
  import { homedir as homedir6 } from "os";
2845
- import { join as join10 } from "path";
3003
+ import { join as join11 } from "path";
2846
3004
  function debugDir() {
2847
- const xdg = process.env.XDG_DATA_HOME || join10(homedir6(), ".local", "share");
2848
- return join10(xdg, "kimiflare");
3005
+ const xdg = process.env.XDG_DATA_HOME || join11(homedir6(), ".local", "share");
3006
+ return join11(xdg, "kimiflare");
2849
3007
  }
2850
3008
  function debugPath() {
2851
- return join10(debugDir(), "cost-debug.jsonl");
3009
+ return join11(debugDir(), "cost-debug.jsonl");
2852
3010
  }
2853
3011
  function now() {
2854
3012
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -3562,7 +3720,7 @@ var require_isolated_vm = __commonJS({
3562
3720
  });
3563
3721
 
3564
3722
  // src/code-mode/sandbox.ts
3565
- import { join as join11, dirname as dirname5 } from "path";
3723
+ import { join as join12, dirname as dirname5 } from "path";
3566
3724
  import { pathToFileURL } from "url";
3567
3725
  function stripTypescript(code) {
3568
3726
  let js = code;
@@ -3589,7 +3747,7 @@ async function loadTypescript(cwd) {
3589
3747
  let dir = cwd;
3590
3748
  while (dir !== dirname5(dir)) {
3591
3749
  try {
3592
- const tsPath = join11(dir, "node_modules", "typescript", "lib", "typescript.js");
3750
+ const tsPath = join12(dir, "node_modules", "typescript", "lib", "typescript.js");
3593
3751
  return await import(pathToFileURL(tsPath).href);
3594
3752
  } catch {
3595
3753
  }
@@ -4433,11 +4591,11 @@ Prefer non-interactive flags when they exist (\`--yes\`, \`-m\`, \`--with-token\
4433
4591
 
4434
4592
  // src/agent/system-prompt.ts
4435
4593
  import { platform, release, homedir as homedir7 } from "os";
4436
- import { basename, join as join12 } from "path";
4594
+ import { basename, join as join13 } from "path";
4437
4595
  import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
4438
4596
  function loadContextFile(cwd) {
4439
4597
  for (const name of CONTEXT_FILENAMES) {
4440
- const path = join12(cwd, name);
4598
+ const path = join13(cwd, name);
4441
4599
  try {
4442
4600
  const s = statSync2(path);
4443
4601
  if (!s.isFile() || s.size > MAX_CONTEXT_BYTES) continue;
@@ -4964,6 +5122,403 @@ var init_completion_check = __esm({
4964
5122
  }
4965
5123
  });
4966
5124
 
5125
+ // src/trace/paths.ts
5126
+ import { homedir as homedir8 } from "os";
5127
+ import { join as join14 } from "path";
5128
+ function defaultTraceRoot() {
5129
+ const xdg = process.env.XDG_DATA_HOME || join14(homedir8(), ".local", "share");
5130
+ return join14(xdg, "kimiflare", "traces");
5131
+ }
5132
+ function traceRoot(configRoot) {
5133
+ const env2 = process.env.KIMIFLARE_TRACE_DIR;
5134
+ if (env2 && env2.trim()) return env2.trim();
5135
+ if (configRoot && configRoot.trim()) return configRoot.trim();
5136
+ return defaultTraceRoot();
5137
+ }
5138
+ function safeSessionSegment(sessionId) {
5139
+ const cleaned = sessionId.replace(/[\\/\0]/g, "_").replace(/^\.+$/, "_");
5140
+ return cleaned || "session";
5141
+ }
5142
+ function sessionTraceDir(sessionId, root) {
5143
+ return join14(traceRoot(root), safeSessionSegment(sessionId));
5144
+ }
5145
+ function traceFilePath(sessionDir) {
5146
+ return join14(sessionDir, TRACE_FILE_NAME);
5147
+ }
5148
+ function blobsDir(sessionDir) {
5149
+ return join14(sessionDir, BLOBS_DIR_NAME);
5150
+ }
5151
+ var TRACE_FILE_NAME, BLOBS_DIR_NAME;
5152
+ var init_paths = __esm({
5153
+ "src/trace/paths.ts"() {
5154
+ "use strict";
5155
+ TRACE_FILE_NAME = "trace.jsonl";
5156
+ BLOBS_DIR_NAME = "blobs";
5157
+ }
5158
+ });
5159
+
5160
+ // src/trace/redact.ts
5161
+ function redactLight(text) {
5162
+ if (!text) return text;
5163
+ let out = text.replace(PEM_BLOCK, "<redacted-key>");
5164
+ for (const re of TOKEN_SHAPES) out = out.replace(re, "<redacted>");
5165
+ out = out.replace(KEY_VALUE, "$1$2<redacted>");
5166
+ return out;
5167
+ }
5168
+ function isSecretConfigKey(key) {
5169
+ const lower = key.toLowerCase();
5170
+ return SECRET_KEY_SUFFIXES.some((suffix) => lower.endsWith(suffix));
5171
+ }
5172
+ function redactConfig(value) {
5173
+ return redactValue(value, true);
5174
+ }
5175
+ function redactDeep(value) {
5176
+ return redactValue(value, false);
5177
+ }
5178
+ function redactValue(value, redactSecretKeys) {
5179
+ if (typeof value === "string") return redactLight(value);
5180
+ if (Array.isArray(value)) return value.map((v) => redactValue(v, redactSecretKeys));
5181
+ if (value && typeof value === "object") {
5182
+ const out = {};
5183
+ for (const [k, v] of Object.entries(value)) {
5184
+ if (v === void 0) continue;
5185
+ if (redactSecretKeys && isSecretConfigKey(k) && v !== null && v !== "") {
5186
+ out[k] = "<redacted>";
5187
+ } else {
5188
+ out[k] = redactValue(v, redactSecretKeys);
5189
+ }
5190
+ }
5191
+ return out;
5192
+ }
5193
+ return value;
5194
+ }
5195
+ var PEM_BLOCK, TOKEN_SHAPES, KEY_VALUE, SECRET_KEY_SUFFIXES;
5196
+ var init_redact = __esm({
5197
+ "src/trace/redact.ts"() {
5198
+ "use strict";
5199
+ PEM_BLOCK = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
5200
+ TOKEN_SHAPES = [
5201
+ /sk-or-v1-[A-Za-z0-9]+/g,
5202
+ /ghp_[A-Za-z0-9]+/g,
5203
+ /github_pat_[A-Za-z0-9_]+/g,
5204
+ /Bearer [A-Za-z0-9._-]+/g,
5205
+ /Basic [A-Za-z0-9+/=]+/g
5206
+ ];
5207
+ KEY_VALUE = /(api[_-]?key|secret|token|password|authorization)(["']?\s*[:=]\s*["']?)[^\s"']+/gi;
5208
+ SECRET_KEY_SUFFIXES = ["apikey", "token", "secret", "password"];
5209
+ }
5210
+ });
5211
+
5212
+ // src/trace/types.ts
5213
+ var TRACE_SCHEMA_VERSION;
5214
+ var init_types = __esm({
5215
+ "src/trace/types.ts"() {
5216
+ "use strict";
5217
+ TRACE_SCHEMA_VERSION = 1;
5218
+ }
5219
+ });
5220
+
5221
+ // src/trace/recorder.ts
5222
+ import { randomUUID } from "crypto";
5223
+ import { appendFileSync as appendFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3 } from "fs";
5224
+ function createNoopRecorder(sessionId = "") {
5225
+ return new NoopRecorder(sessionId);
5226
+ }
5227
+ function makeTurnId() {
5228
+ return randomUUID();
5229
+ }
5230
+ function createRecorder(opts2) {
5231
+ if (!opts2.enabled) return new NoopRecorder(opts2.sessionId);
5232
+ const dir = sessionTraceDir(opts2.sessionId, opts2.root);
5233
+ return new FileTraceRecorder(opts2.sessionId, dir, {
5234
+ maxBlobBytes: opts2.maxBlobBytes,
5235
+ maxSessionBytes: opts2.maxSessionBytes
5236
+ });
5237
+ }
5238
+ var NoopRecorder, FileTraceRecorder;
5239
+ var init_recorder = __esm({
5240
+ "src/trace/recorder.ts"() {
5241
+ "use strict";
5242
+ init_logger();
5243
+ init_log_sink();
5244
+ init_storage_limits();
5245
+ init_blobs();
5246
+ init_paths();
5247
+ init_redact();
5248
+ init_types();
5249
+ NoopRecorder = class {
5250
+ constructor(sessionId) {
5251
+ this.sessionId = sessionId;
5252
+ }
5253
+ sessionId;
5254
+ enabled = false;
5255
+ dir = null;
5256
+ emit() {
5257
+ }
5258
+ blob() {
5259
+ return null;
5260
+ }
5261
+ flush() {
5262
+ return Promise.resolve();
5263
+ }
5264
+ };
5265
+ FileTraceRecorder = class {
5266
+ constructor(sessionId, dir, opts2) {
5267
+ this.sessionId = sessionId;
5268
+ this.dir = dir;
5269
+ this.file = traceFilePath(dir);
5270
+ this.blobs = new BlobStore(blobsDir(dir), { maxBlobBytes: opts2.maxBlobBytes });
5271
+ this.maxSessionBytes = opts2.maxSessionBytes ?? RETENTION.traceMaxSessionBytes;
5272
+ }
5273
+ sessionId;
5274
+ enabled = true;
5275
+ dir;
5276
+ file;
5277
+ blobs;
5278
+ maxSessionBytes;
5279
+ seq = 0;
5280
+ bytesWritten = 0;
5281
+ dirReady = false;
5282
+ emit(type, payload, turnId) {
5283
+ if (!this.enabled) return;
5284
+ try {
5285
+ const event = {
5286
+ v: TRACE_SCHEMA_VERSION,
5287
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5288
+ seq: this.seq,
5289
+ session_id: this.sessionId,
5290
+ turn_id: turnId === void 0 ? getLogTurnId() : turnId,
5291
+ type,
5292
+ payload: redactDeep(payload)
5293
+ };
5294
+ if (!this.dirReady) {
5295
+ mkdirSync4(this.dir, { recursive: true });
5296
+ this.dirReady = true;
5297
+ this.resumeSeq();
5298
+ event.seq = this.seq;
5299
+ }
5300
+ const line = JSON.stringify(event) + "\n";
5301
+ if (!this.ensureBudget(Buffer.byteLength(line, "utf8"))) return;
5302
+ appendFileSync2(this.file, line, "utf8");
5303
+ this.seq++;
5304
+ } catch (err) {
5305
+ this.fail(err);
5306
+ }
5307
+ }
5308
+ blob(content) {
5309
+ if (!this.enabled) return null;
5310
+ try {
5311
+ const result2 = this.blobs.put(redactLight(content));
5312
+ if (result2.written > 0) this.ensureBudget(result2.written);
5313
+ return result2.sha256;
5314
+ } catch (err) {
5315
+ this.fail(err);
5316
+ return null;
5317
+ }
5318
+ }
5319
+ flush() {
5320
+ return Promise.resolve();
5321
+ }
5322
+ /** Continue `seq` after the lines already in an existing trace file. */
5323
+ resumeSeq() {
5324
+ if (!existsSync3(this.file)) return;
5325
+ const existing = readFileSync3(this.file, "utf8");
5326
+ let lines = 0;
5327
+ for (let i = 0; i < existing.length; i++) if (existing.charCodeAt(i) === 10) lines++;
5328
+ this.seq = lines;
5329
+ this.bytesWritten += Buffer.byteLength(existing, "utf8");
5330
+ }
5331
+ /** Account for `bytes`; returns false (and disables) once the session cap is exceeded. */
5332
+ ensureBudget(bytes) {
5333
+ this.bytesWritten += bytes;
5334
+ if (this.bytesWritten > this.maxSessionBytes) {
5335
+ this.enabled = false;
5336
+ logger.warn("trace:session_cap", {
5337
+ session_id: this.sessionId,
5338
+ dir: this.dir,
5339
+ max_bytes: this.maxSessionBytes
5340
+ });
5341
+ return false;
5342
+ }
5343
+ return true;
5344
+ }
5345
+ fail(err) {
5346
+ this.enabled = false;
5347
+ logger.warn("trace:write_failed", {
5348
+ session_id: this.sessionId,
5349
+ dir: this.dir,
5350
+ error: err instanceof Error ? err.message : String(err)
5351
+ });
5352
+ }
5353
+ };
5354
+ }
5355
+ });
5356
+
5357
+ // src/cost-attribution/git-diff.ts
5358
+ import { execFile } from "child_process";
5359
+ import { promisify } from "util";
5360
+ function parseNumstat(raw) {
5361
+ const entries = [];
5362
+ for (const line of raw.split("\n")) {
5363
+ if (!line.trim()) continue;
5364
+ const parts = line.split(" ");
5365
+ if (parts.length < 3) continue;
5366
+ const ins = parseInt(parts[0], 10);
5367
+ const del = parseInt(parts[1], 10);
5368
+ entries.push({
5369
+ file: parts.slice(2).join(" "),
5370
+ insertions: Number.isNaN(ins) ? 0 : ins,
5371
+ deletions: Number.isNaN(del) ? 0 : del
5372
+ });
5373
+ }
5374
+ return entries;
5375
+ }
5376
+ async function gitNumstat(cwd, opts2 = {}) {
5377
+ let raw;
5378
+ try {
5379
+ const args = ["diff", "--numstat"];
5380
+ if (opts2.againstHead) args.push("HEAD");
5381
+ args.push("--", ".");
5382
+ try {
5383
+ raw = (await execFileAsync("git", args, { cwd, timeout: 5e3 })).stdout;
5384
+ } catch (err) {
5385
+ if (!opts2.againstHead) throw err;
5386
+ raw = (await execFileAsync("git", ["diff", "--numstat", "--", "."], { cwd, timeout: 5e3 })).stdout;
5387
+ }
5388
+ } catch {
5389
+ return null;
5390
+ }
5391
+ const entries = parseNumstat(raw);
5392
+ if (opts2.includeUntracked) {
5393
+ try {
5394
+ const { stdout } = await execFileAsync("git", ["ls-files", "--others", "--exclude-standard"], { cwd, timeout: 5e3 });
5395
+ const { readFile: readFile32, stat: stat11 } = await import("fs/promises");
5396
+ const { join: join54 } = await import("path");
5397
+ for (const file of stdout.split("\n").filter(Boolean)) {
5398
+ let lines = 0;
5399
+ try {
5400
+ const path = join54(cwd, file);
5401
+ const size = (await stat11(path)).size;
5402
+ if (size > 0 && size <= 1024 * 1024) {
5403
+ const text = await readFile32(path, "utf8");
5404
+ lines = text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
5405
+ }
5406
+ } catch {
5407
+ }
5408
+ entries.push({ file, insertions: lines, deletions: 0 });
5409
+ raw += `${lines} 0 ${file}
5410
+ `;
5411
+ }
5412
+ } catch {
5413
+ }
5414
+ }
5415
+ return { raw, entries };
5416
+ }
5417
+ async function gitPatch(cwd, maxBytes) {
5418
+ try {
5419
+ let stdout;
5420
+ try {
5421
+ stdout = (await execFileAsync("git", ["diff", "HEAD", "--", "."], { cwd, timeout: 1e4, maxBuffer: maxBytes * 4 })).stdout;
5422
+ } catch {
5423
+ stdout = (await execFileAsync("git", ["diff", "--", "."], { cwd, timeout: 1e4, maxBuffer: maxBytes * 4 })).stdout;
5424
+ }
5425
+ return Buffer.byteLength(stdout, "utf8") <= maxBytes ? stdout : null;
5426
+ } catch {
5427
+ return null;
5428
+ }
5429
+ }
5430
+ var execFileAsync;
5431
+ var init_git_diff = __esm({
5432
+ "src/cost-attribution/git-diff.ts"() {
5433
+ "use strict";
5434
+ execFileAsync = promisify(execFile);
5435
+ }
5436
+ });
5437
+
5438
+ // src/trace/git-delta.ts
5439
+ function toMap(entries) {
5440
+ return new Map(entries.map((e) => [e.file, e]));
5441
+ }
5442
+ function diffNumstat(before, after) {
5443
+ const b = toMap(before);
5444
+ const a = toMap(after);
5445
+ const files = /* @__PURE__ */ new Set([...b.keys(), ...a.keys()]);
5446
+ let insertions = 0;
5447
+ let deletions = 0;
5448
+ const changed = [];
5449
+ for (const file of files) {
5450
+ const x = b.get(file) ?? { file, insertions: 0, deletions: 0 };
5451
+ const y = a.get(file) ?? { file, insertions: 0, deletions: 0 };
5452
+ const di = y.insertions - x.insertions;
5453
+ const dd = y.deletions - x.deletions;
5454
+ if (di === 0 && dd === 0) continue;
5455
+ changed.push(file);
5456
+ insertions += Math.max(0, di);
5457
+ deletions += Math.max(0, dd);
5458
+ }
5459
+ changed.sort();
5460
+ return { files_changed: changed.length, insertions, deletions, files: changed };
5461
+ }
5462
+ function rememberGitDelta(sessionId, turnId, files) {
5463
+ if (files.length === 0) return;
5464
+ lastDeltaBySession.set(sessionId, { turnId, files });
5465
+ }
5466
+ function detectGitRevert(sessionId, command) {
5467
+ const last = lastDeltaBySession.get(sessionId);
5468
+ if (!last) return null;
5469
+ if (!REVERT_COMMAND.test(command)) return null;
5470
+ const isStash = /git\s+stash\b/.test(command) && !/git\s+stash\s+(pop|apply|list|show|drop)\b/.test(command);
5471
+ if (isStash) return { turnId: last.turnId };
5472
+ if (/git\s+(checkout\s+--|restore)\s*$/.test(command.trim())) return null;
5473
+ const hit = last.files.some((file) => {
5474
+ const base = file.slice(file.lastIndexOf("/") + 1);
5475
+ return command.includes(file) || base && new RegExp(`(^|[\\s/'"])${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(['"\\s]|$)`).test(command);
5476
+ });
5477
+ if (hit) return { turnId: last.turnId };
5478
+ if (/git\s+restore\s+(\.|--staged\s+\.)(\s|$)/.test(command) || /git\s+checkout\s+--\s+\.(\s|$)/.test(command)) return { turnId: last.turnId };
5479
+ return null;
5480
+ }
5481
+ async function beginGitDelta(opts2) {
5482
+ if (!opts2.trace.enabled) return null;
5483
+ const before = await gitNumstat(opts2.cwd, { againstHead: true, includeUntracked: true });
5484
+ if (!before) return null;
5485
+ let done = false;
5486
+ return {
5487
+ async finish() {
5488
+ if (done) return;
5489
+ done = true;
5490
+ const after = await gitNumstat(opts2.cwd, { againstHead: true, includeUntracked: true });
5491
+ if (!after || !opts2.trace.enabled) return;
5492
+ const delta = diffNumstat(before.entries, after.entries);
5493
+ const patch = delta.files_changed > 0 ? await gitPatch(opts2.cwd, MAX_PATCH_BYTES) : null;
5494
+ opts2.trace.emit(
5495
+ "outcome",
5496
+ {
5497
+ kind: "git_delta",
5498
+ turn_id: opts2.turnId,
5499
+ files_changed: delta.files_changed,
5500
+ insertions: delta.insertions,
5501
+ deletions: delta.deletions,
5502
+ numstat_blob: opts2.trace.blob(after.raw) ?? "",
5503
+ ...patch ? { patch_blob: opts2.trace.blob(patch) ?? "" } : {}
5504
+ },
5505
+ opts2.turnId
5506
+ );
5507
+ if (opts2.sessionId) rememberGitDelta(opts2.sessionId, opts2.turnId, delta.files);
5508
+ }
5509
+ };
5510
+ }
5511
+ var MAX_PATCH_BYTES, lastDeltaBySession, REVERT_COMMAND;
5512
+ var init_git_delta = __esm({
5513
+ "src/trace/git-delta.ts"() {
5514
+ "use strict";
5515
+ init_git_diff();
5516
+ MAX_PATCH_BYTES = 512 * 1024;
5517
+ lastDeltaBySession = /* @__PURE__ */ new Map();
5518
+ REVERT_COMMAND = /(^|[\s;&|(])git\s+(checkout\s+--|restore\b|stash\b)/;
5519
+ }
5520
+ });
5521
+
4967
5522
  // src/agent/loop.ts
4968
5523
  function getSessionWebFetchHistory(sessionId) {
4969
5524
  const key = sessionId ?? "default";
@@ -5002,7 +5557,90 @@ function raceWithSignal(promise, signal) {
5002
5557
  ]);
5003
5558
  }
5004
5559
  async function runAgentTurn(opts2) {
5005
- const turnStart = performance.now();
5560
+ const tr = {
5561
+ trace: opts2.trace ?? createNoopRecorder(opts2.sessionId ?? ""),
5562
+ turnId: opts2.turnId ?? makeTurnId(),
5563
+ startedAt: performance.now(),
5564
+ toolsUsed: [],
5565
+ partialText: "",
5566
+ finalText: null,
5567
+ costUsd: 0,
5568
+ endReason: null
5569
+ };
5570
+ const endTurn = (reason, finalText) => {
5571
+ if (!tr.trace.enabled) return;
5572
+ const finalBlob = finalText ? tr.trace.blob(finalText) : null;
5573
+ tr.trace.emit(
5574
+ "turn_end",
5575
+ {
5576
+ reason,
5577
+ tools_used: tr.toolsUsed,
5578
+ ...tr.costUsd > 0 ? { cost_usd: tr.costUsd } : {},
5579
+ duration_ms: Math.round(performance.now() - tr.startedAt),
5580
+ ...finalBlob ? { final_text_blob: finalBlob } : {}
5581
+ },
5582
+ tr.turnId
5583
+ );
5584
+ };
5585
+ const gitDelta = await beginGitDelta({ trace: tr.trace, turnId: tr.turnId, sessionId: opts2.sessionId, cwd: opts2.cwd }).catch(
5586
+ () => null
5587
+ );
5588
+ try {
5589
+ await runAgentTurnInner(opts2, tr);
5590
+ endTurn(tr.endReason ?? "complete", tr.finalText);
5591
+ } catch (err) {
5592
+ if (err instanceof DOMException && err.name === "AbortError") {
5593
+ endTurn("aborted", tr.partialText || tr.finalText);
5594
+ } else if (err instanceof AgentLoopError || err instanceof BudgetExhaustedError) {
5595
+ endTurn("guardrail", tr.finalText);
5596
+ } else {
5597
+ endTurn("error", tr.partialText || tr.finalText);
5598
+ }
5599
+ throw err;
5600
+ } finally {
5601
+ await gitDelta?.finish().catch(() => void 0);
5602
+ }
5603
+ }
5604
+ async function runAgentTurnInner(opts2, tr) {
5605
+ const turnStart = tr.startedAt;
5606
+ const { trace, turnId } = tr;
5607
+ const notifyGuardrail = (ev) => {
5608
+ if (ev.kind !== "completion_nudge") trace.emit("guardrail", { kind: ev.kind, message: ev.message }, turnId);
5609
+ opts2.callbacks.onGuardrail?.(ev);
5610
+ };
5611
+ const traceBlocked = (tc, result2, errorCode, redirected = false) => {
5612
+ if (!trace.enabled) return;
5613
+ const argsBlob = trace.blob(tc.function.arguments) ?? "";
5614
+ trace.emit(
5615
+ "tool_call",
5616
+ {
5617
+ tool_call_id: tc.id,
5618
+ name: tc.function.name,
5619
+ args_blob: argsBlob,
5620
+ ...tc.function.arguments.length <= 2048 ? { args_inline: parseArgsObject(tc.function.arguments) } : {},
5621
+ needs_permission: false,
5622
+ code_mode_redirected: redirected
5623
+ },
5624
+ turnId
5625
+ );
5626
+ const blob = trace.blob(result2.content) ?? "";
5627
+ const bytes = Buffer.byteLength(result2.content, "utf8");
5628
+ trace.emit(
5629
+ "tool_result",
5630
+ {
5631
+ tool_call_id: tc.id,
5632
+ name: tc.function.name,
5633
+ ok: false,
5634
+ error_code: errorCode,
5635
+ raw_blob: blob,
5636
+ raw_bytes: bytes,
5637
+ reduced_blob: blob,
5638
+ reduced_bytes: bytes,
5639
+ duration_ms: 0
5640
+ },
5641
+ turnId
5642
+ );
5643
+ };
5006
5644
  logger.info("turn:start", { sessionId: opts2.sessionId, codeMode: opts2.codeMode ?? false });
5007
5645
  const max = opts2.maxToolIterations ?? 200;
5008
5646
  const limitBehavior = opts2.toolLimitBehavior ?? (opts2.continueOnLimit ? "continue" : "throw");
@@ -5164,6 +5802,18 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5164
5802
  const webFetchHistory = getSessionWebFetchHistory(opts2.sessionId);
5165
5803
  let webFetchesThisTurn = 0;
5166
5804
  let codeModeRedirects = 0;
5805
+ const noteCodeModeRedirect = () => {
5806
+ if (codeModeRedirects === MAX_CODE_MODE_REDIRECTS) {
5807
+ trace.emit(
5808
+ "guardrail",
5809
+ {
5810
+ kind: "code_mode_redirect_cap",
5811
+ message: `Code Mode redirect cap (${MAX_CODE_MODE_REDIRECTS}) reached \u2014 direct tool calls now execute normally.`
5812
+ },
5813
+ turnId
5814
+ );
5815
+ }
5816
+ };
5167
5817
  const MAX_WEB_FETCH_PER_TURN = 5;
5168
5818
  let cumulativePromptTokens = 0;
5169
5819
  let iter = 0;
@@ -5201,7 +5851,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5201
5851
  role: "system",
5202
5852
  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
5853
  });
5204
- opts2.callbacks.onGuardrail?.({
5854
+ notifyGuardrail({
5205
5855
  kind: "limit_ceiling",
5206
5856
  message: `Tool-iteration ceiling (${hardCeiling}) reached \u2014 wrapping up the turn with a summary.`,
5207
5857
  totalIterations: totalIter
@@ -5213,17 +5863,18 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5213
5863
  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
5864
  });
5215
5865
  iter = 0;
5216
- opts2.callbacks.onGuardrail?.({
5866
+ notifyGuardrail({
5217
5867
  kind: "limit_reset",
5218
5868
  message: `Tool-call limit (${max}) reached \u2014 counter reset automatically (${totalIter}/${hardCeiling} total).`,
5219
5869
  totalIterations: totalIter
5220
5870
  });
5221
5871
  } else if (limitBehavior === "stop") {
5222
- opts2.callbacks.onGuardrail?.({
5872
+ notifyGuardrail({
5223
5873
  kind: "limit_stopped",
5224
5874
  message: `Tool-call limit (${max}) reached \u2014 turn stopped.`,
5225
5875
  totalIterations: totalIter
5226
5876
  });
5877
+ tr.endReason = "guardrail";
5227
5878
  await fireStopHook();
5228
5879
  return;
5229
5880
  } else {
@@ -5240,6 +5891,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5240
5891
  let reasoning = "";
5241
5892
  let responseMeta;
5242
5893
  let reasoningDetails = [];
5894
+ tr.partialText = "";
5243
5895
  opts2.callbacks.onAssistantStart?.();
5244
5896
  const stripReasoning = process.env.KIMIFLARE_STRIP_REASONING === "1";
5245
5897
  const shadowStrip = process.env.KIMIFLARE_SHADOW_STRIP === "1";
@@ -5286,6 +5938,17 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5286
5938
  budgetTokens: maxPromptTokens
5287
5939
  };
5288
5940
  logger.info("context:compacted", { sessionId: opts2.sessionId, ...info });
5941
+ trace.emit(
5942
+ "compaction",
5943
+ {
5944
+ method: "preflight",
5945
+ tokens_before: info.tokensBefore,
5946
+ tokens_after: info.tokensAfter,
5947
+ turns_removed: info.turnsRemoved,
5948
+ artifacts_archived: info.artifactsArchived
5949
+ },
5950
+ turnId
5951
+ );
5289
5952
  opts2.callbacks.onCompacted?.(info);
5290
5953
  }
5291
5954
  }
@@ -5305,7 +5968,9 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5305
5968
  reasoningEffort: opts2.reasoningEffort,
5306
5969
  sessionId: opts2.sessionId,
5307
5970
  idleTimeoutMs: opts2.idleTimeoutMs ?? 6e4,
5308
- postFirstByteIdleTimeoutMs: opts2.postFirstByteIdleTimeoutMs
5971
+ postFirstByteIdleTimeoutMs: opts2.postFirstByteIdleTimeoutMs,
5972
+ trace,
5973
+ turnId
5309
5974
  });
5310
5975
  let gotFirstChunk = false;
5311
5976
  for await (const ev of events) {
@@ -5327,6 +5992,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5327
5992
  break;
5328
5993
  case "text":
5329
5994
  content += ev.delta;
5995
+ tr.partialText = content;
5330
5996
  opts2.callbacks.onTextDelta?.(ev.delta);
5331
5997
  break;
5332
5998
  case "tool_call_start":
@@ -5343,6 +6009,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5343
6009
  function: { name: normalizeToolName(ev.name, opts2.tools), arguments: safeArgs }
5344
6010
  };
5345
6011
  toolCalls.push(call);
6012
+ tr.toolsUsed.push(call.function.name);
5346
6013
  opts2.callbacks.onToolCallFinalized?.(call);
5347
6014
  break;
5348
6015
  }
@@ -5356,6 +6023,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5356
6023
  }
5357
6024
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
5358
6025
  if (lastUsage) {
6026
+ if (typeof lastUsage.cost === "number") tr.costUsd += lastUsage.cost;
5359
6027
  opts2.callbacks.onUsageFinal?.(lastUsage, responseMeta);
5360
6028
  cumulativePromptTokens += lastUsage.prompt_tokens;
5361
6029
  if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens) {
@@ -5384,8 +6052,10 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5384
6052
  }
5385
6053
  }
5386
6054
  opts2.messages.push(assistantMsg);
6055
+ tr.finalText = typeof assistantMsg.content === "string" ? assistantMsg.content : null;
5387
6056
  opts2.callbacks.onAssistantFinal?.(assistantMsg);
5388
6057
  if (finalizeReason !== null) {
6058
+ tr.endReason = "guardrail";
5389
6059
  logger.info("turn:finalized", { sessionId: opts2.sessionId, reason: finalizeReason, totalIter });
5390
6060
  if (budgetExhausted) throw new BudgetExhaustedError();
5391
6061
  await fireStopHook();
@@ -5424,11 +6094,12 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5424
6094
  if (shouldCheckCompletion(input, opts2.intentClassification?.tier)) {
5425
6095
  const p = await opts2.completionCheck(input, opts2.signal).catch(() => null);
5426
6096
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
6097
+ if (p !== null) trace.emit("completion_check", { probability: p, nudged: p < INCOMPLETE_THRESHOLD }, turnId);
5427
6098
  if (p !== null && p < INCOMPLETE_THRESHOLD) {
5428
6099
  completionNudges++;
5429
6100
  logger.info("turn:completion_nudge", { sessionId: opts2.sessionId, probability: p });
5430
6101
  opts2.messages.push({ role: "system", content: CONTINUE_NUDGE });
5431
- opts2.callbacks.onGuardrail?.({
6102
+ notifyGuardrail({
5432
6103
  kind: "completion_nudge",
5433
6104
  message: "The agent stopped before finishing \u2014 asked it to continue.",
5434
6105
  probability: p
@@ -5468,6 +6139,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5468
6139
  kind: "blocked",
5469
6140
  tc,
5470
6141
  loopSignature,
6142
+ errorCode: "loop_guardrail",
5471
6143
  result: {
5472
6144
  tool_call_id: tc.id,
5473
6145
  name: tc.function.name,
@@ -5480,10 +6152,13 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5480
6152
  }
5481
6153
  if (codeMode && CODE_MODE_REDIRECT_TOOLS.has(tc.function.name) && codeModeRedirects < MAX_CODE_MODE_REDIRECTS) {
5482
6154
  codeModeRedirects++;
6155
+ noteCodeModeRedirect();
5483
6156
  items.push({
5484
6157
  kind: "blocked",
5485
6158
  tc,
5486
6159
  loopSignature,
6160
+ errorCode: "code_mode_redirect",
6161
+ redirected: true,
5487
6162
  result: { tool_call_id: tc.id, name: tc.function.name, content: codeModeRedirectMessage(tc.function.name), ok: false }
5488
6163
  });
5489
6164
  continue;
@@ -5500,6 +6175,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5500
6175
  kind: "blocked",
5501
6176
  tc,
5502
6177
  loopSignature,
6178
+ errorCode: "web_fetch_budget",
5503
6179
  result: { tool_call_id: tc.id, name: "web_fetch", content: warning, ok: false, guardrail: true }
5504
6180
  });
5505
6181
  continue;
@@ -5520,6 +6196,8 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5520
6196
  { id: it.tc.id, name: it.tc.function.name, arguments: it.tc.function.arguments },
5521
6197
  batchAskPermission,
5522
6198
  {
6199
+ trace,
6200
+ turnId,
5523
6201
  cwd: opts2.cwd,
5524
6202
  signal: opts2.signal,
5525
6203
  onTasks: wrappedOnTasks,
@@ -5562,6 +6240,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5562
6240
  }
5563
6241
  for (const it of items) {
5564
6242
  if (it.kind === "blocked") {
6243
+ traceBlocked(it.tc, it.result, it.errorCode, it.redirected === true);
5565
6244
  toolResults.push(it.result);
5566
6245
  opts2.messages.push({
5567
6246
  role: "tool",
@@ -5697,6 +6376,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5697
6376
  content: `Tool ${tc.function.name} is not available under this turn's policy. Continue without it or ask the user to enable it.`,
5698
6377
  ok: false
5699
6378
  };
6379
+ traceBlocked(tc, unavailableResult, "unavailable");
5700
6380
  toolResults.push(unavailableResult);
5701
6381
  opts2.messages.push({
5702
6382
  role: "tool",
@@ -5720,6 +6400,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5720
6400
  ok: false,
5721
6401
  guardrail: true
5722
6402
  };
6403
+ traceBlocked(tc, loopResult, "loop_guardrail");
5723
6404
  toolResults.push(loopResult);
5724
6405
  opts2.messages.push({
5725
6406
  role: "tool",
@@ -5748,6 +6429,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5748
6429
  ok: false,
5749
6430
  guardrail: true
5750
6431
  };
6432
+ traceBlocked(tc, budgetResult, "web_fetch_budget");
5751
6433
  toolResults.push(budgetResult);
5752
6434
  opts2.messages.push({
5753
6435
  role: "tool",
@@ -5770,6 +6452,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5770
6452
  ok: false,
5771
6453
  guardrail: true
5772
6454
  };
6455
+ traceBlocked(tc, sessionCapResult, "web_fetch_budget");
5773
6456
  toolResults.push(sessionCapResult);
5774
6457
  opts2.messages.push({
5775
6458
  role: "tool",
@@ -5791,12 +6474,39 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5791
6474
  if (codeMode && tc.function.name === "execute_code") {
5792
6475
  const args = JSON.parse(tc.function.arguments || "{}");
5793
6476
  const code = args.code || "";
6477
+ const sandboxStartedAt = performance.now();
6478
+ if (trace.enabled) {
6479
+ trace.emit(
6480
+ "tool_call",
6481
+ {
6482
+ tool_call_id: tc.id,
6483
+ name: "execute_code",
6484
+ args_blob: trace.blob(tc.function.arguments) ?? "",
6485
+ ...tc.function.arguments.length <= 2048 ? { args_inline: parseArgsObject(tc.function.arguments) } : {},
6486
+ needs_permission: false,
6487
+ code_mode_redirected: false
6488
+ },
6489
+ turnId
6490
+ );
6491
+ }
5794
6492
  const sandboxResult = await runInSandbox({
5795
6493
  code,
5796
6494
  tools: sandboxTools,
5797
6495
  executor: opts2.executor,
5798
6496
  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 },
6497
+ ctx: {
6498
+ cwd: opts2.cwd,
6499
+ signal: opts2.signal,
6500
+ onTasks: wrappedOnTasks,
6501
+ onPlanOptions: opts2.callbacks.onPlanOptions,
6502
+ coauthor: opts2.coauthor,
6503
+ memoryManager: opts2.memoryManager,
6504
+ sessionId: opts2.sessionId,
6505
+ githubToken: opts2.githubToken,
6506
+ trace,
6507
+ turnId,
6508
+ codeModeParentToolCallId: tc.id
6509
+ },
5800
6510
  timeoutMs: 3e4,
5801
6511
  memoryLimitMB: 128
5802
6512
  });
@@ -5819,6 +6529,7 @@ Not in this API \u2014 call these as separate tools instead: ${directTools.map((
5819
6529
 
5820
6530
  Output:
5821
6531
  ${sandboxResult.output}` : sandboxResult.output;
6532
+ const sandboxRawContent = resultContent;
5822
6533
  if (resultContent.length > MAX_TOOL_CONTENT_CHARS) {
5823
6534
  const rawBytes = resultContent.length;
5824
6535
  resultContent = resultContent.slice(0, MAX_TOOL_CONTENT_CHARS) + `
@@ -5837,6 +6548,24 @@ ${sandboxResult.output}` : sandboxResult.output;
5837
6548
  content: resultContent,
5838
6549
  ok: !sandboxResult.error
5839
6550
  };
6551
+ if (trace.enabled) {
6552
+ const rawBlob = trace.blob(sandboxRawContent) ?? "";
6553
+ trace.emit(
6554
+ "tool_result",
6555
+ {
6556
+ tool_call_id: tc.id,
6557
+ name: "execute_code",
6558
+ ok: result2.ok,
6559
+ ...sandboxResult.error ? { error_code: "sandbox_error" } : {},
6560
+ raw_blob: rawBlob,
6561
+ raw_bytes: Buffer.byteLength(sandboxRawContent, "utf8"),
6562
+ reduced_blob: sandboxRawContent === resultContent ? rawBlob : trace.blob(resultContent) ?? "",
6563
+ reduced_bytes: Buffer.byteLength(resultContent, "utf8"),
6564
+ duration_ms: Math.round(performance.now() - sandboxStartedAt)
6565
+ },
6566
+ turnId
6567
+ );
6568
+ }
5840
6569
  toolResults.push(result2);
5841
6570
  opts2.messages.push({
5842
6571
  role: "tool",
@@ -5849,8 +6578,10 @@ ${sandboxResult.output}` : sandboxResult.output;
5849
6578
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
5850
6579
  } else if (codeMode && CODE_MODE_REDIRECT_TOOLS.has(tc.function.name) && codeModeRedirects < MAX_CODE_MODE_REDIRECTS) {
5851
6580
  codeModeRedirects++;
6581
+ noteCodeModeRedirect();
5852
6582
  const msg = codeModeRedirectMessage(tc.function.name);
5853
6583
  const redirectResult = { tool_call_id: tc.id, name: tc.function.name, content: msg, ok: false };
6584
+ traceBlocked(tc, redirectResult, "code_mode_redirect", true);
5854
6585
  toolResults.push(redirectResult);
5855
6586
  opts2.messages.push({ role: "tool", tool_call_id: tc.id, content: msg, name: tc.function.name });
5856
6587
  opts2.callbacks.onToolResult?.(redirectResult);
@@ -5861,6 +6592,8 @@ ${sandboxResult.output}` : sandboxResult.output;
5861
6592
  { id: tc.id, name: tc.function.name, arguments: tc.function.arguments },
5862
6593
  opts2.callbacks.askPermission,
5863
6594
  {
6595
+ trace,
6596
+ turnId,
5864
6597
  cwd: opts2.cwd,
5865
6598
  signal: opts2.signal,
5866
6599
  onTasks: wrappedOnTasks,
@@ -5929,6 +6662,7 @@ ${sandboxResult.output}` : sandboxResult.output;
5929
6662
  condition: result2.waitRequest.condition,
5930
6663
  wakeAt: result2.waitRequest.wakeAt
5931
6664
  });
6665
+ tr.endReason = "needs_input";
5932
6666
  try {
5933
6667
  opts2.callbacks.onRunYield?.(result2.waitRequest);
5934
6668
  } catch (error) {
@@ -6087,7 +6821,7 @@ ${sandboxResult.output}` : sandboxResult.output;
6087
6821
  role: "system",
6088
6822
  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
6823
  });
6090
- opts2.callbacks.onGuardrail?.({
6824
+ notifyGuardrail({
6091
6825
  kind: "loop_recovery",
6092
6826
  message: "Loop guardrail: repeated tool calls were blocked \u2014 asked the agent to change approach."
6093
6827
  });
@@ -6098,7 +6832,7 @@ ${sandboxResult.output}` : sandboxResult.output;
6098
6832
  role: "system",
6099
6833
  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
6834
  });
6101
- opts2.callbacks.onGuardrail?.({
6835
+ notifyGuardrail({
6102
6836
  kind: "loop_stopped",
6103
6837
  message: "Loop guardrail: the agent kept repeating blocked tool calls \u2014 ending the turn with a summary."
6104
6838
  });
@@ -6159,6 +6893,8 @@ var init_loop = __esm({
6159
6893
  init_context_budget();
6160
6894
  init_turn_directive();
6161
6895
  init_completion_check();
6896
+ init_recorder();
6897
+ init_git_delta();
6162
6898
  MAX_LOOP_RECOVERIES = 1;
6163
6899
  BudgetExhaustedError = class extends Error {
6164
6900
  constructor(message2 = "Cumulative input token budget exhausted") {
@@ -6214,10 +6950,10 @@ var init_pricing = __esm({
6214
6950
  // src/util/atomic-file.ts
6215
6951
  import { mkdir as mkdir6, rename, rm, stat as stat3, writeFile as writeFile5 } from "fs/promises";
6216
6952
  import { dirname as dirname6 } from "path";
6217
- import { randomUUID } from "crypto";
6953
+ import { randomUUID as randomUUID2 } from "crypto";
6218
6954
  async function writeFileAtomic(path, data) {
6219
6955
  await mkdir6(dirname6(path), { recursive: true });
6220
- const tmp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
6956
+ const tmp = `${path}.${process.pid}.${randomUUID2().slice(0, 8)}.tmp`;
6221
6957
  try {
6222
6958
  await writeFile5(tmp, data, "utf8");
6223
6959
  await rename(tmp, path);
@@ -6272,19 +7008,19 @@ var init_atomic_file = __esm({
6272
7008
 
6273
7009
  // src/usage-tracker.ts
6274
7010
  import { readFile as readFile5, rename as rename2 } from "fs/promises";
6275
- import { homedir as homedir8 } from "os";
6276
- import { join as join13 } from "path";
7011
+ import { homedir as homedir9 } from "os";
7012
+ import { join as join15 } from "path";
6277
7013
  import { EventEmitter } from "events";
6278
- import { randomUUID as randomUUID2 } from "crypto";
7014
+ import { randomUUID as randomUUID3 } from "crypto";
6279
7015
  function usageDir() {
6280
- const xdg = process.env.XDG_DATA_HOME || join13(homedir8(), ".local", "share");
6281
- return join13(xdg, "kimiflare");
7016
+ const xdg = process.env.XDG_DATA_HOME || join15(homedir9(), ".local", "share");
7017
+ return join15(xdg, "kimiflare");
6282
7018
  }
6283
7019
  function usagePath() {
6284
- return join13(usageDir(), "usage.json");
7020
+ return join15(usageDir(), "usage.json");
6285
7021
  }
6286
7022
  function historyPath() {
6287
- return join13(usageDir(), "history.jsonl");
7023
+ return join15(usageDir(), "history.jsonl");
6288
7024
  }
6289
7025
  function today() {
6290
7026
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -6404,7 +7140,7 @@ async function recordUsage(sessionId, usage, lookup, model) {
6404
7140
  );
6405
7141
  const estimatedCost = cost.total;
6406
7142
  const cachedTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
6407
- const turnId = randomUUID2();
7143
+ const turnId = randomUUID3();
6408
7144
  const generationId = lookup?.meta.generationId;
6409
7145
  const inlineCost = typeof usage.cost === "number" && Number.isFinite(usage.cost) ? usage.cost : void 0;
6410
7146
  await withLock(async () => {
@@ -6705,12 +7441,41 @@ var init_tool_error = __esm({
6705
7441
  }
6706
7442
  });
6707
7443
 
7444
+ // src/trace/verification.ts
7445
+ function segment(alternatives) {
7446
+ return new RegExp(`${SEG}(?:${alternatives})(?:\\s|$)`);
7447
+ }
7448
+ function classifyVerification(command) {
7449
+ const trimmed = command.trim();
7450
+ if (!trimmed) return null;
7451
+ for (const [category, re] of TABLE) {
7452
+ if (re.test(trimmed)) return category;
7453
+ }
7454
+ return null;
7455
+ }
7456
+ var SEG, TABLE;
7457
+ var init_verification = __esm({
7458
+ "src/trace/verification.ts"() {
7459
+ "use strict";
7460
+ 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\/)?`;
7461
+ TABLE = [
7462
+ [
7463
+ "test",
7464
+ 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`)
7465
+ ],
7466
+ ["lint", segment(String.raw`eslint|ruff|golangci-lint|npm\s+run\s+lint|pnpm\s+(?:run\s+)?lint|yarn\s+(?:run\s+)?lint`)],
7467
+ ["typecheck", segment(String.raw`tsc|mypy|pyright|npm\s+run\s+typecheck|pnpm\s+(?:run\s+)?typecheck|yarn\s+(?:run\s+)?typecheck`)],
7468
+ ["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`)]
7469
+ ];
7470
+ }
7471
+ });
7472
+
6708
7473
  // src/util/paths.ts
6709
7474
  import { resolve, isAbsolute, relative, sep } from "path";
6710
- import { homedir as homedir9 } from "os";
7475
+ import { homedir as homedir10 } from "os";
6711
7476
  function resolvePath(cwd, input) {
6712
7477
  if (input.startsWith("~/") || input === "~") {
6713
- return resolve(homedir9(), input === "~" ? "." : input.slice(2));
7478
+ return resolve(homedir10(), input === "~" ? "." : input.slice(2));
6714
7479
  }
6715
7480
  return isAbsolute(input) ? input : resolve(cwd, input);
6716
7481
  }
@@ -6737,7 +7502,7 @@ function collapsePath(input, cwd, maxLen = 40) {
6737
7502
  function collapsePathsInText(s, cwd, maxLen = 40) {
6738
7503
  return s.replace(/([~/][^\s"',)}\]]+)/g, (match) => collapsePath(match, cwd, maxLen));
6739
7504
  }
6740
- var init_paths = __esm({
7505
+ var init_paths2 = __esm({
6741
7506
  "src/util/paths.ts"() {
6742
7507
  "use strict";
6743
7508
  }
@@ -6794,7 +7559,7 @@ var MAX_BYTES, MAX_STREAM_BYTES, readTool;
6794
7559
  var init_read = __esm({
6795
7560
  "src/tools/read.ts"() {
6796
7561
  "use strict";
6797
- init_paths();
7562
+ init_paths2();
6798
7563
  MAX_BYTES = 2 * 1024 * 1024;
6799
7564
  MAX_STREAM_BYTES = 50 * 1024 * 1024;
6800
7565
  readTool = {
@@ -6845,7 +7610,7 @@ var writeTool;
6845
7610
  var init_write = __esm({
6846
7611
  "src/tools/write.ts"() {
6847
7612
  "use strict";
6848
- init_paths();
7613
+ init_paths2();
6849
7614
  writeTool = {
6850
7615
  name: "write",
6851
7616
  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 +7661,7 @@ var editTool;
6896
7661
  var init_edit = __esm({
6897
7662
  "src/tools/edit.ts"() {
6898
7663
  "use strict";
6899
- init_paths();
7664
+ init_paths2();
6900
7665
  editTool = {
6901
7666
  name: "edit",
6902
7667
  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 +7702,7 @@ var init_edit = __esm({
6937
7702
  // src/tools/bash.ts
6938
7703
  import { spawn } from "child_process";
6939
7704
  import { tmpdir, platform as platform2 } from "os";
6940
- import { join as join14 } from "path";
7705
+ import { join as join16 } from "path";
6941
7706
  function getShellCommand(override) {
6942
7707
  const raw = override?.trim();
6943
7708
  if (raw && raw !== "auto") {
@@ -7139,7 +7904,7 @@ function injectCoauthor(command, coauthor) {
7139
7904
  if (movesHeadOnly) return unchanged;
7140
7905
  const env2 = {
7141
7906
  [COAUTHOR_TRAILER_ENV]: trailer,
7142
- [COAUTHOR_MSG_ENV]: join14(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`)
7907
+ [COAUTHOR_MSG_ENV]: join16(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`)
7143
7908
  };
7144
7909
  if (createsCommit || isRebaseContinue) {
7145
7910
  return { command: `(${command}) && { ${AMEND_BLOCK}
@@ -7232,7 +7997,8 @@ ${stderr}`
7232
7997
  resolve15({
7233
7998
  content: raw,
7234
7999
  rawBytes: Buffer.byteLength(raw, "utf8"),
7235
- reducedBytes: Buffer.byteLength(raw, "utf8")
8000
+ reducedBytes: Buffer.byteLength(raw, "utf8"),
8001
+ meta: { exitCode: code, timedOut: killedByTimeout }
7236
8002
  });
7237
8003
  });
7238
8004
  });
@@ -7280,7 +8046,7 @@ var init_bash = __esm({
7280
8046
 
7281
8047
  // src/util/glob.ts
7282
8048
  import { readdir as readdir3, lstat, realpath } from "fs/promises";
7283
- import { join as join15, relative as relative2, resolve as resolve2 } from "path";
8049
+ import { join as join17, relative as relative2, resolve as resolve2 } from "path";
7284
8050
  function parsePattern(pattern) {
7285
8051
  const parts = pattern.split(/\//g);
7286
8052
  return parts.map((p) => {
@@ -7321,8 +8087,8 @@ function globToRegex(glob2) {
7321
8087
  re += "$";
7322
8088
  return new RegExp(re);
7323
8089
  }
7324
- function matchSegment(segment, pattern) {
7325
- return globToRegex(pattern).test(segment);
8090
+ function matchSegment(segment2, pattern) {
8091
+ return globToRegex(pattern).test(segment2);
7326
8092
  }
7327
8093
  function shouldIgnore(relativePath, ignorePatterns) {
7328
8094
  for (const pat of ignorePatterns) {
@@ -7389,7 +8155,7 @@ async function* walk(root, pattern, options) {
7389
8155
  if (!dot && name.startsWith(".")) continue;
7390
8156
  const matched = seg.type === "literal" ? seg.value === name : matchSegment(name, seg.value);
7391
8157
  if (!matched) continue;
7392
- const childPath = join15(dirPath, name);
8158
+ const childPath = join17(dirPath, name);
7393
8159
  const childRel = [...relativeParts, name];
7394
8160
  const childRelStr = childRel.join("/");
7395
8161
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -7414,7 +8180,7 @@ async function* walk(root, pattern, options) {
7414
8180
  if (!dot && name.startsWith(".")) continue;
7415
8181
  const matched = seg.type === "literal" ? seg.value === name : matchSegment(name, seg.value);
7416
8182
  if (!matched) continue;
7417
- const childPath = join15(dirPath, name);
8183
+ const childPath = join17(dirPath, name);
7418
8184
  const childRel = [...relativeParts, name];
7419
8185
  const childRelStr = childRel.join("/");
7420
8186
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -7456,7 +8222,7 @@ async function* walk(root, pattern, options) {
7456
8222
  const name = String(ent.name);
7457
8223
  if (name === "." || name === "..") continue;
7458
8224
  if (!dot && name.startsWith(".")) continue;
7459
- const childPath = join15(dirPath, name);
8225
+ const childPath = join17(dirPath, name);
7460
8226
  const childRel = [...relativeParts, name];
7461
8227
  const childRelStr = childRel.join("/");
7462
8228
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -7550,7 +8316,7 @@ var globTool;
7550
8316
  var init_glob2 = __esm({
7551
8317
  "src/tools/glob.ts"() {
7552
8318
  "use strict";
7553
- init_paths();
8319
+ init_paths2();
7554
8320
  init_glob();
7555
8321
  globTool = {
7556
8322
  name: "glob",
@@ -7601,8 +8367,8 @@ var init_glob2 = __esm({
7601
8367
  });
7602
8368
 
7603
8369
  // src/tools/grep.ts
7604
- import { execFile } from "child_process";
7605
- import { promisify } from "util";
8370
+ import { execFile as execFile2 } from "child_process";
8371
+ import { promisify as promisify2 } from "util";
7606
8372
  import { readFile as readFile9 } from "fs/promises";
7607
8373
  async function hasRipgrep() {
7608
8374
  if (cachedHasRg !== null) return cachedHasRg;
@@ -7678,9 +8444,9 @@ var pExecFile, cachedHasRg, grepTool;
7678
8444
  var init_grep = __esm({
7679
8445
  "src/tools/grep.ts"() {
7680
8446
  "use strict";
7681
- init_paths();
8447
+ init_paths2();
7682
8448
  init_glob();
7683
- pExecFile = promisify(execFile);
8449
+ pExecFile = promisify2(execFile2);
7684
8450
  cachedHasRg = null;
7685
8451
  grepTool = {
7686
8452
  name: "grep",
@@ -8224,7 +8990,7 @@ __export(changelog_image_exports, {
8224
8990
  });
8225
8991
  import { readFile as readFile10 } from "fs/promises";
8226
8992
  import { writeFile as writeFile8 } from "fs/promises";
8227
- import { join as join16 } from "path";
8993
+ import { join as join18 } from "path";
8228
8994
  async function githubFetch2(path, token) {
8229
8995
  const controller = new AbortController();
8230
8996
  const timer3 = setTimeout(() => controller.abort(), TIMEOUT_MS4);
@@ -8255,7 +9021,7 @@ function escapeXml(str) {
8255
9021
  }
8256
9022
  async function loadLogoBase64() {
8257
9023
  try {
8258
- const buf = await readFile10(join16(process.cwd(), "docs", "logo.png"));
9024
+ const buf = await readFile10(join18(process.cwd(), "docs", "logo.png"));
8259
9025
  return `data:image/png;base64,${buf.toString("base64")}`;
8260
9026
  } catch {
8261
9027
  return null;
@@ -8563,7 +9329,7 @@ Format your response as plain text bullet points, one per line, starting with "\
8563
9329
  // src/tools/browser.ts
8564
9330
  import { mkdir as mkdir8 } from "fs/promises";
8565
9331
  import { tmpdir as tmpdir2 } from "os";
8566
- import { join as join17, dirname as dirname8 } from "path";
9332
+ import { join as join19, dirname as dirname8 } from "path";
8567
9333
  async function autoScroll(page) {
8568
9334
  await page.evaluate(async () => {
8569
9335
  await new Promise((resolve15) => {
@@ -8639,7 +9405,7 @@ var init_browser = __esm({
8639
9405
  }
8640
9406
  let screenshotPath;
8641
9407
  if (args.screenshot) {
8642
- screenshotPath = join17(tmpdir2(), `kimiflare-browser-${Date.now()}.png`);
9408
+ screenshotPath = join19(tmpdir2(), `kimiflare-browser-${Date.now()}.png`);
8643
9409
  await mkdir8(dirname8(screenshotPath), { recursive: true });
8644
9410
  await page.screenshot({ path: screenshotPath, fullPage: true });
8645
9411
  }
@@ -8893,13 +9659,13 @@ var init_memory = __esm({
8893
9659
  });
8894
9660
 
8895
9661
  // src/tools/hotcell-snapshot.ts
8896
- import { execFile as execFile2 } from "child_process";
9662
+ import { execFile as execFile3 } from "child_process";
8897
9663
  import { mkdtemp, rm as rm2 } from "fs/promises";
8898
9664
  import { tmpdir as tmpdir3 } from "os";
8899
- import { join as join18 } from "path";
8900
- import { promisify as promisify2 } from "util";
9665
+ import { join as join20 } from "path";
9666
+ import { promisify as promisify3 } from "util";
8901
9667
  async function git(args, cwd, env2) {
8902
- const { stdout } = await execFileAsync("git", args, {
9668
+ const { stdout } = await execFileAsync2("git", args, {
8903
9669
  cwd,
8904
9670
  env: env2 ? { ...process.env, ...env2 } : process.env,
8905
9671
  maxBuffer: 64 * 1024 * 1024,
@@ -8908,7 +9674,7 @@ async function git(args, cwd, env2) {
8908
9674
  return stdout.toString("utf8");
8909
9675
  }
8910
9676
  async function gitBuffer(args, cwd) {
8911
- const { stdout } = await execFileAsync("git", args, { cwd, maxBuffer: 64 * 1024 * 1024, encoding: "buffer" });
9677
+ const { stdout } = await execFileAsync2("git", args, { cwd, maxBuffer: 64 * 1024 * 1024, encoding: "buffer" });
8912
9678
  return stdout;
8913
9679
  }
8914
9680
  async function originBranchesContaining(commit, cwd) {
@@ -8944,9 +9710,9 @@ async function getRepositorySnapshot(cwd) {
8944
9710
  if (!ref) {
8945
9711
  throw new Error("Could not find an origin branch containing the base commit. Run git fetch, then retry.");
8946
9712
  }
8947
- const scratch = await mkdtemp(join18(tmpdir3(), "autopilot-snapshot-"));
9713
+ const scratch = await mkdtemp(join20(tmpdir3(), "autopilot-snapshot-"));
8948
9714
  try {
8949
- const env2 = { GIT_INDEX_FILE: join18(scratch, "index") };
9715
+ const env2 = { GIT_INDEX_FILE: join20(scratch, "index") };
8950
9716
  await git(["read-tree", "HEAD"], root, env2);
8951
9717
  await git(["add", "-A", "--", ".", ...SECRET_EXCLUDES], root, env2);
8952
9718
  const tree = (await git(["write-tree"], root, env2)).trim();
@@ -8971,9 +9737,9 @@ async function getRepositorySnapshot(cwd) {
8971
9737
  async function getWorkingTreeArchive(cwd, limitBytes = MAX_ARCHIVE_BYTES) {
8972
9738
  const root = (await git(["rev-parse", "--show-toplevel"], cwd).catch(() => "")).trim();
8973
9739
  if (!root) throw new Error("Subagents need to run inside a Git repository.");
8974
- const scratch = await mkdtemp(join18(tmpdir3(), "autopilot-archive-"));
9740
+ const scratch = await mkdtemp(join20(tmpdir3(), "autopilot-archive-"));
8975
9741
  try {
8976
- const env2 = { GIT_INDEX_FILE: join18(scratch, "index") };
9742
+ const env2 = { GIT_INDEX_FILE: join20(scratch, "index") };
8977
9743
  const hasHead = await git(["rev-parse", "--verify", "-q", "HEAD"], root).then(() => true, () => false);
8978
9744
  await git(hasHead ? ["read-tree", "HEAD"] : ["read-tree", "--empty"], root, env2);
8979
9745
  await git(["add", "-A", "--", ".", ...SECRET_EXCLUDES], root, env2);
@@ -8995,11 +9761,11 @@ function chunkPayload(base64, chunkSize = PAYLOAD_CHUNK_CHARS) {
8995
9761
  for (let i = 0; i < base64.length; i += chunkSize) chunks.push(base64.slice(i, i + chunkSize));
8996
9762
  return chunks;
8997
9763
  }
8998
- var execFileAsync, MAX_SNAPSHOT_PATCH_BYTES, MAX_ARCHIVE_BYTES, SECRET_EXCLUDES, PAYLOAD_CHUNK_CHARS;
9764
+ var execFileAsync2, MAX_SNAPSHOT_PATCH_BYTES, MAX_ARCHIVE_BYTES, SECRET_EXCLUDES, PAYLOAD_CHUNK_CHARS;
8999
9765
  var init_hotcell_snapshot = __esm({
9000
9766
  "src/tools/hotcell-snapshot.ts"() {
9001
9767
  "use strict";
9002
- execFileAsync = promisify2(execFile2);
9768
+ execFileAsync2 = promisify3(execFile3);
9003
9769
  MAX_SNAPSHOT_PATCH_BYTES = 4 * 1024 * 1024;
9004
9770
  MAX_ARCHIVE_BYTES = 24 * 1024 * 1024;
9005
9771
  SECRET_EXCLUDES = [
@@ -9022,11 +9788,11 @@ var init_hotcell_snapshot = __esm({
9022
9788
 
9023
9789
  // src/tools/hotcell-worker-cache.ts
9024
9790
  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";
9791
+ import { homedir as homedir11 } from "os";
9792
+ import { dirname as dirname9, join as join21 } from "path";
9027
9793
  function workerCachePath() {
9028
- const xdg = process.env.XDG_CONFIG_HOME || join19(homedir10(), ".config");
9029
- return join19(xdg, "kimiflare", "hotcell-worker-cache.json");
9794
+ const xdg = process.env.XDG_CONFIG_HOME || join21(homedir11(), ".config");
9795
+ return join21(xdg, "kimiflare", "hotcell-worker-cache.json");
9030
9796
  }
9031
9797
  async function readCache2() {
9032
9798
  try {
@@ -9098,8 +9864,8 @@ var init_hotcell_worker_cache = __esm({
9098
9864
 
9099
9865
  // src/tools/hotcell-worker.ts
9100
9866
  import { spawn as spawn2 } from "child_process";
9101
- import { promisify as promisify3 } from "util";
9102
- import { execFile as execFile3 } from "child_process";
9867
+ import { promisify as promisify4 } from "util";
9868
+ import { execFile as execFile4 } from "child_process";
9103
9869
  import { resolve as resolve3 } from "path";
9104
9870
  async function runHotcellWorker(options) {
9105
9871
  const workerId = `hotcell-${crypto.randomUUID().slice(0, 8)}`;
@@ -9505,7 +10271,7 @@ function releaseSlot() {
9505
10271
  runningWorkers = Math.max(0, runningWorkers - 1);
9506
10272
  for (const wake of workerQueue.slice()) wake();
9507
10273
  }
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;
10274
+ 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
10275
  var init_hotcell_worker = __esm({
9510
10276
  "src/tools/hotcell-worker.ts"() {
9511
10277
  "use strict";
@@ -9514,7 +10280,7 @@ var init_hotcell_worker = __esm({
9514
10280
  init_registry();
9515
10281
  init_hotcell_snapshot();
9516
10282
  init_hotcell_worker_cache();
9517
- execFileAsync2 = promisify3(execFile3);
10283
+ execFileAsync3 = promisify4(execFile4);
9518
10284
  MAX_OUTPUT_BYTES = 1e6;
9519
10285
  MAX_PROMPT_CHARS = 4e4;
9520
10286
  DEFAULT_CELL_TIMEOUT_MS = 3e5;
@@ -9636,11 +10402,11 @@ var init_worker_registry = __esm({
9636
10402
 
9637
10403
  // src/tools/subagent-stats.ts
9638
10404
  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";
10405
+ import { homedir as homedir12 } from "os";
10406
+ import { dirname as dirname10, join as join22 } from "path";
9641
10407
  function subagentStatsPath() {
9642
- const xdg = process.env.XDG_DATA_HOME || join20(homedir11(), ".local", "share");
9643
- return join20(xdg, "kimiflare", "subagents.jsonl");
10408
+ const xdg = process.env.XDG_DATA_HOME || join22(homedir12(), ".local", "share");
10409
+ return join22(xdg, "kimiflare", "subagents.jsonl");
9644
10410
  }
9645
10411
  async function recordSubagentEvent(event) {
9646
10412
  const path = subagentStatsPath();
@@ -9866,16 +10632,29 @@ var init_subagent = __esm({
9866
10632
  }
9867
10633
  const handle = workerRegistry.start(args.mission, model, ctx.signal);
9868
10634
  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
- });
10635
+ const record = (status, extra = {}) => {
10636
+ void recordSubagentEvent({
10637
+ kind: "subagent",
10638
+ ts: Date.now(),
10639
+ sessionId: ctx.sessionId ?? "unknown",
10640
+ status,
10641
+ durationMs: Date.now() - startedAt2,
10642
+ costUsd: extra.costUsd ?? 0,
10643
+ ...failureReason(extra.error) ? { reason: failureReason(extra.error) } : {},
10644
+ ...extra.localChanges ? { localChanges: extra.localChanges } : {}
10645
+ });
10646
+ ctx.trace?.emit(
10647
+ "subagent",
10648
+ {
10649
+ worker_id: handle.id,
10650
+ mode: "hotcell",
10651
+ status,
10652
+ ...extra.costUsd !== void 0 ? { cost_usd: extra.costUsd } : {},
10653
+ duration_ms: Date.now() - startedAt2
10654
+ },
10655
+ ctx.turnId ?? null
10656
+ );
10657
+ };
9879
10658
  let result2;
9880
10659
  try {
9881
10660
  result2 = await runHotcellWorker({
@@ -10547,13 +11326,13 @@ var init_expand_artifact = __esm({
10547
11326
  // src/jobs/manager.ts
10548
11327
  import Database from "better-sqlite3";
10549
11328
  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";
11329
+ import { mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync4, readSync, existsSync as existsSync4, statSync as statSync3, closeSync, rmSync } from "fs";
11330
+ import { homedir as homedir13 } from "os";
11331
+ import { dirname as dirname11, join as join23 } from "path";
11332
+ import { randomUUID as randomUUID4 } from "crypto";
10554
11333
  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");
11334
+ const root = process.env.XDG_STATE_HOME || join23(homedir13(), ".local", "state");
11335
+ return process.env.AUTOPILOT_JOBS_DB || join23(root, "autopilot", "jobs.db");
10557
11336
  }
10558
11337
  function isProcessGroupAlive(pgid) {
10559
11338
  try {
@@ -10571,7 +11350,7 @@ function waitForProcessStartTime(pid, jobId, exitPath) {
10571
11350
  let previous = null;
10572
11351
  const title = `autopilot-job-${jobId}`;
10573
11352
  while (Date.now() < deadline) {
10574
- if (existsSync2(exitPath)) break;
11353
+ if (existsSync4(exitPath)) break;
10575
11354
  const current = readProcessStartTime(pid);
10576
11355
  const hasUniqueTitle = process.platform !== "darwin" || current?.includes(title) === true;
10577
11356
  if (current && current === previous && hasUniqueTitle && !isZombie(pid)) return current;
@@ -10583,8 +11362,8 @@ function waitForProcessStartTime(pid, jobId, exitPath) {
10583
11362
  function readProcessStartTime(pid) {
10584
11363
  if (process.platform === "linux") {
10585
11364
  try {
10586
- const stat10 = readFileSync3(`/proc/${pid}/stat`, "utf8");
10587
- const fieldsAfterCommand = stat10.slice(stat10.lastIndexOf(")") + 1).trim().split(/\s+/);
11365
+ const stat11 = readFileSync4(`/proc/${pid}/stat`, "utf8");
11366
+ const fieldsAfterCommand = stat11.slice(stat11.lastIndexOf(")") + 1).trim().split(/\s+/);
10588
11367
  return fieldsAfterCommand[19] ? `linux:${fieldsAfterCommand[19]}` : null;
10589
11368
  } catch {
10590
11369
  return null;
@@ -10696,9 +11475,9 @@ child.on('close', (code, signal) => {
10696
11475
  db;
10697
11476
  jobsDir;
10698
11477
  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 });
11478
+ mkdirSync5(dirname11(dbPath), { recursive: true, mode: 448 });
11479
+ this.jobsDir = join23(dirname11(dbPath), "jobs");
11480
+ mkdirSync5(this.jobsDir, { recursive: true, mode: 448 });
10702
11481
  this.db = new Database(dbPath);
10703
11482
  this.db.pragma("journal_mode = WAL");
10704
11483
  this.db.pragma("busy_timeout = 5000");
@@ -10747,14 +11526,14 @@ child.on('close', (code, signal) => {
10747
11526
  const existing = this.db.prepare("SELECT * FROM jobs WHERE cwd = ? AND idempotency_key = ?").get(options.cwd, options.idempotencyKey);
10748
11527
  if (existing) return toJobRecord(existing);
10749
11528
  }
10750
- const id = randomUUID3();
10751
- const jobDir = join21(this.jobsDir, id);
10752
- mkdirSync3(jobDir, { recursive: true, mode: 448 });
11529
+ const id = randomUUID4();
11530
+ const jobDir = join23(this.jobsDir, id);
11531
+ mkdirSync5(jobDir, { recursive: true, mode: 448 });
10753
11532
  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")
11533
+ stdoutPath: join23(jobDir, "stdout.log"),
11534
+ stderrPath: join23(jobDir, "stderr.log"),
11535
+ exitPath: join23(jobDir, "exit.json"),
11536
+ pidPath: join23(jobDir, "pid")
10758
11537
  };
10759
11538
  const now2 = Date.now();
10760
11539
  try {
@@ -10807,7 +11586,7 @@ child.on('close', (code, signal) => {
10807
11586
  const row = this.getRow(id);
10808
11587
  if (!row) throw new Error(`Job not found: ${id}`);
10809
11588
  const path = stream === "stdout" ? row.stdout_path : row.stderr_path;
10810
- if (!existsSync2(path)) return { content: "", offset: 0, size: 0, truncated: false };
11589
+ if (!existsSync4(path)) return { content: "", offset: 0, size: 0, truncated: false };
10811
11590
  const size = statSync3(path).size;
10812
11591
  if (options.offset !== void 0) {
10813
11592
  const offset = Math.max(0, Math.min(size, Math.trunc(options.offset)));
@@ -10875,9 +11654,9 @@ child.on('close', (code, signal) => {
10875
11654
  reconcile() {
10876
11655
  const rows = this.db.prepare("SELECT * FROM jobs WHERE status IN ('starting', 'running', 'cancelling', 'unknown')").all();
10877
11656
  for (const row of rows) {
10878
- if (existsSync2(row.exit_path)) {
11657
+ if (existsSync4(row.exit_path)) {
10879
11658
  try {
10880
- const result2 = JSON.parse(readFileSync3(row.exit_path, "utf8"));
11659
+ const result2 = JSON.parse(readFileSync4(row.exit_path, "utf8"));
10881
11660
  const status = result2.timedOut ? "timed_out" : row.status === "cancelling" ? "cancelled" : result2.code === 0 ? "completed" : "failed";
10882
11661
  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
11662
  continue;
@@ -10885,8 +11664,8 @@ child.on('close', (code, signal) => {
10885
11664
  }
10886
11665
  }
10887
11666
  let pid = row.pid;
10888
- if (!pid && existsSync2(row.pid_path)) {
10889
- const parsed = Number(readFileSync3(row.pid_path, "utf8").trim());
11667
+ if (!pid && existsSync4(row.pid_path)) {
11668
+ const parsed = Number(readFileSync4(row.pid_path, "utf8").trim());
10890
11669
  if (Number.isInteger(parsed) && parsed > 0) pid = parsed;
10891
11670
  }
10892
11671
  if (pid && row.process_start_time) {
@@ -11032,13 +11811,13 @@ ${result2.content}`;
11032
11811
 
11033
11812
  // src/runs/store.ts
11034
11813
  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";
11814
+ import { createHash as createHash2, randomUUID as randomUUID5 } from "crypto";
11815
+ import { chmodSync, mkdirSync as mkdirSync6 } from "fs";
11816
+ import { homedir as homedir14 } from "os";
11817
+ import { dirname as dirname12, join as join24 } from "path";
11039
11818
  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");
11819
+ const root = process.env.XDG_STATE_HOME || join24(homedir14(), ".local", "state");
11820
+ return process.env.AUTOPILOT_RUNS_DB || join24(root, "autopilot", "runs.db");
11042
11821
  }
11043
11822
  function validateOptionalPositiveInteger(name, value, minimum = 1) {
11044
11823
  if (value !== null && (!Number.isSafeInteger(value) || value < minimum)) {
@@ -11054,7 +11833,7 @@ function stableJson(value) {
11054
11833
  }
11055
11834
  }
11056
11835
  function sha256(value) {
11057
- return createHash("sha256").update(value).digest("hex");
11836
+ return createHash2("sha256").update(value).digest("hex");
11058
11837
  }
11059
11838
  function rowToRun(row) {
11060
11839
  return {
@@ -11126,7 +11905,7 @@ var init_store = __esm({
11126
11905
  RunStore = class {
11127
11906
  db;
11128
11907
  constructor(dbPath = defaultRunsDbPath()) {
11129
- mkdirSync4(dirname12(dbPath), { recursive: true, mode: 448 });
11908
+ mkdirSync6(dirname12(dbPath), { recursive: true, mode: 448 });
11130
11909
  this.db = new Database2(dbPath);
11131
11910
  this.db.pragma("journal_mode = WAL");
11132
11911
  this.db.pragma("foreign_keys = ON");
@@ -11231,7 +12010,7 @@ var init_store = __esm({
11231
12010
  if (maxCostUsd !== null && (!Number.isFinite(maxCostUsd) || maxCostUsd < 0.01)) {
11232
12011
  throw new Error("maxCostUsd must be null or a finite number greater than or equal to 0.01");
11233
12012
  }
11234
- const id = input.id ?? input.worktree?.runId ?? randomUUID4();
12013
+ const id = input.id ?? input.worktree?.runId ?? randomUUID5();
11235
12014
  if (!id.trim()) throw new Error("id must not be empty");
11236
12015
  if (input.worktree && input.worktree.runId !== id) throw new Error("worktree runId must match run id");
11237
12016
  const now2 = Date.now();
@@ -11425,7 +12204,7 @@ var init_store = __esm({
11425
12204
  throw new Error("intervalMs must be an integer from 1000 through 604800000");
11426
12205
  }
11427
12206
  this.assertRunExists(input.runId);
11428
- const id = randomUUID4();
12207
+ const id = randomUUID5();
11429
12208
  const now2 = Date.now();
11430
12209
  this.db.prepare(`INSERT INTO run_timers
11431
12210
  (id, run_id, condition_type, job_id, wake_at, interval_ms, status, max_attempts, created_at)
@@ -11721,6 +12500,12 @@ function capContentForHook(s) {
11721
12500
  return `${cut}
11722
12501
  [\u2026truncated for hook payload]`;
11723
12502
  }
12503
+ function tracePermissionDecision(result2) {
12504
+ if (result2.decision === "deny") return "deny";
12505
+ if (result2.scope === "session") return "allow_session";
12506
+ if (result2.scope === "pattern") return "auto";
12507
+ return "allow";
12508
+ }
11724
12509
  function isDiffCommand(cmd) {
11725
12510
  const trimmed = cmd.trim();
11726
12511
  if (/^git\s+show(?:\s|$)/.test(trimmed)) return true;
@@ -11741,11 +12526,13 @@ function normalizeToolOutput(result2) {
11741
12526
  function truncateForError(s) {
11742
12527
  return s.length <= 200 ? s : `${s.slice(0, 200)}... [${s.length - 200} more chars]`;
11743
12528
  }
11744
- var ALL_TOOLS, RESEARCH_WORKER_TOOL_NAMES, SENSITIVE_RESEARCH_PATHS, HOOK_RESULT_CONTENT_CAP_BYTES, ToolExecutor;
12529
+ var ALL_TOOLS, RESEARCH_WORKER_TOOL_NAMES, SENSITIVE_RESEARCH_PATHS, HOOK_RESULT_CONTENT_CAP_BYTES, ToolExecutor, TRACE_ARGS_INLINE_MAX, ToolTrace;
11745
12530
  var init_executor = __esm({
11746
12531
  "src/tools/executor.ts"() {
11747
12532
  "use strict";
11748
12533
  init_tool_error();
12534
+ init_verification();
12535
+ init_git_delta();
11749
12536
  init_read();
11750
12537
  init_write();
11751
12538
  init_edit();
@@ -11825,17 +12612,25 @@ var init_executor = __esm({
11825
12612
  * every `run` call regardless of caller (standard agent loop,
11826
12613
  * code-mode sandbox, init turn, SDK, CLI print mode). */
11827
12614
  hooks;
12615
+ /** Default session trace recorder; `ctx.trace` (set by the agent loop)
12616
+ * takes precedence per call. */
12617
+ trace;
11828
12618
  constructor(tools = ALL_TOOLS, opts2) {
11829
12619
  this.tools = new Map(tools.map((t) => [t.name, t]));
11830
12620
  this.artifactStore = new ToolArtifactStore();
11831
12621
  this.tools.set("expand_artifact", makeExpandArtifactTool(this.artifactStore));
11832
12622
  this.hooks = opts2?.hooks ?? null;
12623
+ this.trace = opts2?.trace ?? null;
11833
12624
  }
11834
12625
  /** Swap or detach the hooks manager. Used by app startup so the
11835
12626
  * executor created before the manager exists can pick it up later. */
11836
12627
  setHooks(hooks) {
11837
12628
  this.hooks = hooks;
11838
12629
  }
12630
+ /** Swap or detach the default trace recorder (e.g. when the TUI starts a new session). */
12631
+ setTrace(trace) {
12632
+ this.trace = trace;
12633
+ }
11839
12634
  list() {
11840
12635
  return [...this.tools.values()];
11841
12636
  }
@@ -11852,8 +12647,17 @@ var init_executor = __esm({
11852
12647
  this.artifactStore.clear();
11853
12648
  }
11854
12649
  async run(call, askPermission, ctx, onFileChange) {
12650
+ const trace = ctx.trace ?? this.trace;
12651
+ if (!trace?.enabled) return this.runInner(call, askPermission, ctx, onFileChange, null);
12652
+ const tr = new ToolTrace(trace, ctx, call);
12653
+ const result2 = await this.runInner(call, askPermission, ctx, onFileChange, tr);
12654
+ tr.finish(result2);
12655
+ return result2;
12656
+ }
12657
+ async runInner(call, askPermission, ctx, onFileChange, tr) {
11855
12658
  const tool = this.tools.get(call.name) ?? (call.name === "spawn_worker" ? this.tools.get("subagent") : void 0);
11856
12659
  if (!tool) {
12660
+ tr?.call(null, false);
11857
12661
  return {
11858
12662
  tool_call_id: call.id,
11859
12663
  name: call.name,
@@ -11867,6 +12671,7 @@ var init_executor = __esm({
11867
12671
  try {
11868
12672
  args = call.arguments.trim() ? JSON.parse(call.arguments) : {};
11869
12673
  } catch (e) {
12674
+ tr?.call(null, tool.needsPermission);
11870
12675
  return {
11871
12676
  tool_call_id: call.id,
11872
12677
  name: call.name,
@@ -11877,6 +12682,7 @@ var init_executor = __esm({
11877
12682
  suggestion: "reformulate the tool call with valid JSON arguments"
11878
12683
  };
11879
12684
  }
12685
+ tr?.call(args, tool.needsPermission);
11880
12686
  if (this.hooks?.hasEnabledHooks("PreToolUse")) {
11881
12687
  const preOutcome = await this.hooks.fire(
11882
12688
  "PreToolUse",
@@ -11908,8 +12714,10 @@ var init_executor = __esm({
11908
12714
  if (tool.needsPermission) {
11909
12715
  const sessionKey = this.permissionKey(tool, args);
11910
12716
  if (!this.sessionAllowed.has(sessionKey)) {
12717
+ const askedAt = performance.now();
11911
12718
  const raw = await askPermission({ tool, args, sessionKey });
11912
12719
  const result2 = toPermissionResult(raw);
12720
+ tr?.permission(tracePermissionDecision(result2), Math.round(performance.now() - askedAt), result2.feedback);
11913
12721
  if (result2.decision === "deny") {
11914
12722
  const denied = {
11915
12723
  tool_call_id: call.id,
@@ -11924,7 +12732,11 @@ var init_executor = __esm({
11924
12732
  return denied;
11925
12733
  }
11926
12734
  if (result2.scope === "session") this.sessionAllowed.add(sessionKey);
12735
+ } else {
12736
+ tr?.permission("auto", 0);
11927
12737
  }
12738
+ } else {
12739
+ tr?.permission("auto", 0);
11928
12740
  }
11929
12741
  let runStore = null;
11930
12742
  let intentRecorded = false;
@@ -11938,6 +12750,7 @@ var init_executor = __esm({
11938
12750
  const result2 = await tool.run(args, ctx);
11939
12751
  toolCompleted = true;
11940
12752
  const normalized = normalizeToolOutput(result2);
12753
+ tr?.raw(normalized);
11941
12754
  if (runStore && ctx.runId) {
11942
12755
  runStore.recordToolResult(ctx.runId, call.id, call.name, { ok: true, content: normalized.content });
11943
12756
  }
@@ -11991,6 +12804,7 @@ var init_executor = __esm({
11991
12804
  if (e instanceof RunToolBudgetExceededError) ctx.onRunBudgetExceeded?.(e.reason);
11992
12805
  const err = wrapAsToolError(e);
11993
12806
  const msg = `Error running ${call.name}: ${err.message}`;
12807
+ tr?.raw({ content: msg, rawBytes: Buffer.byteLength(msg, "utf8"), reducedBytes: Buffer.byteLength(msg, "utf8") });
11994
12808
  const failure = {
11995
12809
  tool_call_id: call.id,
11996
12810
  name: call.name,
@@ -12051,6 +12865,121 @@ var init_executor = __esm({
12051
12865
  return tool.name;
12052
12866
  }
12053
12867
  };
12868
+ TRACE_ARGS_INLINE_MAX = 2 * 1024;
12869
+ ToolTrace = class {
12870
+ constructor(trace, ctx, invocation) {
12871
+ this.trace = trace;
12872
+ this.invocation = invocation;
12873
+ this.turnId = ctx.turnId ?? null;
12874
+ this.parentId = ctx.codeModeParentToolCallId;
12875
+ this.sessionId = ctx.sessionId;
12876
+ }
12877
+ trace;
12878
+ invocation;
12879
+ startedAt = performance.now();
12880
+ turnId;
12881
+ parentId;
12882
+ sessionId;
12883
+ rawOutput = null;
12884
+ argsBlob = "";
12885
+ args = null;
12886
+ /** The call was parsed (args null when the arguments were not valid JSON). */
12887
+ call(args, needsPermission) {
12888
+ this.args = args;
12889
+ this.argsBlob = this.trace.blob(this.invocation.arguments) ?? "";
12890
+ if (this.parentId) return;
12891
+ const payload = {
12892
+ tool_call_id: this.invocation.id,
12893
+ name: this.invocation.name,
12894
+ args_blob: this.argsBlob,
12895
+ ...args && this.invocation.arguments.length <= TRACE_ARGS_INLINE_MAX ? { args_inline: args } : {},
12896
+ needs_permission: needsPermission,
12897
+ code_mode_redirected: false
12898
+ };
12899
+ this.trace.emit("tool_call", payload, this.turnId);
12900
+ }
12901
+ permission(decision, latencyMs, feedback) {
12902
+ this.trace.emit(
12903
+ "permission_decision",
12904
+ {
12905
+ tool_call_id: this.invocation.id,
12906
+ decision,
12907
+ ...feedback ? { feedback_text: feedback } : {},
12908
+ latency_ms: latencyMs
12909
+ },
12910
+ this.turnId
12911
+ );
12912
+ }
12913
+ /** The tool's output before reduction. */
12914
+ raw(output) {
12915
+ this.rawOutput = output;
12916
+ }
12917
+ finish(result2) {
12918
+ const durationMs = Math.round(performance.now() - this.startedAt);
12919
+ const rawContent = this.rawOutput?.content ?? result2.content;
12920
+ const rawBlob = this.trace.blob(rawContent) ?? "";
12921
+ this.outcomes(result2);
12922
+ if (this.parentId) {
12923
+ this.trace.emit(
12924
+ "code_mode_call",
12925
+ {
12926
+ parent_tool_call_id: this.parentId,
12927
+ name: this.invocation.name,
12928
+ args_blob: this.argsBlob,
12929
+ raw_blob: rawBlob,
12930
+ ok: result2.ok,
12931
+ duration_ms: durationMs
12932
+ },
12933
+ this.turnId
12934
+ );
12935
+ return;
12936
+ }
12937
+ const meta = this.rawOutput?.meta;
12938
+ const payload = {
12939
+ tool_call_id: this.invocation.id,
12940
+ name: this.invocation.name,
12941
+ ok: result2.ok,
12942
+ ...result2.errorCode ? { error_code: result2.errorCode } : {},
12943
+ raw_blob: rawBlob,
12944
+ raw_bytes: Buffer.byteLength(rawContent, "utf8"),
12945
+ reduced_blob: rawContent === result2.content ? rawBlob : this.trace.blob(result2.content) ?? "",
12946
+ reduced_bytes: Buffer.byteLength(result2.content, "utf8"),
12947
+ ...result2.artifactId ? { artifact_id: result2.artifactId } : {},
12948
+ ...meta && meta.exitCode !== void 0 && meta.exitCode !== null ? { exit_code: meta.exitCode } : {},
12949
+ ...meta?.timedOut !== void 0 ? { timed_out: meta.timedOut } : {},
12950
+ duration_ms: durationMs
12951
+ };
12952
+ this.trace.emit("tool_result", payload, this.turnId);
12953
+ }
12954
+ /** Outcome signals a bash call carries: a verification exit code, or a git revert of the last delta. */
12955
+ outcomes(result2) {
12956
+ if (this.invocation.name !== "bash") return;
12957
+ const command = this.args?.command;
12958
+ if (typeof command !== "string" || !command.trim()) return;
12959
+ const exitCode = this.rawOutput?.meta?.exitCode;
12960
+ const category = classifyVerification(command);
12961
+ if (category && typeof exitCode === "number") {
12962
+ this.trace.emit(
12963
+ "outcome",
12964
+ {
12965
+ kind: "verification",
12966
+ turn_id: this.turnId ?? "",
12967
+ tool_call_id: this.invocation.id,
12968
+ command,
12969
+ category,
12970
+ exit_code: exitCode
12971
+ },
12972
+ this.turnId
12973
+ );
12974
+ }
12975
+ if (result2.ok && this.sessionId) {
12976
+ const reverted = detectGitRevert(this.sessionId, command);
12977
+ if (reverted) {
12978
+ this.trace.emit("outcome", { kind: "reverted", turn_id: reverted.turnId, how: "git_checkout" }, this.turnId);
12979
+ }
12980
+ }
12981
+ }
12982
+ };
12054
12983
  }
12055
12984
  });
12056
12985
 
@@ -12068,11 +12997,11 @@ __export(sessions_exports, {
12068
12997
  sessionsDir: () => sessionsDir
12069
12998
  });
12070
12999
  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";
13000
+ import { homedir as homedir15 } from "os";
13001
+ import { join as join25 } from "path";
12073
13002
  function sessionsDir() {
12074
- const xdg = process.env.XDG_DATA_HOME || join23(homedir14(), ".local", "share");
12075
- return join23(xdg, "kimiflare", "sessions");
13003
+ const xdg = process.env.XDG_DATA_HOME || join25(homedir15(), ".local", "share");
13004
+ return join25(xdg, "kimiflare", "sessions");
12076
13005
  }
12077
13006
  function sanitize(text) {
12078
13007
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -12092,7 +13021,7 @@ function makeSessionId(firstPrompt) {
12092
13021
  async function saveSession(file) {
12093
13022
  const dir = sessionsDir();
12094
13023
  await mkdir11(dir, { recursive: true });
12095
- const path = join23(dir, `${file.id}.json`);
13024
+ const path = join25(dir, `${file.id}.json`);
12096
13025
  await writeFile11(path, JSON.stringify(file, null, 2), "utf8");
12097
13026
  return path;
12098
13027
  }
@@ -12112,7 +13041,7 @@ async function listSessions(limit = 30, cwd) {
12112
13041
  const summaries = [];
12113
13042
  for (const name of entries) {
12114
13043
  if (!name.endsWith(".json")) continue;
12115
- const path = join23(dir, name);
13044
+ const path = join25(dir, name);
12116
13045
  try {
12117
13046
  const [s, raw] = await Promise.all([stat5(path), readFile13(path, "utf8")]);
12118
13047
  const parsed = JSON.parse(raw);
@@ -12223,7 +13152,7 @@ var init_image = __esm({
12223
13152
 
12224
13153
  // src/permissions-evaluator.ts
12225
13154
  import { resolve as resolve5, relative as relative4 } from "path";
12226
- import { homedir as homedir15 } from "os";
13155
+ import { homedir as homedir16 } from "os";
12227
13156
  function evaluatePermissionRules(req, rules) {
12228
13157
  const toolRules = rules[req.tool];
12229
13158
  if (!toolRules) return "ask";
@@ -12239,7 +13168,7 @@ function evaluatePermissionRules(req, rules) {
12239
13168
  }
12240
13169
  function expandHome(path) {
12241
13170
  if (path.startsWith("~/")) {
12242
- return resolve5(homedir15(), path.slice(2));
13171
+ return resolve5(homedir16(), path.slice(2));
12243
13172
  }
12244
13173
  return path;
12245
13174
  }
@@ -12265,7 +13194,7 @@ function extractTarget(tool, args, cwd) {
12265
13194
  function matchPattern(target, pattern, cwd) {
12266
13195
  let expandedPattern = pattern;
12267
13196
  if (pattern.startsWith("~/")) {
12268
- expandedPattern = resolve5(homedir15(), pattern.slice(2));
13197
+ expandedPattern = resolve5(homedir16(), pattern.slice(2));
12269
13198
  }
12270
13199
  if (target.startsWith(expandedPattern)) {
12271
13200
  return true;
@@ -12342,9 +13271,237 @@ var init_classify = __esm({
12342
13271
  }
12343
13272
  });
12344
13273
 
13274
+ // src/trace/retention.ts
13275
+ var retention_exports = {};
13276
+ __export(retention_exports, {
13277
+ listTraceSessions: () => listTraceSessions,
13278
+ pruneTraces: () => pruneTraces
13279
+ });
13280
+ import { readdir as readdir5, rm as rm3, stat as stat6 } from "fs/promises";
13281
+ import { join as join26 } from "path";
13282
+ async function dirSize(path) {
13283
+ let total = 0;
13284
+ const entries = await readdir5(path, { withFileTypes: true });
13285
+ for (const entry of entries) {
13286
+ const p = join26(path, entry.name);
13287
+ if (entry.isDirectory()) total += await dirSize(p);
13288
+ else if (entry.isFile()) {
13289
+ try {
13290
+ total += (await stat6(p)).size;
13291
+ } catch {
13292
+ }
13293
+ }
13294
+ }
13295
+ return total;
13296
+ }
13297
+ async function listTraceSessions(root) {
13298
+ const base = traceRoot(root);
13299
+ let names;
13300
+ try {
13301
+ names = await readdir5(base);
13302
+ } catch {
13303
+ return [];
13304
+ }
13305
+ const sessions = [];
13306
+ for (const name of names) {
13307
+ const path = join26(base, name);
13308
+ try {
13309
+ const s = await stat6(path);
13310
+ if (!s.isDirectory()) continue;
13311
+ let mtime = s.mtime;
13312
+ try {
13313
+ mtime = (await stat6(join26(path, TRACE_FILE_NAME))).mtime;
13314
+ } catch {
13315
+ }
13316
+ sessions.push({ sessionId: name, path, mtime, bytes: await dirSize(path) });
13317
+ } catch {
13318
+ }
13319
+ }
13320
+ sessions.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
13321
+ return sessions;
13322
+ }
13323
+ async function pruneTraces(opts2 = {}) {
13324
+ const maxAgeDays = opts2.maxAgeDays ?? RETENTION.traceMaxAgeDays;
13325
+ const maxTotalBytes = opts2.maxTotalBytes ?? RETENTION.traceMaxTotalBytes;
13326
+ const maxSessionBytes = opts2.maxSessionBytes ?? RETENTION.traceMaxSessionBytes;
13327
+ const now2 = opts2.now ?? Date.now();
13328
+ const cutoff = now2 - maxAgeDays * 24 * 60 * 60 * 1e3;
13329
+ const removed = [];
13330
+ let sessions;
13331
+ try {
13332
+ sessions = await listTraceSessions(opts2.root);
13333
+ } catch {
13334
+ return { removed, keptBytes: 0 };
13335
+ }
13336
+ const remove = async (s) => {
13337
+ try {
13338
+ await rm3(s.path, { recursive: true, force: true });
13339
+ removed.push(s.sessionId);
13340
+ } catch {
13341
+ }
13342
+ };
13343
+ const kept = [];
13344
+ for (const s of sessions) {
13345
+ if (s.mtime.getTime() < cutoff || s.bytes > maxSessionBytes) await remove(s);
13346
+ else kept.push(s);
13347
+ }
13348
+ let total = kept.reduce((sum, s) => sum + s.bytes, 0);
13349
+ while (total > maxTotalBytes && kept.length > 0) {
13350
+ const oldest = kept.pop();
13351
+ await remove(oldest);
13352
+ total -= oldest.bytes;
13353
+ }
13354
+ return { removed, keptBytes: total };
13355
+ }
13356
+ var init_retention = __esm({
13357
+ "src/trace/retention.ts"() {
13358
+ "use strict";
13359
+ init_storage_limits();
13360
+ init_paths();
13361
+ }
13362
+ });
13363
+
13364
+ // src/trace/session-start.ts
13365
+ import { execFile as execFile5 } from "child_process";
13366
+ import { existsSync as existsSync5 } from "fs";
13367
+ import { dirname as dirname13, join as join27 } from "path";
13368
+ import { fileURLToPath as fileURLToPath4 } from "url";
13369
+ import { promisify as promisify5 } from "util";
13370
+ async function git2(args, cwd) {
13371
+ try {
13372
+ const { stdout } = await execFileAsync4("git", args, { cwd, timeout: 3e3, windowsHide: true });
13373
+ const out = stdout.trim();
13374
+ return out || null;
13375
+ } catch {
13376
+ return null;
13377
+ }
13378
+ }
13379
+ function harnessCheckoutDir() {
13380
+ const here = dirname13(fileURLToPath4(import.meta.url));
13381
+ for (const candidate of [join27(here, "..", ".."), join27(here, "..")]) {
13382
+ if (existsSync5(join27(candidate, "package.json")) && existsSync5(join27(candidate, ".git"))) return candidate;
13383
+ }
13384
+ return null;
13385
+ }
13386
+ async function resolveHarnessCommit() {
13387
+ const dir = harnessCheckoutDir();
13388
+ if (!dir) return null;
13389
+ return git2(["rev-parse", "HEAD"], dir);
13390
+ }
13391
+ async function collectSessionStart(opts2, blob) {
13392
+ const toolsJson = JSON.stringify(opts2.tools);
13393
+ const [harnessCommit, repoRemote, repoHead] = await Promise.all([
13394
+ resolveHarnessCommit(),
13395
+ git2(["remote", "get-url", "origin"], opts2.cwd),
13396
+ git2(["rev-parse", "HEAD"], opts2.cwd)
13397
+ ]);
13398
+ return {
13399
+ autopilot_version: getAppVersion(),
13400
+ harness_commit: harnessCommit,
13401
+ cwd: opts2.cwd,
13402
+ repo_remote: repoRemote,
13403
+ repo_head: repoHead,
13404
+ model: opts2.model,
13405
+ mode: opts2.mode,
13406
+ config: redactConfig(opts2.config ?? {}),
13407
+ tool_profile: opts2.toolProfile ?? "full",
13408
+ tool_schemas_sha: sha256Hex(toolsJson),
13409
+ tools_blob: blob(toolsJson) ?? "",
13410
+ system_prompt_sha: sha256Hex(opts2.systemPrompt)
13411
+ };
13412
+ }
13413
+ async function emitSessionStart(recorder, opts2) {
13414
+ if (!recorder.enabled) return;
13415
+ const payload = await collectSessionStart(opts2, (content) => recorder.blob(content));
13416
+ recorder.emit("session_start", payload, null);
13417
+ }
13418
+ var execFileAsync4;
13419
+ var init_session_start = __esm({
13420
+ "src/trace/session-start.ts"() {
13421
+ "use strict";
13422
+ init_version();
13423
+ init_blobs();
13424
+ init_redact();
13425
+ execFileAsync4 = promisify5(execFile5);
13426
+ }
13427
+ });
13428
+
13429
+ // src/trace/compaction.ts
13430
+ function traceCompiledCompaction(trace, result2, turnId = null) {
13431
+ if (!trace?.enabled || result2.metrics.rawTurnsRemoved === 0) return;
13432
+ trace.emit(
13433
+ "compaction",
13434
+ {
13435
+ method: "compiled",
13436
+ tokens_before: result2.metrics.estimatedTokensBefore,
13437
+ tokens_after: result2.metrics.estimatedTokensAfter,
13438
+ turns_removed: result2.metrics.rawTurnsRemoved,
13439
+ artifacts_archived: result2.metrics.archivedArtifacts
13440
+ },
13441
+ turnId
13442
+ );
13443
+ }
13444
+ function traceLlmCompaction(trace, before, result2, turnId = null) {
13445
+ if (!trace?.enabled || result2.replacedCount === 0) return;
13446
+ trace.emit(
13447
+ "compaction",
13448
+ {
13449
+ method: "llm",
13450
+ tokens_before: estimatePromptTokens(before),
13451
+ tokens_after: estimatePromptTokens(result2.newMessages),
13452
+ turns_removed: result2.replacedCount,
13453
+ ...result2.summary ? { summary_blob: trace.blob(result2.summary) ?? "" } : {}
13454
+ },
13455
+ turnId
13456
+ );
13457
+ }
13458
+ var init_compaction = __esm({
13459
+ "src/trace/compaction.ts"() {
13460
+ "use strict";
13461
+ init_artifact_compaction();
13462
+ }
13463
+ });
13464
+
13465
+ // src/trace/next-message.ts
13466
+ function classifyNextMessage(text) {
13467
+ const trimmed = text.trim();
13468
+ if (!trimmed) return "other";
13469
+ if (trimmed.startsWith("/")) return "other";
13470
+ if (CORRECTION.test(trimmed)) return "correction";
13471
+ if (CONTINUE.test(trimmed)) return "continue";
13472
+ if (trimmed.endsWith("?")) return "question";
13473
+ return "new_task";
13474
+ }
13475
+ var CORRECTION, CONTINUE;
13476
+ var init_next_message = __esm({
13477
+ "src/trace/next-message.ts"() {
13478
+ "use strict";
13479
+ 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;
13480
+ 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;
13481
+ }
13482
+ });
13483
+
13484
+ // src/trace/index.ts
13485
+ var init_trace = __esm({
13486
+ "src/trace/index.ts"() {
13487
+ "use strict";
13488
+ init_types();
13489
+ init_recorder();
13490
+ init_blobs();
13491
+ init_redact();
13492
+ init_paths();
13493
+ init_retention();
13494
+ init_session_start();
13495
+ init_compaction();
13496
+ init_verification();
13497
+ init_next_message();
13498
+ init_git_delta();
13499
+ }
13500
+ });
13501
+
12345
13502
  // src/hooks/types.ts
12346
13503
  var HOOK_EVENTS;
12347
- var init_types = __esm({
13504
+ var init_types2 = __esm({
12348
13505
  "src/hooks/types.ts"() {
12349
13506
  "use strict";
12350
13507
  HOOK_EVENTS = [
@@ -12358,21 +13515,21 @@ var init_types = __esm({
12358
13515
  });
12359
13516
 
12360
13517
  // 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";
13518
+ import { existsSync as existsSync6, readFileSync as readFileSync5, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
13519
+ import { homedir as homedir17 } from "os";
13520
+ import { join as join28, dirname as dirname14 } from "path";
13521
+ import { createHash as createHash3 } from "crypto";
12365
13522
  function globalSettingsPath() {
12366
- const xdg = process.env.XDG_CONFIG_HOME || join24(homedir16(), ".config");
12367
- return join24(xdg, "kimiflare", "settings.json");
13523
+ const xdg = process.env.XDG_CONFIG_HOME || join28(homedir17(), ".config");
13524
+ return join28(xdg, "kimiflare", "settings.json");
12368
13525
  }
12369
13526
  function projectSettingsPath(cwd) {
12370
- return join24(cwd, ".kimiflare", "settings.json");
13527
+ return join28(cwd, ".kimiflare", "settings.json");
12371
13528
  }
12372
13529
  function readSettingsFile(path) {
12373
- if (!existsSync3(path)) return null;
13530
+ if (!existsSync6(path)) return null;
12374
13531
  try {
12375
- const raw = readFileSync4(path, "utf8");
13532
+ const raw = readFileSync5(path, "utf8");
12376
13533
  const parsed = JSON.parse(raw);
12377
13534
  return parsed && typeof parsed === "object" ? parsed : null;
12378
13535
  } catch {
@@ -12380,7 +13537,7 @@ function readSettingsFile(path) {
12380
13537
  }
12381
13538
  }
12382
13539
  function deriveHookId(event, command) {
12383
- const h = createHash2("sha256").update(`${event}\0${command}`).digest("hex");
13540
+ const h = createHash3("sha256").update(`${event}\0${command}`).digest("hex");
12384
13541
  return h.slice(0, 8);
12385
13542
  }
12386
13543
  function normalizeHook(event, raw, source) {
@@ -12443,8 +13600,8 @@ function appendHook(scope, cwd, event, hook) {
12443
13600
  return rest;
12444
13601
  });
12445
13602
  existing.hooks = hooks;
12446
- mkdirSync5(dirname13(path), { recursive: true });
12447
- writeFileSync3(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
13603
+ mkdirSync7(dirname14(path), { recursive: true });
13604
+ writeFileSync4(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
12448
13605
  return path;
12449
13606
  }
12450
13607
  function setHookEnabled(cwd, id, enabled2) {
@@ -12469,8 +13626,8 @@ function setHookEnabled(cwd, id, enabled2) {
12469
13626
  }
12470
13627
  }
12471
13628
  if (changed) {
12472
- mkdirSync5(dirname13(path), { recursive: true });
12473
- writeFileSync3(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
13629
+ mkdirSync7(dirname14(path), { recursive: true });
13630
+ writeFileSync4(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
12474
13631
  return path;
12475
13632
  }
12476
13633
  void scope;
@@ -12480,7 +13637,7 @@ function setHookEnabled(cwd, id, enabled2) {
12480
13637
  var init_settings = __esm({
12481
13638
  "src/hooks/settings.ts"() {
12482
13639
  "use strict";
12483
- init_types();
13640
+ init_types2();
12484
13641
  }
12485
13642
  });
12486
13643
 
@@ -12698,11 +13855,11 @@ __export(cost_verify_exports, {
12698
13855
  verifyTurn: () => verifyTurn
12699
13856
  });
12700
13857
  import { readFile as readFile16 } from "fs/promises";
12701
- import { homedir as homedir17 } from "os";
12702
- import { join as join25 } from "path";
13858
+ import { homedir as homedir18 } from "os";
13859
+ import { join as join29 } from "path";
12703
13860
  function usagePath2() {
12704
- const xdg = process.env.XDG_DATA_HOME || join25(homedir17(), ".local", "share");
12705
- return join25(xdg, "kimiflare", "usage.json");
13861
+ const xdg = process.env.XDG_DATA_HOME || join29(homedir18(), ".local", "share");
13862
+ return join29(xdg, "kimiflare", "usage.json");
12706
13863
  }
12707
13864
  async function loadSessions() {
12708
13865
  try {
@@ -12795,7 +13952,7 @@ var init_cost_verify = __esm({
12795
13952
 
12796
13953
  // src/cost-attribution/types.ts
12797
13954
  var ALL_CATEGORIES;
12798
- var init_types2 = __esm({
13955
+ var init_types3 = __esm({
12799
13956
  "src/cost-attribution/types.ts"() {
12800
13957
  "use strict";
12801
13958
  ALL_CATEGORIES = [
@@ -12874,7 +14031,7 @@ function buildReport(opts2) {
12874
14031
  var init_report = __esm({
12875
14032
  "src/cost-attribution/report.ts"() {
12876
14033
  "use strict";
12877
- init_types2();
14034
+ init_types3();
12878
14035
  }
12879
14036
  });
12880
14037
 
@@ -13238,11 +14395,11 @@ var init_heuristic = __esm({
13238
14395
 
13239
14396
  // src/cost-attribution/classify-from-session.ts
13240
14397
  import { readFile as readFile17 } from "fs/promises";
13241
- import { join as join26 } from "path";
13242
- import { homedir as homedir18 } from "os";
14398
+ import { join as join30 } from "path";
14399
+ import { homedir as homedir19 } from "os";
13243
14400
  function sessionsDir2() {
13244
- const xdg = process.env.XDG_DATA_HOME || join26(homedir18(), ".local", "share");
13245
- return join26(xdg, "kimiflare", "sessions");
14401
+ const xdg = process.env.XDG_DATA_HOME || join30(homedir19(), ".local", "share");
14402
+ return join30(xdg, "kimiflare", "sessions");
13246
14403
  }
13247
14404
  function parseToolCalls(calls) {
13248
14405
  return calls.map((c) => {
@@ -13256,7 +14413,7 @@ function parseToolCalls(calls) {
13256
14413
  }
13257
14414
  async function classifyFromSessionFile(sessionId) {
13258
14415
  try {
13259
- const raw = await readFile17(join26(sessionsDir2(), `${sessionId}.json`), "utf8");
14416
+ const raw = await readFile17(join30(sessionsDir2(), `${sessionId}.json`), "utf8");
13260
14417
  const session = JSON.parse(raw);
13261
14418
  const messages = session.messages ?? [];
13262
14419
  const turns = [];
@@ -13290,14 +14447,14 @@ __export(cli_exports, {
13290
14447
  runCostCommand: () => runCostCommand
13291
14448
  });
13292
14449
  import { readFile as readFile18 } from "fs/promises";
13293
- import { join as join27 } from "path";
13294
- import { homedir as homedir19 } from "os";
14450
+ import { join as join31 } from "path";
14451
+ import { homedir as homedir20 } from "os";
13295
14452
  function usageDir2() {
13296
- const xdg = process.env.XDG_DATA_HOME || join27(homedir19(), ".local", "share");
13297
- return join27(xdg, "kimiflare");
14453
+ const xdg = process.env.XDG_DATA_HOME || join31(homedir20(), ".local", "share");
14454
+ return join31(xdg, "kimiflare");
13298
14455
  }
13299
14456
  function usagePath3() {
13300
- return join27(usageDir2(), "usage.json");
14457
+ return join31(usageDir2(), "usage.json");
13301
14458
  }
13302
14459
  function today2() {
13303
14460
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -13399,7 +14556,7 @@ __export(aster_workspaces_exports, {
13399
14556
  findAsterWorkspace: () => findAsterWorkspace,
13400
14557
  loadAsterServerConfig: () => loadAsterServerConfig
13401
14558
  });
13402
- import { readFile as readFile19, realpath as realpath3, stat as stat6 } from "fs/promises";
14559
+ import { readFile as readFile19, realpath as realpath3, stat as stat7 } from "fs/promises";
13403
14560
  import { isAbsolute as isAbsolute3, relative as relative5, sep as sep3 } from "path";
13404
14561
  async function loadAsterServerConfig(configPath3 = process.env.AUTOPILOT_ASTER_CONFIG || "/etc/autopilot/aster.json") {
13405
14562
  let raw;
@@ -13441,7 +14598,7 @@ async function loadAsterServerConfig(configPath3 = process.env.AUTOPILOT_ASTER_C
13441
14598
  let rootPath;
13442
14599
  try {
13443
14600
  rootPath = await realpath3(workspace.rootPath);
13444
- if (!(await stat6(rootPath)).isDirectory()) throw new Error("not a directory");
14601
+ if (!(await stat7(rootPath)).isDirectory()) throw new Error("not a directory");
13445
14602
  } catch {
13446
14603
  throw new AsterConfigError();
13447
14604
  }
@@ -13481,16 +14638,16 @@ __export(aster_store_exports, {
13481
14638
  defaultAsterDbPath: () => defaultAsterDbPath
13482
14639
  });
13483
14640
  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";
14641
+ import { createHash as createHash4, randomBytes as randomBytes2, randomUUID as randomUUID6 } from "crypto";
14642
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync8 } from "fs";
14643
+ import { homedir as homedir21 } from "os";
14644
+ import { dirname as dirname15, join as join32 } from "path";
13488
14645
  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");
14646
+ const root = process.env.XDG_STATE_HOME || join32(homedir21(), ".local", "state");
14647
+ return process.env.AUTOPILOT_ASTER_DB || join32(root, "autopilot", "aster.db");
13491
14648
  }
13492
14649
  function hashToken(token) {
13493
- return createHash3("sha256").update(token).digest("hex");
14650
+ return createHash4("sha256").update(token).digest("hex");
13494
14651
  }
13495
14652
  function cellMappingFromRow(row) {
13496
14653
  return {
@@ -13554,9 +14711,9 @@ var init_aster_store = __esm({
13554
14711
  AsterStore = class {
13555
14712
  db;
13556
14713
  constructor(dbPath = defaultAsterDbPath()) {
13557
- mkdirSync6(dirname14(dbPath), { recursive: true, mode: 448 });
14714
+ mkdirSync8(dirname15(dbPath), { recursive: true, mode: 448 });
13558
14715
  try {
13559
- chmodSync2(dirname14(dbPath), 448);
14716
+ chmodSync2(dirname15(dbPath), 448);
13560
14717
  } catch {
13561
14718
  }
13562
14719
  this.db = new Database3(dbPath);
@@ -13667,8 +14824,8 @@ var init_aster_store = __esm({
13667
14824
  this.db.close();
13668
14825
  }
13669
14826
  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");
14827
+ const keyHash = createHash4("sha256").update(input.idempotencyKey).digest("hex");
14828
+ const fingerprint = createHash4("sha256").update(JSON.stringify([input.workspaceId, input.model])).digest("hex");
13672
14829
  const reserve = this.db.transaction(() => {
13673
14830
  const existing = this.db.prepare("SELECT * FROM aster_cell_mappings WHERE credential_id = ? AND create_key_hash = ?").get(input.credentialId, keyHash);
13674
14831
  if (existing) {
@@ -13719,7 +14876,7 @@ var init_aster_store = __esm({
13719
14876
  const scopes = [...new Set(input.scopes)];
13720
14877
  if (scopes.length === 0 || scopes.some((scope) => !VALID_SCOPES.has(scope))) throw new Error("credential scopes are invalid");
13721
14878
  if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= Date.now()) throw new Error("credential expiry must be in the future");
13722
- const id = randomUUID5();
14879
+ const id = randomUUID6();
13723
14880
  const token = `aster_${randomBytes2(32).toString("base64url")}`;
13724
14881
  const now2 = Date.now();
13725
14882
  this.db.prepare(`INSERT INTO aster_credentials
@@ -13892,7 +15049,7 @@ var init_aster_store = __esm({
13892
15049
  }));
13893
15050
  }
13894
15051
  createApproval(input) {
13895
- const id = randomUUID5();
15052
+ const id = randomUUID6();
13896
15053
  const now2 = Date.now();
13897
15054
  this.db.prepare(`INSERT INTO aster_approvals
13898
15055
  (id, conversation_id, run_id, tool_name, explanation, arguments_json, status, expires_at, created_at)
@@ -14553,13 +15710,13 @@ var init_openapi = __esm({
14553
15710
  });
14554
15711
 
14555
15712
  // src/runs/worktrees.ts
14556
- import { execFile as execFile4 } from "child_process";
15713
+ import { execFile as execFile6 } from "child_process";
14557
15714
  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";
15715
+ import { homedir as homedir22 } from "os";
15716
+ import { isAbsolute as isAbsolute4, join as join33, parse, relative as relative6, resolve as resolve7, sep as sep4 } from "path";
14560
15717
  function defaultWorktreesDir() {
14561
- const dataHome = process.env.XDG_DATA_HOME || join29(homedir21(), ".local", "share");
14562
- return join29(dataHome, "autopilot", "worktrees");
15718
+ const dataHome = process.env.XDG_DATA_HOME || join33(homedir22(), ".local", "share");
15719
+ return join33(dataHome, "autopilot", "worktrees");
14563
15720
  }
14564
15721
  function isOutside(path) {
14565
15722
  return path === ".." || path.startsWith(`..${sep4}`) || isAbsolute4(path);
@@ -14570,7 +15727,7 @@ function isWithin2(parent, candidate) {
14570
15727
  }
14571
15728
  function runGit(args, cwd) {
14572
15729
  return new Promise((resolveOutput, reject) => {
14573
- execFile4("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
15730
+ execFile6("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
14574
15731
  if (error) {
14575
15732
  const detail = stderr.trim();
14576
15733
  reject(new Error(`git ${args[0]} failed${detail ? `: ${detail}` : ""}`, { cause: error }));
@@ -14610,7 +15767,7 @@ var init_worktrees = __esm({
14610
15767
  throw new Error("worktree root must be outside the source repository");
14611
15768
  }
14612
15769
  await chmod2(worktreesRoot, 448);
14613
- const worktreePath = join29(worktreesRoot, runId);
15770
+ const worktreePath = join33(worktreesRoot, runId);
14614
15771
  try {
14615
15772
  await access2(worktreePath);
14616
15773
  throw new Error(`worktree path already exists for run ${runId}`);
@@ -14787,11 +15944,11 @@ __export(openrouter_catalog_exports, {
14787
15944
  mapOpenRouterModel: () => mapOpenRouterModel
14788
15945
  });
14789
15946
  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";
15947
+ import { homedir as homedir23 } from "os";
15948
+ import { join as join34, dirname as dirname16 } from "path";
14792
15949
  function catalogCachePath() {
14793
- const xdg = process.env.XDG_CONFIG_HOME || join30(homedir22(), ".config");
14794
- return join30(xdg, "kimiflare", "openrouter-models.json");
15950
+ const xdg = process.env.XDG_CONFIG_HOME || join34(homedir23(), ".config");
15951
+ return join34(xdg, "kimiflare", "openrouter-models.json");
14795
15952
  }
14796
15953
  function perMtok(perToken) {
14797
15954
  const n = Number(perToken);
@@ -14852,7 +16009,7 @@ async function readCache3() {
14852
16009
  async function writeCache3(models) {
14853
16010
  const path = catalogCachePath();
14854
16011
  try {
14855
- await mkdir13(dirname15(path), { recursive: true });
16012
+ await mkdir13(dirname16(path), { recursive: true });
14856
16013
  const cache = { version: CACHE_VERSION, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), models };
14857
16014
  await writeFile12(path, JSON.stringify(cache, null, 2), "utf8");
14858
16015
  } catch {
@@ -15083,7 +16240,7 @@ var init_aster_tools = __esm({
15083
16240
  init_edit();
15084
16241
  init_read();
15085
16242
  init_write();
15086
- init_paths();
16243
+ init_paths2();
15087
16244
  ASTR_TOOLS = [readTool, writeTool, editTool];
15088
16245
  SENSITIVE_NAMES = /* @__PURE__ */ new Set([
15089
16246
  ".git",
@@ -15105,9 +16262,9 @@ var init_aster_tools = __esm({
15105
16262
 
15106
16263
  // src/server/aster-memory.ts
15107
16264
  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";
16265
+ import { randomUUID as randomUUID7 } from "crypto";
16266
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync9 } from "fs";
16267
+ import { dirname as dirname17 } from "path";
15111
16268
  function rowToItem(row) {
15112
16269
  return {
15113
16270
  id: row.id,
@@ -15160,7 +16317,7 @@ var init_aster_memory = __esm({
15160
16317
  AsterMemory = class {
15161
16318
  constructor(dbPath = defaultAsterDbPath(), options = {}) {
15162
16319
  this.options = options;
15163
- mkdirSync7(dirname16(dbPath), { recursive: true, mode: 448 });
16320
+ mkdirSync9(dirname17(dbPath), { recursive: true, mode: 448 });
15164
16321
  this.db = new Database4(dbPath);
15165
16322
  this.db.pragma("journal_mode = WAL");
15166
16323
  this.db.pragma("busy_timeout = 5000");
@@ -15224,7 +16381,7 @@ var init_aster_memory = __esm({
15224
16381
  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
16382
  return { item: this.get(existing.id), change: "updated" };
15226
16383
  }
15227
- const id = randomUUID6();
16384
+ const id = randomUUID7();
15228
16385
  this.db.prepare(`INSERT INTO aster_memories (id, kind, key, title, content, pinned, source_conversation_id, created_at, updated_at)
15229
16386
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.kind, input.key ?? null, title, content, input.pinned ? 1 : 0, input.sourceConversationId ?? null, now2, now2);
15230
16387
  return { item: this.get(id), change: "added" };
@@ -15436,9 +16593,9 @@ var init_aster_github_proxy = __esm({
15436
16593
  }
15437
16594
  handle(req, res) {
15438
16595
  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");
16596
+ const git3 = url.pathname.match(GIT_PATH_RE);
16597
+ const api = git3 ? null : url.pathname.match(API_PATH_RE);
16598
+ if (!git3 && !api) return deny(res, 404, "Only this conversation's repository is reachable");
15442
16599
  const grantToken = extractToken(req);
15443
16600
  if (!grantToken) {
15444
16601
  res.writeHead(401, { "www-authenticate": 'Basic realm="aster-github"', "content-type": "text/plain" });
@@ -15447,18 +16604,18 @@ var init_aster_github_proxy = __esm({
15447
16604
  }
15448
16605
  const grant = this.options.resolveGrant(grantToken);
15449
16606
  if (!grant) return deny(res, 403, "This repository grant is invalid or was revoked");
15450
- const [, owner, name] = git2 ?? api;
16607
+ const [, owner, name] = git3 ?? api;
15451
16608
  if (`${owner}/${name}`.toLowerCase() !== grant.repository.toLowerCase()) {
15452
16609
  return deny(res, 403, `This conversation can only access ${grant.repository}`);
15453
16610
  }
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);
16611
+ 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
16612
  const headers = {};
15456
16613
  for (const name2 of FORWARDED_REQUEST_HEADERS) {
15457
16614
  const value = req.headers[name2];
15458
16615
  if (typeof value === "string") headers[name2] = value;
15459
16616
  }
15460
16617
  headers["user-agent"] ??= "autopilot-aster";
15461
- headers.authorization = git2 ? "Basic " + Buffer.from(`x-access-token:${this.options.token}`).toString("base64") : `Bearer ${this.options.token}`;
16618
+ headers.authorization = git3 ? "Basic " + Buffer.from(`x-access-token:${this.options.token}`).toString("base64") : `Bearer ${this.options.token}`;
15462
16619
  const send = upstream.protocol === "https:" ? httpsRequest : httpRequest;
15463
16620
  const forward = send(upstream, { method: req.method, headers }, (upstreamRes) => {
15464
16621
  const responseHeaders = {};
@@ -15481,7 +16638,7 @@ var init_aster_github_proxy = __esm({
15481
16638
  // src/server/aster-push.ts
15482
16639
  import Database5 from "better-sqlite3";
15483
16640
  import { createSign } from "crypto";
15484
- import { readFileSync as readFileSync5 } from "fs";
16641
+ import { readFileSync as readFileSync6 } from "fs";
15485
16642
  import { connect } from "http2";
15486
16643
  function truncate2(value, limit) {
15487
16644
  const clean = value.replace(/\s+/g, " ").trim();
@@ -15599,7 +16756,7 @@ var init_aster_push = __esm({
15599
16756
  token() {
15600
16757
  const now2 = Math.floor(Date.now() / 1e3);
15601
16758
  if (this.jwt && now2 - this.jwt.issuedAt < 50 * 60) return this.jwt.value;
15602
- this.privateKey ??= readFileSync5(this.options.keyPath, "utf8");
16759
+ this.privateKey ??= readFileSync6(this.options.keyPath, "utf8");
15603
16760
  const encode = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
15604
16761
  const unsigned = `${encode({ alg: "ES256", kid: this.options.keyId })}.${encode({ iss: this.options.teamId, iat: now2 })}`;
15605
16762
  const signature = createSign("SHA256").update(unsigned).sign({ key: this.privateKey, dsaEncoding: "ieee-p1363" }).toString("base64url");
@@ -15611,7 +16768,7 @@ var init_aster_push = __esm({
15611
16768
  });
15612
16769
 
15613
16770
  // src/server/aster-api.ts
15614
- import { randomUUID as randomUUID7 } from "crypto";
16771
+ import { randomUUID as randomUUID8 } from "crypto";
15615
16772
  import { unlink as unlink2 } from "fs/promises";
15616
16773
  import { resolve as resolve8 } from "path";
15617
16774
  function parseCursor(header, query) {
@@ -16168,8 +17325,8 @@ var init_aster_api = __esm({
16168
17325
  };
16169
17326
  }
16170
17327
  async createConversation(res, store, principal, workspace, model, idempotencyKey, kind, repository) {
16171
- const candidateId = randomUUID7();
16172
- const candidateSessionId = randomUUID7();
17328
+ const candidateId = randomUUID8();
17329
+ const candidateSessionId = randomUUID8();
16173
17330
  const reservation = store.reserveCellConversation({
16174
17331
  conversationId: candidateId,
16175
17332
  credentialId: principal.credentialId,
@@ -16299,7 +17456,7 @@ var init_aster_api = __esm({
16299
17456
  sendError(res, 409, "conversation_cell_unavailable", "Conversation cell is not available");
16300
17457
  return;
16301
17458
  }
16302
- const runId = randomUUID7();
17459
+ const runId = randomUUID8();
16303
17460
  if (!idempotent && !store.beginTurn(conversation.id, runId)) {
16304
17461
  sendError(res, 409, "conversation_busy", "A turn is already active for this conversation");
16305
17462
  return;
@@ -16747,9 +17904,10 @@ data: ${JSON.stringify(event.data)}
16747
17904
  });
16748
17905
 
16749
17906
  // src/server/aster-cell-runtime.ts
16750
- import { execFile as execFile5 } from "child_process";
16751
- import { promisify as promisify4 } from "util";
16752
- import { randomUUID as randomUUID8 } from "crypto";
17907
+ import { execFile as execFile7 } from "child_process";
17908
+ import { readFile as readFile21 } from "fs/promises";
17909
+ import { promisify as promisify6 } from "util";
17910
+ import { randomUUID as randomUUID9 } from "crypto";
16753
17911
  async function writeCellFileChunked(cell, path, content) {
16754
17912
  if (Buffer.byteLength(content, "utf8") <= CELL_FILE_WRITE_CHUNK_CHARS) {
16755
17913
  await cell.writeFile(path, content, { mode: "0600" });
@@ -16774,8 +17932,17 @@ async function writeCellFileChunked(cell, path, content) {
16774
17932
  });
16775
17933
  }
16776
17934
  }
17935
+ async function readBundledCellSdkBundle() {
17936
+ for (const url of [new URL("./sdk/index.js", import.meta.url), new URL("../sdk/index.js", import.meta.url)]) {
17937
+ try {
17938
+ return await readFile21(url, "utf8");
17939
+ } catch {
17940
+ }
17941
+ }
17942
+ throw new Error("cell_runtime_bundle_unavailable");
17943
+ }
16777
17944
  async function defaultArchiveWorkspace(workspaceRoot) {
16778
- const { stdout } = await execFileAsync3("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
17945
+ const { stdout } = await execFileAsync5("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
16779
17946
  encoding: "buffer",
16780
17947
  maxBuffer: 512 * 1024 * 1024
16781
17948
  });
@@ -16823,17 +17990,19 @@ ${redacted.slice(-half)}`;
16823
17990
  function sleep4(ms) {
16824
17991
  return new Promise((resolve15) => setTimeout(resolve15, ms));
16825
17992
  }
16826
- var execFileAsync3, CELL_WORKSPACE, CELL_STATE_DIR, CHECKPOINT_MESSAGE_FILE, INSTALLED_RUNTIME_PACKAGE, 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;
17993
+ 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;
16827
17994
  var init_aster_cell_runtime = __esm({
16828
17995
  "src/server/aster-cell-runtime.ts"() {
16829
17996
  "use strict";
16830
17997
  init_version();
16831
17998
  init_aster_tools();
16832
- execFileAsync3 = promisify4(execFile5);
17999
+ execFileAsync5 = promisify6(execFile7);
16833
18000
  CELL_WORKSPACE = "/workspace";
16834
18001
  CELL_STATE_DIR = "/workspace/.aster";
16835
18002
  CHECKPOINT_MESSAGE_FILE = `${CELL_STATE_DIR}/checkpoint-message`;
16836
- INSTALLED_RUNTIME_PACKAGE = "/opt/autopilot/node_modules/autopilot-ai/package.json";
18003
+ CELL_RUNTIME_DIR = "/opt/autopilot/node_modules/autopilot-ai";
18004
+ INSTALLED_RUNTIME_PACKAGE = `${CELL_RUNTIME_DIR}/package.json`;
18005
+ CELL_RUNTIME_SDK_ENTRY = `${CELL_RUNTIME_DIR}/dist/sdk/index.js`;
16837
18006
  NOTES_FILE = `${CELL_STATE_DIR}/pending-notes`;
16838
18007
  MEMORY_FILE = `${CELL_STATE_DIR}/memory.md`;
16839
18008
  MEMORY_EXTRACTION_TIMEOUT_MS = 15e3;
@@ -16858,6 +18027,7 @@ var init_aster_cell_runtime = __esm({
16858
18027
  constructor(provider, options = {}) {
16859
18028
  this.provider = provider;
16860
18029
  this.packageSpec = options.autopilotPackageSpec ?? `autopilot-ai@${getAppVersion()}`;
18030
+ this.cellRuntimeSdkBundle = options.cellRuntimeSdkBundle;
16861
18031
  this.archiveWorkspace = options.archiveWorkspace ?? defaultArchiveWorkspace;
16862
18032
  this.pollIntervalMs = options.pollIntervalMs ?? EVENT_POLL_MS;
16863
18033
  this.healthTimeoutMs = options.healthTimeoutMs ?? HEALTH_TIMEOUT_MS;
@@ -16872,6 +18042,7 @@ var init_aster_cell_runtime = __esm({
16872
18042
  }
16873
18043
  provider;
16874
18044
  packageSpec;
18045
+ cellRuntimeSdkBundle;
16875
18046
  archiveWorkspace;
16876
18047
  pollIntervalMs;
16877
18048
  healthTimeoutMs;
@@ -16932,7 +18103,7 @@ var init_aster_cell_runtime = __esm({
16932
18103
  await this.installRuntime(cell);
16933
18104
  await this.ensureBridge(cell);
16934
18105
  await this.control(cell, "POST", "/rpc", {
16935
- id: randomUUID8(),
18106
+ id: randomUUID9(),
16936
18107
  type: "new_session",
16937
18108
  sessionId: input.sessionId,
16938
18109
  config: { model: input.model, cwd: CELL_WORKSPACE }
@@ -16951,7 +18122,7 @@ var init_aster_cell_runtime = __esm({
16951
18122
  /** Switch the cell session's model between turns. The bridge persists it for session restore. */
16952
18123
  async setModel(cellId, model) {
16953
18124
  await this.ensureReady(cellId);
16954
- await this.controlById(cellId, "POST", "/rpc", { id: randomUUID8(), type: "set_model", modelId: model });
18125
+ await this.controlById(cellId, "POST", "/rpc", { id: randomUUID9(), type: "set_model", modelId: model });
16955
18126
  }
16956
18127
  /** Reinstall the runtime if the cell's image lost it and restart the bridge if it isn't running. */
16957
18128
  async ensureReady(cellId) {
@@ -17014,7 +18185,7 @@ var init_aster_cell_runtime = __esm({
17014
18185
  const active = this.activeRuns.get(runId);
17015
18186
  if (!active) return;
17016
18187
  active.stop();
17017
- void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID8(), type: "abort" }).catch(() => {
18188
+ void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID9(), type: "abort" }).catch(() => {
17018
18189
  });
17019
18190
  }
17020
18191
  async runTurn(turn, options = {}) {
@@ -17113,7 +18284,7 @@ ${turn.userText}` : turn.userText;
17113
18284
  args: event.args ?? {}
17114
18285
  });
17115
18286
  await this.controlById(cellId, "POST", "/rpc", {
17116
- id: randomUUID8(),
18287
+ id: randomUUID9(),
17117
18288
  type: "resolve_permission",
17118
18289
  requestId: event.requestId,
17119
18290
  decision
@@ -17241,9 +18412,9 @@ ${turn.userText}` : turn.userText;
17241
18412
  if (!isSafeArtifactPath(path)) throw new Error("invalid_artifact_path");
17242
18413
  const cell = await this.provider.getCell(cellId);
17243
18414
  const file = shellQuote2(`${ARTIFACTS_DIR}/${path}`);
17244
- const stat10 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
17245
- if (stat10.exitCode !== 0) throw new Error("artifact_not_found");
17246
- if (Number(stat10.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
18415
+ const stat11 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
18416
+ if (stat11.exitCode !== 0) throw new Error("artifact_not_found");
18417
+ if (Number(stat11.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
17247
18418
  const encoded = await cell.exec(`base64 -w0 -- ${file}`);
17248
18419
  if (encoded.exitCode !== 0) throw new Error("artifact_not_found");
17249
18420
  return { data: Buffer.from(encoded.stdout.trim(), "base64"), mimeType: artifactMimeType(path), name: path.split("/").pop() ?? path };
@@ -17268,11 +18439,38 @@ ${text}` : text, { mode: "0600" });
17268
18439
  }
17269
18440
  async installRuntime(cell) {
17270
18441
  const expectedVersion = /@(\d[^@/]*)$/.exec(this.packageSpec)?.[1];
18442
+ let installed;
17271
18443
  try {
17272
- const installed = JSON.parse(await cell.readFile(INSTALLED_RUNTIME_PACKAGE));
17273
- if (!expectedVersion || installed.version === expectedVersion) return;
18444
+ installed = JSON.parse(await cell.readFile(INSTALLED_RUNTIME_PACKAGE));
18445
+ if (!expectedVersion || installed?.version === expectedVersion) return;
17274
18446
  } catch {
17275
18447
  }
18448
+ if (installed && expectedVersion) {
18449
+ const sdkBundle = this.cellRuntimeSdkBundle ?? await readBundledCellSdkBundle();
18450
+ await cell.mkdir(`${CELL_RUNTIME_DIR}/dist/sdk`, { parents: true });
18451
+ await writeCellFileChunked(cell, CELL_RUNTIME_SDK_ENTRY, sdkBundle);
18452
+ const bridge = (await cell.listProcesses()).find(
18453
+ (process3) => process3.status === "running" && process3.command.includes(BRIDGE_MARKER)
18454
+ );
18455
+ if (bridge) {
18456
+ await cell.killProcess(bridge.procId);
18457
+ const deadline = Date.now() + 5e3;
18458
+ for (; ; ) {
18459
+ const stillRunning = (await cell.listProcesses()).some(
18460
+ (process3) => process3.procId === bridge.procId && process3.status === "running"
18461
+ );
18462
+ if (!stillRunning) break;
18463
+ if (Date.now() >= deadline) throw new Error("cell_runtime_restart_failed");
18464
+ await sleep4(50);
18465
+ }
18466
+ }
18467
+ await cell.writeFile(
18468
+ INSTALLED_RUNTIME_PACKAGE,
18469
+ JSON.stringify({ ...installed, version: expectedVersion }),
18470
+ { mode: "0600" }
18471
+ );
18472
+ return;
18473
+ }
17276
18474
  const install = await cell.exec(`mkdir -p /opt/autopilot && npm install --prefix /opt/autopilot --no-audit --no-fund ${this.packageSpec}`);
17277
18475
  if (install.exitCode !== 0) throw new Error("cell_runtime_install_failed");
17278
18476
  }
@@ -17295,7 +18493,7 @@ ${text}` : text, { mode: "0600" });
17295
18493
  }
17296
18494
  }
17297
18495
  async control(cell, method, path, body) {
17298
- const requestId = randomUUID8();
18496
+ const requestId = randomUUID9();
17299
18497
  await cell.mkdir(CELL_CONTROL_DIR, { parents: true });
17300
18498
  await writeCellFileChunked(cell, `${CELL_CONTROL_DIR}/${requestId}.json`, JSON.stringify({ method, path, body }));
17301
18499
  const result2 = await cell.exec(`${CONTROL_RUNNER} ${requestId}`);
@@ -17511,9 +18709,9 @@ var init_hotcell_provider = __esm({
17511
18709
  });
17512
18710
 
17513
18711
  // src/server/routes.ts
17514
- import { randomUUID as randomUUID9 } from "crypto";
18712
+ import { randomUUID as randomUUID10 } from "crypto";
17515
18713
  import { URL as URL2 } from "url";
17516
- import { readFile as readFile21, unlink as unlink3 } from "fs/promises";
18714
+ import { readFile as readFile22, unlink as unlink3 } from "fs/promises";
17517
18715
  import { resolve as resolve9, basename as basename5 } from "path";
17518
18716
  function createHotcellAsterRuntime() {
17519
18717
  try {
@@ -17526,6 +18724,27 @@ function createHotcellAsterRuntime() {
17526
18724
  throw error;
17527
18725
  }
17528
18726
  }
18727
+ async function startSessionTrace(config2, sessionFile, tools, messages) {
18728
+ const trace = createRecorder({
18729
+ enabled: config2.trace?.enabled === true,
18730
+ sessionId: sessionFile.id,
18731
+ root: config2.trace?.root
18732
+ });
18733
+ const systemPrompt = messages.filter((m) => m.role === "system" && typeof m.content === "string").map((m) => m.content).join("\n\n");
18734
+ await emitSessionStart(trace, {
18735
+ cwd: sessionFile.cwd,
18736
+ model: sessionFile.model,
18737
+ mode: "server",
18738
+ config: config2,
18739
+ tools: toOpenAIToolDefs(tools),
18740
+ systemPrompt
18741
+ });
18742
+ return trace;
18743
+ }
18744
+ function traceUserMessage(trace, text, attachments, turnId) {
18745
+ if (!trace?.enabled) return;
18746
+ trace.emit("user_message", { content_blob: trace.blob(text) ?? "", source: "server", attachments }, turnId);
18747
+ }
17529
18748
  function parseOptionalBudgetInteger(body, name, minimum = 1) {
17530
18749
  if (!Object.hasOwn(body, name)) return void 0;
17531
18750
  const value = body[name];
@@ -17576,8 +18795,8 @@ async function resolveFiles2(filePatterns, cwd) {
17576
18795
  const resolved = /* @__PURE__ */ new Set();
17577
18796
  for (const pattern of filePatterns) {
17578
18797
  try {
17579
- const stat10 = await import("fs/promises").then((m) => m.stat(resolve9(cwd, pattern)));
17580
- if (stat10.isFile()) {
18798
+ const stat11 = await import("fs/promises").then((m) => m.stat(resolve9(cwd, pattern)));
18799
+ if (stat11.isFile()) {
17581
18800
  resolved.add(resolve9(cwd, pattern));
17582
18801
  continue;
17583
18802
  }
@@ -17606,7 +18825,7 @@ async function buildUserMessage2(prompt, files, cwd) {
17606
18825
  }
17607
18826
  } else {
17608
18827
  try {
17609
- const content = await readFile21(filePath, "utf8");
18828
+ const content = await readFile22(filePath, "utf8");
17610
18829
  const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
17611
18830
  fileContents.push(`
17612
18831
  --- ${relPath} ---
@@ -17645,7 +18864,8 @@ function setupRoutes(config2, options = {}) {
17645
18864
  executor: new ToolExecutor(tools),
17646
18865
  sseClients: /* @__PURE__ */ new Set(),
17647
18866
  runId: run.id,
17648
- allowedTools
18867
+ allowedTools,
18868
+ trace: await startSessionTrace(config2, sessionFile, tools, sessionFile.messages)
17649
18869
  };
17650
18870
  activeSessions.set(run.sessionId, active);
17651
18871
  activeRuns.set(run.id, active);
@@ -17833,7 +19053,7 @@ function setupRoutes(config2, options = {}) {
17833
19053
  const executor = new ToolExecutor(tools);
17834
19054
  const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
17835
19055
  const sessionId = makeSessionId2(task);
17836
- const runId = randomUUID9();
19056
+ const runId = randomUUID10();
17837
19057
  const worktreeManager = new RunWorktreeManager();
17838
19058
  let worktree;
17839
19059
  if (useWorktree) {
@@ -17891,7 +19111,10 @@ function setupRoutes(config2, options = {}) {
17891
19111
  }
17892
19112
  throw error;
17893
19113
  }
19114
+ const runTrace = await startSessionTrace(config2, sessionFile, tools, messages);
19115
+ traceUserMessage(runTrace, task, 0, makeTurnId());
17894
19116
  const active = {
19117
+ trace: runTrace,
17895
19118
  sessionFile,
17896
19119
  messages,
17897
19120
  executor,
@@ -18073,11 +19296,16 @@ function setupRoutes(config2, options = {}) {
18073
19296
  const resolvedFiles = await resolveFiles2(files, cwd);
18074
19297
  const userContent = await buildUserMessage2(prompt, resolvedFiles, cwd);
18075
19298
  messages.push({ role: "user", content: userContent });
19299
+ const trace = await startSessionTrace(config2, sessionFile, ALL_TOOLS, messages);
19300
+ const firstTurnId = makeTurnId();
19301
+ traceUserMessage(trace, prompt, resolvedFiles.length, firstTurnId);
18076
19302
  const active = {
18077
19303
  sessionFile,
18078
19304
  messages,
18079
19305
  executor,
18080
- sseClients: /* @__PURE__ */ new Set()
19306
+ sseClients: /* @__PURE__ */ new Set(),
19307
+ trace,
19308
+ nextTurnId: firstTurnId
18081
19309
  };
18082
19310
  activeSessions.set(sessionId, active);
18083
19311
  launchAgentTurnForSession(active, config2, allowAll);
@@ -18105,6 +19333,15 @@ function setupRoutes(config2, options = {}) {
18105
19333
  const resolvedFiles = await resolveFiles2(files, active.sessionFile.cwd);
18106
19334
  const userContent = await buildUserMessage2(prompt, resolvedFiles, active.sessionFile.cwd);
18107
19335
  active.messages.push({ role: "user", content: userContent });
19336
+ active.nextTurnId = makeTurnId();
19337
+ if (active.trace?.enabled && active.lastTurnId) {
19338
+ active.trace.emit(
19339
+ "outcome",
19340
+ { kind: "next_message", turn_id: active.lastTurnId, classification: classifyNextMessage(prompt), classifier: "regex" },
19341
+ active.lastTurnId
19342
+ );
19343
+ }
19344
+ traceUserMessage(active.trace, prompt, resolvedFiles.length, active.nextTurnId);
18108
19345
  launchAgentTurnForSession(active, config2, allowAll);
18109
19346
  json2(res, 202, { sessionId, status: "started" });
18110
19347
  return;
@@ -18119,6 +19356,7 @@ function setupRoutes(config2, options = {}) {
18119
19356
  wakeScheduler.dispose();
18120
19357
  for (const [, active] of activeSessions) {
18121
19358
  active.shuttingDown = true;
19359
+ if (active.trace?.enabled) active.trace.emit("session_end", { reason: "exit" }, null);
18122
19360
  active.controller?.abort(new Error("server_shutdown"));
18123
19361
  for (const client of active.sseClients) {
18124
19362
  client.close();
@@ -18287,6 +19525,9 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18287
19525
  }
18288
19526
  };
18289
19527
  const compaction = sessionFileCompactionTarget(sessionFile);
19528
+ const turnId = active.nextTurnId ?? makeTurnId();
19529
+ active.nextTurnId = void 0;
19530
+ active.lastTurnId = turnId;
18290
19531
  try {
18291
19532
  if (run && runStore) {
18292
19533
  const tools = ALL_TOOLS.filter((tool) => active.allowedTools?.has(tool.name));
@@ -18308,6 +19549,8 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18308
19549
  preferPullRequests: config2.preferPullRequests,
18309
19550
  compaction,
18310
19551
  callbacks,
19552
+ trace: active.trace,
19553
+ turnId,
18311
19554
  onIterationEnd: async (updatedMessages) => {
18312
19555
  sessionFile.messages = updatedMessages;
18313
19556
  sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -18334,7 +19577,9 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18334
19577
  allowDirectPush: config2.allowDirectPush,
18335
19578
  preferPullRequests: config2.preferPullRequests,
18336
19579
  compaction,
18337
- callbacks
19580
+ callbacks,
19581
+ trace: active.trace,
19582
+ turnId
18338
19583
  });
18339
19584
  }
18340
19585
  sessionFile.messages = messages;
@@ -18386,6 +19631,8 @@ var init_routes = __esm({
18386
19631
  "use strict";
18387
19632
  init_config();
18388
19633
  init_llm_auth();
19634
+ init_trace();
19635
+ init_registry2();
18389
19636
  init_loop();
18390
19637
  init_system_prompt();
18391
19638
  init_executor();
@@ -18518,8 +19765,8 @@ __export(db_exports, {
18518
19765
  updateMemoryEmbedding: () => updateMemoryEmbedding
18519
19766
  });
18520
19767
  import Database6 from "better-sqlite3";
18521
- import { dirname as dirname17 } from "path";
18522
- import { mkdirSync as mkdirSync8, statSync as statSync4 } from "fs";
19768
+ import { dirname as dirname18 } from "path";
19769
+ import { mkdirSync as mkdirSync10, statSync as statSync4 } from "fs";
18523
19770
  function initSchema(db) {
18524
19771
  db.exec(`
18525
19772
  CREATE TABLE IF NOT EXISTS memories (
@@ -18603,7 +19850,7 @@ function openMemoryDb(dbPath) {
18603
19850
  if (dbInstance) {
18604
19851
  dbInstance.close();
18605
19852
  }
18606
- mkdirSync8(dirname17(dbPath), { recursive: true });
19853
+ mkdirSync10(dirname18(dbPath), { recursive: true });
18607
19854
  dbInstance = new Database6(dbPath);
18608
19855
  dbInstance.pragma("journal_mode = WAL");
18609
19856
  dbInstance.pragma("foreign_keys = ON");
@@ -19737,12 +20984,12 @@ var init_connection = __esm({
19737
20984
  });
19738
20985
 
19739
20986
  // src/lsp/protocol.ts
19740
- import { pathToFileURL as pathToFileURL2, fileURLToPath as fileURLToPath4 } from "url";
20987
+ import { pathToFileURL as pathToFileURL2, fileURLToPath as fileURLToPath5 } from "url";
19741
20988
  function toUri(path) {
19742
20989
  return pathToFileURL2(path).href;
19743
20990
  }
19744
20991
  function fromUri(uri) {
19745
- return fileURLToPath4(uri);
20992
+ return fileURLToPath5(uri);
19746
20993
  }
19747
20994
  function symbolKindName(kind) {
19748
20995
  const names = {
@@ -20553,7 +21800,7 @@ function makeLspTools(manager) {
20553
21800
  var init_lsp = __esm({
20554
21801
  "src/tools/lsp.ts"() {
20555
21802
  "use strict";
20556
- init_paths();
21803
+ init_paths2();
20557
21804
  init_adapter();
20558
21805
  }
20559
21806
  });
@@ -20568,12 +21815,12 @@ __export(requesty_catalog_exports, {
20568
21815
  loadRequestyCatalog: () => loadRequestyCatalog,
20569
21816
  mapRequestyModel: () => mapRequestyModel
20570
21817
  });
20571
- import { readFile as readFile22, writeFile as writeFile13, mkdir as mkdir14 } from "fs/promises";
20572
- import { homedir as homedir23 } from "os";
20573
- import { join as join32, dirname as dirname18 } from "path";
21818
+ import { readFile as readFile23, writeFile as writeFile13, mkdir as mkdir14 } from "fs/promises";
21819
+ import { homedir as homedir24 } from "os";
21820
+ import { join as join36, dirname as dirname19 } from "path";
20574
21821
  function catalogCachePath2() {
20575
- const xdg = process.env.XDG_CONFIG_HOME || join32(homedir23(), ".config");
20576
- return join32(xdg, "kimiflare", "requesty-models.json");
21822
+ const xdg = process.env.XDG_CONFIG_HOME || join36(homedir24(), ".config");
21823
+ return join36(xdg, "kimiflare", "requesty-models.json");
20577
21824
  }
20578
21825
  function isSafeModelId(id) {
20579
21826
  return typeof id === "string" && id.length > 0 && !CONTROL_CHARS.test(id);
@@ -20629,7 +21876,7 @@ async function fetchRequestyCatalog(fetchImpl2 = fetch) {
20629
21876
  }
20630
21877
  async function readCache4() {
20631
21878
  try {
20632
- const raw = await readFile22(catalogCachePath2(), "utf8");
21879
+ const raw = await readFile23(catalogCachePath2(), "utf8");
20633
21880
  const parsed = JSON.parse(raw);
20634
21881
  if (parsed.version !== CACHE_VERSION2) return null;
20635
21882
  return {
@@ -20644,7 +21891,7 @@ async function readCache4() {
20644
21891
  async function writeCache4(catalog) {
20645
21892
  const path = catalogCachePath2();
20646
21893
  try {
20647
- await mkdir14(dirname18(path), { recursive: true });
21894
+ await mkdir14(dirname19(path), { recursive: true });
20648
21895
  const cache = { version: CACHE_VERSION2, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), ...catalog };
20649
21896
  await writeFile13(path, JSON.stringify(cache, null, 2), "utf8");
20650
21897
  } catch {
@@ -20736,9 +21983,9 @@ var init_permissions = __esm({
20736
21983
 
20737
21984
  // src/sdk/session.ts
20738
21985
  import { resolve as resolve10 } from "path";
20739
- import { homedir as homedir24 } from "os";
20740
- import { join as join33 } from "path";
20741
- import { existsSync as existsSync4 } from "fs";
21986
+ import { homedir as homedir25 } from "os";
21987
+ import { join as join37 } from "path";
21988
+ import { existsSync as existsSync7 } from "fs";
20742
21989
  async function createAgentSession(opts2) {
20743
21990
  const config2 = await resolveSdkConfig(opts2);
20744
21991
  if (usesRequesty(llmAuthFromConfig(config2))) await ensureRequestyCatalog();
@@ -20754,7 +22001,7 @@ async function createAgentSession(opts2) {
20754
22001
  let memoryManager = null;
20755
22002
  const memoryEnabled = opts2.memoryEnabled ?? config2.memoryEnabled ?? false;
20756
22003
  if (memoryEnabled) {
20757
- const dbPath = config2.memoryDbPath ?? join33(homedir24(), ".local", "share", "kimiflare", "memory.db");
22004
+ const dbPath = config2.memoryDbPath ?? join37(homedir25(), ".local", "share", "kimiflare", "memory.db");
20758
22005
  memoryManager = new MemoryManager({
20759
22006
  dbPath,
20760
22007
  ...llmAuthFromConfig(config2),
@@ -20784,7 +22031,7 @@ async function createAgentSession(opts2) {
20784
22031
  }
20785
22032
  let sessionFile;
20786
22033
  if (opts2.sessionId) {
20787
- const filePath = join33(sessionsDir(), `${opts2.sessionId}.json`);
22034
+ const filePath = join37(sessionsDir(), `${opts2.sessionId}.json`);
20788
22035
  try {
20789
22036
  sessionFile = await loadSession(filePath);
20790
22037
  } catch {
@@ -20819,6 +22066,19 @@ async function createAgentSession(opts2) {
20819
22066
  ...sessionFile.messages.filter((m) => m.role !== "system")
20820
22067
  ];
20821
22068
  const isNewSession = sessionFile.messages.length === 0;
22069
+ const trace = createRecorder({
22070
+ enabled: config2.trace?.enabled === true,
22071
+ sessionId: sessionFile.id,
22072
+ root: config2.trace?.root
22073
+ });
22074
+ await emitSessionStart(trace, {
22075
+ cwd,
22076
+ model: config2.model,
22077
+ mode: "sdk",
22078
+ config: config2,
22079
+ tools: toOpenAIToolDefs(allTools),
22080
+ systemPrompt
22081
+ });
20822
22082
  const session = new InternalSession({
20823
22083
  sessionFile,
20824
22084
  cwd,
@@ -20832,7 +22092,8 @@ async function createAgentSession(opts2) {
20832
22092
  permissionHandler: opts2.permissionHandler,
20833
22093
  onKimiMdStale: opts2.onKimiMdStale,
20834
22094
  hooks,
20835
- provider: opts2.provider
22095
+ provider: opts2.provider,
22096
+ trace
20836
22097
  });
20837
22098
  if (isNewSession) {
20838
22099
  await session.save().catch((err) => {
@@ -20865,6 +22126,8 @@ var init_session = __esm({
20865
22126
  init_context_budget();
20866
22127
  init_config2();
20867
22128
  init_permissions();
22129
+ init_trace();
22130
+ init_registry2();
20868
22131
  InternalSession = class {
20869
22132
  sessionId;
20870
22133
  cwd;
@@ -20880,6 +22143,10 @@ var init_session = __esm({
20880
22143
  onKimiMdStale;
20881
22144
  provider;
20882
22145
  hooks;
22146
+ trace;
22147
+ traceCostUsd = 0;
22148
+ /** Turn id of the last turn that ran, for next-message outcome signals. */
22149
+ lastTurnId = null;
20883
22150
  listeners = /* @__PURE__ */ new Set();
20884
22151
  steerQueue = [];
20885
22152
  followUpQueue = [];
@@ -20920,6 +22187,7 @@ var init_session = __esm({
20920
22187
  this.onKimiMdStale = opts2.onKimiMdStale;
20921
22188
  this.provider = opts2.provider;
20922
22189
  this.hooks = opts2.hooks;
22190
+ this.trace = opts2.trace;
20923
22191
  this.permissionHandler = opts2.permissionHandler ?? createDefaultPermissionHandler({
20924
22192
  mode: this.currentMode,
20925
22193
  onRequest: (req) => {
@@ -20961,8 +22229,8 @@ var init_session = __esm({
20961
22229
  const parts = [{ type: "text", text }];
20962
22230
  for (const img of options.images) {
20963
22231
  if ("path" in img) {
20964
- const { readFile: readFile31 } = await import("fs/promises");
20965
- const data = await readFile31(img.path, "base64");
22232
+ const { readFile: readFile32 } = await import("fs/promises");
22233
+ const data = await readFile32(img.path, "base64");
20966
22234
  const mimeType = img.path.endsWith(".png") ? "image/png" : img.path.endsWith(".jpg") || img.path.endsWith(".jpeg") ? "image/jpeg" : "image/webp";
20967
22235
  parts.push({ type: "image_url", image_url: { url: `data:${mimeType};base64,${data}` } });
20968
22236
  } else {
@@ -20973,6 +22241,22 @@ var init_session = __esm({
20973
22241
  }
20974
22242
  const userMessage = { role: "user", content: userContent };
20975
22243
  this.messages.push(userMessage);
22244
+ const turnId = makeTurnId();
22245
+ if (this.trace.enabled) {
22246
+ if (this.lastTurnId) {
22247
+ this.trace.emit(
22248
+ "outcome",
22249
+ { kind: "next_message", turn_id: this.lastTurnId, classification: classifyNextMessage(text), classifier: "regex" },
22250
+ this.lastTurnId
22251
+ );
22252
+ }
22253
+ this.trace.emit(
22254
+ "user_message",
22255
+ { content_blob: this.trace.blob(text) ?? "", source: "sdk", attachments: options?.images?.length ?? 0 },
22256
+ turnId
22257
+ );
22258
+ }
22259
+ this.lastTurnId = turnId;
20976
22260
  this.emit({ type: "message.start", messageId, role: "user" });
20977
22261
  this.emit({ type: "message.end", messageId });
20978
22262
  this.isStreaming = true;
@@ -20980,7 +22264,7 @@ var init_session = __esm({
20980
22264
  this.abortController = new AbortController();
20981
22265
  let succeeded = false;
20982
22266
  try {
20983
- await this.runTurn(mode, options?.maxToolIterations);
22267
+ await this.runTurn(mode, options?.maxToolIterations, turnId);
20984
22268
  succeeded = true;
20985
22269
  this.isStreaming = false;
20986
22270
  this.emit({ type: "status", status: "idle" });
@@ -21075,6 +22359,9 @@ var init_session = __esm({
21075
22359
  }
21076
22360
  dispose() {
21077
22361
  this.disposed = true;
22362
+ if (this.trace.enabled) {
22363
+ this.trace.emit("session_end", { reason: "exit", ...this.traceCostUsd > 0 ? { total_cost_usd: this.traceCostUsd } : {} }, null);
22364
+ }
21078
22365
  this.denyPendingPermissions();
21079
22366
  this.abortController?.abort();
21080
22367
  this.lspManager?.stopAll().catch(() => {
@@ -21082,7 +22369,7 @@ var init_session = __esm({
21082
22369
  this.memoryManager?.close();
21083
22370
  this.listeners.clear();
21084
22371
  }
21085
- async runTurn(mode, maxToolIterations) {
22372
+ async runTurn(mode, maxToolIterations, turnId = makeTurnId()) {
21086
22373
  const signal = this.abortController.signal;
21087
22374
  const coauthor = this.config.coauthor !== false ? {
21088
22375
  name: this.config.coauthorName || "autopilot",
@@ -21155,6 +22442,7 @@ var init_session = __esm({
21155
22442
  });
21156
22443
  },
21157
22444
  onUsageFinal: (usage, gatewayMeta) => {
22445
+ if (typeof usage.cost === "number") this.traceCostUsd += usage.cost;
21158
22446
  this.usage.totalInputTokens += usage.prompt_tokens;
21159
22447
  this.usage.totalOutputTokens += usage.completion_tokens;
21160
22448
  this.usage.turnCount += 1;
@@ -21232,6 +22520,8 @@ var init_session = __esm({
21232
22520
  compaction: this.compaction,
21233
22521
  allowDirectPush: this.config.allowDirectPush,
21234
22522
  preferPullRequests: this.config.preferPullRequests,
22523
+ trace: this.trace,
22524
+ turnId,
21235
22525
  // Steering lands at the next tool boundary, or before the final answer
21236
22526
  // ends the turn (the loop continues so the model can respond to it).
21237
22527
  drainPendingInput: () => {
@@ -21240,6 +22530,9 @@ var init_session = __esm({
21240
22530
  const messageId = `msg_${this.nextMessageId++}`;
21241
22531
  this.emit({ type: "message.start", messageId, role: "user" });
21242
22532
  this.emit({ type: "message.end", messageId });
22533
+ if (this.trace.enabled) {
22534
+ this.trace.emit("user_message", { content_blob: this.trace.blob(text) ?? "", source: "continuation", attachments: 0 }, turnId);
22535
+ }
21243
22536
  return { role: "user", content: text };
21244
22537
  });
21245
22538
  },
@@ -21248,14 +22541,14 @@ var init_session = __esm({
21248
22541
  this.lspManager?.notifyChange(path, content);
21249
22542
  } else {
21250
22543
  void import("fs/promises").then(
21251
- ({ readFile: readFile31 }) => readFile31(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
22544
+ ({ readFile: readFile32 }) => readFile32(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
21252
22545
  })
21253
22546
  ).catch(() => {
21254
22547
  });
21255
22548
  }
21256
22549
  }
21257
22550
  });
21258
- if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync4(join33(this.cwd, name)))) {
22551
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync7(join37(this.cwd, name)))) {
21259
22552
  this.messages[0] = {
21260
22553
  role: "system",
21261
22554
  content: buildSystemPrompt({
@@ -22067,6 +23360,8 @@ var init_builtins = __esm({
22067
23360
  { name: "checkpoint", argHint: "[label]", description: "Save current point in session", source: "builtin" },
22068
23361
  { name: "checkpoints", description: "List checkpoints in current session", source: "builtin" },
22069
23362
  { name: "compact", description: "Summarize old turns to free context", source: "builtin" },
23363
+ { name: "good", description: "Rate the last turn as good (recorded in the local session trace)", source: "builtin" },
23364
+ { name: "bad", argHint: "[reason]", description: "Rate the last turn as bad (recorded in the local session trace)", source: "builtin" },
22070
23365
  { name: "clear", description: "Clear current conversation", source: "builtin" },
22071
23366
  { name: "fresh", description: "Start a fresh session with a summarized plan or continuation context", source: "builtin" },
22072
23367
  { name: "init", description: "Scan repo and write AGENTS.md", source: "builtin" },
@@ -22533,7 +23828,9 @@ var init_help_menu = __esm({
22533
23828
  { command: "/resume", description: "pick a past conversation" },
22534
23829
  { command: "/compact", description: "summarize old turns to free context" },
22535
23830
  { command: "/clear", description: "clear current conversation" },
22536
- { command: "/fresh", description: "reset session and start fresh with the last plan" }
23831
+ { command: "/fresh", description: "reset session and start fresh with the last plan" },
23832
+ { command: "/good", description: "rate the last turn good (local session trace)" },
23833
+ { command: "/bad [reason]", description: "rate the last turn bad (local session trace)", selectable: false }
22537
23834
  ]
22538
23835
  },
22539
23836
  {
@@ -22692,8 +23989,8 @@ __export(app_helpers_exports, {
22692
23989
  trackRecentFile: () => trackRecentFile
22693
23990
  });
22694
23991
  import { execSync as execSync3, spawn as spawn6 } from "child_process";
22695
- import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
22696
- import { join as join34 } from "path";
23992
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
23993
+ import { join as join38 } from "path";
22697
23994
  import { platform as platform3 } from "os";
22698
23995
  function replaceQueuedPrompt(queue2, key, full, display) {
22699
23996
  const index = queue2.findIndex((item) => item.key === key);
@@ -22801,12 +24098,12 @@ function buildFilePickerIgnoreList(cwd) {
22801
24098
  ];
22802
24099
  const gitignorePatterns = [];
22803
24100
  try {
22804
- const gitignorePath = join34(cwd, ".gitignore");
24101
+ const gitignorePath = join38(cwd, ".gitignore");
22805
24102
  const stats = statSync5(gitignorePath);
22806
24103
  if (stats.size > MAX_GITIGNORE_SIZE) {
22807
24104
  return hardcoded;
22808
24105
  }
22809
- const content = readFileSync6(gitignorePath, "utf-8");
24106
+ const content = readFileSync7(gitignorePath, "utf-8");
22810
24107
  for (const line of content.split(/\r?\n/)) {
22811
24108
  const trimmed = line.trim();
22812
24109
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -22940,7 +24237,7 @@ function findImagePaths(text) {
22940
24237
  let match;
22941
24238
  while ((match = quotedRegex.exec(text)) !== null) {
22942
24239
  const path = match[1] ?? match[2];
22943
- if (path && isImagePath(path) && existsSync5(path)) {
24240
+ if (path && isImagePath(path) && existsSync8(path)) {
22944
24241
  paths.push(path);
22945
24242
  }
22946
24243
  }
@@ -22949,7 +24246,7 @@ function findImagePaths(text) {
22949
24246
  const processed = remaining.replace(/\\ /g, ESCAPED_SPACE);
22950
24247
  for (const token of processed.split(/\s+/)) {
22951
24248
  const clean = token.replace(new RegExp(ESCAPED_SPACE, "g"), " ").replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
22952
- if (clean && isImagePath(clean) && existsSync5(clean) && !paths.includes(clean)) {
24249
+ if (clean && isImagePath(clean) && existsSync8(clean) && !paths.includes(clean)) {
22953
24250
  paths.push(clean);
22954
24251
  }
22955
24252
  }
@@ -22979,9 +24276,9 @@ var init_app_helpers = __esm({
22979
24276
 
22980
24277
  // src/ui/bang-command.ts
22981
24278
  import { spawn as spawn7 } from "child_process";
22982
- import { mkdtemp as mkdtemp2, readFile as readFile23, rm as rm3 } from "fs/promises";
24279
+ import { mkdtemp as mkdtemp2, readFile as readFile24, rm as rm4 } from "fs/promises";
22983
24280
  import { tmpdir as tmpdir4, platform as platform4 } from "os";
22984
- import { join as join35 } from "path";
24281
+ import { join as join39 } from "path";
22985
24282
  function registerInkInstance(instance) {
22986
24283
  inkInstance = instance;
22987
24284
  }
@@ -23084,8 +24381,8 @@ async function runBangCommand(command, cwd) {
23084
24381
  const origErr = stderr.write;
23085
24382
  const readable = stdin.listeners("readable");
23086
24383
  const wasRaw = stdin.isTTY ? stdin.isRaw : false;
23087
- const logDir2 = isWindows ? null : await mkdtemp2(join35(tmpdir4(), "autopilot-bang-"));
23088
- const logPath = logDir2 ? join35(logDir2, "out.log") : null;
24384
+ const logDir2 = isWindows ? null : await mkdtemp2(join39(tmpdir4(), "autopilot-bang-"));
24385
+ const logPath = logDir2 ? join39(logDir2, "out.log") : null;
23089
24386
  const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
23090
24387
  inkInstance?.clear();
23091
24388
  origOut.call(stdout, `
@@ -23127,9 +24424,9 @@ $ ${command}
23127
24424
  }
23128
24425
  let output = spawnError ? `failed to start: ${spawnError}` : null;
23129
24426
  if (captured && logPath) {
23130
- output = cleanTerminalOutput(await readFile23(logPath, "utf8").catch(() => ""));
24427
+ output = cleanTerminalOutput(await readFile24(logPath, "utf8").catch(() => ""));
23131
24428
  }
23132
- if (logDir2) await rm3(logDir2, { recursive: true, force: true }).catch(() => {
24429
+ if (logDir2) await rm4(logDir2, { recursive: true, force: true }).catch(() => {
23133
24430
  });
23134
24431
  return { command, exitCode: result2.code, signal: result2.signal, output };
23135
24432
  }
@@ -25316,9 +26613,9 @@ __export(camouflage_view_exports, {
25316
26613
  validateCommandName: () => validateCommandName
25317
26614
  });
25318
26615
  import { execFileSync as execFileSync2, spawn as spawn9 } from "child_process";
25319
- import { readdir as readdir5 } from "fs/promises";
25320
- import { homedir as homedir25 } from "os";
25321
- import { join as join36, resolve as resolve11 } from "path";
26616
+ import { readdir as readdir6 } from "fs/promises";
26617
+ import { homedir as homedir26 } from "os";
26618
+ import { join as join40, resolve as resolve11 } from "path";
25322
26619
  async function loadSdk() {
25323
26620
  const sdk = await import("camouflage-tui").catch((err) => {
25324
26621
  throw new CamouflageUnavailable(`camouflage-tui is not installed (${err.message})`);
@@ -25497,10 +26794,10 @@ function truncateText(s, max) {
25497
26794
  async function listMentionDir(cwd, query) {
25498
26795
  const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
25499
26796
  const dir = exact ? `${query}/` : query.slice(0, query.lastIndexOf("/") + 1);
25500
- const expanded = dir.startsWith("~") ? join36(homedir25(), dir.slice(1)) : dir;
26797
+ const expanded = dir.startsWith("~") ? join40(homedir26(), dir.slice(1)) : dir;
25501
26798
  const target = resolve11(cwd, expanded || ".");
25502
26799
  try {
25503
- const items = await readdir5(target, { withFileTypes: true });
26800
+ const items = await readdir6(target, { withFileTypes: true });
25504
26801
  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 ? "/" : ""}`);
25505
26802
  return { dir, entries };
25506
26803
  } catch {
@@ -25578,7 +26875,7 @@ function shortModel(id) {
25578
26875
  return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
25579
26876
  }
25580
26877
  function shortPath(p) {
25581
- const home = homedir25();
26878
+ const home = homedir26();
25582
26879
  return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
25583
26880
  }
25584
26881
  function gitBranch(cwd) {
@@ -27585,7 +28882,7 @@ var init_tool_view = __esm({
27585
28882
  "use strict";
27586
28883
  init_spinner();
27587
28884
  init_diff_view();
27588
- init_paths();
28885
+ init_paths2();
27589
28886
  init_theme_context();
27590
28887
  init_narrator();
27591
28888
  ToolView = React7.memo(function ToolView2({ evt, verbose, isRepeated, intentTier }) {
@@ -28332,7 +29629,7 @@ function PermissionModal({ tool, args, onDecide, onFeedback }) {
28332
29629
  );
28333
29630
  const handleFeedbackSubmit = useCallback(
28334
29631
  (text) => {
28335
- onDecide(DENY);
29632
+ onDecide(text.trim() ? { ...DENY, feedback: text.trim() } : DENY);
28336
29633
  if (text.trim()) {
28337
29634
  onFeedback?.(text);
28338
29635
  }
@@ -28847,8 +30144,8 @@ var init_queue_plan_picker = __esm({
28847
30144
 
28848
30145
  // src/agent/jev-context.ts
28849
30146
  import { execFileSync as execFileSync3 } from "child_process";
28850
- import { readFile as readFile24 } from "fs/promises";
28851
- import { basename as basename6, join as join37, resolve as resolve12 } from "path";
30147
+ import { readFile as readFile25 } from "fs/promises";
30148
+ import { basename as basename6, join as join41, resolve as resolve12 } from "path";
28852
30149
  function messageText(message2) {
28853
30150
  if (typeof message2.content === "string") return message2.content.trim();
28854
30151
  if (!Array.isArray(message2.content)) return "";
@@ -28870,7 +30167,7 @@ function projectRoot(cwd) {
28870
30167
  }
28871
30168
  async function readPackageMetadata(root) {
28872
30169
  try {
28873
- const parsed = JSON.parse(await readFile24(join37(root, "package.json"), "utf8"));
30170
+ const parsed = JSON.parse(await readFile25(join41(root, "package.json"), "utf8"));
28874
30171
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
28875
30172
  const pkg = parsed;
28876
30173
  const metadata = {};
@@ -28887,7 +30184,7 @@ async function readPackageMetadata(root) {
28887
30184
  async function readLicenseEvidence(root) {
28888
30185
  for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
28889
30186
  try {
28890
- const content = (await readFile24(join37(root, name), "utf8")).trim();
30187
+ const content = (await readFile25(join41(root, name), "utf8")).trim();
28891
30188
  if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
28892
30189
  } catch {
28893
30190
  }
@@ -29969,8 +31266,8 @@ var init_frontmatter = __esm({
29969
31266
  });
29970
31267
 
29971
31268
  // src/skills/loader.ts
29972
- import { readFile as readFile25, readdir as readdir6, stat as stat7 } from "fs/promises";
29973
- import { join as join38, extname } from "path";
31269
+ import { readFile as readFile26, readdir as readdir7, stat as stat8 } from "fs/promises";
31270
+ import { join as join42, extname } from "path";
29974
31271
  function normalizeManifest(raw, filePath) {
29975
31272
  const name = typeof raw.name === "string" ? raw.name : "";
29976
31273
  const description = typeof raw.description === "string" ? raw.description : "";
@@ -29984,7 +31281,7 @@ function normalizeManifest(raw, filePath) {
29984
31281
  return { name, description, match, scope, priority, enabled: enabled2 };
29985
31282
  }
29986
31283
  async function loadSkillFile(filePath) {
29987
- const raw = await readFile25(filePath, "utf-8");
31284
+ const raw = await readFile26(filePath, "utf-8");
29988
31285
  const parsed = parseFrontmatter(raw);
29989
31286
  const manifest = normalizeManifest(parsed.data, filePath);
29990
31287
  const body = parsed.content.trim();
@@ -30003,11 +31300,11 @@ async function loadSkillFile(filePath) {
30003
31300
  }
30004
31301
  async function loadSkillsFromDir(dirPath) {
30005
31302
  try {
30006
- const entries = await readdir6(dirPath);
31303
+ const entries = await readdir7(dirPath);
30007
31304
  const files = [];
30008
31305
  for (const entry of entries) {
30009
- const full = join38(dirPath, entry);
30010
- const s = await stat7(full);
31306
+ const full = join42(dirPath, entry);
31307
+ const s = await stat8(full);
30011
31308
  if (s.isFile() && extname(entry) === ".md") {
30012
31309
  files.push(full);
30013
31310
  }
@@ -30034,12 +31331,12 @@ var init_loader = __esm({
30034
31331
  });
30035
31332
 
30036
31333
  // src/skills/manager.ts
30037
- import { mkdir as mkdir15, writeFile as writeFile14, unlink as unlink4, readFile as readFile26 } from "fs/promises";
30038
- import { join as join39 } from "path";
31334
+ import { mkdir as mkdir15, writeFile as writeFile14, unlink as unlink4, readFile as readFile27 } from "fs/promises";
31335
+ import { join as join43 } from "path";
30039
31336
  function getSkillDirs(cwd) {
30040
31337
  return {
30041
- projectDir: join39(cwd, ".kimiflare", "skills"),
30042
- globalDir: join39(process.env.HOME ?? "", ".config", "kimiflare", "skills")
31338
+ projectDir: join43(cwd, ".kimiflare", "skills"),
31339
+ globalDir: join43(process.env.HOME ?? "", ".config", "kimiflare", "skills")
30043
31340
  };
30044
31341
  }
30045
31342
  async function listAllSkills(cwd) {
@@ -30053,7 +31350,7 @@ async function listAllSkills(cwd) {
30053
31350
  async function createSkill(opts2) {
30054
31351
  const dirs = getSkillDirs(opts2.cwd);
30055
31352
  const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
30056
- const filepath = join39(dir, `${opts2.name}.md`);
31353
+ const filepath = join43(dir, `${opts2.name}.md`);
30057
31354
  const frontmatter = {
30058
31355
  name: opts2.name,
30059
31356
  enabled: true,
@@ -30089,7 +31386,7 @@ async function setSkillEnabled(name, enabled2, cwd) {
30089
31386
  const all = await listAllSkills(cwd);
30090
31387
  const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
30091
31388
  if (!skill) throw new Error(`skill "${name}" not found`);
30092
- const raw = await readFile26(skill.filePath, "utf-8");
31389
+ const raw = await readFile27(skill.filePath, "utf-8");
30093
31390
  const parsed = parseFrontmatter(raw);
30094
31391
  parsed.data.enabled = enabled2;
30095
31392
  const yaml = Object.entries(parsed.data).map(([k, v]) => {
@@ -30361,15 +31658,15 @@ var tui_report_exports = {};
30361
31658
  __export(tui_report_exports, {
30362
31659
  getCategoryReportText: () => getCategoryReportText
30363
31660
  });
30364
- import { readFile as readFile27 } from "fs/promises";
30365
- import { join as join40 } from "path";
30366
- import { homedir as homedir26 } from "os";
31661
+ import { readFile as readFile28 } from "fs/promises";
31662
+ import { join as join44 } from "path";
31663
+ import { homedir as homedir27 } from "os";
30367
31664
  function usageDir3() {
30368
- const xdg = process.env.XDG_DATA_HOME || join40(homedir26(), ".local", "share");
30369
- return join40(xdg, "kimiflare");
31665
+ const xdg = process.env.XDG_DATA_HOME || join44(homedir27(), ".local", "share");
31666
+ return join44(xdg, "kimiflare");
30370
31667
  }
30371
31668
  function usagePath4() {
30372
- return join40(usageDir3(), "usage.json");
31669
+ return join44(usageDir3(), "usage.json");
30373
31670
  }
30374
31671
  function today3() {
30375
31672
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -30381,7 +31678,7 @@ function daysAgo2(n) {
30381
31678
  }
30382
31679
  async function loadLog3() {
30383
31680
  try {
30384
- const raw = await readFile27(usagePath4(), "utf8");
31681
+ const raw = await readFile28(usagePath4(), "utf8");
30385
31682
  return JSON.parse(raw);
30386
31683
  } catch {
30387
31684
  return { version: 1, days: [], sessions: [] };
@@ -30438,7 +31735,7 @@ var init_tui_report = __esm({
30438
31735
  });
30439
31736
 
30440
31737
  // src/ui/slash-commands.ts
30441
- import { join as join41 } from "path";
31738
+ import { join as join45 } from "path";
30442
31739
  import QRCode from "qrcode";
30443
31740
  function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
30444
31741
  const oldSessionId = ctx.sessionIdRef.current;
@@ -30529,6 +31826,28 @@ function modesGate(ctx) {
30529
31826
  ]);
30530
31827
  return false;
30531
31828
  }
31829
+ function rateLastTurn(ctx, value, reason) {
31830
+ const { mkKey: mkKey2, setEvents } = ctx;
31831
+ const last = ctx.turnHistoryRef.current.at(-1);
31832
+ if (!last) {
31833
+ setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to rate yet \u2014 finish a turn first" }]);
31834
+ return true;
31835
+ }
31836
+ const trace = ctx.traceRef.current;
31837
+ if (!trace.enabled) {
31838
+ setEvents((e) => [
31839
+ ...e,
31840
+ { kind: "info", key: mkKey2(), text: "session tracing is off, so the rating was not recorded (enable with KIMIFLARE_TRACE=1)" }
31841
+ ]);
31842
+ return true;
31843
+ }
31844
+ trace.emit("outcome", { kind: "rating", turn_id: last.turnId, value, ...reason ? { reason } : {} }, last.turnId);
31845
+ setEvents((e) => [
31846
+ ...e,
31847
+ { kind: "info", key: mkKey2(), text: `recorded ${value === "good" ? "\u{1F44D} good" : "\u{1F44E} bad"} for the last turn${reason ? `: ${reason}` : ""}` }
31848
+ ]);
31849
+ return true;
31850
+ }
30532
31851
  function dispatchSlashCommand(ctx, cmd) {
30533
31852
  const raw = cmd.trim();
30534
31853
  const [head, ...rest] = raw.split(/\s+/);
@@ -30538,7 +31857,7 @@ function dispatchSlashCommand(ctx, cmd) {
30538
31857
  if (!handler) return false;
30539
31858
  return handler(ctx, rest, arg);
30540
31859
  }
30541
- 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;
31860
+ 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;
30542
31861
  var init_slash_commands = __esm({
30543
31862
  "src/ui/slash-commands.ts"() {
30544
31863
  "use strict";
@@ -30561,7 +31880,7 @@ var init_slash_commands = __esm({
30561
31880
  init_executor();
30562
31881
  init_recommended();
30563
31882
  init_settings();
30564
- init_types();
31883
+ init_types2();
30565
31884
  init_update_check();
30566
31885
  init_version();
30567
31886
  init_app_helpers();
@@ -31348,7 +32667,7 @@ ${lines.join("\n")}` }]);
31348
32667
  void (async () => {
31349
32668
  try {
31350
32669
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
31351
- const file = await loadSession(join41(sessionsDir3(), `${currentId}.json`));
32670
+ const file = await loadSession(join45(sessionsDir3(), `${currentId}.json`));
31352
32671
  const cps = file.checkpoints ?? [];
31353
32672
  if (cps.length === 0) {
31354
32673
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
@@ -31393,7 +32712,7 @@ ${lines.join("\n")}` }]);
31393
32712
  try {
31394
32713
  ctx.ensureSessionId();
31395
32714
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
31396
- const filePath = join41(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
32715
+ const filePath = join45(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
31397
32716
  await addCheckpoint(filePath, cp);
31398
32717
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
31399
32718
  } catch (e) {
@@ -31984,6 +33303,8 @@ project: ${projectSettingsPath(cwd)}`
31984
33303
  });
31985
33304
  return true;
31986
33305
  };
33306
+ handleGood = (ctx, rest) => rateLastTurn(ctx, "good", rest.join(" ").trim());
33307
+ handleBad = (ctx, rest) => rateLastTurn(ctx, "bad", rest.join(" ").trim());
31987
33308
  handlers = {
31988
33309
  "/exit": handleExit,
31989
33310
  "/clear": handleClear,
@@ -32007,6 +33328,8 @@ project: ${projectSettingsPath(cwd)}`
32007
33328
  "/resume": handleResume,
32008
33329
  "/checkpoint": handleCheckpoint,
32009
33330
  "/compact": handleCompact,
33331
+ "/good": handleGood,
33332
+ "/bad": handleBad,
32010
33333
  "/init": handleInit,
32011
33334
  "/update": handleUpdate,
32012
33335
  "/mcp": handleMcp,
@@ -32338,7 +33661,7 @@ __export(openrouter_oauth_exports, {
32338
33661
  pkceChallenge: () => pkceChallenge,
32339
33662
  startLoopbackListener: () => startLoopbackListener
32340
33663
  });
32341
- import { createHash as createHash4, randomBytes as randomBytes3 } from "crypto";
33664
+ import { createHash as createHash5, randomBytes as randomBytes3 } from "crypto";
32342
33665
  import { createServer as createServer3 } from "http";
32343
33666
  import { hostname } from "os";
32344
33667
  function createPkce() {
@@ -32346,7 +33669,7 @@ function createPkce() {
32346
33669
  return { verifier, challenge: pkceChallenge(verifier) };
32347
33670
  }
32348
33671
  function pkceChallenge(verifier) {
32349
- return createHash4("sha256").update(verifier).digest("base64url");
33672
+ return createHash5("sha256").update(verifier).digest("base64url");
32350
33673
  }
32351
33674
  function openRouterAuthPageUrl() {
32352
33675
  return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
@@ -32858,14 +34181,14 @@ var init_frontmatter2 = __esm({
32858
34181
 
32859
34182
  // src/commands/loader.ts
32860
34183
  import { open, realpath as realpath6 } from "fs/promises";
32861
- import { homedir as homedir27 } from "os";
32862
- import { join as join42, relative as relative10, sep as sep6 } from "path";
34184
+ import { homedir as homedir28 } from "os";
34185
+ import { join as join46, relative as relative10, sep as sep6 } from "path";
32863
34186
  function projectCommandsDir(cwd = process.cwd()) {
32864
- return join42(cwd, ".kimiflare", "commands");
34187
+ return join46(cwd, ".kimiflare", "commands");
32865
34188
  }
32866
34189
  function globalCommandsDir() {
32867
- const xdg = process.env.XDG_CONFIG_HOME || join42(homedir27(), ".config");
32868
- return join42(xdg, "kimiflare", "commands");
34190
+ const xdg = process.env.XDG_CONFIG_HOME || join46(homedir28(), ".config");
34191
+ return join46(xdg, "kimiflare", "commands");
32869
34192
  }
32870
34193
  async function loadCustomCommands(cwd = process.cwd()) {
32871
34194
  const warnings = [];
@@ -33009,7 +34332,7 @@ var init_loader2 = __esm({
33009
34332
  init_mode();
33010
34333
  init_glob();
33011
34334
  init_config();
33012
- init_paths();
34335
+ init_paths2();
33013
34336
  init_frontmatter2();
33014
34337
  MAX_COMMAND_FILE_BYTES = 256 * 1024;
33015
34338
  }
@@ -33019,7 +34342,7 @@ var init_loader2 = __esm({
33019
34342
  import { exec } from "child_process";
33020
34343
  import { open as open2, realpath as realpath7 } from "fs/promises";
33021
34344
  import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolvePathJoin, basename as pathBasename } from "path";
33022
- import { promisify as promisify5 } from "util";
34345
+ import { promisify as promisify7 } from "util";
33023
34346
  function tokenizeArgs(s) {
33024
34347
  return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
33025
34348
  const token = match[0];
@@ -33171,8 +34494,8 @@ var execAsync, ARG_TOKEN_RE, POSITIONAL_RE, HAS_POSITIONAL, SHELL_RE, FILE_RE, D
33171
34494
  var init_renderer2 = __esm({
33172
34495
  "src/commands/renderer.ts"() {
33173
34496
  "use strict";
33174
- init_paths();
33175
- execAsync = promisify5(exec);
34497
+ init_paths2();
34498
+ execAsync = promisify7(exec);
33176
34499
  ARG_TOKEN_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
33177
34500
  POSITIONAL_RE = /\$(\d+)/g;
33178
34501
  HAS_POSITIONAL = /\$\d+/;
@@ -33247,11 +34570,11 @@ var init_wcag = __esm({
33247
34570
  });
33248
34571
 
33249
34572
  // src/ui/theme-loader.ts
33250
- import { readFile as readFile28, readdir as readdir7 } from "fs/promises";
33251
- import { join as join43 } from "path";
33252
- import { homedir as homedir28 } from "os";
34573
+ import { readFile as readFile29, readdir as readdir8 } from "fs/promises";
34574
+ import { join as join47 } from "path";
34575
+ import { homedir as homedir29 } from "os";
33253
34576
  function projectThemesDir(cwd = process.cwd()) {
33254
- return join43(cwd, ".kimiflare", "themes");
34577
+ return join47(cwd, ".kimiflare", "themes");
33255
34578
  }
33256
34579
  function isHexColor(c) {
33257
34580
  return /^#[0-9a-fA-F]{6}$/.test(c);
@@ -33335,15 +34658,15 @@ async function loadThemesFromDir(dir, source) {
33335
34658
  const errors = [];
33336
34659
  let files;
33337
34660
  try {
33338
- files = await readdir7(dir);
34661
+ files = await readdir8(dir);
33339
34662
  } catch {
33340
34663
  return { themes, errors };
33341
34664
  }
33342
34665
  for (const file of files.filter((f) => f.endsWith(".json"))) {
33343
- const path = join43(dir, file);
34666
+ const path = join47(dir, file);
33344
34667
  let raw;
33345
34668
  try {
33346
- raw = await readFile28(path, "utf-8");
34669
+ raw = await readFile29(path, "utf-8");
33347
34670
  } catch (e) {
33348
34671
  errors.push(`${path}: ${e instanceof Error ? e.message : String(e)}`);
33349
34672
  continue;
@@ -33489,8 +34812,8 @@ var init_theme_loader = __esm({
33489
34812
  "use strict";
33490
34813
  init_wcag();
33491
34814
  init_theme();
33492
- USER_THEMES_DIR = join43(
33493
- process.env.XDG_CONFIG_HOME || join43(homedir28(), ".config"),
34815
+ USER_THEMES_DIR = join47(
34816
+ process.env.XDG_CONFIG_HOME || join47(homedir29(), ".config"),
33494
34817
  "kimiflare",
33495
34818
  "themes"
33496
34819
  );
@@ -35648,6 +36971,61 @@ function useSessionManager(deps) {
35648
36971
  setLogSessionId(sessionIdRef.current);
35649
36972
  return sessionIdRef.current;
35650
36973
  }, []);
36974
+ const traceRef = useRef7(createNoopRecorder());
36975
+ const traceStartedRef = useRef7(false);
36976
+ const turnHistoryRef = useRef7([]);
36977
+ const noteTurnEnded = useCallback6((turnId) => {
36978
+ turnHistoryRef.current.push({ turnId, endedAt: Date.now() });
36979
+ if (turnHistoryRef.current.length > 100) turnHistoryRef.current.shift();
36980
+ }, []);
36981
+ const endTrace = useCallback6((reason) => {
36982
+ const trace = traceRef.current;
36983
+ if (trace.enabled && traceStartedRef.current) trace.emit("session_end", { reason }, null);
36984
+ traceStartedRef.current = false;
36985
+ traceRef.current = createNoopRecorder();
36986
+ }, []);
36987
+ const traceReverted = useCallback6((how, turns) => {
36988
+ const trace = traceRef.current;
36989
+ if (!trace.enabled) return;
36990
+ for (const turn of turns) trace.emit("outcome", { kind: "reverted", turn_id: turn.turnId, how }, turn.turnId);
36991
+ }, []);
36992
+ const ensureTrace = useCallback6(() => {
36993
+ const id = ensureSessionId();
36994
+ const cfgTrace = depsRef.current.cfg?.trace;
36995
+ const wantEnabled = cfgTrace?.enabled === true;
36996
+ const current = traceRef.current;
36997
+ if (current.sessionId === id && current.enabled === wantEnabled) return current;
36998
+ if (current.enabled && traceStartedRef.current) current.emit("session_end", { reason: "exit" }, null);
36999
+ traceStartedRef.current = false;
37000
+ traceRef.current = createRecorder({ enabled: wantEnabled, sessionId: id, root: cfgTrace?.root });
37001
+ return traceRef.current;
37002
+ }, [ensureSessionId]);
37003
+ const ensureTraceStarted = useCallback6(
37004
+ async (info) => {
37005
+ const trace = ensureTrace();
37006
+ if (!trace.enabled || traceStartedRef.current) return trace;
37007
+ traceStartedRef.current = true;
37008
+ await emitSessionStart(trace, {
37009
+ cwd: process.cwd(),
37010
+ model: info.model,
37011
+ mode: info.mode,
37012
+ config: info.config,
37013
+ tools: info.tools,
37014
+ systemPrompt: info.systemPrompt,
37015
+ toolProfile: info.toolProfile
37016
+ });
37017
+ return trace;
37018
+ },
37019
+ [ensureTrace]
37020
+ );
37021
+ React23.useEffect(() => {
37022
+ const onExit = () => endTrace("exit");
37023
+ process.on("exit", onExit);
37024
+ return () => {
37025
+ process.off("exit", onExit);
37026
+ endTrace("exit");
37027
+ };
37028
+ }, [endTrace]);
35651
37029
  const saveSessionSafe = useCallback6(async () => {
35652
37030
  const d = depsRef.current;
35653
37031
  if (!d.cfg) return;
@@ -35686,11 +37064,18 @@ function useSessionManager(deps) {
35686
37064
  setResumeProgress(0);
35687
37065
  setResumeStage("Loading session file\u2026");
35688
37066
  startSmoothProgress(0, 10, 400);
35689
- const file = checkpointId ? (await loadSessionFromCheckpoint(filePath, checkpointId)).file : await loadSession(filePath);
37067
+ const loaded = checkpointId ? await loadSessionFromCheckpoint(filePath, checkpointId) : null;
37068
+ const file = loaded ? loaded.file : await loadSession(filePath);
35690
37069
  setResumeProgress(10);
35691
37070
  setResumeStage("Restoring artifact store\u2026");
35692
37071
  startSmoothProgress(10, 20, 400);
35693
37072
  d.messagesRef.current = file.messages;
37073
+ if (loaded && file.id === sessionIdRef.current) {
37074
+ const since = Date.parse(loaded.checkpoint.timestamp);
37075
+ traceReverted("restore_checkpoint", turnHistoryRef.current.filter((t) => Number.isNaN(since) || t.endedAt > since));
37076
+ }
37077
+ endTrace(checkpointId ? "restore_checkpoint" : "exit");
37078
+ turnHistoryRef.current = [];
35694
37079
  sessionIdRef.current = file.id;
35695
37080
  setLogSessionId(file.id);
35696
37081
  sessionCreatedAtRef.current = file.createdAt;
@@ -35866,6 +37251,9 @@ ${summary}`
35866
37251
  [checkpointSession, doResumeSession]
35867
37252
  );
35868
37253
  const resetSession = useCallback6(() => {
37254
+ traceReverted("clear", turnHistoryRef.current.slice(-2));
37255
+ turnHistoryRef.current = [];
37256
+ endTrace("clear");
35869
37257
  sessionIdRef.current = null;
35870
37258
  setLogSessionId(null);
35871
37259
  sessionCreatedAtRef.current = null;
@@ -35873,7 +37261,7 @@ ${summary}`
35873
37261
  const d = depsRef.current;
35874
37262
  d.sessionStateRef.current = emptySessionState();
35875
37263
  d.artifactStoreRef.current = new ArtifactStore();
35876
- }, []);
37264
+ }, [endTrace, traceReverted]);
35877
37265
  return {
35878
37266
  sessionIdRef,
35879
37267
  sessionCreatedAtRef,
@@ -35895,7 +37283,13 @@ ${summary}`
35895
37283
  doResumeSession,
35896
37284
  handleResumePick,
35897
37285
  handleCheckpointPick,
35898
- resetSession
37286
+ resetSession,
37287
+ traceRef,
37288
+ ensureTrace,
37289
+ ensureTraceStarted,
37290
+ endTrace,
37291
+ turnHistoryRef,
37292
+ noteTurnEnded
35899
37293
  };
35900
37294
  }
35901
37295
  var init_use_session_manager = __esm({
@@ -35907,6 +37301,7 @@ var init_use_session_manager = __esm({
35907
37301
  init_recall_inject();
35908
37302
  init_usage_tracker();
35909
37303
  init_log_sink();
37304
+ init_trace();
35910
37305
  init_app_helpers();
35911
37306
  init_continuation_summary();
35912
37307
  init_config();
@@ -35945,9 +37340,9 @@ var init_resume_progress = __esm({
35945
37340
  });
35946
37341
 
35947
37342
  // src/agent/supervisor.ts
35948
- import { readdir as readdir8, readFile as readFile29, stat as stat8 } from "fs/promises";
35949
- import { createHash as createHash5 } from "crypto";
35950
- import { resolve as resolve13, join as join44 } from "path";
37343
+ import { readdir as readdir9, readFile as readFile30, stat as stat9 } from "fs/promises";
37344
+ import { createHash as createHash6 } from "crypto";
37345
+ import { resolve as resolve13, join as join48 } from "path";
35951
37346
  var TurnSupervisor;
35952
37347
  var init_supervisor = __esm({
35953
37348
  "src/agent/supervisor.ts"() {
@@ -36334,13 +37729,13 @@ var init_input_handlers = __esm({
36334
37729
  });
36335
37730
 
36336
37731
  // src/init/context-generator.ts
36337
- import { existsSync as existsSync6, statSync as statSync6 } from "fs";
36338
- import { join as join45 } from "path";
37732
+ import { existsSync as existsSync9, statSync as statSync6 } from "fs";
37733
+ import { join as join49 } from "path";
36339
37734
  function detectFlavor(cwd) {
36340
37735
  for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
36341
37736
  if (flavor === "generic") continue;
36342
37737
  for (const sig of signatures) {
36343
- const path = join45(cwd, sig);
37738
+ const path = join49(cwd, sig);
36344
37739
  if (sig.includes("*")) {
36345
37740
  try {
36346
37741
  const parts = sig.split("*");
@@ -36352,7 +37747,7 @@ function detectFlavor(cwd) {
36352
37747
  }
36353
37748
  } catch {
36354
37749
  }
36355
- } else if (existsSync6(path)) {
37750
+ } else if (existsSync9(path)) {
36356
37751
  return flavor;
36357
37752
  }
36358
37753
  }
@@ -36361,14 +37756,14 @@ function detectFlavor(cwd) {
36361
37756
  }
36362
37757
  function findFile(cwd, candidates) {
36363
37758
  for (const c of candidates) {
36364
- if (existsSync6(join45(cwd, c))) return c;
37759
+ if (existsSync9(join49(cwd, c))) return c;
36365
37760
  }
36366
37761
  return null;
36367
37762
  }
36368
37763
  function findSourceRoots(cwd) {
36369
37764
  const roots = [];
36370
37765
  for (const r of SOURCE_ROOT_CANDIDATES) {
36371
- const p = join45(cwd, r);
37766
+ const p = join49(cwd, r);
36372
37767
  try {
36373
37768
  const s = statSync6(p);
36374
37769
  if (s.isDirectory()) roots.push(r);
@@ -36379,9 +37774,9 @@ function findSourceRoots(cwd) {
36379
37774
  }
36380
37775
  function findCiConfig(cwd) {
36381
37776
  for (const c of CI_PATHS) {
36382
- if (existsSync6(join45(cwd, c))) {
37777
+ if (existsSync9(join49(cwd, c))) {
36383
37778
  try {
36384
- const s = statSync6(join45(cwd, c));
37779
+ const s = statSync6(join49(cwd, c));
36385
37780
  return s.isDirectory() ? c : c;
36386
37781
  } catch {
36387
37782
  }
@@ -36518,7 +37913,7 @@ function analyzeProject(cwd) {
36518
37913
  ciConfig: findCiConfig(cwd),
36519
37914
  readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
36520
37915
  sourceRoots: findSourceRoots(cwd),
36521
- hasGit: existsSync6(join45(cwd, ".git"))
37916
+ hasGit: existsSync9(join49(cwd, ".git"))
36522
37917
  };
36523
37918
  }
36524
37919
  function bashDiscoveryCommands(profile) {
@@ -36683,7 +38078,7 @@ Aim for 100\u2013200 lines total. Use markdown tables where they save space.
36683
38078
  }
36684
38079
  function buildInitPrompt(cwd) {
36685
38080
  const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
36686
- (n) => existsSync6(join45(cwd, n))
38081
+ (n) => existsSync9(join49(cwd, n))
36687
38082
  );
36688
38083
  const isRefresh = existingName !== void 0;
36689
38084
  const targetFilename = existingName ?? "AGENTS.md";
@@ -36766,8 +38161,8 @@ var init_context_generator = __esm({
36766
38161
  });
36767
38162
 
36768
38163
  // src/init/run-init.ts
36769
- import { existsSync as existsSync7 } from "fs";
36770
- import { join as join46 } from "path";
38164
+ import { existsSync as existsSync10 } from "fs";
38165
+ import { join as join50 } from "path";
36771
38166
  async function runInit(deps) {
36772
38167
  const {
36773
38168
  cfg,
@@ -36860,7 +38255,7 @@ async function runInit(deps) {
36860
38255
  lspManagerRef.current.notifyChange(path, content);
36861
38256
  } else {
36862
38257
  void import("fs/promises").then(
36863
- ({ readFile: readFile31 }) => readFile31(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
38258
+ ({ readFile: readFile32 }) => readFile32(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
36864
38259
  })
36865
38260
  );
36866
38261
  }
@@ -36965,7 +38360,7 @@ async function runInit(deps) {
36965
38360
  }
36966
38361
  }
36967
38362
  });
36968
- if (existsSync7(join46(cwd, targetFilename))) {
38363
+ if (existsSync10(join50(cwd, targetFilename))) {
36969
38364
  if (cacheStableRef.current) {
36970
38365
  messagesRef.current[1] = {
36971
38366
  role: "system",
@@ -37057,11 +38452,11 @@ var init_run_init = __esm({
37057
38452
  });
37058
38453
 
37059
38454
  // src/skills/discovery.ts
37060
- import { readdir as readdir9, stat as stat9, readFile as readFile30 } from "fs/promises";
37061
- import { join as join47, extname as extname2 } from "path";
38455
+ import { readdir as readdir10, stat as stat10, readFile as readFile31 } from "fs/promises";
38456
+ import { join as join51, extname as extname2 } from "path";
37062
38457
  async function dirExists(path) {
37063
38458
  try {
37064
- const s = await stat9(path);
38459
+ const s = await stat10(path);
37065
38460
  return s.isDirectory();
37066
38461
  } catch {
37067
38462
  return false;
@@ -37069,7 +38464,7 @@ async function dirExists(path) {
37069
38464
  }
37070
38465
  async function fileExists(path) {
37071
38466
  try {
37072
- const s = await stat9(path);
38467
+ const s = await stat10(path);
37073
38468
  return s.isFile();
37074
38469
  } catch {
37075
38470
  return false;
@@ -37077,25 +38472,25 @@ async function fileExists(path) {
37077
38472
  }
37078
38473
  async function scanSkillDir(dirPath, source) {
37079
38474
  if (!await dirExists(dirPath)) return [];
37080
- const entries = await readdir9(dirPath, { withFileTypes: true });
38475
+ const entries = await readdir10(dirPath, { withFileTypes: true });
37081
38476
  const files = [];
37082
38477
  for (const entry of entries) {
37083
38478
  if (!entry.isFile()) continue;
37084
38479
  if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
37085
- files.push({ filePath: join47(dirPath, entry.name), source });
38480
+ files.push({ filePath: join51(dirPath, entry.name), source });
37086
38481
  }
37087
38482
  return files;
37088
38483
  }
37089
38484
  async function discoverSkills(cwd) {
37090
- const agentsSkills = await scanSkillDir(join47(cwd, ".agents", "skills"), "agents");
37091
- const agentsMd = await fileExists(join47(cwd, "AGENTS.md")) ? [{ filePath: join47(cwd, "AGENTS.md"), source: "agents-md" }] : [];
37092
- const githubSkills = await scanSkillDir(join47(cwd, ".github", "skills"), "github");
37093
- const kimiflareSkills = await scanSkillDir(join47(cwd, ".kimiflare", "skills"), "kimiflare");
38485
+ const agentsSkills = await scanSkillDir(join51(cwd, ".agents", "skills"), "agents");
38486
+ const agentsMd = await fileExists(join51(cwd, "AGENTS.md")) ? [{ filePath: join51(cwd, "AGENTS.md"), source: "agents-md" }] : [];
38487
+ const githubSkills = await scanSkillDir(join51(cwd, ".github", "skills"), "github");
38488
+ const kimiflareSkills = await scanSkillDir(join51(cwd, ".kimiflare", "skills"), "kimiflare");
37094
38489
  const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
37095
38490
  return ordered;
37096
38491
  }
37097
38492
  async function readSkillFile(filePath) {
37098
- const bytes = await readFile30(filePath);
38493
+ const bytes = await readFile31(filePath);
37099
38494
  return { bytes, text: bytes.toString("utf-8") };
37100
38495
  }
37101
38496
  var SKILL_EXTENSIONS;
@@ -37107,9 +38502,9 @@ var init_discovery = __esm({
37107
38502
  });
37108
38503
 
37109
38504
  // src/skills/parser.ts
37110
- import { createHash as createHash6 } from "crypto";
38505
+ import { createHash as createHash7 } from "crypto";
37111
38506
  function sha2562(input) {
37112
- return createHash6("sha256").update(input).digest("hex");
38507
+ return createHash7("sha256").update(input).digest("hex");
37113
38508
  }
37114
38509
  function computeContentHash(rawText, parserVersion) {
37115
38510
  return sha2562(`${rawText}
@@ -37300,8 +38695,8 @@ var init_skills = __esm({
37300
38695
  });
37301
38696
 
37302
38697
  // src/ui/run-startup-tasks.ts
37303
- import { existsSync as existsSync8 } from "fs";
37304
- import { join as join48 } from "path";
38698
+ import { existsSync as existsSync11 } from "fs";
38699
+ import { join as join52 } from "path";
37305
38700
  function runStartupTasks(deps) {
37306
38701
  const {
37307
38702
  cfg,
@@ -37315,6 +38710,7 @@ function runStartupTasks(deps) {
37315
38710
  } = deps;
37316
38711
  const auth = llmAuthFromConfig(cfg);
37317
38712
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(({ pruneSessions: pruneSessions2 }) => pruneSessions2());
38713
+ void Promise.resolve().then(() => (init_retention(), retention_exports)).then(({ pruneTraces: pruneTraces2 }) => pruneTraces2({ root: cfg.trace?.root }));
37318
38714
  void Promise.resolve().then(() => (init_log_sink(), log_sink_exports)).then(({ pruneOldLogs: pruneOldLogs2, isLogSinkEnabled: isLogSinkEnabled2 }) => {
37319
38715
  if (!isLogSinkEnabled2()) return;
37320
38716
  try {
@@ -37323,7 +38719,7 @@ function runStartupTasks(deps) {
37323
38719
  }
37324
38720
  });
37325
38721
  if (cfg.memoryEnabled) {
37326
- const dbPath = cfg.memoryDbPath ?? join48(process.cwd(), ".kimiflare", "memory.db");
38722
+ const dbPath = cfg.memoryDbPath ?? join52(process.cwd(), ".kimiflare", "memory.db");
37327
38723
  const manager = new MemoryManager({
37328
38724
  dbPath,
37329
38725
  ...auth,
@@ -37355,7 +38751,7 @@ function runStartupTasks(deps) {
37355
38751
  });
37356
38752
  const cwd = process.cwd();
37357
38753
  sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
37358
- if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(join48(cwd, name)))) {
38754
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync11(join52(cwd, name)))) {
37359
38755
  const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
37360
38756
  const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
37361
38757
  if (driftCount >= 5) {
@@ -37366,7 +38762,7 @@ function runStartupTasks(deps) {
37366
38762
  memoryManagerRef.current?.close();
37367
38763
  memoryManagerRef.current = null;
37368
38764
  }
37369
- const skillDbPath = cfg.memoryDbPath ?? join48(process.cwd(), ".kimiflare", "memory.db");
38765
+ const skillDbPath = cfg.memoryDbPath ?? join52(process.cwd(), ".kimiflare", "memory.db");
37370
38766
  const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
37371
38767
  initSkillsSchema(skillDb);
37372
38768
  void indexSkills({
@@ -37611,7 +39007,8 @@ async function runCompact(deps) {
37611
39007
  pendingToolCallsRef,
37612
39008
  hooks,
37613
39009
  sessionId,
37614
- supervisorRef
39010
+ supervisorRef,
39011
+ trace
37615
39012
  } = deps;
37616
39013
  if (busy || supervisorRef.current.isRunning) {
37617
39014
  setEvents((e) => [
@@ -37647,6 +39044,7 @@ async function runCompact(deps) {
37647
39044
  if (result2.metrics.rawTurnsRemoved === 0) {
37648
39045
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to compact yet" }]);
37649
39046
  } else {
39047
+ traceCompiledCompaction(trace, result2);
37650
39048
  messagesRef.current = result2.newMessages;
37651
39049
  sessionStateRef.current = result2.newState;
37652
39050
  setEvents(
@@ -37665,6 +39063,7 @@ async function runCompact(deps) {
37665
39063
  await saveSessionSafe();
37666
39064
  }
37667
39065
  } else {
39066
+ const before = messagesRef.current;
37668
39067
  const result2 = await summarizeMessagesViaLlm({
37669
39068
  ...llmAuthFromConfig(cfg),
37670
39069
  model: cfg.model,
@@ -37674,6 +39073,7 @@ async function runCompact(deps) {
37674
39073
  if (result2.replacedCount === 0) {
37675
39074
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to compact yet" }]);
37676
39075
  } else {
39076
+ traceLlmCompaction(trace, before, result2);
37677
39077
  messagesRef.current = result2.newMessages;
37678
39078
  setEvents(
37679
39079
  (e) => compactEventsVisual(
@@ -37714,12 +39114,13 @@ var init_run_compact = __esm({
37714
39114
  init_logger();
37715
39115
  init_app_helpers();
37716
39116
  init_llm_auth();
39117
+ init_compaction();
37717
39118
  }
37718
39119
  });
37719
39120
 
37720
39121
  // src/commands/save.ts
37721
39122
  import { mkdir as mkdir16, writeFile as writeFile15, unlink as unlink5 } from "fs/promises";
37722
- import { dirname as dirname20 } from "path";
39123
+ import { dirname as dirname21 } from "path";
37723
39124
  async function saveCustomCommand(opts2) {
37724
39125
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
37725
39126
  const filepath = `${dir}/${opts2.name}.md`;
@@ -37730,7 +39131,7 @@ async function saveCustomCommand(opts2) {
37730
39131
  if (opts2.effort) data.effort = opts2.effort;
37731
39132
  const frontmatter = serializeFrontmatter(data);
37732
39133
  const content = frontmatter + opts2.template;
37733
- await mkdir16(dirname20(filepath), { recursive: true });
39134
+ await mkdir16(dirname21(filepath), { recursive: true });
37734
39135
  await writeFile15(filepath, content, "utf8");
37735
39136
  return { filepath };
37736
39137
  }
@@ -37853,9 +39254,9 @@ __export(app_exports, {
37853
39254
  });
37854
39255
  import React25, { useState as useState27, useRef as useRef9, useEffect as useEffect11, useCallback as useCallback8, useMemo as useMemo6 } from "react";
37855
39256
  import { Box as Box40, Text as Text41, useApp, useInput as useInput19, useWindowSize as useWindowSize2, render } from "ink";
37856
- import { existsSync as existsSync9 } from "fs";
37857
- import { join as join49, resolve as resolve14 } from "path";
37858
- import { homedir as homedir29 } from "os";
39257
+ import { existsSync as existsSync12 } from "fs";
39258
+ import { join as join53, resolve as resolve14 } from "path";
39259
+ import { homedir as homedir30 } from "os";
37859
39260
  import { jsx as jsx42, jsxs as jsxs39 } from "react/jsx-runtime";
37860
39261
  function App({
37861
39262
  initialCfg,
@@ -38112,6 +39513,8 @@ ${wcagWarnings.join("\n")}` }
38112
39513
  }, []);
38113
39514
  const sessionStartRecallRef = useRef9(null);
38114
39515
  const kimiMdStaleNudgedRef = useRef9(false);
39516
+ const currentTurnIdRef = useRef9(null);
39517
+ const denyFeedbackRef = useRef9(false);
38115
39518
  const sessionPlanRef = useRef9(null);
38116
39519
  const planCompletePlanRef = useRef9(null);
38117
39520
  const sessionMgr = useSessionManager({
@@ -38149,7 +39552,11 @@ ${wcagWarnings.join("\n")}` }
38149
39552
  doResumeSession,
38150
39553
  handleResumePick,
38151
39554
  handleCheckpointPick,
38152
- resetSession
39555
+ resetSession,
39556
+ traceRef,
39557
+ ensureTraceStarted,
39558
+ turnHistoryRef,
39559
+ noteTurnEnded
38153
39560
  } = sessionMgr;
38154
39561
  const pendingTextRef = useRef9(/* @__PURE__ */ new Map());
38155
39562
  const flushTimeoutRef = useRef9(null);
@@ -38175,7 +39582,7 @@ ${wcagWarnings.join("\n")}` }
38175
39582
  const exactDirectory = query === "~" || query === "." || query === ".." || query.endsWith("/..");
38176
39583
  const prefix = exactDirectory ? `${query.replace(/\/$/, "")}/` : slash >= 0 ? query.slice(0, slash + 1) : "";
38177
39584
  const directory = exactDirectory ? query : slash >= 0 ? query.slice(0, slash + 1) : query;
38178
- const expanded = directory.startsWith("~") ? join49(homedir29(), directory.slice(1)) : directory;
39585
+ const expanded = directory.startsWith("~") ? join53(homedir30(), directory.slice(1)) : directory;
38179
39586
  const target = resolve14(cwd, expanded || ".");
38180
39587
  const entries2 = await glob("*", {
38181
39588
  cwd: target,
@@ -38436,6 +39843,7 @@ ${wcagWarnings.join("\n")}` }
38436
39843
  store
38437
39844
  });
38438
39845
  if (result2.metrics.rawTurnsRemoved > 0) {
39846
+ traceCompiledCompaction(traceRef.current, result2, currentTurnIdRef.current);
38439
39847
  sessionStateRef.current = result2.newState;
38440
39848
  setEvents((e) => [
38441
39849
  ...e,
@@ -38481,6 +39889,7 @@ ${wcagWarnings.join("\n")}` }
38481
39889
  signal
38482
39890
  });
38483
39891
  if (result2.replacedCount > 0) {
39892
+ traceLlmCompaction(traceRef.current, messages, result2, currentTurnIdRef.current);
38484
39893
  setEvents((e) => [
38485
39894
  ...e,
38486
39895
  {
@@ -38654,7 +40063,8 @@ ${wcagWarnings.join("\n")}` }
38654
40063
  pendingToolCallsRef,
38655
40064
  hooks: hooksManagerRef.current,
38656
40065
  sessionId: sessionIdRef.current,
38657
- supervisorRef
40066
+ supervisorRef,
40067
+ trace: traceRef.current
38658
40068
  });
38659
40069
  }, [cfg, busy, saveSessionSafe]);
38660
40070
  const runInit2 = useCallback8(async () => {
@@ -38834,7 +40244,9 @@ ${wcagWarnings.join("\n")}` }
38834
40244
  compiledContextRef,
38835
40245
  lastApiErrorRef,
38836
40246
  activeScopeRef,
38837
- sessionPlanRef
40247
+ sessionPlanRef,
40248
+ traceRef,
40249
+ turnHistoryRef
38838
40250
  }), [
38839
40251
  exit,
38840
40252
  busy,
@@ -39137,6 +40549,8 @@ ${result2.output ?? "(output not captured)"}`,
39137
40549
  }
39138
40550
  }
39139
40551
  messagesRef.current.push({ role: "user", content });
40552
+ const traceUserSource = denyFeedbackRef.current ? "deny_feedback" : "typed";
40553
+ denyFeedbackRef.current = false;
39140
40554
  await saveSessionSafe();
39141
40555
  if (compiledContextRef.current) {
39142
40556
  const { ids, recalled } = recallArtifacts(messagesRef.current, artifactStoreRef.current, sessionStateRef.current);
@@ -39150,7 +40564,7 @@ ${result2.output ?? "(output not captured)"}`,
39150
40564
  }
39151
40565
  }
39152
40566
  turnCounterRef.current += 1;
39153
- if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(join49(process.cwd(), name))) && !kimiMdStale) {
40567
+ if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync12(join53(process.cwd(), name))) && !kimiMdStale) {
39154
40568
  setEvents((e) => [
39155
40569
  ...e,
39156
40570
  { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps autopilot (formerly kimiflare) stay accurate as your project evolves." }
@@ -39174,7 +40588,7 @@ ${result2.output ?? "(output not captured)"}`,
39174
40588
  };
39175
40589
  ensureSessionId();
39176
40590
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39177
- const filePath = join49(sessionsDir3(), `${sessionIdRef.current}.json`);
40591
+ const filePath = join53(sessionsDir3(), `${sessionIdRef.current}.json`);
39178
40592
  await addCheckpoint(filePath, cp);
39179
40593
  }
39180
40594
  const summary = await generateContinuationSummary({
@@ -39423,6 +40837,34 @@ ${result2.output ?? "(output not captured)"}`,
39423
40837
  offered: turnTools.some((tool) => tool.name === "subagent"),
39424
40838
  guidance: delegationGuidance.kind
39425
40839
  });
40840
+ const turnId = makeTurnId();
40841
+ currentTurnIdRef.current = turnId;
40842
+ const trace = await ensureTraceStarted({
40843
+ tools: toOpenAIToolDefs(turnTools),
40844
+ systemPrompt: messagesRef.current.filter((m) => m.role === "system" && typeof m.content === "string").map((m) => m.content).join("\n\n"),
40845
+ model: overrideModel ?? cfg.model,
40846
+ mode: "tui",
40847
+ config: cfg
40848
+ });
40849
+ if (trace.enabled) {
40850
+ const previousTurn = turnHistoryRef.current.at(-1);
40851
+ if (previousTurn) {
40852
+ trace.emit(
40853
+ "outcome",
40854
+ { kind: "next_message", turn_id: previousTurn.turnId, classification: classifyNextMessage(trimmed), classifier: "regex" },
40855
+ previousTurn.turnId
40856
+ );
40857
+ }
40858
+ trace.emit(
40859
+ "user_message",
40860
+ {
40861
+ content_blob: trace.blob(typeof content === "string" ? content : trimmed) ?? "",
40862
+ source: traceUserSource,
40863
+ attachments: Array.isArray(content) ? content.filter((p) => p.type !== "text").length : 0
40864
+ },
40865
+ turnId
40866
+ );
40867
+ }
39426
40868
  supervisorRef.current.startTurn(
39427
40869
  {
39428
40870
  ...llmAuthFromConfig(cfg),
@@ -39468,12 +40910,14 @@ ${result2.output ?? "(output not captured)"}`,
39468
40910
  },
39469
40911
  mode: modeRef.current,
39470
40912
  cacheStable: cacheStableRef.current,
40913
+ trace,
40914
+ turnId,
39471
40915
  onFileChange: (path, content2) => {
39472
40916
  if (content2) {
39473
40917
  lspManagerRef.current.notifyChange(path, content2);
39474
40918
  } else {
39475
40919
  void import("fs/promises").then(
39476
- ({ readFile: readFile31 }) => readFile31(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
40920
+ ({ readFile: readFile32 }) => readFile32(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
39477
40921
  })
39478
40922
  );
39479
40923
  }
@@ -39482,6 +40926,7 @@ ${result2.output ?? "(output not captured)"}`,
39482
40926
  },
39483
40927
  {
39484
40928
  onDone: () => {
40929
+ noteTurnEnded(turnId);
39485
40930
  void (async () => {
39486
40931
  await saveSessionSafe();
39487
40932
  if (turnScope.signal.aborted) {
@@ -39509,6 +40954,7 @@ ${result2.output ?? "(output not captured)"}`,
39509
40954
  store
39510
40955
  });
39511
40956
  if (result2.metrics.rawTurnsRemoved > 0) {
40957
+ traceCompiledCompaction(traceRef.current, result2, turnId);
39512
40958
  messagesRef.current = result2.newMessages;
39513
40959
  sessionStateRef.current = result2.newState;
39514
40960
  setEvents((e) => [
@@ -39524,6 +40970,7 @@ ${result2.output ?? "(output not captured)"}`,
39524
40970
  }
39525
40971
  } else {
39526
40972
  try {
40973
+ const before = messagesRef.current;
39527
40974
  const result2 = await summarizeMessagesViaLlm({
39528
40975
  ...llmAuthFromConfig(cfg),
39529
40976
  model: cfg.model,
@@ -39531,6 +40978,7 @@ ${result2.output ?? "(output not captured)"}`,
39531
40978
  signal: turnScope.signal
39532
40979
  });
39533
40980
  if (result2.replacedCount > 0) {
40981
+ traceLlmCompaction(traceRef.current, before, result2, turnId);
39534
40982
  messagesRef.current = result2.newMessages;
39535
40983
  setEvents((e) => [
39536
40984
  ...e,
@@ -39596,6 +41044,7 @@ ${result2.output ?? "(output not captured)"}`,
39596
41044
  keepLastTurns: 1
39597
41045
  });
39598
41046
  if (result2.metrics.rawTurnsRemoved > 0) {
41047
+ traceCompiledCompaction(traceRef.current, result2, turnId);
39599
41048
  messagesRef.current = result2.newMessages;
39600
41049
  sessionStateRef.current = result2.newState;
39601
41050
  setEvents((e) => [
@@ -39611,6 +41060,7 @@ ${result2.output ?? "(output not captured)"}`,
39611
41060
  }
39612
41061
  } else {
39613
41062
  try {
41063
+ const before = messagesRef.current;
39614
41064
  const result2 = await summarizeMessagesViaLlm({
39615
41065
  ...llmAuthFromConfig(cfg),
39616
41066
  model: cfg.model,
@@ -39619,6 +41069,7 @@ ${result2.output ?? "(output not captured)"}`,
39619
41069
  signal: turnScope.signal
39620
41070
  });
39621
41071
  if (result2.replacedCount > 0) {
41072
+ traceLlmCompaction(traceRef.current, before, result2, turnId);
39622
41073
  messagesRef.current = result2.newMessages;
39623
41074
  setEvents((e) => [
39624
41075
  ...e,
@@ -39659,7 +41110,7 @@ ${result2.output ?? "(output not captured)"}`,
39659
41110
  };
39660
41111
  ensureSessionId();
39661
41112
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39662
- const filePath = join49(sessionsDir3(), `${sessionIdRef.current}.json`);
41113
+ const filePath = join53(sessionsDir3(), `${sessionIdRef.current}.json`);
39663
41114
  await addCheckpoint(filePath, cp);
39664
41115
  }
39665
41116
  const summary = await generateContinuationSummary({
@@ -39710,6 +41161,7 @@ ${result2.output ?? "(output not captured)"}`,
39710
41161
  }
39711
41162
  },
39712
41163
  onError: async (e) => {
41164
+ noteTurnEnded(turnId);
39713
41165
  if (e.name === "AbortError") {
39714
41166
  for (const [tcId, tcName] of pendingToolCallsRef.current) {
39715
41167
  messagesRef.current.push({
@@ -39741,7 +41193,7 @@ ${result2.output ?? "(output not captured)"}`,
39741
41193
  }
39742
41194
  );
39743
41195
  },
39744
- [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand]
41196
+ [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand, ensureTraceStarted]
39745
41197
  );
39746
41198
  useEffect11(() => {
39747
41199
  if (!busy && queuePlanDraft === null && supervisorRef.current.phase === "idle") {
@@ -40242,7 +41694,7 @@ ${result2.output ?? "(output not captured)"}`,
40242
41694
  lsp: {
40243
41695
  servers: cfg?.lspServers ?? {},
40244
41696
  scope: lspScope,
40245
- hasProjectDir: existsSync9(join49(process.cwd(), ".kimiflare"))
41697
+ hasProjectDir: existsSync12(join53(process.cwd(), ".kimiflare"))
40246
41698
  }
40247
41699
  });
40248
41700
  });
@@ -40299,7 +41751,7 @@ ${result2.output ?? "(output not captured)"}`,
40299
41751
  onCommandDelete: handleCommandDelete2,
40300
41752
  lspServers: cfg?.lspServers ?? {},
40301
41753
  lspScope,
40302
- hasProjectDir: existsSync9(join49(process.cwd(), ".kimiflare")),
41754
+ hasProjectDir: existsSync12(join53(process.cwd(), ".kimiflare")),
40303
41755
  onLspSave: handleLspSave2,
40304
41756
  themes: themeList(),
40305
41757
  onPickTheme: handleThemePick,
@@ -40370,6 +41822,7 @@ ${result2.output ?? "(output not captured)"}`,
40370
41822
  args: perm.args,
40371
41823
  onDecide: decidePermission2,
40372
41824
  onFeedback: (text) => {
41825
+ denyFeedbackRef.current = true;
40373
41826
  submitRef.current(text);
40374
41827
  }
40375
41828
  }
@@ -40635,6 +42088,8 @@ var init_app = __esm({
40635
42088
  init_run_startup_tasks();
40636
42089
  init_manager_init();
40637
42090
  init_run_compact();
42091
+ init_trace();
42092
+ init_registry2();
40638
42093
  init_distill();
40639
42094
  init_plan_resolver();
40640
42095
  init_user_shell();
@@ -40780,6 +42235,8 @@ init_glob();
40780
42235
  init_permissions_evaluator();
40781
42236
  init_classify();
40782
42237
  init_subagent_policy();
42238
+ init_trace();
42239
+ init_registry2();
40783
42240
  import { readFile as readFile15 } from "fs/promises";
40784
42241
  import { resolve as resolve6, basename as basename3 } from "path";
40785
42242
  async function resolveSession(opts2) {
@@ -40819,8 +42276,8 @@ async function resolveFiles(filePatterns, cwd) {
40819
42276
  const resolved = /* @__PURE__ */ new Set();
40820
42277
  for (const pattern of filePatterns) {
40821
42278
  try {
40822
- const stat10 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
40823
- if (stat10.isFile()) {
42279
+ const stat11 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
42280
+ if (stat11.isFile()) {
40824
42281
  resolved.add(resolve6(cwd, pattern));
40825
42282
  continue;
40826
42283
  }
@@ -40914,24 +42371,43 @@ async function runPrintMode(opts2) {
40914
42371
  });
40915
42372
  }
40916
42373
  const messages = [];
42374
+ const systemPrompt = buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests });
40917
42375
  if (isNew || sessionFile.messages.length === 0) {
40918
- messages.push({
40919
- role: "system",
40920
- content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
40921
- });
42376
+ messages.push({ role: "system", content: systemPrompt });
40922
42377
  } else {
40923
42378
  const nonSystem = sessionFile.messages.filter(
40924
42379
  (m) => m.role !== "system" || typeof m.content === "string" && m.content.startsWith("[compiled session state]")
40925
42380
  );
40926
- messages.push({
40927
- role: "system",
40928
- content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
40929
- });
42381
+ messages.push({ role: "system", content: systemPrompt });
40930
42382
  messages.push(...nonSystem);
40931
42383
  }
42384
+ const trace = createRecorder({
42385
+ enabled: opts2.trace?.enabled === true,
42386
+ sessionId: sessionFile.id,
42387
+ root: opts2.trace?.root
42388
+ });
42389
+ await emitSessionStart(trace, {
42390
+ cwd,
42391
+ model: opts2.model,
42392
+ mode: "print",
42393
+ config: opts2.traceConfigSnapshot,
42394
+ tools: toOpenAIToolDefs(workerTools),
42395
+ systemPrompt,
42396
+ toolProfile: opts2.workerProfile ?? "full"
42397
+ });
42398
+ let traceCostUsd = 0;
42399
+ let traceEndReason = "exit";
40932
42400
  const files = opts2.files ? await resolveFiles(opts2.files, cwd) : [];
40933
42401
  const { content: userContent } = await buildUserMessage(opts2.prompt, files, cwd);
40934
42402
  messages.push({ role: "user", content: userContent });
42403
+ const turnId = makeTurnId();
42404
+ if (trace.enabled) {
42405
+ trace.emit(
42406
+ "user_message",
42407
+ { content_blob: trace.blob(opts2.prompt) ?? "", source: "print", attachments: files.length },
42408
+ turnId
42409
+ );
42410
+ }
40935
42411
  const controller = new AbortController();
40936
42412
  process.on("SIGINT", () => controller.abort());
40937
42413
  const format = opts2.format ?? "text";
@@ -40955,6 +42431,7 @@ async function runPrintMode(opts2) {
40955
42431
  onUsageFinal: (u, meta) => {
40956
42432
  const lookup = opts2.openrouterApiKey && !auth.customEndpoint && meta?.generationId ? { apiKey: opts2.openrouterApiKey, meta } : void 0;
40957
42433
  usageWrites.push(recordUsage(sessionFile.id, u, lookup, opts2.model).catch(() => void 0));
42434
+ if (typeof u.cost === "number") traceCostUsd += u.cost;
40958
42435
  },
40959
42436
  onReasoningDelta: opts2.showReasoning ? (delta) => {
40960
42437
  if (format === "text") {
@@ -41096,7 +42573,9 @@ async function runPrintMode(opts2) {
41096
42573
  // --continue sessions grow across runs; archive older turns onto the
41097
42574
  // session file (saved below) instead of failing on context size.
41098
42575
  compaction: sessionFileCompactionTarget(sessionFile),
41099
- callbacks
42576
+ callbacks,
42577
+ trace,
42578
+ turnId
41100
42579
  });
41101
42580
  } catch (err) {
41102
42581
  if (err instanceof BudgetExhaustedError) {
@@ -41126,9 +42605,12 @@ async function runPrintMode(opts2) {
41126
42605
  process.exitCode = 1;
41127
42606
  return;
41128
42607
  }
42608
+ traceEndReason = "crash";
41129
42609
  throw err;
41130
42610
  } finally {
41131
42611
  await Promise.all(usageWrites);
42612
+ trace.emit("session_end", { reason: traceEndReason, total_cost_usd: traceCostUsd || void 0 }, null);
42613
+ await trace.flush();
41132
42614
  }
41133
42615
  sessionFile.messages = messages;
41134
42616
  sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -41442,7 +42924,9 @@ To use Requesty instead, set REQUESTY_API_KEY or run autopilot auth requesty`
41442
42924
  dir: opts.dir,
41443
42925
  title: opts.title,
41444
42926
  permissions: cfg.permissions,
41445
- workerProfile: opts.workerProfile
42927
+ workerProfile: opts.workerProfile,
42928
+ trace: cfg.trace,
42929
+ traceConfigSnapshot: cfg
41446
42930
  });
41447
42931
  return;
41448
42932
  }