autopilot-ai 1.19.3 → 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,10 +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";
17907
+ import { execFile as execFile7 } from "child_process";
16751
17908
  import { readFile as readFile21 } from "fs/promises";
16752
- import { promisify as promisify4 } from "util";
16753
- import { randomUUID as randomUUID8 } from "crypto";
17909
+ import { promisify as promisify6 } from "util";
17910
+ import { randomUUID as randomUUID9 } from "crypto";
16754
17911
  async function writeCellFileChunked(cell, path, content) {
16755
17912
  if (Buffer.byteLength(content, "utf8") <= CELL_FILE_WRITE_CHUNK_CHARS) {
16756
17913
  await cell.writeFile(path, content, { mode: "0600" });
@@ -16785,7 +17942,7 @@ async function readBundledCellSdkBundle() {
16785
17942
  throw new Error("cell_runtime_bundle_unavailable");
16786
17943
  }
16787
17944
  async function defaultArchiveWorkspace(workspaceRoot) {
16788
- const { stdout } = await execFileAsync3("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
17945
+ const { stdout } = await execFileAsync5("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
16789
17946
  encoding: "buffer",
16790
17947
  maxBuffer: 512 * 1024 * 1024
16791
17948
  });
@@ -16833,13 +17990,13 @@ ${redacted.slice(-half)}`;
16833
17990
  function sleep4(ms) {
16834
17991
  return new Promise((resolve15) => setTimeout(resolve15, ms));
16835
17992
  }
16836
- var execFileAsync3, CELL_WORKSPACE, CELL_STATE_DIR, CHECKPOINT_MESSAGE_FILE, CELL_RUNTIME_DIR, INSTALLED_RUNTIME_PACKAGE, CELL_RUNTIME_SDK_ENTRY, NOTES_FILE, MEMORY_FILE, MEMORY_EXTRACTION_TIMEOUT_MS, LIVENESS_CHECK_MS, ARTIFACTS_DIR, MAX_ARTIFACTS, MAX_ARTIFACT_BYTES, BRANCH_RE, REPOSITORY_RE, GIT, COMMIT_SHA_RE, MAX_TOOL_OUTPUT_CHARS, MAX_TOOL_INPUT_CHARS, MAX_CHECKPOINTS, CELL_CONTROL_DIR, BRIDGE_MARKER, CONTROL_RUNNER, BRIDGE_RUNNER, HEALTH_TIMEOUT_MS, EVENT_POLL_MS, AsterHotcellRuntime, CELL_FILE_WRITE_CHUNK_CHARS, ARTIFACT_MIME_TYPES;
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;
16837
17994
  var init_aster_cell_runtime = __esm({
16838
17995
  "src/server/aster-cell-runtime.ts"() {
16839
17996
  "use strict";
16840
17997
  init_version();
16841
17998
  init_aster_tools();
16842
- execFileAsync3 = promisify4(execFile5);
17999
+ execFileAsync5 = promisify6(execFile7);
16843
18000
  CELL_WORKSPACE = "/workspace";
16844
18001
  CELL_STATE_DIR = "/workspace/.aster";
16845
18002
  CHECKPOINT_MESSAGE_FILE = `${CELL_STATE_DIR}/checkpoint-message`;
@@ -16946,7 +18103,7 @@ var init_aster_cell_runtime = __esm({
16946
18103
  await this.installRuntime(cell);
16947
18104
  await this.ensureBridge(cell);
16948
18105
  await this.control(cell, "POST", "/rpc", {
16949
- id: randomUUID8(),
18106
+ id: randomUUID9(),
16950
18107
  type: "new_session",
16951
18108
  sessionId: input.sessionId,
16952
18109
  config: { model: input.model, cwd: CELL_WORKSPACE }
@@ -16965,7 +18122,7 @@ var init_aster_cell_runtime = __esm({
16965
18122
  /** Switch the cell session's model between turns. The bridge persists it for session restore. */
16966
18123
  async setModel(cellId, model) {
16967
18124
  await this.ensureReady(cellId);
16968
- 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 });
16969
18126
  }
16970
18127
  /** Reinstall the runtime if the cell's image lost it and restart the bridge if it isn't running. */
16971
18128
  async ensureReady(cellId) {
@@ -17028,7 +18185,7 @@ var init_aster_cell_runtime = __esm({
17028
18185
  const active = this.activeRuns.get(runId);
17029
18186
  if (!active) return;
17030
18187
  active.stop();
17031
- 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(() => {
17032
18189
  });
17033
18190
  }
17034
18191
  async runTurn(turn, options = {}) {
@@ -17127,7 +18284,7 @@ ${turn.userText}` : turn.userText;
17127
18284
  args: event.args ?? {}
17128
18285
  });
17129
18286
  await this.controlById(cellId, "POST", "/rpc", {
17130
- id: randomUUID8(),
18287
+ id: randomUUID9(),
17131
18288
  type: "resolve_permission",
17132
18289
  requestId: event.requestId,
17133
18290
  decision
@@ -17255,9 +18412,9 @@ ${turn.userText}` : turn.userText;
17255
18412
  if (!isSafeArtifactPath(path)) throw new Error("invalid_artifact_path");
17256
18413
  const cell = await this.provider.getCell(cellId);
17257
18414
  const file = shellQuote2(`${ARTIFACTS_DIR}/${path}`);
17258
- const stat10 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
17259
- if (stat10.exitCode !== 0) throw new Error("artifact_not_found");
17260
- if (Number(stat10.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
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");
17261
18418
  const encoded = await cell.exec(`base64 -w0 -- ${file}`);
17262
18419
  if (encoded.exitCode !== 0) throw new Error("artifact_not_found");
17263
18420
  return { data: Buffer.from(encoded.stdout.trim(), "base64"), mimeType: artifactMimeType(path), name: path.split("/").pop() ?? path };
@@ -17336,7 +18493,7 @@ ${text}` : text, { mode: "0600" });
17336
18493
  }
17337
18494
  }
17338
18495
  async control(cell, method, path, body) {
17339
- const requestId = randomUUID8();
18496
+ const requestId = randomUUID9();
17340
18497
  await cell.mkdir(CELL_CONTROL_DIR, { parents: true });
17341
18498
  await writeCellFileChunked(cell, `${CELL_CONTROL_DIR}/${requestId}.json`, JSON.stringify({ method, path, body }));
17342
18499
  const result2 = await cell.exec(`${CONTROL_RUNNER} ${requestId}`);
@@ -17552,7 +18709,7 @@ var init_hotcell_provider = __esm({
17552
18709
  });
17553
18710
 
17554
18711
  // src/server/routes.ts
17555
- import { randomUUID as randomUUID9 } from "crypto";
18712
+ import { randomUUID as randomUUID10 } from "crypto";
17556
18713
  import { URL as URL2 } from "url";
17557
18714
  import { readFile as readFile22, unlink as unlink3 } from "fs/promises";
17558
18715
  import { resolve as resolve9, basename as basename5 } from "path";
@@ -17567,6 +18724,27 @@ function createHotcellAsterRuntime() {
17567
18724
  throw error;
17568
18725
  }
17569
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
+ }
17570
18748
  function parseOptionalBudgetInteger(body, name, minimum = 1) {
17571
18749
  if (!Object.hasOwn(body, name)) return void 0;
17572
18750
  const value = body[name];
@@ -17617,8 +18795,8 @@ async function resolveFiles2(filePatterns, cwd) {
17617
18795
  const resolved = /* @__PURE__ */ new Set();
17618
18796
  for (const pattern of filePatterns) {
17619
18797
  try {
17620
- const stat10 = await import("fs/promises").then((m) => m.stat(resolve9(cwd, pattern)));
17621
- if (stat10.isFile()) {
18798
+ const stat11 = await import("fs/promises").then((m) => m.stat(resolve9(cwd, pattern)));
18799
+ if (stat11.isFile()) {
17622
18800
  resolved.add(resolve9(cwd, pattern));
17623
18801
  continue;
17624
18802
  }
@@ -17686,7 +18864,8 @@ function setupRoutes(config2, options = {}) {
17686
18864
  executor: new ToolExecutor(tools),
17687
18865
  sseClients: /* @__PURE__ */ new Set(),
17688
18866
  runId: run.id,
17689
- allowedTools
18867
+ allowedTools,
18868
+ trace: await startSessionTrace(config2, sessionFile, tools, sessionFile.messages)
17690
18869
  };
17691
18870
  activeSessions.set(run.sessionId, active);
17692
18871
  activeRuns.set(run.id, active);
@@ -17874,7 +19053,7 @@ function setupRoutes(config2, options = {}) {
17874
19053
  const executor = new ToolExecutor(tools);
17875
19054
  const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
17876
19055
  const sessionId = makeSessionId2(task);
17877
- const runId = randomUUID9();
19056
+ const runId = randomUUID10();
17878
19057
  const worktreeManager = new RunWorktreeManager();
17879
19058
  let worktree;
17880
19059
  if (useWorktree) {
@@ -17932,7 +19111,10 @@ function setupRoutes(config2, options = {}) {
17932
19111
  }
17933
19112
  throw error;
17934
19113
  }
19114
+ const runTrace = await startSessionTrace(config2, sessionFile, tools, messages);
19115
+ traceUserMessage(runTrace, task, 0, makeTurnId());
17935
19116
  const active = {
19117
+ trace: runTrace,
17936
19118
  sessionFile,
17937
19119
  messages,
17938
19120
  executor,
@@ -18114,11 +19296,16 @@ function setupRoutes(config2, options = {}) {
18114
19296
  const resolvedFiles = await resolveFiles2(files, cwd);
18115
19297
  const userContent = await buildUserMessage2(prompt, resolvedFiles, cwd);
18116
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);
18117
19302
  const active = {
18118
19303
  sessionFile,
18119
19304
  messages,
18120
19305
  executor,
18121
- sseClients: /* @__PURE__ */ new Set()
19306
+ sseClients: /* @__PURE__ */ new Set(),
19307
+ trace,
19308
+ nextTurnId: firstTurnId
18122
19309
  };
18123
19310
  activeSessions.set(sessionId, active);
18124
19311
  launchAgentTurnForSession(active, config2, allowAll);
@@ -18146,6 +19333,15 @@ function setupRoutes(config2, options = {}) {
18146
19333
  const resolvedFiles = await resolveFiles2(files, active.sessionFile.cwd);
18147
19334
  const userContent = await buildUserMessage2(prompt, resolvedFiles, active.sessionFile.cwd);
18148
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);
18149
19345
  launchAgentTurnForSession(active, config2, allowAll);
18150
19346
  json2(res, 202, { sessionId, status: "started" });
18151
19347
  return;
@@ -18160,6 +19356,7 @@ function setupRoutes(config2, options = {}) {
18160
19356
  wakeScheduler.dispose();
18161
19357
  for (const [, active] of activeSessions) {
18162
19358
  active.shuttingDown = true;
19359
+ if (active.trace?.enabled) active.trace.emit("session_end", { reason: "exit" }, null);
18163
19360
  active.controller?.abort(new Error("server_shutdown"));
18164
19361
  for (const client of active.sseClients) {
18165
19362
  client.close();
@@ -18328,6 +19525,9 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18328
19525
  }
18329
19526
  };
18330
19527
  const compaction = sessionFileCompactionTarget(sessionFile);
19528
+ const turnId = active.nextTurnId ?? makeTurnId();
19529
+ active.nextTurnId = void 0;
19530
+ active.lastTurnId = turnId;
18331
19531
  try {
18332
19532
  if (run && runStore) {
18333
19533
  const tools = ALL_TOOLS.filter((tool) => active.allowedTools?.has(tool.name));
@@ -18349,6 +19549,8 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18349
19549
  preferPullRequests: config2.preferPullRequests,
18350
19550
  compaction,
18351
19551
  callbacks,
19552
+ trace: active.trace,
19553
+ turnId,
18352
19554
  onIterationEnd: async (updatedMessages) => {
18353
19555
  sessionFile.messages = updatedMessages;
18354
19556
  sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -18375,7 +19577,9 @@ async function runAgentTurnForSession(active, config2, allowAll) {
18375
19577
  allowDirectPush: config2.allowDirectPush,
18376
19578
  preferPullRequests: config2.preferPullRequests,
18377
19579
  compaction,
18378
- callbacks
19580
+ callbacks,
19581
+ trace: active.trace,
19582
+ turnId
18379
19583
  });
18380
19584
  }
18381
19585
  sessionFile.messages = messages;
@@ -18427,6 +19631,8 @@ var init_routes = __esm({
18427
19631
  "use strict";
18428
19632
  init_config();
18429
19633
  init_llm_auth();
19634
+ init_trace();
19635
+ init_registry2();
18430
19636
  init_loop();
18431
19637
  init_system_prompt();
18432
19638
  init_executor();
@@ -18559,8 +19765,8 @@ __export(db_exports, {
18559
19765
  updateMemoryEmbedding: () => updateMemoryEmbedding
18560
19766
  });
18561
19767
  import Database6 from "better-sqlite3";
18562
- import { dirname as dirname17 } from "path";
18563
- 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";
18564
19770
  function initSchema(db) {
18565
19771
  db.exec(`
18566
19772
  CREATE TABLE IF NOT EXISTS memories (
@@ -18644,7 +19850,7 @@ function openMemoryDb(dbPath) {
18644
19850
  if (dbInstance) {
18645
19851
  dbInstance.close();
18646
19852
  }
18647
- mkdirSync8(dirname17(dbPath), { recursive: true });
19853
+ mkdirSync10(dirname18(dbPath), { recursive: true });
18648
19854
  dbInstance = new Database6(dbPath);
18649
19855
  dbInstance.pragma("journal_mode = WAL");
18650
19856
  dbInstance.pragma("foreign_keys = ON");
@@ -19778,12 +20984,12 @@ var init_connection = __esm({
19778
20984
  });
19779
20985
 
19780
20986
  // src/lsp/protocol.ts
19781
- import { pathToFileURL as pathToFileURL2, fileURLToPath as fileURLToPath4 } from "url";
20987
+ import { pathToFileURL as pathToFileURL2, fileURLToPath as fileURLToPath5 } from "url";
19782
20988
  function toUri(path) {
19783
20989
  return pathToFileURL2(path).href;
19784
20990
  }
19785
20991
  function fromUri(uri) {
19786
- return fileURLToPath4(uri);
20992
+ return fileURLToPath5(uri);
19787
20993
  }
19788
20994
  function symbolKindName(kind) {
19789
20995
  const names = {
@@ -20594,7 +21800,7 @@ function makeLspTools(manager) {
20594
21800
  var init_lsp = __esm({
20595
21801
  "src/tools/lsp.ts"() {
20596
21802
  "use strict";
20597
- init_paths();
21803
+ init_paths2();
20598
21804
  init_adapter();
20599
21805
  }
20600
21806
  });
@@ -20610,11 +21816,11 @@ __export(requesty_catalog_exports, {
20610
21816
  mapRequestyModel: () => mapRequestyModel
20611
21817
  });
20612
21818
  import { readFile as readFile23, writeFile as writeFile13, mkdir as mkdir14 } from "fs/promises";
20613
- import { homedir as homedir23 } from "os";
20614
- import { join as join32, dirname as dirname18 } from "path";
21819
+ import { homedir as homedir24 } from "os";
21820
+ import { join as join36, dirname as dirname19 } from "path";
20615
21821
  function catalogCachePath2() {
20616
- const xdg = process.env.XDG_CONFIG_HOME || join32(homedir23(), ".config");
20617
- 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");
20618
21824
  }
20619
21825
  function isSafeModelId(id) {
20620
21826
  return typeof id === "string" && id.length > 0 && !CONTROL_CHARS.test(id);
@@ -20685,7 +21891,7 @@ async function readCache4() {
20685
21891
  async function writeCache4(catalog) {
20686
21892
  const path = catalogCachePath2();
20687
21893
  try {
20688
- await mkdir14(dirname18(path), { recursive: true });
21894
+ await mkdir14(dirname19(path), { recursive: true });
20689
21895
  const cache = { version: CACHE_VERSION2, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), ...catalog };
20690
21896
  await writeFile13(path, JSON.stringify(cache, null, 2), "utf8");
20691
21897
  } catch {
@@ -20777,9 +21983,9 @@ var init_permissions = __esm({
20777
21983
 
20778
21984
  // src/sdk/session.ts
20779
21985
  import { resolve as resolve10 } from "path";
20780
- import { homedir as homedir24 } from "os";
20781
- import { join as join33 } from "path";
20782
- import { existsSync as existsSync4 } from "fs";
21986
+ import { homedir as homedir25 } from "os";
21987
+ import { join as join37 } from "path";
21988
+ import { existsSync as existsSync7 } from "fs";
20783
21989
  async function createAgentSession(opts2) {
20784
21990
  const config2 = await resolveSdkConfig(opts2);
20785
21991
  if (usesRequesty(llmAuthFromConfig(config2))) await ensureRequestyCatalog();
@@ -20795,7 +22001,7 @@ async function createAgentSession(opts2) {
20795
22001
  let memoryManager = null;
20796
22002
  const memoryEnabled = opts2.memoryEnabled ?? config2.memoryEnabled ?? false;
20797
22003
  if (memoryEnabled) {
20798
- const dbPath = config2.memoryDbPath ?? join33(homedir24(), ".local", "share", "kimiflare", "memory.db");
22004
+ const dbPath = config2.memoryDbPath ?? join37(homedir25(), ".local", "share", "kimiflare", "memory.db");
20799
22005
  memoryManager = new MemoryManager({
20800
22006
  dbPath,
20801
22007
  ...llmAuthFromConfig(config2),
@@ -20825,7 +22031,7 @@ async function createAgentSession(opts2) {
20825
22031
  }
20826
22032
  let sessionFile;
20827
22033
  if (opts2.sessionId) {
20828
- const filePath = join33(sessionsDir(), `${opts2.sessionId}.json`);
22034
+ const filePath = join37(sessionsDir(), `${opts2.sessionId}.json`);
20829
22035
  try {
20830
22036
  sessionFile = await loadSession(filePath);
20831
22037
  } catch {
@@ -20860,6 +22066,19 @@ async function createAgentSession(opts2) {
20860
22066
  ...sessionFile.messages.filter((m) => m.role !== "system")
20861
22067
  ];
20862
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
+ });
20863
22082
  const session = new InternalSession({
20864
22083
  sessionFile,
20865
22084
  cwd,
@@ -20873,7 +22092,8 @@ async function createAgentSession(opts2) {
20873
22092
  permissionHandler: opts2.permissionHandler,
20874
22093
  onKimiMdStale: opts2.onKimiMdStale,
20875
22094
  hooks,
20876
- provider: opts2.provider
22095
+ provider: opts2.provider,
22096
+ trace
20877
22097
  });
20878
22098
  if (isNewSession) {
20879
22099
  await session.save().catch((err) => {
@@ -20906,6 +22126,8 @@ var init_session = __esm({
20906
22126
  init_context_budget();
20907
22127
  init_config2();
20908
22128
  init_permissions();
22129
+ init_trace();
22130
+ init_registry2();
20909
22131
  InternalSession = class {
20910
22132
  sessionId;
20911
22133
  cwd;
@@ -20921,6 +22143,10 @@ var init_session = __esm({
20921
22143
  onKimiMdStale;
20922
22144
  provider;
20923
22145
  hooks;
22146
+ trace;
22147
+ traceCostUsd = 0;
22148
+ /** Turn id of the last turn that ran, for next-message outcome signals. */
22149
+ lastTurnId = null;
20924
22150
  listeners = /* @__PURE__ */ new Set();
20925
22151
  steerQueue = [];
20926
22152
  followUpQueue = [];
@@ -20961,6 +22187,7 @@ var init_session = __esm({
20961
22187
  this.onKimiMdStale = opts2.onKimiMdStale;
20962
22188
  this.provider = opts2.provider;
20963
22189
  this.hooks = opts2.hooks;
22190
+ this.trace = opts2.trace;
20964
22191
  this.permissionHandler = opts2.permissionHandler ?? createDefaultPermissionHandler({
20965
22192
  mode: this.currentMode,
20966
22193
  onRequest: (req) => {
@@ -21014,6 +22241,22 @@ var init_session = __esm({
21014
22241
  }
21015
22242
  const userMessage = { role: "user", content: userContent };
21016
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;
21017
22260
  this.emit({ type: "message.start", messageId, role: "user" });
21018
22261
  this.emit({ type: "message.end", messageId });
21019
22262
  this.isStreaming = true;
@@ -21021,7 +22264,7 @@ var init_session = __esm({
21021
22264
  this.abortController = new AbortController();
21022
22265
  let succeeded = false;
21023
22266
  try {
21024
- await this.runTurn(mode, options?.maxToolIterations);
22267
+ await this.runTurn(mode, options?.maxToolIterations, turnId);
21025
22268
  succeeded = true;
21026
22269
  this.isStreaming = false;
21027
22270
  this.emit({ type: "status", status: "idle" });
@@ -21116,6 +22359,9 @@ var init_session = __esm({
21116
22359
  }
21117
22360
  dispose() {
21118
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
+ }
21119
22365
  this.denyPendingPermissions();
21120
22366
  this.abortController?.abort();
21121
22367
  this.lspManager?.stopAll().catch(() => {
@@ -21123,7 +22369,7 @@ var init_session = __esm({
21123
22369
  this.memoryManager?.close();
21124
22370
  this.listeners.clear();
21125
22371
  }
21126
- async runTurn(mode, maxToolIterations) {
22372
+ async runTurn(mode, maxToolIterations, turnId = makeTurnId()) {
21127
22373
  const signal = this.abortController.signal;
21128
22374
  const coauthor = this.config.coauthor !== false ? {
21129
22375
  name: this.config.coauthorName || "autopilot",
@@ -21196,6 +22442,7 @@ var init_session = __esm({
21196
22442
  });
21197
22443
  },
21198
22444
  onUsageFinal: (usage, gatewayMeta) => {
22445
+ if (typeof usage.cost === "number") this.traceCostUsd += usage.cost;
21199
22446
  this.usage.totalInputTokens += usage.prompt_tokens;
21200
22447
  this.usage.totalOutputTokens += usage.completion_tokens;
21201
22448
  this.usage.turnCount += 1;
@@ -21273,6 +22520,8 @@ var init_session = __esm({
21273
22520
  compaction: this.compaction,
21274
22521
  allowDirectPush: this.config.allowDirectPush,
21275
22522
  preferPullRequests: this.config.preferPullRequests,
22523
+ trace: this.trace,
22524
+ turnId,
21276
22525
  // Steering lands at the next tool boundary, or before the final answer
21277
22526
  // ends the turn (the loop continues so the model can respond to it).
21278
22527
  drainPendingInput: () => {
@@ -21281,6 +22530,9 @@ var init_session = __esm({
21281
22530
  const messageId = `msg_${this.nextMessageId++}`;
21282
22531
  this.emit({ type: "message.start", messageId, role: "user" });
21283
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
+ }
21284
22536
  return { role: "user", content: text };
21285
22537
  });
21286
22538
  },
@@ -21296,7 +22548,7 @@ var init_session = __esm({
21296
22548
  }
21297
22549
  }
21298
22550
  });
21299
- 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)))) {
21300
22552
  this.messages[0] = {
21301
22553
  role: "system",
21302
22554
  content: buildSystemPrompt({
@@ -22108,6 +23360,8 @@ var init_builtins = __esm({
22108
23360
  { name: "checkpoint", argHint: "[label]", description: "Save current point in session", source: "builtin" },
22109
23361
  { name: "checkpoints", description: "List checkpoints in current session", source: "builtin" },
22110
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" },
22111
23365
  { name: "clear", description: "Clear current conversation", source: "builtin" },
22112
23366
  { name: "fresh", description: "Start a fresh session with a summarized plan or continuation context", source: "builtin" },
22113
23367
  { name: "init", description: "Scan repo and write AGENTS.md", source: "builtin" },
@@ -22574,7 +23828,9 @@ var init_help_menu = __esm({
22574
23828
  { command: "/resume", description: "pick a past conversation" },
22575
23829
  { command: "/compact", description: "summarize old turns to free context" },
22576
23830
  { command: "/clear", description: "clear current conversation" },
22577
- { 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 }
22578
23834
  ]
22579
23835
  },
22580
23836
  {
@@ -22733,8 +23989,8 @@ __export(app_helpers_exports, {
22733
23989
  trackRecentFile: () => trackRecentFile
22734
23990
  });
22735
23991
  import { execSync as execSync3, spawn as spawn6 } from "child_process";
22736
- import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
22737
- import { join as join34 } from "path";
23992
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
23993
+ import { join as join38 } from "path";
22738
23994
  import { platform as platform3 } from "os";
22739
23995
  function replaceQueuedPrompt(queue2, key, full, display) {
22740
23996
  const index = queue2.findIndex((item) => item.key === key);
@@ -22842,12 +24098,12 @@ function buildFilePickerIgnoreList(cwd) {
22842
24098
  ];
22843
24099
  const gitignorePatterns = [];
22844
24100
  try {
22845
- const gitignorePath = join34(cwd, ".gitignore");
24101
+ const gitignorePath = join38(cwd, ".gitignore");
22846
24102
  const stats = statSync5(gitignorePath);
22847
24103
  if (stats.size > MAX_GITIGNORE_SIZE) {
22848
24104
  return hardcoded;
22849
24105
  }
22850
- const content = readFileSync6(gitignorePath, "utf-8");
24106
+ const content = readFileSync7(gitignorePath, "utf-8");
22851
24107
  for (const line of content.split(/\r?\n/)) {
22852
24108
  const trimmed = line.trim();
22853
24109
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -22981,7 +24237,7 @@ function findImagePaths(text) {
22981
24237
  let match;
22982
24238
  while ((match = quotedRegex.exec(text)) !== null) {
22983
24239
  const path = match[1] ?? match[2];
22984
- if (path && isImagePath(path) && existsSync5(path)) {
24240
+ if (path && isImagePath(path) && existsSync8(path)) {
22985
24241
  paths.push(path);
22986
24242
  }
22987
24243
  }
@@ -22990,7 +24246,7 @@ function findImagePaths(text) {
22990
24246
  const processed = remaining.replace(/\\ /g, ESCAPED_SPACE);
22991
24247
  for (const token of processed.split(/\s+/)) {
22992
24248
  const clean = token.replace(new RegExp(ESCAPED_SPACE, "g"), " ").replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
22993
- if (clean && isImagePath(clean) && existsSync5(clean) && !paths.includes(clean)) {
24249
+ if (clean && isImagePath(clean) && existsSync8(clean) && !paths.includes(clean)) {
22994
24250
  paths.push(clean);
22995
24251
  }
22996
24252
  }
@@ -23020,9 +24276,9 @@ var init_app_helpers = __esm({
23020
24276
 
23021
24277
  // src/ui/bang-command.ts
23022
24278
  import { spawn as spawn7 } from "child_process";
23023
- import { mkdtemp as mkdtemp2, readFile as readFile24, rm as rm3 } from "fs/promises";
24279
+ import { mkdtemp as mkdtemp2, readFile as readFile24, rm as rm4 } from "fs/promises";
23024
24280
  import { tmpdir as tmpdir4, platform as platform4 } from "os";
23025
- import { join as join35 } from "path";
24281
+ import { join as join39 } from "path";
23026
24282
  function registerInkInstance(instance) {
23027
24283
  inkInstance = instance;
23028
24284
  }
@@ -23125,8 +24381,8 @@ async function runBangCommand(command, cwd) {
23125
24381
  const origErr = stderr.write;
23126
24382
  const readable = stdin.listeners("readable");
23127
24383
  const wasRaw = stdin.isTTY ? stdin.isRaw : false;
23128
- const logDir2 = isWindows ? null : await mkdtemp2(join35(tmpdir4(), "autopilot-bang-"));
23129
- const logPath = logDir2 ? join35(logDir2, "out.log") : null;
24384
+ const logDir2 = isWindows ? null : await mkdtemp2(join39(tmpdir4(), "autopilot-bang-"));
24385
+ const logPath = logDir2 ? join39(logDir2, "out.log") : null;
23130
24386
  const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
23131
24387
  inkInstance?.clear();
23132
24388
  origOut.call(stdout, `
@@ -23170,7 +24426,7 @@ $ ${command}
23170
24426
  if (captured && logPath) {
23171
24427
  output = cleanTerminalOutput(await readFile24(logPath, "utf8").catch(() => ""));
23172
24428
  }
23173
- if (logDir2) await rm3(logDir2, { recursive: true, force: true }).catch(() => {
24429
+ if (logDir2) await rm4(logDir2, { recursive: true, force: true }).catch(() => {
23174
24430
  });
23175
24431
  return { command, exitCode: result2.code, signal: result2.signal, output };
23176
24432
  }
@@ -25357,9 +26613,9 @@ __export(camouflage_view_exports, {
25357
26613
  validateCommandName: () => validateCommandName
25358
26614
  });
25359
26615
  import { execFileSync as execFileSync2, spawn as spawn9 } from "child_process";
25360
- import { readdir as readdir5 } from "fs/promises";
25361
- import { homedir as homedir25 } from "os";
25362
- import { join as join36, resolve as resolve11 } from "path";
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";
25363
26619
  async function loadSdk() {
25364
26620
  const sdk = await import("camouflage-tui").catch((err) => {
25365
26621
  throw new CamouflageUnavailable(`camouflage-tui is not installed (${err.message})`);
@@ -25538,10 +26794,10 @@ function truncateText(s, max) {
25538
26794
  async function listMentionDir(cwd, query) {
25539
26795
  const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
25540
26796
  const dir = exact ? `${query}/` : query.slice(0, query.lastIndexOf("/") + 1);
25541
- const expanded = dir.startsWith("~") ? join36(homedir25(), dir.slice(1)) : dir;
26797
+ const expanded = dir.startsWith("~") ? join40(homedir26(), dir.slice(1)) : dir;
25542
26798
  const target = resolve11(cwd, expanded || ".");
25543
26799
  try {
25544
- const items = await readdir5(target, { withFileTypes: true });
26800
+ const items = await readdir6(target, { withFileTypes: true });
25545
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 ? "/" : ""}`);
25546
26802
  return { dir, entries };
25547
26803
  } catch {
@@ -25619,7 +26875,7 @@ function shortModel(id) {
25619
26875
  return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
25620
26876
  }
25621
26877
  function shortPath(p) {
25622
- const home = homedir25();
26878
+ const home = homedir26();
25623
26879
  return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
25624
26880
  }
25625
26881
  function gitBranch(cwd) {
@@ -27626,7 +28882,7 @@ var init_tool_view = __esm({
27626
28882
  "use strict";
27627
28883
  init_spinner();
27628
28884
  init_diff_view();
27629
- init_paths();
28885
+ init_paths2();
27630
28886
  init_theme_context();
27631
28887
  init_narrator();
27632
28888
  ToolView = React7.memo(function ToolView2({ evt, verbose, isRepeated, intentTier }) {
@@ -28373,7 +29629,7 @@ function PermissionModal({ tool, args, onDecide, onFeedback }) {
28373
29629
  );
28374
29630
  const handleFeedbackSubmit = useCallback(
28375
29631
  (text) => {
28376
- onDecide(DENY);
29632
+ onDecide(text.trim() ? { ...DENY, feedback: text.trim() } : DENY);
28377
29633
  if (text.trim()) {
28378
29634
  onFeedback?.(text);
28379
29635
  }
@@ -28889,7 +30145,7 @@ var init_queue_plan_picker = __esm({
28889
30145
  // src/agent/jev-context.ts
28890
30146
  import { execFileSync as execFileSync3 } from "child_process";
28891
30147
  import { readFile as readFile25 } from "fs/promises";
28892
- import { basename as basename6, join as join37, resolve as resolve12 } from "path";
30148
+ import { basename as basename6, join as join41, resolve as resolve12 } from "path";
28893
30149
  function messageText(message2) {
28894
30150
  if (typeof message2.content === "string") return message2.content.trim();
28895
30151
  if (!Array.isArray(message2.content)) return "";
@@ -28911,7 +30167,7 @@ function projectRoot(cwd) {
28911
30167
  }
28912
30168
  async function readPackageMetadata(root) {
28913
30169
  try {
28914
- const parsed = JSON.parse(await readFile25(join37(root, "package.json"), "utf8"));
30170
+ const parsed = JSON.parse(await readFile25(join41(root, "package.json"), "utf8"));
28915
30171
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
28916
30172
  const pkg = parsed;
28917
30173
  const metadata = {};
@@ -28928,7 +30184,7 @@ async function readPackageMetadata(root) {
28928
30184
  async function readLicenseEvidence(root) {
28929
30185
  for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
28930
30186
  try {
28931
- const content = (await readFile25(join37(root, name), "utf8")).trim();
30187
+ const content = (await readFile25(join41(root, name), "utf8")).trim();
28932
30188
  if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
28933
30189
  } catch {
28934
30190
  }
@@ -30010,8 +31266,8 @@ var init_frontmatter = __esm({
30010
31266
  });
30011
31267
 
30012
31268
  // src/skills/loader.ts
30013
- import { readFile as readFile26, readdir as readdir6, stat as stat7 } from "fs/promises";
30014
- import { join as join38, extname } from "path";
31269
+ import { readFile as readFile26, readdir as readdir7, stat as stat8 } from "fs/promises";
31270
+ import { join as join42, extname } from "path";
30015
31271
  function normalizeManifest(raw, filePath) {
30016
31272
  const name = typeof raw.name === "string" ? raw.name : "";
30017
31273
  const description = typeof raw.description === "string" ? raw.description : "";
@@ -30044,11 +31300,11 @@ async function loadSkillFile(filePath) {
30044
31300
  }
30045
31301
  async function loadSkillsFromDir(dirPath) {
30046
31302
  try {
30047
- const entries = await readdir6(dirPath);
31303
+ const entries = await readdir7(dirPath);
30048
31304
  const files = [];
30049
31305
  for (const entry of entries) {
30050
- const full = join38(dirPath, entry);
30051
- const s = await stat7(full);
31306
+ const full = join42(dirPath, entry);
31307
+ const s = await stat8(full);
30052
31308
  if (s.isFile() && extname(entry) === ".md") {
30053
31309
  files.push(full);
30054
31310
  }
@@ -30076,11 +31332,11 @@ var init_loader = __esm({
30076
31332
 
30077
31333
  // src/skills/manager.ts
30078
31334
  import { mkdir as mkdir15, writeFile as writeFile14, unlink as unlink4, readFile as readFile27 } from "fs/promises";
30079
- import { join as join39 } from "path";
31335
+ import { join as join43 } from "path";
30080
31336
  function getSkillDirs(cwd) {
30081
31337
  return {
30082
- projectDir: join39(cwd, ".kimiflare", "skills"),
30083
- globalDir: join39(process.env.HOME ?? "", ".config", "kimiflare", "skills")
31338
+ projectDir: join43(cwd, ".kimiflare", "skills"),
31339
+ globalDir: join43(process.env.HOME ?? "", ".config", "kimiflare", "skills")
30084
31340
  };
30085
31341
  }
30086
31342
  async function listAllSkills(cwd) {
@@ -30094,7 +31350,7 @@ async function listAllSkills(cwd) {
30094
31350
  async function createSkill(opts2) {
30095
31351
  const dirs = getSkillDirs(opts2.cwd);
30096
31352
  const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
30097
- const filepath = join39(dir, `${opts2.name}.md`);
31353
+ const filepath = join43(dir, `${opts2.name}.md`);
30098
31354
  const frontmatter = {
30099
31355
  name: opts2.name,
30100
31356
  enabled: true,
@@ -30403,14 +31659,14 @@ __export(tui_report_exports, {
30403
31659
  getCategoryReportText: () => getCategoryReportText
30404
31660
  });
30405
31661
  import { readFile as readFile28 } from "fs/promises";
30406
- import { join as join40 } from "path";
30407
- import { homedir as homedir26 } from "os";
31662
+ import { join as join44 } from "path";
31663
+ import { homedir as homedir27 } from "os";
30408
31664
  function usageDir3() {
30409
- const xdg = process.env.XDG_DATA_HOME || join40(homedir26(), ".local", "share");
30410
- return join40(xdg, "kimiflare");
31665
+ const xdg = process.env.XDG_DATA_HOME || join44(homedir27(), ".local", "share");
31666
+ return join44(xdg, "kimiflare");
30411
31667
  }
30412
31668
  function usagePath4() {
30413
- return join40(usageDir3(), "usage.json");
31669
+ return join44(usageDir3(), "usage.json");
30414
31670
  }
30415
31671
  function today3() {
30416
31672
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -30479,7 +31735,7 @@ var init_tui_report = __esm({
30479
31735
  });
30480
31736
 
30481
31737
  // src/ui/slash-commands.ts
30482
- import { join as join41 } from "path";
31738
+ import { join as join45 } from "path";
30483
31739
  import QRCode from "qrcode";
30484
31740
  function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
30485
31741
  const oldSessionId = ctx.sessionIdRef.current;
@@ -30570,6 +31826,28 @@ function modesGate(ctx) {
30570
31826
  ]);
30571
31827
  return false;
30572
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
+ }
30573
31851
  function dispatchSlashCommand(ctx, cmd) {
30574
31852
  const raw = cmd.trim();
30575
31853
  const [head, ...rest] = raw.split(/\s+/);
@@ -30579,7 +31857,7 @@ function dispatchSlashCommand(ctx, cmd) {
30579
31857
  if (!handler) return false;
30580
31858
  return handler(ctx, rest, arg);
30581
31859
  }
30582
- var handleExit, handleClear, handleFresh, handleReasoning, handleCost, handleShell, handleSubagents, handleJev, handleModel, SETTINGS, handleSettings, handleKey, handleMode, handleMultiAgent, handleTheme, handlePlan, handleAuto, handleEdit, handleSkills, handleMemory, handleResume, handleCheckpoint, handleCompact, handleInit, handleUpdate, handleMcp, handleLsp, handleHooks, handleHello, handleInbox, handleLogout, handleCommand, handleRemote, handleHelp, handleChangelogImage, handlers;
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;
30583
31861
  var init_slash_commands = __esm({
30584
31862
  "src/ui/slash-commands.ts"() {
30585
31863
  "use strict";
@@ -30602,7 +31880,7 @@ var init_slash_commands = __esm({
30602
31880
  init_executor();
30603
31881
  init_recommended();
30604
31882
  init_settings();
30605
- init_types();
31883
+ init_types2();
30606
31884
  init_update_check();
30607
31885
  init_version();
30608
31886
  init_app_helpers();
@@ -31389,7 +32667,7 @@ ${lines.join("\n")}` }]);
31389
32667
  void (async () => {
31390
32668
  try {
31391
32669
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
31392
- const file = await loadSession(join41(sessionsDir3(), `${currentId}.json`));
32670
+ const file = await loadSession(join45(sessionsDir3(), `${currentId}.json`));
31393
32671
  const cps = file.checkpoints ?? [];
31394
32672
  if (cps.length === 0) {
31395
32673
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
@@ -31434,7 +32712,7 @@ ${lines.join("\n")}` }]);
31434
32712
  try {
31435
32713
  ctx.ensureSessionId();
31436
32714
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
31437
- const filePath = join41(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
32715
+ const filePath = join45(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
31438
32716
  await addCheckpoint(filePath, cp);
31439
32717
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
31440
32718
  } catch (e) {
@@ -32025,6 +33303,8 @@ project: ${projectSettingsPath(cwd)}`
32025
33303
  });
32026
33304
  return true;
32027
33305
  };
33306
+ handleGood = (ctx, rest) => rateLastTurn(ctx, "good", rest.join(" ").trim());
33307
+ handleBad = (ctx, rest) => rateLastTurn(ctx, "bad", rest.join(" ").trim());
32028
33308
  handlers = {
32029
33309
  "/exit": handleExit,
32030
33310
  "/clear": handleClear,
@@ -32048,6 +33328,8 @@ project: ${projectSettingsPath(cwd)}`
32048
33328
  "/resume": handleResume,
32049
33329
  "/checkpoint": handleCheckpoint,
32050
33330
  "/compact": handleCompact,
33331
+ "/good": handleGood,
33332
+ "/bad": handleBad,
32051
33333
  "/init": handleInit,
32052
33334
  "/update": handleUpdate,
32053
33335
  "/mcp": handleMcp,
@@ -32379,7 +33661,7 @@ __export(openrouter_oauth_exports, {
32379
33661
  pkceChallenge: () => pkceChallenge,
32380
33662
  startLoopbackListener: () => startLoopbackListener
32381
33663
  });
32382
- import { createHash as createHash4, randomBytes as randomBytes3 } from "crypto";
33664
+ import { createHash as createHash5, randomBytes as randomBytes3 } from "crypto";
32383
33665
  import { createServer as createServer3 } from "http";
32384
33666
  import { hostname } from "os";
32385
33667
  function createPkce() {
@@ -32387,7 +33669,7 @@ function createPkce() {
32387
33669
  return { verifier, challenge: pkceChallenge(verifier) };
32388
33670
  }
32389
33671
  function pkceChallenge(verifier) {
32390
- return createHash4("sha256").update(verifier).digest("base64url");
33672
+ return createHash5("sha256").update(verifier).digest("base64url");
32391
33673
  }
32392
33674
  function openRouterAuthPageUrl() {
32393
33675
  return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
@@ -32899,14 +34181,14 @@ var init_frontmatter2 = __esm({
32899
34181
 
32900
34182
  // src/commands/loader.ts
32901
34183
  import { open, realpath as realpath6 } from "fs/promises";
32902
- import { homedir as homedir27 } from "os";
32903
- import { join as join42, relative as relative10, sep as sep6 } from "path";
34184
+ import { homedir as homedir28 } from "os";
34185
+ import { join as join46, relative as relative10, sep as sep6 } from "path";
32904
34186
  function projectCommandsDir(cwd = process.cwd()) {
32905
- return join42(cwd, ".kimiflare", "commands");
34187
+ return join46(cwd, ".kimiflare", "commands");
32906
34188
  }
32907
34189
  function globalCommandsDir() {
32908
- const xdg = process.env.XDG_CONFIG_HOME || join42(homedir27(), ".config");
32909
- return join42(xdg, "kimiflare", "commands");
34190
+ const xdg = process.env.XDG_CONFIG_HOME || join46(homedir28(), ".config");
34191
+ return join46(xdg, "kimiflare", "commands");
32910
34192
  }
32911
34193
  async function loadCustomCommands(cwd = process.cwd()) {
32912
34194
  const warnings = [];
@@ -33050,7 +34332,7 @@ var init_loader2 = __esm({
33050
34332
  init_mode();
33051
34333
  init_glob();
33052
34334
  init_config();
33053
- init_paths();
34335
+ init_paths2();
33054
34336
  init_frontmatter2();
33055
34337
  MAX_COMMAND_FILE_BYTES = 256 * 1024;
33056
34338
  }
@@ -33060,7 +34342,7 @@ var init_loader2 = __esm({
33060
34342
  import { exec } from "child_process";
33061
34343
  import { open as open2, realpath as realpath7 } from "fs/promises";
33062
34344
  import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolvePathJoin, basename as pathBasename } from "path";
33063
- import { promisify as promisify5 } from "util";
34345
+ import { promisify as promisify7 } from "util";
33064
34346
  function tokenizeArgs(s) {
33065
34347
  return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
33066
34348
  const token = match[0];
@@ -33212,8 +34494,8 @@ var execAsync, ARG_TOKEN_RE, POSITIONAL_RE, HAS_POSITIONAL, SHELL_RE, FILE_RE, D
33212
34494
  var init_renderer2 = __esm({
33213
34495
  "src/commands/renderer.ts"() {
33214
34496
  "use strict";
33215
- init_paths();
33216
- execAsync = promisify5(exec);
34497
+ init_paths2();
34498
+ execAsync = promisify7(exec);
33217
34499
  ARG_TOKEN_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
33218
34500
  POSITIONAL_RE = /\$(\d+)/g;
33219
34501
  HAS_POSITIONAL = /\$\d+/;
@@ -33288,11 +34570,11 @@ var init_wcag = __esm({
33288
34570
  });
33289
34571
 
33290
34572
  // src/ui/theme-loader.ts
33291
- import { readFile as readFile29, readdir as readdir7 } from "fs/promises";
33292
- import { join as join43 } from "path";
33293
- import { homedir as homedir28 } from "os";
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";
33294
34576
  function projectThemesDir(cwd = process.cwd()) {
33295
- return join43(cwd, ".kimiflare", "themes");
34577
+ return join47(cwd, ".kimiflare", "themes");
33296
34578
  }
33297
34579
  function isHexColor(c) {
33298
34580
  return /^#[0-9a-fA-F]{6}$/.test(c);
@@ -33376,12 +34658,12 @@ async function loadThemesFromDir(dir, source) {
33376
34658
  const errors = [];
33377
34659
  let files;
33378
34660
  try {
33379
- files = await readdir7(dir);
34661
+ files = await readdir8(dir);
33380
34662
  } catch {
33381
34663
  return { themes, errors };
33382
34664
  }
33383
34665
  for (const file of files.filter((f) => f.endsWith(".json"))) {
33384
- const path = join43(dir, file);
34666
+ const path = join47(dir, file);
33385
34667
  let raw;
33386
34668
  try {
33387
34669
  raw = await readFile29(path, "utf-8");
@@ -33530,8 +34812,8 @@ var init_theme_loader = __esm({
33530
34812
  "use strict";
33531
34813
  init_wcag();
33532
34814
  init_theme();
33533
- USER_THEMES_DIR = join43(
33534
- process.env.XDG_CONFIG_HOME || join43(homedir28(), ".config"),
34815
+ USER_THEMES_DIR = join47(
34816
+ process.env.XDG_CONFIG_HOME || join47(homedir29(), ".config"),
33535
34817
  "kimiflare",
33536
34818
  "themes"
33537
34819
  );
@@ -35689,6 +36971,61 @@ function useSessionManager(deps) {
35689
36971
  setLogSessionId(sessionIdRef.current);
35690
36972
  return sessionIdRef.current;
35691
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]);
35692
37029
  const saveSessionSafe = useCallback6(async () => {
35693
37030
  const d = depsRef.current;
35694
37031
  if (!d.cfg) return;
@@ -35727,11 +37064,18 @@ function useSessionManager(deps) {
35727
37064
  setResumeProgress(0);
35728
37065
  setResumeStage("Loading session file\u2026");
35729
37066
  startSmoothProgress(0, 10, 400);
35730
- 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);
35731
37069
  setResumeProgress(10);
35732
37070
  setResumeStage("Restoring artifact store\u2026");
35733
37071
  startSmoothProgress(10, 20, 400);
35734
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 = [];
35735
37079
  sessionIdRef.current = file.id;
35736
37080
  setLogSessionId(file.id);
35737
37081
  sessionCreatedAtRef.current = file.createdAt;
@@ -35907,6 +37251,9 @@ ${summary}`
35907
37251
  [checkpointSession, doResumeSession]
35908
37252
  );
35909
37253
  const resetSession = useCallback6(() => {
37254
+ traceReverted("clear", turnHistoryRef.current.slice(-2));
37255
+ turnHistoryRef.current = [];
37256
+ endTrace("clear");
35910
37257
  sessionIdRef.current = null;
35911
37258
  setLogSessionId(null);
35912
37259
  sessionCreatedAtRef.current = null;
@@ -35914,7 +37261,7 @@ ${summary}`
35914
37261
  const d = depsRef.current;
35915
37262
  d.sessionStateRef.current = emptySessionState();
35916
37263
  d.artifactStoreRef.current = new ArtifactStore();
35917
- }, []);
37264
+ }, [endTrace, traceReverted]);
35918
37265
  return {
35919
37266
  sessionIdRef,
35920
37267
  sessionCreatedAtRef,
@@ -35936,7 +37283,13 @@ ${summary}`
35936
37283
  doResumeSession,
35937
37284
  handleResumePick,
35938
37285
  handleCheckpointPick,
35939
- resetSession
37286
+ resetSession,
37287
+ traceRef,
37288
+ ensureTrace,
37289
+ ensureTraceStarted,
37290
+ endTrace,
37291
+ turnHistoryRef,
37292
+ noteTurnEnded
35940
37293
  };
35941
37294
  }
35942
37295
  var init_use_session_manager = __esm({
@@ -35948,6 +37301,7 @@ var init_use_session_manager = __esm({
35948
37301
  init_recall_inject();
35949
37302
  init_usage_tracker();
35950
37303
  init_log_sink();
37304
+ init_trace();
35951
37305
  init_app_helpers();
35952
37306
  init_continuation_summary();
35953
37307
  init_config();
@@ -35986,9 +37340,9 @@ var init_resume_progress = __esm({
35986
37340
  });
35987
37341
 
35988
37342
  // src/agent/supervisor.ts
35989
- import { readdir as readdir8, readFile as readFile30, stat as stat8 } from "fs/promises";
35990
- import { createHash as createHash5 } from "crypto";
35991
- import { resolve as resolve13, join as join44 } from "path";
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";
35992
37346
  var TurnSupervisor;
35993
37347
  var init_supervisor = __esm({
35994
37348
  "src/agent/supervisor.ts"() {
@@ -36375,13 +37729,13 @@ var init_input_handlers = __esm({
36375
37729
  });
36376
37730
 
36377
37731
  // src/init/context-generator.ts
36378
- import { existsSync as existsSync6, statSync as statSync6 } from "fs";
36379
- import { join as join45 } from "path";
37732
+ import { existsSync as existsSync9, statSync as statSync6 } from "fs";
37733
+ import { join as join49 } from "path";
36380
37734
  function detectFlavor(cwd) {
36381
37735
  for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
36382
37736
  if (flavor === "generic") continue;
36383
37737
  for (const sig of signatures) {
36384
- const path = join45(cwd, sig);
37738
+ const path = join49(cwd, sig);
36385
37739
  if (sig.includes("*")) {
36386
37740
  try {
36387
37741
  const parts = sig.split("*");
@@ -36393,7 +37747,7 @@ function detectFlavor(cwd) {
36393
37747
  }
36394
37748
  } catch {
36395
37749
  }
36396
- } else if (existsSync6(path)) {
37750
+ } else if (existsSync9(path)) {
36397
37751
  return flavor;
36398
37752
  }
36399
37753
  }
@@ -36402,14 +37756,14 @@ function detectFlavor(cwd) {
36402
37756
  }
36403
37757
  function findFile(cwd, candidates) {
36404
37758
  for (const c of candidates) {
36405
- if (existsSync6(join45(cwd, c))) return c;
37759
+ if (existsSync9(join49(cwd, c))) return c;
36406
37760
  }
36407
37761
  return null;
36408
37762
  }
36409
37763
  function findSourceRoots(cwd) {
36410
37764
  const roots = [];
36411
37765
  for (const r of SOURCE_ROOT_CANDIDATES) {
36412
- const p = join45(cwd, r);
37766
+ const p = join49(cwd, r);
36413
37767
  try {
36414
37768
  const s = statSync6(p);
36415
37769
  if (s.isDirectory()) roots.push(r);
@@ -36420,9 +37774,9 @@ function findSourceRoots(cwd) {
36420
37774
  }
36421
37775
  function findCiConfig(cwd) {
36422
37776
  for (const c of CI_PATHS) {
36423
- if (existsSync6(join45(cwd, c))) {
37777
+ if (existsSync9(join49(cwd, c))) {
36424
37778
  try {
36425
- const s = statSync6(join45(cwd, c));
37779
+ const s = statSync6(join49(cwd, c));
36426
37780
  return s.isDirectory() ? c : c;
36427
37781
  } catch {
36428
37782
  }
@@ -36559,7 +37913,7 @@ function analyzeProject(cwd) {
36559
37913
  ciConfig: findCiConfig(cwd),
36560
37914
  readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
36561
37915
  sourceRoots: findSourceRoots(cwd),
36562
- hasGit: existsSync6(join45(cwd, ".git"))
37916
+ hasGit: existsSync9(join49(cwd, ".git"))
36563
37917
  };
36564
37918
  }
36565
37919
  function bashDiscoveryCommands(profile) {
@@ -36724,7 +38078,7 @@ Aim for 100\u2013200 lines total. Use markdown tables where they save space.
36724
38078
  }
36725
38079
  function buildInitPrompt(cwd) {
36726
38080
  const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
36727
- (n) => existsSync6(join45(cwd, n))
38081
+ (n) => existsSync9(join49(cwd, n))
36728
38082
  );
36729
38083
  const isRefresh = existingName !== void 0;
36730
38084
  const targetFilename = existingName ?? "AGENTS.md";
@@ -36807,8 +38161,8 @@ var init_context_generator = __esm({
36807
38161
  });
36808
38162
 
36809
38163
  // src/init/run-init.ts
36810
- import { existsSync as existsSync7 } from "fs";
36811
- import { join as join46 } from "path";
38164
+ import { existsSync as existsSync10 } from "fs";
38165
+ import { join as join50 } from "path";
36812
38166
  async function runInit(deps) {
36813
38167
  const {
36814
38168
  cfg,
@@ -37006,7 +38360,7 @@ async function runInit(deps) {
37006
38360
  }
37007
38361
  }
37008
38362
  });
37009
- if (existsSync7(join46(cwd, targetFilename))) {
38363
+ if (existsSync10(join50(cwd, targetFilename))) {
37010
38364
  if (cacheStableRef.current) {
37011
38365
  messagesRef.current[1] = {
37012
38366
  role: "system",
@@ -37098,11 +38452,11 @@ var init_run_init = __esm({
37098
38452
  });
37099
38453
 
37100
38454
  // src/skills/discovery.ts
37101
- import { readdir as readdir9, stat as stat9, readFile as readFile31 } from "fs/promises";
37102
- import { join as join47, extname as extname2 } from "path";
38455
+ import { readdir as readdir10, stat as stat10, readFile as readFile31 } from "fs/promises";
38456
+ import { join as join51, extname as extname2 } from "path";
37103
38457
  async function dirExists(path) {
37104
38458
  try {
37105
- const s = await stat9(path);
38459
+ const s = await stat10(path);
37106
38460
  return s.isDirectory();
37107
38461
  } catch {
37108
38462
  return false;
@@ -37110,7 +38464,7 @@ async function dirExists(path) {
37110
38464
  }
37111
38465
  async function fileExists(path) {
37112
38466
  try {
37113
- const s = await stat9(path);
38467
+ const s = await stat10(path);
37114
38468
  return s.isFile();
37115
38469
  } catch {
37116
38470
  return false;
@@ -37118,20 +38472,20 @@ async function fileExists(path) {
37118
38472
  }
37119
38473
  async function scanSkillDir(dirPath, source) {
37120
38474
  if (!await dirExists(dirPath)) return [];
37121
- const entries = await readdir9(dirPath, { withFileTypes: true });
38475
+ const entries = await readdir10(dirPath, { withFileTypes: true });
37122
38476
  const files = [];
37123
38477
  for (const entry of entries) {
37124
38478
  if (!entry.isFile()) continue;
37125
38479
  if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
37126
- files.push({ filePath: join47(dirPath, entry.name), source });
38480
+ files.push({ filePath: join51(dirPath, entry.name), source });
37127
38481
  }
37128
38482
  return files;
37129
38483
  }
37130
38484
  async function discoverSkills(cwd) {
37131
- const agentsSkills = await scanSkillDir(join47(cwd, ".agents", "skills"), "agents");
37132
- const agentsMd = await fileExists(join47(cwd, "AGENTS.md")) ? [{ filePath: join47(cwd, "AGENTS.md"), source: "agents-md" }] : [];
37133
- const githubSkills = await scanSkillDir(join47(cwd, ".github", "skills"), "github");
37134
- const kimiflareSkills = await scanSkillDir(join47(cwd, ".kimiflare", "skills"), "kimiflare");
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");
37135
38489
  const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
37136
38490
  return ordered;
37137
38491
  }
@@ -37148,9 +38502,9 @@ var init_discovery = __esm({
37148
38502
  });
37149
38503
 
37150
38504
  // src/skills/parser.ts
37151
- import { createHash as createHash6 } from "crypto";
38505
+ import { createHash as createHash7 } from "crypto";
37152
38506
  function sha2562(input) {
37153
- return createHash6("sha256").update(input).digest("hex");
38507
+ return createHash7("sha256").update(input).digest("hex");
37154
38508
  }
37155
38509
  function computeContentHash(rawText, parserVersion) {
37156
38510
  return sha2562(`${rawText}
@@ -37341,8 +38695,8 @@ var init_skills = __esm({
37341
38695
  });
37342
38696
 
37343
38697
  // src/ui/run-startup-tasks.ts
37344
- import { existsSync as existsSync8 } from "fs";
37345
- import { join as join48 } from "path";
38698
+ import { existsSync as existsSync11 } from "fs";
38699
+ import { join as join52 } from "path";
37346
38700
  function runStartupTasks(deps) {
37347
38701
  const {
37348
38702
  cfg,
@@ -37356,6 +38710,7 @@ function runStartupTasks(deps) {
37356
38710
  } = deps;
37357
38711
  const auth = llmAuthFromConfig(cfg);
37358
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 }));
37359
38714
  void Promise.resolve().then(() => (init_log_sink(), log_sink_exports)).then(({ pruneOldLogs: pruneOldLogs2, isLogSinkEnabled: isLogSinkEnabled2 }) => {
37360
38715
  if (!isLogSinkEnabled2()) return;
37361
38716
  try {
@@ -37364,7 +38719,7 @@ function runStartupTasks(deps) {
37364
38719
  }
37365
38720
  });
37366
38721
  if (cfg.memoryEnabled) {
37367
- const dbPath = cfg.memoryDbPath ?? join48(process.cwd(), ".kimiflare", "memory.db");
38722
+ const dbPath = cfg.memoryDbPath ?? join52(process.cwd(), ".kimiflare", "memory.db");
37368
38723
  const manager = new MemoryManager({
37369
38724
  dbPath,
37370
38725
  ...auth,
@@ -37396,7 +38751,7 @@ function runStartupTasks(deps) {
37396
38751
  });
37397
38752
  const cwd = process.cwd();
37398
38753
  sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
37399
- if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(join48(cwd, name)))) {
38754
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync11(join52(cwd, name)))) {
37400
38755
  const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
37401
38756
  const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
37402
38757
  if (driftCount >= 5) {
@@ -37407,7 +38762,7 @@ function runStartupTasks(deps) {
37407
38762
  memoryManagerRef.current?.close();
37408
38763
  memoryManagerRef.current = null;
37409
38764
  }
37410
- const skillDbPath = cfg.memoryDbPath ?? join48(process.cwd(), ".kimiflare", "memory.db");
38765
+ const skillDbPath = cfg.memoryDbPath ?? join52(process.cwd(), ".kimiflare", "memory.db");
37411
38766
  const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
37412
38767
  initSkillsSchema(skillDb);
37413
38768
  void indexSkills({
@@ -37652,7 +39007,8 @@ async function runCompact(deps) {
37652
39007
  pendingToolCallsRef,
37653
39008
  hooks,
37654
39009
  sessionId,
37655
- supervisorRef
39010
+ supervisorRef,
39011
+ trace
37656
39012
  } = deps;
37657
39013
  if (busy || supervisorRef.current.isRunning) {
37658
39014
  setEvents((e) => [
@@ -37688,6 +39044,7 @@ async function runCompact(deps) {
37688
39044
  if (result2.metrics.rawTurnsRemoved === 0) {
37689
39045
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to compact yet" }]);
37690
39046
  } else {
39047
+ traceCompiledCompaction(trace, result2);
37691
39048
  messagesRef.current = result2.newMessages;
37692
39049
  sessionStateRef.current = result2.newState;
37693
39050
  setEvents(
@@ -37706,6 +39063,7 @@ async function runCompact(deps) {
37706
39063
  await saveSessionSafe();
37707
39064
  }
37708
39065
  } else {
39066
+ const before = messagesRef.current;
37709
39067
  const result2 = await summarizeMessagesViaLlm({
37710
39068
  ...llmAuthFromConfig(cfg),
37711
39069
  model: cfg.model,
@@ -37715,6 +39073,7 @@ async function runCompact(deps) {
37715
39073
  if (result2.replacedCount === 0) {
37716
39074
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "nothing to compact yet" }]);
37717
39075
  } else {
39076
+ traceLlmCompaction(trace, before, result2);
37718
39077
  messagesRef.current = result2.newMessages;
37719
39078
  setEvents(
37720
39079
  (e) => compactEventsVisual(
@@ -37755,12 +39114,13 @@ var init_run_compact = __esm({
37755
39114
  init_logger();
37756
39115
  init_app_helpers();
37757
39116
  init_llm_auth();
39117
+ init_compaction();
37758
39118
  }
37759
39119
  });
37760
39120
 
37761
39121
  // src/commands/save.ts
37762
39122
  import { mkdir as mkdir16, writeFile as writeFile15, unlink as unlink5 } from "fs/promises";
37763
- import { dirname as dirname20 } from "path";
39123
+ import { dirname as dirname21 } from "path";
37764
39124
  async function saveCustomCommand(opts2) {
37765
39125
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
37766
39126
  const filepath = `${dir}/${opts2.name}.md`;
@@ -37771,7 +39131,7 @@ async function saveCustomCommand(opts2) {
37771
39131
  if (opts2.effort) data.effort = opts2.effort;
37772
39132
  const frontmatter = serializeFrontmatter(data);
37773
39133
  const content = frontmatter + opts2.template;
37774
- await mkdir16(dirname20(filepath), { recursive: true });
39134
+ await mkdir16(dirname21(filepath), { recursive: true });
37775
39135
  await writeFile15(filepath, content, "utf8");
37776
39136
  return { filepath };
37777
39137
  }
@@ -37894,9 +39254,9 @@ __export(app_exports, {
37894
39254
  });
37895
39255
  import React25, { useState as useState27, useRef as useRef9, useEffect as useEffect11, useCallback as useCallback8, useMemo as useMemo6 } from "react";
37896
39256
  import { Box as Box40, Text as Text41, useApp, useInput as useInput19, useWindowSize as useWindowSize2, render } from "ink";
37897
- import { existsSync as existsSync9 } from "fs";
37898
- import { join as join49, resolve as resolve14 } from "path";
37899
- import { homedir as homedir29 } from "os";
39257
+ import { existsSync as existsSync12 } from "fs";
39258
+ import { join as join53, resolve as resolve14 } from "path";
39259
+ import { homedir as homedir30 } from "os";
37900
39260
  import { jsx as jsx42, jsxs as jsxs39 } from "react/jsx-runtime";
37901
39261
  function App({
37902
39262
  initialCfg,
@@ -38153,6 +39513,8 @@ ${wcagWarnings.join("\n")}` }
38153
39513
  }, []);
38154
39514
  const sessionStartRecallRef = useRef9(null);
38155
39515
  const kimiMdStaleNudgedRef = useRef9(false);
39516
+ const currentTurnIdRef = useRef9(null);
39517
+ const denyFeedbackRef = useRef9(false);
38156
39518
  const sessionPlanRef = useRef9(null);
38157
39519
  const planCompletePlanRef = useRef9(null);
38158
39520
  const sessionMgr = useSessionManager({
@@ -38190,7 +39552,11 @@ ${wcagWarnings.join("\n")}` }
38190
39552
  doResumeSession,
38191
39553
  handleResumePick,
38192
39554
  handleCheckpointPick,
38193
- resetSession
39555
+ resetSession,
39556
+ traceRef,
39557
+ ensureTraceStarted,
39558
+ turnHistoryRef,
39559
+ noteTurnEnded
38194
39560
  } = sessionMgr;
38195
39561
  const pendingTextRef = useRef9(/* @__PURE__ */ new Map());
38196
39562
  const flushTimeoutRef = useRef9(null);
@@ -38216,7 +39582,7 @@ ${wcagWarnings.join("\n")}` }
38216
39582
  const exactDirectory = query === "~" || query === "." || query === ".." || query.endsWith("/..");
38217
39583
  const prefix = exactDirectory ? `${query.replace(/\/$/, "")}/` : slash >= 0 ? query.slice(0, slash + 1) : "";
38218
39584
  const directory = exactDirectory ? query : slash >= 0 ? query.slice(0, slash + 1) : query;
38219
- const expanded = directory.startsWith("~") ? join49(homedir29(), directory.slice(1)) : directory;
39585
+ const expanded = directory.startsWith("~") ? join53(homedir30(), directory.slice(1)) : directory;
38220
39586
  const target = resolve14(cwd, expanded || ".");
38221
39587
  const entries2 = await glob("*", {
38222
39588
  cwd: target,
@@ -38477,6 +39843,7 @@ ${wcagWarnings.join("\n")}` }
38477
39843
  store
38478
39844
  });
38479
39845
  if (result2.metrics.rawTurnsRemoved > 0) {
39846
+ traceCompiledCompaction(traceRef.current, result2, currentTurnIdRef.current);
38480
39847
  sessionStateRef.current = result2.newState;
38481
39848
  setEvents((e) => [
38482
39849
  ...e,
@@ -38522,6 +39889,7 @@ ${wcagWarnings.join("\n")}` }
38522
39889
  signal
38523
39890
  });
38524
39891
  if (result2.replacedCount > 0) {
39892
+ traceLlmCompaction(traceRef.current, messages, result2, currentTurnIdRef.current);
38525
39893
  setEvents((e) => [
38526
39894
  ...e,
38527
39895
  {
@@ -38695,7 +40063,8 @@ ${wcagWarnings.join("\n")}` }
38695
40063
  pendingToolCallsRef,
38696
40064
  hooks: hooksManagerRef.current,
38697
40065
  sessionId: sessionIdRef.current,
38698
- supervisorRef
40066
+ supervisorRef,
40067
+ trace: traceRef.current
38699
40068
  });
38700
40069
  }, [cfg, busy, saveSessionSafe]);
38701
40070
  const runInit2 = useCallback8(async () => {
@@ -38875,7 +40244,9 @@ ${wcagWarnings.join("\n")}` }
38875
40244
  compiledContextRef,
38876
40245
  lastApiErrorRef,
38877
40246
  activeScopeRef,
38878
- sessionPlanRef
40247
+ sessionPlanRef,
40248
+ traceRef,
40249
+ turnHistoryRef
38879
40250
  }), [
38880
40251
  exit,
38881
40252
  busy,
@@ -39178,6 +40549,8 @@ ${result2.output ?? "(output not captured)"}`,
39178
40549
  }
39179
40550
  }
39180
40551
  messagesRef.current.push({ role: "user", content });
40552
+ const traceUserSource = denyFeedbackRef.current ? "deny_feedback" : "typed";
40553
+ denyFeedbackRef.current = false;
39181
40554
  await saveSessionSafe();
39182
40555
  if (compiledContextRef.current) {
39183
40556
  const { ids, recalled } = recallArtifacts(messagesRef.current, artifactStoreRef.current, sessionStateRef.current);
@@ -39191,7 +40564,7 @@ ${result2.output ?? "(output not captured)"}`,
39191
40564
  }
39192
40565
  }
39193
40566
  turnCounterRef.current += 1;
39194
- if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(join49(process.cwd(), name))) && !kimiMdStale) {
40567
+ if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync12(join53(process.cwd(), name))) && !kimiMdStale) {
39195
40568
  setEvents((e) => [
39196
40569
  ...e,
39197
40570
  { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps autopilot (formerly kimiflare) stay accurate as your project evolves." }
@@ -39215,7 +40588,7 @@ ${result2.output ?? "(output not captured)"}`,
39215
40588
  };
39216
40589
  ensureSessionId();
39217
40590
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39218
- const filePath = join49(sessionsDir3(), `${sessionIdRef.current}.json`);
40591
+ const filePath = join53(sessionsDir3(), `${sessionIdRef.current}.json`);
39219
40592
  await addCheckpoint(filePath, cp);
39220
40593
  }
39221
40594
  const summary = await generateContinuationSummary({
@@ -39464,6 +40837,34 @@ ${result2.output ?? "(output not captured)"}`,
39464
40837
  offered: turnTools.some((tool) => tool.name === "subagent"),
39465
40838
  guidance: delegationGuidance.kind
39466
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
+ }
39467
40868
  supervisorRef.current.startTurn(
39468
40869
  {
39469
40870
  ...llmAuthFromConfig(cfg),
@@ -39509,6 +40910,8 @@ ${result2.output ?? "(output not captured)"}`,
39509
40910
  },
39510
40911
  mode: modeRef.current,
39511
40912
  cacheStable: cacheStableRef.current,
40913
+ trace,
40914
+ turnId,
39512
40915
  onFileChange: (path, content2) => {
39513
40916
  if (content2) {
39514
40917
  lspManagerRef.current.notifyChange(path, content2);
@@ -39523,6 +40926,7 @@ ${result2.output ?? "(output not captured)"}`,
39523
40926
  },
39524
40927
  {
39525
40928
  onDone: () => {
40929
+ noteTurnEnded(turnId);
39526
40930
  void (async () => {
39527
40931
  await saveSessionSafe();
39528
40932
  if (turnScope.signal.aborted) {
@@ -39550,6 +40954,7 @@ ${result2.output ?? "(output not captured)"}`,
39550
40954
  store
39551
40955
  });
39552
40956
  if (result2.metrics.rawTurnsRemoved > 0) {
40957
+ traceCompiledCompaction(traceRef.current, result2, turnId);
39553
40958
  messagesRef.current = result2.newMessages;
39554
40959
  sessionStateRef.current = result2.newState;
39555
40960
  setEvents((e) => [
@@ -39565,6 +40970,7 @@ ${result2.output ?? "(output not captured)"}`,
39565
40970
  }
39566
40971
  } else {
39567
40972
  try {
40973
+ const before = messagesRef.current;
39568
40974
  const result2 = await summarizeMessagesViaLlm({
39569
40975
  ...llmAuthFromConfig(cfg),
39570
40976
  model: cfg.model,
@@ -39572,6 +40978,7 @@ ${result2.output ?? "(output not captured)"}`,
39572
40978
  signal: turnScope.signal
39573
40979
  });
39574
40980
  if (result2.replacedCount > 0) {
40981
+ traceLlmCompaction(traceRef.current, before, result2, turnId);
39575
40982
  messagesRef.current = result2.newMessages;
39576
40983
  setEvents((e) => [
39577
40984
  ...e,
@@ -39637,6 +41044,7 @@ ${result2.output ?? "(output not captured)"}`,
39637
41044
  keepLastTurns: 1
39638
41045
  });
39639
41046
  if (result2.metrics.rawTurnsRemoved > 0) {
41047
+ traceCompiledCompaction(traceRef.current, result2, turnId);
39640
41048
  messagesRef.current = result2.newMessages;
39641
41049
  sessionStateRef.current = result2.newState;
39642
41050
  setEvents((e) => [
@@ -39652,6 +41060,7 @@ ${result2.output ?? "(output not captured)"}`,
39652
41060
  }
39653
41061
  } else {
39654
41062
  try {
41063
+ const before = messagesRef.current;
39655
41064
  const result2 = await summarizeMessagesViaLlm({
39656
41065
  ...llmAuthFromConfig(cfg),
39657
41066
  model: cfg.model,
@@ -39660,6 +41069,7 @@ ${result2.output ?? "(output not captured)"}`,
39660
41069
  signal: turnScope.signal
39661
41070
  });
39662
41071
  if (result2.replacedCount > 0) {
41072
+ traceLlmCompaction(traceRef.current, before, result2, turnId);
39663
41073
  messagesRef.current = result2.newMessages;
39664
41074
  setEvents((e) => [
39665
41075
  ...e,
@@ -39700,7 +41110,7 @@ ${result2.output ?? "(output not captured)"}`,
39700
41110
  };
39701
41111
  ensureSessionId();
39702
41112
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39703
- const filePath = join49(sessionsDir3(), `${sessionIdRef.current}.json`);
41113
+ const filePath = join53(sessionsDir3(), `${sessionIdRef.current}.json`);
39704
41114
  await addCheckpoint(filePath, cp);
39705
41115
  }
39706
41116
  const summary = await generateContinuationSummary({
@@ -39751,6 +41161,7 @@ ${result2.output ?? "(output not captured)"}`,
39751
41161
  }
39752
41162
  },
39753
41163
  onError: async (e) => {
41164
+ noteTurnEnded(turnId);
39754
41165
  if (e.name === "AbortError") {
39755
41166
  for (const [tcId, tcName] of pendingToolCallsRef.current) {
39756
41167
  messagesRef.current.push({
@@ -39782,7 +41193,7 @@ ${result2.output ?? "(output not captured)"}`,
39782
41193
  }
39783
41194
  );
39784
41195
  },
39785
- [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand]
41196
+ [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand, ensureTraceStarted]
39786
41197
  );
39787
41198
  useEffect11(() => {
39788
41199
  if (!busy && queuePlanDraft === null && supervisorRef.current.phase === "idle") {
@@ -40283,7 +41694,7 @@ ${result2.output ?? "(output not captured)"}`,
40283
41694
  lsp: {
40284
41695
  servers: cfg?.lspServers ?? {},
40285
41696
  scope: lspScope,
40286
- hasProjectDir: existsSync9(join49(process.cwd(), ".kimiflare"))
41697
+ hasProjectDir: existsSync12(join53(process.cwd(), ".kimiflare"))
40287
41698
  }
40288
41699
  });
40289
41700
  });
@@ -40340,7 +41751,7 @@ ${result2.output ?? "(output not captured)"}`,
40340
41751
  onCommandDelete: handleCommandDelete2,
40341
41752
  lspServers: cfg?.lspServers ?? {},
40342
41753
  lspScope,
40343
- hasProjectDir: existsSync9(join49(process.cwd(), ".kimiflare")),
41754
+ hasProjectDir: existsSync12(join53(process.cwd(), ".kimiflare")),
40344
41755
  onLspSave: handleLspSave2,
40345
41756
  themes: themeList(),
40346
41757
  onPickTheme: handleThemePick,
@@ -40411,6 +41822,7 @@ ${result2.output ?? "(output not captured)"}`,
40411
41822
  args: perm.args,
40412
41823
  onDecide: decidePermission2,
40413
41824
  onFeedback: (text) => {
41825
+ denyFeedbackRef.current = true;
40414
41826
  submitRef.current(text);
40415
41827
  }
40416
41828
  }
@@ -40676,6 +42088,8 @@ var init_app = __esm({
40676
42088
  init_run_startup_tasks();
40677
42089
  init_manager_init();
40678
42090
  init_run_compact();
42091
+ init_trace();
42092
+ init_registry2();
40679
42093
  init_distill();
40680
42094
  init_plan_resolver();
40681
42095
  init_user_shell();
@@ -40821,6 +42235,8 @@ init_glob();
40821
42235
  init_permissions_evaluator();
40822
42236
  init_classify();
40823
42237
  init_subagent_policy();
42238
+ init_trace();
42239
+ init_registry2();
40824
42240
  import { readFile as readFile15 } from "fs/promises";
40825
42241
  import { resolve as resolve6, basename as basename3 } from "path";
40826
42242
  async function resolveSession(opts2) {
@@ -40860,8 +42276,8 @@ async function resolveFiles(filePatterns, cwd) {
40860
42276
  const resolved = /* @__PURE__ */ new Set();
40861
42277
  for (const pattern of filePatterns) {
40862
42278
  try {
40863
- const stat10 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
40864
- if (stat10.isFile()) {
42279
+ const stat11 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
42280
+ if (stat11.isFile()) {
40865
42281
  resolved.add(resolve6(cwd, pattern));
40866
42282
  continue;
40867
42283
  }
@@ -40955,24 +42371,43 @@ async function runPrintMode(opts2) {
40955
42371
  });
40956
42372
  }
40957
42373
  const messages = [];
42374
+ const systemPrompt = buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests });
40958
42375
  if (isNew || sessionFile.messages.length === 0) {
40959
- messages.push({
40960
- role: "system",
40961
- content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
40962
- });
42376
+ messages.push({ role: "system", content: systemPrompt });
40963
42377
  } else {
40964
42378
  const nonSystem = sessionFile.messages.filter(
40965
42379
  (m) => m.role !== "system" || typeof m.content === "string" && m.content.startsWith("[compiled session state]")
40966
42380
  );
40967
- messages.push({
40968
- role: "system",
40969
- content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
40970
- });
42381
+ messages.push({ role: "system", content: systemPrompt });
40971
42382
  messages.push(...nonSystem);
40972
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";
40973
42400
  const files = opts2.files ? await resolveFiles(opts2.files, cwd) : [];
40974
42401
  const { content: userContent } = await buildUserMessage(opts2.prompt, files, cwd);
40975
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
+ }
40976
42411
  const controller = new AbortController();
40977
42412
  process.on("SIGINT", () => controller.abort());
40978
42413
  const format = opts2.format ?? "text";
@@ -40996,6 +42431,7 @@ async function runPrintMode(opts2) {
40996
42431
  onUsageFinal: (u, meta) => {
40997
42432
  const lookup = opts2.openrouterApiKey && !auth.customEndpoint && meta?.generationId ? { apiKey: opts2.openrouterApiKey, meta } : void 0;
40998
42433
  usageWrites.push(recordUsage(sessionFile.id, u, lookup, opts2.model).catch(() => void 0));
42434
+ if (typeof u.cost === "number") traceCostUsd += u.cost;
40999
42435
  },
41000
42436
  onReasoningDelta: opts2.showReasoning ? (delta) => {
41001
42437
  if (format === "text") {
@@ -41137,7 +42573,9 @@ async function runPrintMode(opts2) {
41137
42573
  // --continue sessions grow across runs; archive older turns onto the
41138
42574
  // session file (saved below) instead of failing on context size.
41139
42575
  compaction: sessionFileCompactionTarget(sessionFile),
41140
- callbacks
42576
+ callbacks,
42577
+ trace,
42578
+ turnId
41141
42579
  });
41142
42580
  } catch (err) {
41143
42581
  if (err instanceof BudgetExhaustedError) {
@@ -41167,9 +42605,12 @@ async function runPrintMode(opts2) {
41167
42605
  process.exitCode = 1;
41168
42606
  return;
41169
42607
  }
42608
+ traceEndReason = "crash";
41170
42609
  throw err;
41171
42610
  } finally {
41172
42611
  await Promise.all(usageWrites);
42612
+ trace.emit("session_end", { reason: traceEndReason, total_cost_usd: traceCostUsd || void 0 }, null);
42613
+ await trace.flush();
41173
42614
  }
41174
42615
  sessionFile.messages = messages;
41175
42616
  sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -41483,7 +42924,9 @@ To use Requesty instead, set REQUESTY_API_KEY or run autopilot auth requesty`
41483
42924
  dir: opts.dir,
41484
42925
  title: opts.title,
41485
42926
  permissions: cfg.permissions,
41486
- workerProfile: opts.workerProfile
42927
+ workerProfile: opts.workerProfile,
42928
+ trace: cfg.trace,
42929
+ traceConfigSnapshot: cfg
41487
42930
  });
41488
42931
  return;
41489
42932
  }