@jun133/kitty 0.0.13 → 0.0.15

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.
@@ -1,27 +1,20 @@
1
1
  import {
2
- PROJECT_STATE_DIR_NAME,
3
- PROJECT_STATE_IGNORE_FILE_NAME,
4
2
  applyCurrentTurnFrame,
5
3
  buildRunTurnResult,
4
+ collapseContentParts,
6
5
  createEmptyAssistantResponseTransition,
7
6
  createExecutionWaitYieldTransition,
8
7
  createFinalizeTransition,
9
8
  createInternalReminder,
10
9
  createMessage,
11
- createProviderClientPool,
12
10
  createProviderRecoveryTransition,
13
11
  createToolMessage,
14
- ensureProjectStateDirectories,
15
12
  expandStartToToolBoundary,
16
- fetchAssistantResponse,
17
13
  fingerprintFocus,
18
14
  formatSessionMemorySectionList,
19
15
  formatSessionMemorySectionTemplate,
20
16
  formatTodoBlock,
21
- getProjectStatePaths,
22
- isAbortError,
23
17
  isInternalMessage,
24
- isRetryableApiError,
25
18
  normalizeCheckpoint,
26
19
  normalizeText,
27
20
  normalizeTodoItems,
@@ -31,20 +24,20 @@ import {
31
24
  noteCheckpointTransition,
32
25
  noteCheckpointTurnInput,
33
26
  noteSessionDiff,
27
+ readReasoningContent,
34
28
  readUserInput,
35
- recordObservabilityEvent,
36
29
  recordSessionWorksetFile,
30
+ resolveModelProfile,
31
+ resolveProviderCapabilities,
37
32
  shouldIncludeStoredAssistantReasoning,
38
- sleepWithSignal,
39
33
  sliceCurrentUserInputFrame,
40
34
  takeLastUnique,
41
- throwIfAborted,
42
35
  updateSessionMemory
43
- } from "./chunk-3KMC6H5K.mjs";
36
+ } from "./chunk-S4QTRPZ7.mjs";
44
37
 
45
38
  // src/context/projectContext.ts
46
- import fs5 from "fs/promises";
47
- import path6 from "path";
39
+ import fs6 from "fs/promises";
40
+ import path7 from "path";
48
41
 
49
42
  // src/context/repoRoots.ts
50
43
  import fs from "fs/promises";
@@ -248,13 +241,67 @@ function formatList(values) {
248
241
  }
249
242
 
250
243
  // src/skills/discovery.ts
251
- import fs4 from "fs/promises";
252
- import path5 from "path";
244
+ import fs5 from "fs/promises";
245
+ import path6 from "path";
253
246
  import fg2 from "fast-glob";
254
247
 
255
248
  // src/utils/ignore.ts
249
+ import fs4 from "fs/promises";
250
+ import path4 from "path";
251
+
252
+ // src/project/statePaths.ts
256
253
  import fs3 from "fs/promises";
257
254
  import path3 from "path";
255
+ var PROJECT_STATE_DIR_NAME = ".kitty";
256
+ var PROJECT_STATE_ENV_FILE_NAME = ".env";
257
+ var PROJECT_STATE_ENV_EXAMPLE_FILE_NAME = ".env.example";
258
+ var PROJECT_STATE_IGNORE_FILE_NAME = ".kittyignore";
259
+ var PRESERVED_PROJECT_STATE_ENTRY_NAMES = [
260
+ PROJECT_STATE_ENV_FILE_NAME,
261
+ PROJECT_STATE_ENV_EXAMPLE_FILE_NAME
262
+ ];
263
+ function getProjectStatePaths(rootDir) {
264
+ const normalizedRoot = path3.resolve(rootDir);
265
+ const kittyDir = path3.join(normalizedRoot, PROJECT_STATE_DIR_NAME);
266
+ const extensionsDir = path3.join(kittyDir, "extensions");
267
+ const memoryDir = path3.join(kittyDir, "memory");
268
+ const observabilityDir = path3.join(kittyDir, "observability");
269
+ return {
270
+ rootDir: normalizedRoot,
271
+ kittyDir,
272
+ cacheDir: path3.join(kittyDir, "cache"),
273
+ sessionsDir: path3.join(kittyDir, "sessions"),
274
+ changesDir: path3.join(kittyDir, "changes"),
275
+ eventsDir: path3.join(kittyDir, "events"),
276
+ extensionsDir,
277
+ memoryDir,
278
+ evidenceMemoryDir: path3.join(memoryDir, "evidence"),
279
+ projectMemoryDir: path3.join(memoryDir, "project"),
280
+ sessionMemoryDir: path3.join(memoryDir, "sessions"),
281
+ userMemoryDir: path3.join(memoryDir, "user"),
282
+ controlPlaneLedgerFile: path3.join(kittyDir, "control-plane.sqlite"),
283
+ observabilityDir,
284
+ observabilityEventsDir: path3.join(observabilityDir, "events"),
285
+ observabilityCrashesDir: path3.join(observabilityDir, "crashes")
286
+ };
287
+ }
288
+ async function ensureProjectStateDirectories(rootDir) {
289
+ const paths = getProjectStatePaths(rootDir);
290
+ await fs3.mkdir(paths.extensionsDir, { recursive: true });
291
+ await fs3.mkdir(paths.cacheDir, { recursive: true });
292
+ await fs3.mkdir(paths.sessionsDir, { recursive: true });
293
+ await fs3.mkdir(paths.changesDir, { recursive: true });
294
+ await fs3.mkdir(paths.eventsDir, { recursive: true });
295
+ await fs3.mkdir(paths.evidenceMemoryDir, { recursive: true });
296
+ await fs3.mkdir(paths.projectMemoryDir, { recursive: true });
297
+ await fs3.mkdir(paths.sessionMemoryDir, { recursive: true });
298
+ await fs3.mkdir(paths.userMemoryDir, { recursive: true });
299
+ await fs3.mkdir(paths.observabilityEventsDir, { recursive: true });
300
+ await fs3.mkdir(paths.observabilityCrashesDir, { recursive: true });
301
+ return paths;
302
+ }
303
+
304
+ // src/utils/ignore.ts
258
305
  var BUILTIN_PATTERNS = [
259
306
  ".git/",
260
307
  "node_modules/",
@@ -268,11 +315,11 @@ async function loadProjectIgnoreRules(rootDir, cwd) {
268
315
  })).filter((rule) => Boolean(rule));
269
316
  const candidateFiles = uniqueIgnoreFiles([
270
317
  {
271
- path: path3.join(rootDir, PROJECT_STATE_DIR_NAME, PROJECT_STATE_IGNORE_FILE_NAME),
318
+ path: path4.join(rootDir, PROJECT_STATE_DIR_NAME, PROJECT_STATE_IGNORE_FILE_NAME),
272
319
  baseDir: rootDir
273
320
  },
274
321
  {
275
- path: path3.join(cwd, PROJECT_STATE_DIR_NAME, PROJECT_STATE_IGNORE_FILE_NAME),
322
+ path: path4.join(cwd, PROJECT_STATE_DIR_NAME, PROJECT_STATE_IGNORE_FILE_NAME),
276
323
  baseDir: cwd
277
324
  }
278
325
  ]);
@@ -332,7 +379,7 @@ function buildFastGlobIgnorePatterns(baseDir, rules) {
332
379
  }
333
380
  async function tryReadUtf8File(filePath) {
334
381
  try {
335
- return await fs3.readFile(filePath, "utf8");
382
+ return await fs4.readFile(filePath, "utf8");
336
383
  } catch (error) {
337
384
  const errno = error;
338
385
  if (errno.code === "ENOENT") {
@@ -383,11 +430,11 @@ function compileIgnoreRule(rawPattern, options) {
383
430
  };
384
431
  }
385
432
  function toRelativePosix(baseDir, targetPath) {
386
- const relativePath = path3.relative(path3.resolve(baseDir), path3.resolve(targetPath));
433
+ const relativePath = path4.relative(path4.resolve(baseDir), path4.resolve(targetPath));
387
434
  if (!relativePath || relativePath === ".") {
388
435
  return "";
389
436
  }
390
- if (relativePath.startsWith("..") || path3.isAbsolute(relativePath)) {
437
+ if (relativePath.startsWith("..") || path4.isAbsolute(relativePath)) {
391
438
  return null;
392
439
  }
393
440
  return relativePath.replace(/\\/g, "/");
@@ -453,21 +500,21 @@ function uniqueIgnoreFiles(files) {
453
500
  const seen = /* @__PURE__ */ new Set();
454
501
  const unique = [];
455
502
  for (const file of files) {
456
- const normalizedPath = path3.normalize(file.path);
503
+ const normalizedPath = path4.normalize(file.path);
457
504
  if (seen.has(normalizedPath)) {
458
505
  continue;
459
506
  }
460
507
  seen.add(normalizedPath);
461
508
  unique.push({
462
509
  path: normalizedPath,
463
- baseDir: path3.resolve(file.baseDir)
510
+ baseDir: path4.resolve(file.baseDir)
464
511
  });
465
512
  }
466
513
  return unique;
467
514
  }
468
515
 
469
516
  // src/skills/schema.ts
470
- import path4 from "path";
517
+ import path5 from "path";
471
518
  var FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
472
519
  var SkillSchemaError = class extends Error {
473
520
  constructor(message, filePath) {
@@ -487,7 +534,7 @@ function parseSkillSource(text, input) {
487
534
  return {
488
535
  name,
489
536
  description,
490
- path: path4.relative(input.rootDir, input.absolutePath) || "SKILL.md",
537
+ path: path5.relative(input.rootDir, input.absolutePath) || "SKILL.md",
491
538
  absolutePath: input.absolutePath,
492
539
  body,
493
540
  dependencies: parseDependencies(metadata.requires),
@@ -557,12 +604,12 @@ async function discoverSkills(rootDir, cwd, ignoreRules) {
557
604
  const seenNames = /* @__PURE__ */ new Map();
558
605
  const skills = [];
559
606
  for (const skillFile of skillFiles) {
560
- const normalizedPath = path5.normalize(skillFile);
607
+ const normalizedPath = path6.normalize(skillFile);
561
608
  if (seenPaths.has(normalizedPath) || isPathIgnored(normalizedPath, ignoreRules)) {
562
609
  continue;
563
610
  }
564
611
  seenPaths.add(normalizedPath);
565
- const skill = parseSkillSource(await fs4.readFile(normalizedPath, "utf8"), {
612
+ const skill = parseSkillSource(await fs5.readFile(normalizedPath, "utf8"), {
566
613
  absolutePath: normalizedPath,
567
614
  rootDir
568
615
  });
@@ -578,7 +625,7 @@ async function discoverSkills(rootDir, cwd, ignoreRules) {
578
625
  return skills.sort((left, right) => left.name.localeCompare(right.name));
579
626
  }
580
627
  async function listSkillResources(skillPath, rootDir, ignoreRules) {
581
- const skillDir = path5.dirname(skillPath);
628
+ const skillDir = path6.dirname(skillPath);
582
629
  const files = await fg2([...SKILL_RESOURCE_GLOBS], {
583
630
  cwd: skillDir,
584
631
  absolute: true,
@@ -592,11 +639,11 @@ async function listSkillResources(skillPath, rootDir, ignoreRules) {
592
639
  if (isPathIgnored(file, ignoreRules)) {
593
640
  continue;
594
641
  }
595
- const stat = await fs4.stat(file);
642
+ const stat = await fs5.stat(file);
596
643
  resources.push({
597
- path: path5.relative(rootDir, file),
644
+ path: path6.relative(rootDir, file),
598
645
  size: stat.size,
599
- kind: readSkillResourceKind(path5.relative(skillDir, file))
646
+ kind: readSkillResourceKind(path6.relative(skillDir, file))
600
647
  });
601
648
  }
602
649
  return resources;
@@ -643,14 +690,14 @@ function buildSkillPackageHealth(skill) {
643
690
  }
644
691
  async function findSkillFiles(rootDir, cwd) {
645
692
  const candidates = uniquePaths([
646
- path5.join(rootDir, "SKILL.md"),
647
- path5.join(cwd, "SKILL.md")
693
+ path6.join(rootDir, "SKILL.md"),
694
+ path6.join(cwd, "SKILL.md")
648
695
  ]);
649
696
  const roots = uniquePaths([
650
- path5.join(rootDir, ".skills"),
651
- path5.join(rootDir, "skills"),
652
- path5.join(cwd, ".skills"),
653
- path5.join(cwd, "skills")
697
+ path6.join(rootDir, ".skills"),
698
+ path6.join(rootDir, "skills"),
699
+ path6.join(cwd, ".skills"),
700
+ path6.join(cwd, "skills")
654
701
  ]);
655
702
  const files = [];
656
703
  for (const candidate of candidates) {
@@ -675,20 +722,20 @@ async function findSkillFiles(rootDir, cwd) {
675
722
  }
676
723
  async function isRegularFile(filePath) {
677
724
  try {
678
- return (await fs4.stat(filePath)).isFile();
725
+ return (await fs5.stat(filePath)).isFile();
679
726
  } catch {
680
727
  return false;
681
728
  }
682
729
  }
683
730
  async function isDirectory2(filePath) {
684
731
  try {
685
- return (await fs4.stat(filePath)).isDirectory();
732
+ return (await fs5.stat(filePath)).isDirectory();
686
733
  } catch {
687
734
  return false;
688
735
  }
689
736
  }
690
737
  function uniquePaths(paths) {
691
- return [...new Set(paths.map((item) => path5.normalize(item)))];
738
+ return [...new Set(paths.map((item) => path6.normalize(item)))];
692
739
  }
693
740
 
694
741
  // src/context/projectContext.ts
@@ -717,8 +764,8 @@ async function getInstructionFiles(rootDir, cwd) {
717
764
  const directories = getDirectoriesFromRootToCwd(rootDir, cwd);
718
765
  const results = [];
719
766
  for (const directory of directories) {
720
- const overridePath = path6.join(directory, "AGENTS.override.md");
721
- const agentsPath = path6.join(directory, "AGENTS.md");
767
+ const overridePath = path7.join(directory, "AGENTS.override.md");
768
+ const agentsPath = path7.join(directory, "AGENTS.md");
722
769
  if (await isRegularFile2(overridePath)) {
723
770
  results.push(await readInstructionFile(rootDir, overridePath, "AGENTS.override.md"));
724
771
  continue;
@@ -730,22 +777,22 @@ async function getInstructionFiles(rootDir, cwd) {
730
777
  return results;
731
778
  }
732
779
  function getDirectoriesFromRootToCwd(rootDir, cwd) {
733
- const absoluteRoot = path6.resolve(rootDir);
734
- const absoluteCwd = path6.resolve(cwd);
735
- const relativePath = path6.relative(absoluteRoot, absoluteCwd);
780
+ const absoluteRoot = path7.resolve(rootDir);
781
+ const absoluteCwd = path7.resolve(cwd);
782
+ const relativePath = path7.relative(absoluteRoot, absoluteCwd);
736
783
  if (!relativePath || relativePath === ".") {
737
784
  return [absoluteRoot];
738
785
  }
739
- const parts = relativePath.split(path6.sep).filter(Boolean);
786
+ const parts = relativePath.split(path7.sep).filter(Boolean);
740
787
  const directories = [absoluteRoot];
741
788
  for (let index = 0; index < parts.length; index += 1) {
742
- directories.push(path6.join(absoluteRoot, ...parts.slice(0, index + 1)));
789
+ directories.push(path7.join(absoluteRoot, ...parts.slice(0, index + 1)));
743
790
  }
744
791
  return directories;
745
792
  }
746
793
  async function isRegularFile2(filePath) {
747
794
  try {
748
- const stat = await fs5.stat(filePath);
795
+ const stat = await fs6.stat(filePath);
749
796
  return stat.isFile();
750
797
  } catch {
751
798
  return false;
@@ -754,9 +801,9 @@ async function isRegularFile2(filePath) {
754
801
  async function readInstructionFile(rootDir, absolutePath, filename) {
755
802
  return {
756
803
  path: absolutePath,
757
- relativePath: path6.relative(rootDir, absolutePath) || filename,
804
+ relativePath: path7.relative(rootDir, absolutePath) || filename,
758
805
  filename,
759
- content: await fs5.readFile(absolutePath, "utf8")
806
+ content: await fs6.readFile(absolutePath, "utf8")
760
807
  };
761
808
  }
762
809
  function concatInstructionFiles(files, maxBytes) {
@@ -832,7 +879,7 @@ function getErrorMessage(error) {
832
879
  return "Environment error: network connection failed; the current provider/base URL is unreachable. Check network, proxy settings, or `KITTY_BASE_URL`.";
833
880
  }
834
881
  if (status === 404 || lower.includes("returned 404")) {
835
- return "User-fixable error: provider returned 404. Check whether `KITTY_BASE_URL` is the correct OpenAI-compatible API base URL.";
882
+ return "User-fixable error: provider endpoint returned 404. Check `KITTY_PROVIDER`, `KITTY_MODEL`, and `KITTY_BASE_URL` as one provider profile; the selected provider may use Responses instead of Chat Completions.";
836
883
  }
837
884
  if (typeof status === "number" && status >= 500) {
838
885
  return `Provider error: service returned ${status}. Retry later or confirm the provider service is healthy.`;
@@ -884,39 +931,1149 @@ async function handleCompletedAssistantResponse(params) {
884
931
  transition: transition2
885
932
  };
886
933
  }
887
- const transition = createFinalizeTransition({
888
- changedPaths: params.changedPaths
934
+ const transition = createFinalizeTransition({
935
+ changedPaths: params.changedPaths
936
+ });
937
+ const session = await params.options.sessionStore.save(
938
+ noteCheckpointCompleted(
939
+ await params.options.sessionStore.appendMessages(params.session, [assistantMessage]),
940
+ transition
941
+ )
942
+ );
943
+ return {
944
+ kind: "return",
945
+ result: buildRunTurnResult({
946
+ session,
947
+ changedPaths: params.changedPaths,
948
+ transition
949
+ })
950
+ };
951
+ }
952
+ function emitAssistantReasoning(response, options) {
953
+ if (response.reasoningContent && options.config.showReasoning && !response.streamedReasoningContent) {
954
+ options.callbacks?.onReasoning?.(response.reasoningContent);
955
+ }
956
+ }
957
+ function emitAssistantFinalOutput(response, options) {
958
+ if (response.content && !response.streamedAssistantContent) {
959
+ options.callbacks?.onAssistantText?.(response.content);
960
+ }
961
+ if (response.content) {
962
+ options.callbacks?.onAssistantDone?.(response.content);
963
+ }
964
+ }
965
+ function hasVisibleAssistantResult(content) {
966
+ return typeof content === "string" && content.trim().length > 0;
967
+ }
968
+
969
+ // src/utils/abort.ts
970
+ function createAbortError(message = "Operation aborted") {
971
+ const error = new Error(message);
972
+ error.name = "AbortError";
973
+ error.code = "ABORT_ERR";
974
+ return error;
975
+ }
976
+ function isAbortError(error) {
977
+ if (!error) {
978
+ return false;
979
+ }
980
+ if (error instanceof Error) {
981
+ if (error.name === "AbortError") {
982
+ return true;
983
+ }
984
+ const code = String(error.code ?? "");
985
+ if (code === "ABORT_ERR" || code === "ERR_ABORTED" || code === "ABORTED") {
986
+ return true;
987
+ }
988
+ const message = error.message.toLowerCase();
989
+ if (message.includes("abort") || message.includes("aborted") || message.includes("cancelled") || message.includes("canceled")) {
990
+ return true;
991
+ }
992
+ }
993
+ if (typeof error === "object" && error && "cause" in error) {
994
+ return isAbortError(error.cause);
995
+ }
996
+ return false;
997
+ }
998
+ function throwIfAborted(signal, message) {
999
+ if (signal?.aborted) {
1000
+ throw createAbortError(message ?? "Operation aborted");
1001
+ }
1002
+ }
1003
+ function sleepWithSignal(ms, signal) {
1004
+ if (!signal) {
1005
+ return new Promise((resolve) => {
1006
+ setTimeout(resolve, ms);
1007
+ });
1008
+ }
1009
+ if (signal.aborted) {
1010
+ return Promise.reject(createAbortError("Sleep aborted"));
1011
+ }
1012
+ return new Promise((resolve, reject) => {
1013
+ const timer = setTimeout(() => {
1014
+ signal.removeEventListener("abort", onAbort);
1015
+ resolve();
1016
+ }, ms);
1017
+ const onAbort = () => {
1018
+ clearTimeout(timer);
1019
+ signal.removeEventListener("abort", onAbort);
1020
+ reject(createAbortError("Sleep aborted"));
1021
+ };
1022
+ signal.addEventListener("abort", onAbort);
1023
+ });
1024
+ }
1025
+
1026
+ // src/provider/apiRetry.ts
1027
+ var API_MAX_RETRIES = 3;
1028
+ var API_RETRY_BASE_DELAY_MS = 1200;
1029
+ async function withApiRetries(operation, abortSignal) {
1030
+ let lastError;
1031
+ for (let attempt = 1; attempt <= API_MAX_RETRIES; attempt += 1) {
1032
+ try {
1033
+ return await operation();
1034
+ } catch (error) {
1035
+ if (isAbortError(error)) {
1036
+ throw error;
1037
+ }
1038
+ lastError = error;
1039
+ if (!isRetryableApiError(error) || attempt === API_MAX_RETRIES) {
1040
+ break;
1041
+ }
1042
+ await sleepWithSignal(API_RETRY_BASE_DELAY_MS * attempt, abortSignal);
1043
+ }
1044
+ }
1045
+ throw lastError;
1046
+ }
1047
+ function isRetryableApiError(error) {
1048
+ const status = error.status;
1049
+ if (typeof status === "number") {
1050
+ return status === 408 || status === 409 || status === 429 || status >= 500;
1051
+ }
1052
+ const message = String(error.message ?? error).toLowerCase();
1053
+ return message.includes("timeout") || message.includes("network") || message.includes("connection error") || message.includes("connection reset") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("connect timeout") || message.includes("temporarily") || message.includes("rate limit") || message.includes("overloaded");
1054
+ }
1055
+
1056
+ // src/provider/usageNormalizer.ts
1057
+ function normalizeProviderUsage(usage) {
1058
+ if (!usage || typeof usage !== "object") {
1059
+ return void 0;
1060
+ }
1061
+ const record = usage;
1062
+ const promptDetails = readObject(record.prompt_tokens_details);
1063
+ const completionDetails = readObject(record.completion_tokens_details);
1064
+ const outputDetails = readObject(record.output_tokens_details);
1065
+ const cacheCreation = readObject(record.cache_creation);
1066
+ const inputTokens = readUsageNumber(record.prompt_tokens ?? record.input_tokens);
1067
+ const outputTokens = readUsageNumber(record.completion_tokens ?? record.output_tokens);
1068
+ const totalTokens = readUsageNumber(record.total_tokens);
1069
+ const reasoningTokens = readUsageNumber(
1070
+ completionDetails?.reasoning_tokens ?? outputDetails?.reasoning_tokens
1071
+ );
1072
+ const openAiCachedTokens = readUsageNumber(promptDetails?.cached_tokens);
1073
+ const deepSeekHitTokens = readUsageNumber(record.prompt_cache_hit_tokens);
1074
+ const deepSeekMissTokens = readUsageNumber(record.prompt_cache_miss_tokens);
1075
+ const anthropicCacheReadTokens = readUsageNumber(record.cache_read_input_tokens);
1076
+ const anthropicCacheCreationTokens = readUsageNumber(record.cache_creation_input_tokens) ?? sumUsageNumbers([
1077
+ cacheCreation?.ephemeral_1h_input_tokens,
1078
+ cacheCreation?.ephemeral_5m_input_tokens
1079
+ ]);
1080
+ const geminiCachedTokens = readUsageNumber(record.cachedContentTokenCount ?? record.cached_content_token_count);
1081
+ const cacheReadTokens = firstNumber(
1082
+ anthropicCacheReadTokens,
1083
+ openAiCachedTokens,
1084
+ geminiCachedTokens
1085
+ );
1086
+ const cacheHitTokens = firstNumber(deepSeekHitTokens, cacheReadTokens);
1087
+ const cacheMissTokens = deepSeekMissTokens;
1088
+ const cacheCreationTokens = anthropicCacheCreationTokens;
1089
+ const snapshot = {
1090
+ inputTokens,
1091
+ outputTokens,
1092
+ totalTokens,
1093
+ reasoningTokens,
1094
+ cacheReadTokens,
1095
+ cacheCreationTokens,
1096
+ cacheHitTokens,
1097
+ cacheMissTokens
1098
+ };
1099
+ const cacheHitRate = computeCacheHitRate(snapshot);
1100
+ if (cacheHitRate !== void 0) {
1101
+ snapshot.cacheHitRate = cacheHitRate;
1102
+ }
1103
+ return Object.values(snapshot).some((value) => typeof value === "number") ? snapshot : void 0;
1104
+ }
1105
+ function hasProviderUsageSnapshot(usage) {
1106
+ return Boolean(usage && Object.values(usage).some((value) => typeof value === "number"));
1107
+ }
1108
+ function computeCacheHitRate(snapshot) {
1109
+ if (typeof snapshot.cacheHitTokens === "number" && typeof snapshot.cacheMissTokens === "number") {
1110
+ return ratio(snapshot.cacheHitTokens, snapshot.cacheHitTokens + snapshot.cacheMissTokens);
1111
+ }
1112
+ if (typeof snapshot.cacheReadTokens === "number") {
1113
+ const denominator = (snapshot.inputTokens ?? 0) + snapshot.cacheReadTokens + (snapshot.cacheCreationTokens ?? 0);
1114
+ return ratio(snapshot.cacheReadTokens, denominator);
1115
+ }
1116
+ return void 0;
1117
+ }
1118
+ function ratio(numerator, denominator) {
1119
+ if (denominator <= 0) {
1120
+ return void 0;
1121
+ }
1122
+ return Math.round(numerator / denominator * 1e4) / 1e4;
1123
+ }
1124
+ function readObject(value) {
1125
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1126
+ }
1127
+ function firstNumber(...values) {
1128
+ return values.find((value) => typeof value === "number");
1129
+ }
1130
+ function sumUsageNumbers(values) {
1131
+ const numbers = values.map(readUsageNumber).filter((value) => typeof value === "number");
1132
+ if (numbers.length === 0) {
1133
+ return void 0;
1134
+ }
1135
+ return numbers.reduce((total, value) => total + value, 0);
1136
+ }
1137
+ function readUsageNumber(value) {
1138
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.round(value) : void 0;
1139
+ }
1140
+
1141
+ // src/observability/writer.ts
1142
+ import fs7 from "fs/promises";
1143
+ import path8 from "path";
1144
+
1145
+ // src/observability/schema.ts
1146
+ var OBSERVABILITY_VERSION = 1;
1147
+ function buildObservabilityEventRecord(input) {
1148
+ return {
1149
+ version: OBSERVABILITY_VERSION,
1150
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1151
+ event: normalizeText2(input.event, "unknown"),
1152
+ status: normalizeText2(input.status, "unknown"),
1153
+ host: normalizeOptionalText(input.host),
1154
+ sessionId: normalizeOptionalText(input.sessionId),
1155
+ executionId: normalizeOptionalText(input.executionId),
1156
+ identityKind: normalizeOptionalText(input.identityKind),
1157
+ identityName: normalizeOptionalText(input.identityName),
1158
+ durationMs: normalizeOptionalNumber(input.durationMs),
1159
+ toolName: normalizeOptionalText(input.toolName),
1160
+ model: normalizeOptionalText(input.model),
1161
+ error: normalizeObservabilityError(input.error),
1162
+ details: normalizeDetails(input.details)
1163
+ };
1164
+ }
1165
+ function normalizeObservabilityError(error) {
1166
+ if (error == null) {
1167
+ return void 0;
1168
+ }
1169
+ if (typeof error === "object" && error !== null && "message" in error) {
1170
+ const record = error;
1171
+ const message2 = normalizeText2(record.message, "");
1172
+ if (!message2) {
1173
+ return void 0;
1174
+ }
1175
+ return {
1176
+ message: message2,
1177
+ code: normalizeOptionalText(record.code),
1178
+ details: normalizeValue(record.details)
1179
+ };
1180
+ }
1181
+ const message = readErrorMessage(error);
1182
+ return message ? { message } : void 0;
1183
+ }
1184
+ function normalizeDetails(details) {
1185
+ if (!details || typeof details !== "object") {
1186
+ return void 0;
1187
+ }
1188
+ const normalized = normalizeValue(details);
1189
+ return normalized && typeof normalized === "object" && !Array.isArray(normalized) ? normalized : void 0;
1190
+ }
1191
+ function normalizeValue(value, depth = 0) {
1192
+ if (value == null) {
1193
+ return void 0;
1194
+ }
1195
+ if (depth >= 4) {
1196
+ return "[truncated]";
1197
+ }
1198
+ if (typeof value === "string") {
1199
+ return value.length <= 2e3 ? value : `${value.slice(0, 1997)}...`;
1200
+ }
1201
+ if (typeof value === "number") {
1202
+ return Number.isFinite(value) ? value : void 0;
1203
+ }
1204
+ if (typeof value === "boolean") {
1205
+ return value;
1206
+ }
1207
+ if (value instanceof Error) {
1208
+ return {
1209
+ name: normalizeText2(value.name, "Error"),
1210
+ message: normalizeText2(value.message, "Unknown error"),
1211
+ stack: normalizeValue(value.stack, depth + 1)
1212
+ };
1213
+ }
1214
+ if (Array.isArray(value)) {
1215
+ return value.slice(0, 20).map((item) => normalizeValue(item, depth + 1)).filter((item) => item !== void 0);
1216
+ }
1217
+ if (typeof value === "object") {
1218
+ const entries = Object.entries(value).slice(0, 30);
1219
+ const normalizedEntries = entries.map(([key, item]) => [key, normalizeValue(item, depth + 1)]).filter(([, item]) => item !== void 0);
1220
+ return Object.fromEntries(normalizedEntries);
1221
+ }
1222
+ return normalizeText2(String(value), "");
1223
+ }
1224
+ function normalizeText2(value, fallback) {
1225
+ const normalized = String(value ?? "").trim();
1226
+ return normalized || fallback;
1227
+ }
1228
+ function normalizeOptionalText(value) {
1229
+ const normalized = String(value ?? "").trim();
1230
+ return normalized || void 0;
1231
+ }
1232
+ function normalizeOptionalNumber(value) {
1233
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.round(value) : void 0;
1234
+ }
1235
+ function readErrorMessage(error) {
1236
+ if (error instanceof Error) {
1237
+ return normalizeText2(error.message, error.name || "Unknown error");
1238
+ }
1239
+ if (typeof error === "object" && error !== null && "message" in error) {
1240
+ return normalizeText2(error.message, "Unknown error");
1241
+ }
1242
+ return normalizeText2(String(error ?? "Unknown error"), "Unknown error");
1243
+ }
1244
+
1245
+ // src/observability/writer.ts
1246
+ async function appendObservabilityEvent(rootDir, input) {
1247
+ const paths = await ensureProjectStateDirectories(rootDir);
1248
+ const record = buildObservabilityEventRecord(input);
1249
+ const filePath = path8.join(paths.observabilityEventsDir, `${record.timestamp.slice(0, 10)}.jsonl`);
1250
+ await fs7.appendFile(filePath, `${JSON.stringify(record)}
1251
+ `, "utf8");
1252
+ return record;
1253
+ }
1254
+ async function recordObservabilityEvent(rootDir, input) {
1255
+ try {
1256
+ await appendObservabilityEvent(rootDir, input);
1257
+ } catch {
1258
+ }
1259
+ }
1260
+
1261
+ // src/provider/cachePolicy.ts
1262
+ function resolveProviderCachePolicy(input) {
1263
+ const profile = resolveModelProfile(input);
1264
+ if (profile.model.capabilities.cache === "prompt-cache-key") {
1265
+ return {
1266
+ provider: "openai",
1267
+ automaticPrefixCache: true,
1268
+ promptCacheKey: buildPromptCacheKey(input)
1269
+ };
1270
+ }
1271
+ if (profile.model.capabilities.cache === "provider-automatic") {
1272
+ return {
1273
+ provider: profile.provider.id === "deepseek" ? "deepseek" : "generic",
1274
+ automaticPrefixCache: true
1275
+ };
1276
+ }
1277
+ return {
1278
+ provider: "generic",
1279
+ automaticPrefixCache: false
1280
+ };
1281
+ }
1282
+ function buildPromptCacheKey(input) {
1283
+ const seed = input.sessionId || input.projectRoot;
1284
+ if (!seed) {
1285
+ return void 0;
1286
+ }
1287
+ return `kitty:${stableHash(seed)}`;
1288
+ }
1289
+ function stableHash(value) {
1290
+ let hash = 2166136261;
1291
+ for (let index = 0; index < value.length; index += 1) {
1292
+ hash ^= value.charCodeAt(index);
1293
+ hash = Math.imul(hash, 16777619);
1294
+ }
1295
+ return (hash >>> 0).toString(16).padStart(8, "0");
1296
+ }
1297
+
1298
+ // src/provider/chatRequestBody.ts
1299
+ function buildProviderRequestBody(input) {
1300
+ const capabilities = resolveProviderCapabilities(input);
1301
+ const thinking = capabilities.provider === "deepseek" ? resolveDeepSeekThinking(input.messages, input.thinking ?? "enabled") : input.thinking;
1302
+ const body = {
1303
+ model: input.model,
1304
+ messages: toChatCompletionMessages(input.messages),
1305
+ tools: input.tools,
1306
+ stream: input.stream
1307
+ };
1308
+ if (capabilities.provider !== "deepseek" && input.tools?.length) {
1309
+ body.tool_choice = "auto";
1310
+ }
1311
+ if (input.stream) {
1312
+ body.stream_options = {
1313
+ include_usage: true
1314
+ };
1315
+ }
1316
+ const cachePolicy = resolveProviderCachePolicy(input);
1317
+ if (cachePolicy.promptCacheKey) {
1318
+ body.prompt_cache_key = cachePolicy.promptCacheKey;
1319
+ }
1320
+ if (typeof input.maxOutputTokens === "number" && Number.isFinite(input.maxOutputTokens)) {
1321
+ body.max_tokens = Math.max(1, Math.trunc(input.maxOutputTokens));
1322
+ }
1323
+ if (capabilities.provider === "deepseek") {
1324
+ body.thinking = { type: thinking };
1325
+ if (thinking === "enabled") {
1326
+ body.reasoning_effort = normalizeDeepSeekReasoningEffort(input.reasoningEffort ?? capabilities.defaultReasoningEffort);
1327
+ }
1328
+ } else if (input.forceReasoning || capabilities.defaultReasoningEnabled) {
1329
+ body.thinking = { type: "enabled" };
1330
+ }
1331
+ return body;
1332
+ }
1333
+ function resolveDeepSeekThinking(messages, requested) {
1334
+ if (requested === "disabled") {
1335
+ return "disabled";
1336
+ }
1337
+ if (hasUnreplayableAssistantReasoning(messages)) {
1338
+ throw new Error("DeepSeek thinking tool-call replay requires stored reasoning_content. Start a new turn or disable KITTY_THINKING.");
1339
+ }
1340
+ return "enabled";
1341
+ }
1342
+ function hasUnreplayableAssistantReasoning(messages) {
1343
+ return messages.some(
1344
+ (message) => message.role === "assistant" && Array.isArray(message.toolCalls) && message.toolCalls.length > 0 && message.reasoningContent === void 0
1345
+ );
1346
+ }
1347
+ function normalizeDeepSeekReasoningEffort(effort) {
1348
+ if (effort === void 0 || effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") {
1349
+ return "high";
1350
+ }
1351
+ if (effort === "xhigh" || effort === "max") {
1352
+ return "max";
1353
+ }
1354
+ return "high";
1355
+ }
1356
+
1357
+ // src/provider/chatCompletionsAdapter.ts
1358
+ var chatCompletionsAdapter = {
1359
+ wireApi: "chat.completions",
1360
+ async fetchStreaming(client, request) {
1361
+ const startedAt = Date.now();
1362
+ let usage;
1363
+ throwIfAborted(request.abortSignal, "Streaming request aborted");
1364
+ try {
1365
+ const stream = await client.chat.completions.create(
1366
+ {
1367
+ ...buildProviderRequestBody({
1368
+ provider: request.provider,
1369
+ model: request.model,
1370
+ messages: request.messages,
1371
+ tools: request.tools,
1372
+ stream: true,
1373
+ forceReasoning: request.forceReasoning,
1374
+ thinking: request.thinking,
1375
+ reasoningEffort: request.reasoningEffort,
1376
+ maxOutputTokens: request.maxOutputTokens,
1377
+ sessionId: request.sessionId,
1378
+ projectRoot: request.projectRoot
1379
+ }),
1380
+ signal: request.abortSignal
1381
+ }
1382
+ );
1383
+ if (request.abortSignal?.aborted) {
1384
+ abortStream(stream);
1385
+ throw createAbortError("Streaming aborted");
1386
+ }
1387
+ let content = "";
1388
+ let reasoningContent = "";
1389
+ const toolCallParts = /* @__PURE__ */ new Map();
1390
+ for await (const chunk of stream) {
1391
+ if (request.abortSignal?.aborted) {
1392
+ abortStream(stream);
1393
+ throw createAbortError("Streaming aborted");
1394
+ }
1395
+ usage = normalizeProviderUsage(chunk.usage) ?? usage;
1396
+ const delta = chunk.choices?.[0]?.delta;
1397
+ if (!delta) {
1398
+ continue;
1399
+ }
1400
+ if (typeof delta.content === "string" && delta.content.length > 0) {
1401
+ content += delta.content;
1402
+ request.callbacks?.onAssistantDelta?.(delta.content);
1403
+ }
1404
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
1405
+ reasoningContent += delta.reasoning_content;
1406
+ request.callbacks?.onReasoningDelta?.(delta.reasoning_content);
1407
+ }
1408
+ if (Array.isArray(delta.tool_calls)) {
1409
+ for (const toolCall of delta.tool_calls) {
1410
+ const index = typeof toolCall.index === "number" ? toolCall.index : 0;
1411
+ const existing = toolCallParts.get(index) ?? {
1412
+ id: toolCall.id ?? `tool-${index}`,
1413
+ name: "",
1414
+ arguments: ""
1415
+ };
1416
+ if (toolCall.id) {
1417
+ existing.id = toolCall.id;
1418
+ }
1419
+ if (toolCall.function?.name) {
1420
+ existing.name += toolCall.function.name;
1421
+ }
1422
+ if (toolCall.function?.arguments) {
1423
+ existing.arguments += toolCall.function.arguments;
1424
+ }
1425
+ toolCallParts.set(index, existing);
1426
+ }
1427
+ }
1428
+ }
1429
+ return {
1430
+ content: content.length > 0 ? content : null,
1431
+ reasoningContent: reasoningContent.length > 0 ? reasoningContent : void 0,
1432
+ streamedAssistantContent: content.length > 0,
1433
+ streamedReasoningContent: reasoningContent.length > 0,
1434
+ toolCalls: [...toolCallParts.entries()].sort((left, right) => left[0] - right[0]).map(([, toolCall]) => ({
1435
+ id: toolCall.id,
1436
+ type: "function",
1437
+ function: {
1438
+ name: toolCall.name,
1439
+ arguments: toolCall.arguments
1440
+ }
1441
+ }))
1442
+ };
1443
+ } finally {
1444
+ request.onRequestMetric?.({
1445
+ durationMs: Date.now() - startedAt,
1446
+ usage
1447
+ });
1448
+ }
1449
+ },
1450
+ async fetchNonStreaming(client, request) {
1451
+ const startedAt = Date.now();
1452
+ let usage;
1453
+ throwIfAborted(request.abortSignal, "Request aborted");
1454
+ try {
1455
+ const completion = await client.chat.completions.create(
1456
+ {
1457
+ ...buildProviderRequestBody({
1458
+ provider: request.provider,
1459
+ model: request.model,
1460
+ messages: request.messages,
1461
+ tools: request.tools,
1462
+ stream: false,
1463
+ forceReasoning: request.forceReasoning,
1464
+ thinking: request.thinking,
1465
+ reasoningEffort: request.reasoningEffort,
1466
+ maxOutputTokens: request.maxOutputTokens,
1467
+ sessionId: request.sessionId,
1468
+ projectRoot: request.projectRoot
1469
+ }),
1470
+ signal: request.abortSignal
1471
+ }
1472
+ );
1473
+ usage = normalizeProviderUsage(completion.usage);
1474
+ const message = completion.choices[0]?.message;
1475
+ if (!message) {
1476
+ throw new Error("API returned no message.");
1477
+ }
1478
+ return {
1479
+ content: typeof message.content === "string" ? message.content : collapseContentParts(message.content),
1480
+ reasoningContent: readReasoningContent(message),
1481
+ streamedAssistantContent: false,
1482
+ streamedReasoningContent: false,
1483
+ toolCalls: (message.tool_calls ?? []).filter((call) => call.type === "function").map((call) => ({
1484
+ id: call.id,
1485
+ type: "function",
1486
+ function: {
1487
+ name: call.function.name,
1488
+ arguments: call.function.arguments
1489
+ }
1490
+ }))
1491
+ };
1492
+ } finally {
1493
+ request.onRequestMetric?.({
1494
+ durationMs: Date.now() - startedAt,
1495
+ usage
1496
+ });
1497
+ }
1498
+ }
1499
+ };
1500
+ function abortStream(stream) {
1501
+ try {
1502
+ stream?.controller?.abort();
1503
+ } catch {
1504
+ }
1505
+ }
1506
+ function toChatCompletionMessages(messages) {
1507
+ return messages.map((message) => {
1508
+ if (message.role === "tool") {
1509
+ return {
1510
+ role: "tool",
1511
+ content: message.content ?? "",
1512
+ tool_call_id: message.toolCallId ?? ""
1513
+ };
1514
+ }
1515
+ if (message.role === "assistant" && message.toolCalls?.length) {
1516
+ const assistantMessage = {
1517
+ role: "assistant",
1518
+ content: message.content ?? "",
1519
+ tool_calls: message.toolCalls
1520
+ };
1521
+ if (message.reasoningContent !== void 0) {
1522
+ assistantMessage.reasoning_content = message.reasoningContent;
1523
+ }
1524
+ return assistantMessage;
1525
+ }
1526
+ const baseMessage = {
1527
+ role: message.role,
1528
+ content: message.content ?? "",
1529
+ name: message.name
1530
+ };
1531
+ if (message.role === "assistant" && message.reasoningContent !== void 0) {
1532
+ baseMessage.reasoning_content = message.reasoningContent;
1533
+ }
1534
+ return baseMessage;
1535
+ });
1536
+ }
1537
+
1538
+ // src/provider/responsesAdapter.ts
1539
+ var responsesAdapter = {
1540
+ wireApi: "responses",
1541
+ async fetchStreaming(client, request) {
1542
+ const startedAt = Date.now();
1543
+ let usage;
1544
+ throwIfAborted(request.abortSignal, "Streaming request aborted");
1545
+ try {
1546
+ const stream = await client.responses.create(
1547
+ {
1548
+ ...buildResponsesRequestBody(request),
1549
+ stream: true
1550
+ },
1551
+ {
1552
+ signal: request.abortSignal
1553
+ }
1554
+ );
1555
+ if (request.abortSignal?.aborted) {
1556
+ abortStream2(stream);
1557
+ throw createAbortError("Streaming aborted");
1558
+ }
1559
+ let content = "";
1560
+ let reasoningContent = "";
1561
+ const toolCalls = /* @__PURE__ */ new Map();
1562
+ for await (const event of stream) {
1563
+ if (request.abortSignal?.aborted) {
1564
+ abortStream2(stream);
1565
+ throw createAbortError("Streaming aborted");
1566
+ }
1567
+ usage = normalizeProviderUsage(event.response?.usage) ?? usage;
1568
+ if (event.type === "response.output_text.delta" && typeof event.delta === "string") {
1569
+ content += event.delta;
1570
+ request.callbacks?.onAssistantDelta?.(event.delta);
1571
+ continue;
1572
+ }
1573
+ if ((event.type === "response.reasoning_text.delta" || event.type === "response.reasoning_summary_text.delta") && typeof event.delta === "string") {
1574
+ reasoningContent += event.delta;
1575
+ request.callbacks?.onReasoningDelta?.(event.delta);
1576
+ continue;
1577
+ }
1578
+ if (event.type === "response.function_call_arguments.delta" && typeof event.delta === "string") {
1579
+ const index = typeof event.output_index === "number" ? event.output_index : 0;
1580
+ const existing = toolCalls.get(index) ?? {
1581
+ id: event.item_id ?? `tool-${index}`,
1582
+ name: "",
1583
+ arguments: ""
1584
+ };
1585
+ existing.arguments += event.delta;
1586
+ toolCalls.set(index, existing);
1587
+ continue;
1588
+ }
1589
+ if (event.type === "response.function_call_arguments.done") {
1590
+ const index = typeof event.output_index === "number" ? event.output_index : 0;
1591
+ const existing = toolCalls.get(index) ?? {
1592
+ id: event.item_id ?? `tool-${index}`,
1593
+ name: "",
1594
+ arguments: ""
1595
+ };
1596
+ if (typeof event.name === "string") {
1597
+ existing.name = event.name;
1598
+ }
1599
+ if (typeof event.arguments === "string" && event.arguments.length > 0) {
1600
+ existing.arguments = event.arguments;
1601
+ }
1602
+ toolCalls.set(index, existing);
1603
+ continue;
1604
+ }
1605
+ if (event.type === "response.output_item.done" && event.item?.type === "function_call") {
1606
+ const index = typeof event.output_index === "number" ? event.output_index : 0;
1607
+ toolCalls.set(index, {
1608
+ id: event.item.call_id ?? event.item.id ?? `tool-${index}`,
1609
+ name: event.item.name ?? "",
1610
+ arguments: event.item.arguments ?? ""
1611
+ });
1612
+ }
1613
+ }
1614
+ return {
1615
+ content: content.length > 0 ? content : null,
1616
+ reasoningContent: reasoningContent.length > 0 ? reasoningContent : void 0,
1617
+ streamedAssistantContent: content.length > 0,
1618
+ streamedReasoningContent: reasoningContent.length > 0,
1619
+ toolCalls: [...toolCalls.entries()].sort((left, right) => left[0] - right[0]).map(([, toolCall]) => ({
1620
+ id: toolCall.id,
1621
+ type: "function",
1622
+ function: {
1623
+ name: toolCall.name,
1624
+ arguments: toolCall.arguments
1625
+ }
1626
+ }))
1627
+ };
1628
+ } finally {
1629
+ request.onRequestMetric?.({
1630
+ durationMs: Date.now() - startedAt,
1631
+ usage
1632
+ });
1633
+ }
1634
+ },
1635
+ async fetchNonStreaming(client, request) {
1636
+ const startedAt = Date.now();
1637
+ let usage;
1638
+ throwIfAborted(request.abortSignal, "Request aborted");
1639
+ try {
1640
+ const response = await client.responses.create(
1641
+ {
1642
+ ...buildResponsesRequestBody(request),
1643
+ stream: false
1644
+ },
1645
+ {
1646
+ signal: request.abortSignal
1647
+ }
1648
+ );
1649
+ usage = normalizeProviderUsage(response.usage);
1650
+ return {
1651
+ content: normalizeOutputText(response),
1652
+ reasoningContent: readResponseReasoning(response),
1653
+ streamedAssistantContent: false,
1654
+ streamedReasoningContent: false,
1655
+ toolCalls: readResponseToolCalls(response)
1656
+ };
1657
+ } finally {
1658
+ request.onRequestMetric?.({
1659
+ durationMs: Date.now() - startedAt,
1660
+ usage
1661
+ });
1662
+ }
1663
+ }
1664
+ };
1665
+ function buildResponsesRequestBody(request) {
1666
+ const capabilities = resolveProviderCapabilities({
1667
+ provider: request.provider,
1668
+ model: request.model
1669
+ });
1670
+ const body = {
1671
+ model: request.model,
1672
+ input: toResponsesInput(request.messages),
1673
+ tools: request.tools?.map((tool) => ({
1674
+ type: "function",
1675
+ name: tool.function.name,
1676
+ description: tool.function.description,
1677
+ parameters: tool.function.parameters ?? null,
1678
+ strict: false
1679
+ })),
1680
+ tool_choice: request.tools?.length ? "auto" : void 0
1681
+ };
1682
+ if (typeof request.maxOutputTokens === "number" && Number.isFinite(request.maxOutputTokens)) {
1683
+ body.max_output_tokens = Math.max(1, Math.trunc(request.maxOutputTokens));
1684
+ }
1685
+ const cachePolicy = resolveProviderCachePolicy({
1686
+ provider: request.provider,
1687
+ model: request.model,
1688
+ sessionId: request.sessionId,
1689
+ projectRoot: request.projectRoot
1690
+ });
1691
+ if (cachePolicy.promptCacheKey) {
1692
+ body.prompt_cache_key = cachePolicy.promptCacheKey;
1693
+ }
1694
+ const reasoningEffort = request.thinking === "disabled" ? void 0 : normalizeResponsesReasoningEffort(
1695
+ request.reasoningEffort ?? capabilities.defaultReasoningEffort
1696
+ );
1697
+ if (request.thinking !== "disabled" && (request.forceReasoning || capabilities.defaultReasoningEnabled || request.thinking === "enabled" || reasoningEffort)) {
1698
+ body.reasoning = {
1699
+ effort: reasoningEffort ?? "high",
1700
+ summary: "detailed"
1701
+ };
1702
+ }
1703
+ return body;
1704
+ }
1705
+ function normalizeResponsesReasoningEffort(effort) {
1706
+ if (effort === "xhigh") {
1707
+ return "xhigh";
1708
+ }
1709
+ return effort === "max" ? void 0 : effort;
1710
+ }
1711
+ function toResponsesInput(messages) {
1712
+ const items = [];
1713
+ for (const message of messages) {
1714
+ if (message.role === "tool") {
1715
+ items.push({
1716
+ type: "function_call_output",
1717
+ call_id: message.toolCallId ?? "",
1718
+ output: message.content ?? ""
1719
+ });
1720
+ continue;
1721
+ }
1722
+ if (message.role === "assistant" && message.toolCalls?.length) {
1723
+ if (typeof message.content === "string" && message.content.trim().length > 0) {
1724
+ items.push({
1725
+ type: "message",
1726
+ role: "assistant",
1727
+ content: message.content
1728
+ });
1729
+ }
1730
+ for (const toolCall of message.toolCalls) {
1731
+ items.push({
1732
+ type: "function_call",
1733
+ call_id: toolCall.id,
1734
+ name: toolCall.function.name,
1735
+ arguments: toolCall.function.arguments
1736
+ });
1737
+ }
1738
+ continue;
1739
+ }
1740
+ items.push({
1741
+ type: "message",
1742
+ role: message.role,
1743
+ content: message.content ?? ""
1744
+ });
1745
+ }
1746
+ return items;
1747
+ }
1748
+ function normalizeOutputText(response) {
1749
+ const outputText = response.output_text;
1750
+ if (typeof outputText === "string" && outputText.trim().length > 0) {
1751
+ return outputText;
1752
+ }
1753
+ const output = response.output;
1754
+ if (!Array.isArray(output)) {
1755
+ return null;
1756
+ }
1757
+ const fragments = output.flatMap((item) => {
1758
+ if (!item || typeof item !== "object" || item.type !== "message") {
1759
+ return [];
1760
+ }
1761
+ const content = item.content;
1762
+ if (!Array.isArray(content)) {
1763
+ return [];
1764
+ }
1765
+ return content.flatMap((part) => {
1766
+ if (!part || typeof part !== "object" || part.type !== "output_text") {
1767
+ return [];
1768
+ }
1769
+ return typeof part.text === "string" ? [part.text] : [];
1770
+ });
1771
+ });
1772
+ return fragments.length > 0 ? fragments.join("") : null;
1773
+ }
1774
+ function readResponseToolCalls(response) {
1775
+ const output = response.output;
1776
+ if (!Array.isArray(output)) {
1777
+ return [];
1778
+ }
1779
+ return output.filter((item) => Boolean(item) && typeof item === "object" && item.type === "function_call").map((item) => ({
1780
+ id: item.call_id ?? item.id ?? crypto.randomUUID(),
1781
+ type: "function",
1782
+ function: {
1783
+ name: item.name ?? "",
1784
+ arguments: item.arguments ?? ""
1785
+ }
1786
+ }));
1787
+ }
1788
+ function readResponseReasoning(response) {
1789
+ const output = response.output;
1790
+ if (!Array.isArray(output)) {
1791
+ return void 0;
1792
+ }
1793
+ const fragments = output.flatMap((item) => {
1794
+ if (!item || typeof item !== "object" || item.type !== "reasoning") {
1795
+ return [];
1796
+ }
1797
+ const reasoningItem = item;
1798
+ const summary = Array.isArray(reasoningItem.summary) ? reasoningItem.summary.map((entry) => typeof entry?.text === "string" ? entry.text : "").filter(Boolean) : [];
1799
+ const content = Array.isArray(reasoningItem.content) ? reasoningItem.content.map((entry) => typeof entry?.text === "string" ? entry.text : "").filter(Boolean) : [];
1800
+ return [...content, ...summary];
1801
+ });
1802
+ return fragments.length > 0 ? fragments.join("") : void 0;
1803
+ }
1804
+ function abortStream2(stream) {
1805
+ try {
1806
+ stream?.controller?.abort();
1807
+ } catch {
1808
+ }
1809
+ }
1810
+
1811
+ // src/provider/client.ts
1812
+ import OpenAI from "openai";
1813
+
1814
+ // src/provider/connection.ts
1815
+ function buildProviderBaseUrlCandidates(baseUrl) {
1816
+ const normalized = trimTrailingSlash(baseUrl);
1817
+ if (!normalized) {
1818
+ return [normalized];
1819
+ }
1820
+ const candidates = [normalized];
1821
+ try {
1822
+ const parsed = new URL(normalized);
1823
+ if (parsed.pathname === "" || parsed.pathname === "/") {
1824
+ candidates.push(trimTrailingSlash(new URL("v1", ensureTrailingSlash2(parsed.toString())).toString()));
1825
+ }
1826
+ } catch {
1827
+ return candidates;
1828
+ }
1829
+ return [...new Set(candidates)];
1830
+ }
1831
+ function ensureTrailingSlash2(baseUrl) {
1832
+ return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
1833
+ }
1834
+ function trimTrailingSlash(baseUrl) {
1835
+ const trimmed = String(baseUrl ?? "").trim();
1836
+ if (!trimmed) {
1837
+ return trimmed;
1838
+ }
1839
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
1840
+ }
1841
+
1842
+ // src/provider/client.ts
1843
+ function createProviderClientPool(config) {
1844
+ const capabilities = resolveProviderCapabilities({
1845
+ provider: config.provider,
1846
+ model: config.model
889
1847
  });
890
- const session = await params.options.sessionStore.save(
891
- noteCheckpointCompleted(
892
- await params.options.sessionStore.appendMessages(params.session, [assistantMessage]),
893
- transition
894
- )
895
- );
1848
+ const baseUrls = buildProviderBaseUrlCandidates(config.baseUrl);
1849
+ const clients = /* @__PURE__ */ new Map();
1850
+ let preferredBaseUrl;
896
1851
  return {
897
- kind: "return",
898
- result: buildRunTurnResult({
899
- session,
900
- changedPaths: params.changedPaths,
901
- transition
902
- })
1852
+ candidates() {
1853
+ const ordered = preferredBaseUrl ? [preferredBaseUrl, ...baseUrls.filter((baseUrl) => baseUrl !== preferredBaseUrl)] : baseUrls;
1854
+ return ordered.map((baseUrl) => ({
1855
+ baseUrl,
1856
+ client: getOrCreateClient(baseUrl)
1857
+ }));
1858
+ },
1859
+ markHealthy(baseUrl) {
1860
+ preferredBaseUrl = baseUrl;
1861
+ }
903
1862
  };
1863
+ function getOrCreateClient(baseUrl) {
1864
+ const existing = clients.get(baseUrl);
1865
+ if (existing) {
1866
+ return existing;
1867
+ }
1868
+ const client = new OpenAI({
1869
+ apiKey: config.apiKey,
1870
+ baseURL: baseUrl,
1871
+ timeout: capabilities.requestTimeoutMs,
1872
+ maxRetries: 0
1873
+ });
1874
+ clients.set(baseUrl, client);
1875
+ return client;
1876
+ }
904
1877
  }
905
- function emitAssistantReasoning(response, options) {
906
- if (response.reasoningContent && options.config.showReasoning && !response.streamedReasoningContent) {
907
- options.callbacks?.onReasoning?.(response.reasoningContent);
1878
+ function isProviderClientPool(value) {
1879
+ return Boolean(
1880
+ value && typeof value === "object" && typeof value.candidates === "function" && typeof value.markHealthy === "function"
1881
+ );
1882
+ }
1883
+
1884
+ // src/provider/request.ts
1885
+ async function fetchAssistantResponse(client, messages, request, tools, callbacks, abortSignal, onRequestMetric, observability) {
1886
+ const capabilities = resolveProviderCapabilities(request);
1887
+ const adapter = selectProviderWireAdapter(capabilities.wireApi);
1888
+ return tryFetch(
1889
+ adapter,
1890
+ client,
1891
+ messages,
1892
+ request,
1893
+ tools,
1894
+ callbacks,
1895
+ false,
1896
+ abortSignal,
1897
+ onRequestMetric,
1898
+ observability
1899
+ );
1900
+ }
1901
+ async function tryFetch(adapter, client, messages, request, tools, callbacks, forceReasoning, abortSignal, onRequestMetric, observability) {
1902
+ const startedAt = Date.now();
1903
+ let latestMetric;
1904
+ let resolvedBaseUrl;
1905
+ const forwardMetric = (metric) => {
1906
+ latestMetric = metric;
1907
+ onRequestMetric?.(metric);
1908
+ };
1909
+ if (observability) {
1910
+ await recordObservabilityEvent(observability.rootDir, {
1911
+ event: "model.request",
1912
+ status: "started",
1913
+ sessionId: observability.sessionId,
1914
+ identityKind: observability.identityKind,
1915
+ identityName: observability.identityName,
1916
+ model: request.model,
1917
+ details: {
1918
+ provider: request.provider,
1919
+ configuredModel: observability.configuredModel,
1920
+ requestModel: request.model,
1921
+ wireApi: adapter.wireApi,
1922
+ baseUrl: resolvedBaseUrl
1923
+ }
1924
+ });
1925
+ }
1926
+ try {
1927
+ const response = await withApiRetries(
1928
+ () => invokeWithProviderClients(client, async (providerClient, baseUrl) => {
1929
+ resolvedBaseUrl = baseUrl;
1930
+ return adapter.fetchStreaming(providerClient, {
1931
+ provider: request.provider,
1932
+ model: request.model,
1933
+ messages,
1934
+ tools,
1935
+ callbacks,
1936
+ forceReasoning,
1937
+ thinking: request.thinking,
1938
+ reasoningEffort: request.reasoningEffort,
1939
+ maxOutputTokens: request.maxOutputTokens,
1940
+ sessionId: request.sessionId,
1941
+ projectRoot: request.projectRoot,
1942
+ abortSignal,
1943
+ onRequestMetric: forwardMetric
1944
+ });
1945
+ }),
1946
+ abortSignal
1947
+ );
1948
+ if (observability) {
1949
+ await recordObservabilityEvent(observability.rootDir, {
1950
+ event: "model.request",
1951
+ status: "completed",
1952
+ sessionId: observability.sessionId,
1953
+ identityKind: observability.identityKind,
1954
+ identityName: observability.identityName,
1955
+ model: request.model,
1956
+ durationMs: Date.now() - startedAt,
1957
+ details: {
1958
+ provider: request.provider,
1959
+ configuredModel: observability.configuredModel,
1960
+ requestModel: request.model,
1961
+ wireApi: adapter.wireApi,
1962
+ baseUrl: resolvedBaseUrl,
1963
+ usage: latestMetric?.usage,
1964
+ usageAvailable: hasProviderUsageSnapshot(latestMetric?.usage)
1965
+ }
1966
+ });
1967
+ }
1968
+ return response;
1969
+ } catch (error) {
1970
+ if (isAbortError(error)) {
1971
+ throw error;
1972
+ }
1973
+ try {
1974
+ const response = await withApiRetries(
1975
+ () => invokeWithProviderClients(client, async (providerClient, baseUrl) => {
1976
+ resolvedBaseUrl = baseUrl;
1977
+ return adapter.fetchNonStreaming(providerClient, {
1978
+ provider: request.provider,
1979
+ model: request.model,
1980
+ messages,
1981
+ tools,
1982
+ callbacks,
1983
+ forceReasoning,
1984
+ thinking: request.thinking,
1985
+ reasoningEffort: request.reasoningEffort,
1986
+ maxOutputTokens: request.maxOutputTokens,
1987
+ sessionId: request.sessionId,
1988
+ projectRoot: request.projectRoot,
1989
+ abortSignal,
1990
+ onRequestMetric: forwardMetric
1991
+ });
1992
+ }),
1993
+ abortSignal
1994
+ );
1995
+ if (observability) {
1996
+ await recordObservabilityEvent(observability.rootDir, {
1997
+ event: "model.request",
1998
+ status: "completed",
1999
+ sessionId: observability.sessionId,
2000
+ identityKind: observability.identityKind,
2001
+ identityName: observability.identityName,
2002
+ model: request.model,
2003
+ durationMs: Date.now() - startedAt,
2004
+ details: {
2005
+ provider: request.provider,
2006
+ configuredModel: observability.configuredModel,
2007
+ requestModel: request.model,
2008
+ wireApi: adapter.wireApi,
2009
+ baseUrl: resolvedBaseUrl,
2010
+ usage: latestMetric?.usage,
2011
+ usageAvailable: hasProviderUsageSnapshot(latestMetric?.usage)
2012
+ }
2013
+ });
2014
+ }
2015
+ return response;
2016
+ } catch (fallbackError) {
2017
+ if (!isAbortError(fallbackError) && observability) {
2018
+ await recordObservabilityEvent(observability.rootDir, {
2019
+ event: "model.request",
2020
+ status: "failed",
2021
+ sessionId: observability.sessionId,
2022
+ identityKind: observability.identityKind,
2023
+ identityName: observability.identityName,
2024
+ model: request.model,
2025
+ durationMs: Date.now() - startedAt,
2026
+ error: fallbackError,
2027
+ details: {
2028
+ provider: request.provider,
2029
+ configuredModel: observability.configuredModel,
2030
+ requestModel: request.model,
2031
+ wireApi: adapter.wireApi,
2032
+ baseUrl: resolvedBaseUrl,
2033
+ usage: latestMetric?.usage,
2034
+ usageAvailable: hasProviderUsageSnapshot(latestMetric?.usage)
2035
+ }
2036
+ });
2037
+ }
2038
+ throw fallbackError;
2039
+ }
908
2040
  }
909
2041
  }
910
- function emitAssistantFinalOutput(response, options) {
911
- if (response.content && !response.streamedAssistantContent) {
912
- options.callbacks?.onAssistantText?.(response.content);
2042
+ function selectProviderWireAdapter(wireApi) {
2043
+ if (wireApi === "responses") {
2044
+ return responsesAdapter;
913
2045
  }
914
- if (response.content) {
915
- options.callbacks?.onAssistantDone?.(response.content);
2046
+ return chatCompletionsAdapter;
2047
+ }
2048
+ async function invokeWithProviderClients(client, operation) {
2049
+ if (!isProviderClientPool(client)) {
2050
+ return operation(client, void 0);
2051
+ }
2052
+ let lastError;
2053
+ const candidates = client.candidates();
2054
+ for (let index = 0; index < candidates.length; index += 1) {
2055
+ const candidate = candidates[index];
2056
+ try {
2057
+ const result = await operation(candidate.client, candidate.baseUrl);
2058
+ client.markHealthy(candidate.baseUrl);
2059
+ return result;
2060
+ } catch (error) {
2061
+ lastError = error;
2062
+ if (isAbortError(error)) {
2063
+ throw error;
2064
+ }
2065
+ const hasMoreCandidates = index < candidates.length - 1;
2066
+ if (!hasMoreCandidates || !canRetryWithAlternateBaseUrl(error)) {
2067
+ throw error;
2068
+ }
2069
+ }
916
2070
  }
2071
+ throw lastError;
917
2072
  }
918
- function hasVisibleAssistantResult(content) {
919
- return typeof content === "string" && content.trim().length > 0;
2073
+ function canRetryWithAlternateBaseUrl(error) {
2074
+ const status = error.status;
2075
+ const message = String(error.message ?? error).toLowerCase();
2076
+ return status === 404 || status === 405 || message.includes("404") || message.includes("not found");
920
2077
  }
921
2078
 
922
2079
  // src/provider/retryPolicy.ts
@@ -1650,7 +2807,8 @@ function buildCompressedContextRequest(systemPrompt, messages, config) {
1650
2807
  const safeMaxChars = Math.max(8e3, config.maxContextChars);
1651
2808
  const conversation = buildVisibleConversationWindow(messages);
1652
2809
  const conversationMessages = conversation.messages;
1653
- const fullMessages = composeChatMessages(systemPrompt, conversationMessages, config.model);
2810
+ const provider = config.provider ?? "openai-compatible";
2811
+ const fullMessages = composeChatMessages(systemPrompt, conversationMessages, config.model, provider);
1654
2812
  const initialEstimatedChars = estimateChatMessagesChars(fullMessages);
1655
2813
  const initialPromptMetrics = measureSystemPrompt(systemPrompt);
1656
2814
  const initialSources = buildBudgetSources(systemPrompt, conversationMessages);
@@ -1679,7 +2837,7 @@ function buildCompressedContextRequest(systemPrompt, messages, config) {
1679
2837
  const summary = compressedFrameHead.length > 0 ? summarizeConversation(compressedFrameHead, config.contextSummaryChars) : void 0;
1680
2838
  const summaryPrompt = appendSummary(systemPrompt, summary);
1681
2839
  let workingTail = compactTailMessages(tailMessages, "normal");
1682
- let requestMessages = composeChatMessages(summaryPrompt, workingTail, config.model);
2840
+ let requestMessages = composeChatMessages(summaryPrompt, workingTail, config.model, provider);
1683
2841
  let estimatedChars = estimateChatMessagesChars(requestMessages);
1684
2842
  let promptMetrics = measureSystemPrompt(summaryPrompt);
1685
2843
  let cacheLayout = buildCacheLayoutReport(summaryPrompt, workingTail);
@@ -1704,7 +2862,7 @@ function buildCompressedContextRequest(systemPrompt, messages, config) {
1704
2862
  };
1705
2863
  }
1706
2864
  workingTail = compactTailMessages(tailMessages, "aggressive");
1707
- requestMessages = composeChatMessages(summaryPrompt, workingTail, config.model);
2865
+ requestMessages = composeChatMessages(summaryPrompt, workingTail, config.model, provider);
1708
2866
  estimatedChars = estimateChatMessagesChars(requestMessages);
1709
2867
  promptMetrics = measureSystemPrompt(summaryPrompt);
1710
2868
  cacheLayout = buildCacheLayoutReport(summaryPrompt, workingTail);
@@ -1740,7 +2898,8 @@ function buildCompressedContextRequest(systemPrompt, messages, config) {
1740
2898
  const hardMessages = composeChatMessages(
1741
2899
  hardPrompt,
1742
2900
  compactedHardTail,
1743
- config.model
2901
+ config.model,
2902
+ provider
1744
2903
  );
1745
2904
  const hardEstimatedChars = estimateChatMessagesChars(hardMessages);
1746
2905
  const hardCacheLayout = buildCacheLayoutReport(hardPrompt, compactedHardTail);
@@ -1775,7 +2934,7 @@ function sliceTailMessages(messages, tailCount) {
1775
2934
  const safeStartIndex = expandStartToToolBoundary(messages, startIndex);
1776
2935
  return messages.slice(safeStartIndex);
1777
2936
  }
1778
- function composeChatMessages(systemPrompt, messages, model) {
2937
+ function composeChatMessages(systemPrompt, messages, model, provider) {
1779
2938
  return [
1780
2939
  {
1781
2940
  role: "system",
@@ -1787,7 +2946,7 @@ function composeChatMessages(systemPrompt, messages, model) {
1787
2946
  name: message.name,
1788
2947
  toolCallId: message.tool_call_id,
1789
2948
  toolCalls: message.tool_calls,
1790
- reasoningContent: shouldIncludeStoredAssistantReasoning(messages, index, model) ? message.reasoningContent : void 0
2949
+ reasoningContent: shouldIncludeStoredAssistantReasoning(messages, index, model, provider) ? message.reasoningContent : void 0
1791
2950
  }))
1792
2951
  ];
1793
2952
  }
@@ -1934,8 +3093,8 @@ function buildCacheLayoutReport(systemPrompt, messages) {
1934
3093
  }))
1935
3094
  });
1936
3095
  return {
1937
- stablePrefixFingerprint: stableHash(stablePrefix),
1938
- volatileTailFingerprint: stableHash(volatileTail),
3096
+ stablePrefixFingerprint: stableHash2(stablePrefix),
3097
+ volatileTailFingerprint: stableHash2(volatileTail),
1939
3098
  stablePrefixChars: stablePrefix.length,
1940
3099
  volatileTailChars: volatileTail.length,
1941
3100
  stableSources: typeof systemPrompt === "string" ? ["systemPrompt"] : [
@@ -1966,7 +3125,7 @@ function renderVolatileRuntimeFacts(systemPrompt) {
1966
3125
  joinBlocks(systemPrompt.runtimeFactBlocks)
1967
3126
  ].join("\n").trim();
1968
3127
  }
1969
- function stableHash(value) {
3128
+ function stableHash2(value) {
1970
3129
  let hash = 2166136261;
1971
3130
  for (let index = 0; index < value.length; index += 1) {
1972
3131
  hash ^= value.charCodeAt(index);
@@ -2202,6 +3361,7 @@ async function updateSessionMemoryAfterTurn(input) {
2202
3361
  identityName: input.identity.name,
2203
3362
  model: input.requestModel
2204
3363
  });
3364
+ input.options.callbacks?.onStatus?.("\u603B\u7ED3\u4E2D");
2205
3365
  try {
2206
3366
  const memoryResponse = input.options.fetchSessionMemoryResponse ? await input.options.fetchSessionMemoryResponse(modelRequest) : await fetchAssistantResponse(
2207
3367
  input.client,
@@ -2252,6 +3412,8 @@ async function updateSessionMemoryAfterTurn(input) {
2252
3412
  error
2253
3413
  });
2254
3414
  return input.session;
3415
+ } finally {
3416
+ input.options.callbacks?.onStatus?.("");
2255
3417
  }
2256
3418
  }
2257
3419
  async function updateSessionTitleAfterTurn(input) {
@@ -2299,6 +3461,7 @@ async function updateSessionTitleAfterTurn(input) {
2299
3461
  identityName: input.identity.name,
2300
3462
  model: input.requestModel
2301
3463
  });
3464
+ input.options.callbacks?.onStatus?.("\u6807\u9898\u751F\u6210\u4E2D");
2302
3465
  try {
2303
3466
  const titleResponse = input.options.fetchSessionTitleResponse ? await input.options.fetchSessionTitleResponse(modelRequest) : await fetchAssistantResponse(
2304
3467
  input.client,
@@ -2350,15 +3513,17 @@ async function updateSessionTitleAfterTurn(input) {
2350
3513
  error
2351
3514
  });
2352
3515
  return input.session;
3516
+ } finally {
3517
+ input.options.callbacks?.onStatus?.("");
2353
3518
  }
2354
3519
  }
2355
3520
 
2356
3521
  // src/control/ledger.ts
2357
3522
  import Database from "better-sqlite3";
2358
- import fs6 from "fs";
3523
+ import fs8 from "fs";
2359
3524
 
2360
3525
  // src/control/executions.ts
2361
- import path7 from "path";
3526
+ import path9 from "path";
2362
3527
 
2363
3528
  // src/protocol/leadWait.ts
2364
3529
  var LEAD_WAIT_PROTOCOL = "kitty.lead-wait-policy";
@@ -2587,7 +3752,7 @@ var ExecutionLedgerRepo = class {
2587
3752
  prompt: input.prompt,
2588
3753
  actorName: input.actorName,
2589
3754
  actorRole: input.actorRole,
2590
- cwd: path7.resolve(input.cwd),
3755
+ cwd: path9.resolve(input.cwd),
2591
3756
  requestedBy: input.requestedBy,
2592
3757
  sessionId: input.sessionId,
2593
3758
  pid: input.pid,
@@ -2618,10 +3783,10 @@ var ExecutionLedgerRepo = class {
2618
3783
  }
2619
3784
  list(input = {}) {
2620
3785
  const rows = this.db.prepare("SELECT * FROM executions ORDER BY created_at ASC").all();
2621
- const cwd = input.cwd ? path7.resolve(input.cwd) : void 0;
3786
+ const cwd = input.cwd ? path9.resolve(input.cwd) : void 0;
2622
3787
  const statuses = new Set(input.statuses ?? []);
2623
3788
  const kinds = new Set(input.kinds ?? []);
2624
- return rows.map(fromExecutionRow).filter((record) => !input.kind || record.kind === input.kind).filter((record) => kinds.size === 0 || kinds.has(record.kind)).filter((record) => statuses.size === 0 || statuses.has(record.status)).filter((record) => !cwd || isSameOrDescendant(path7.resolve(record.cwd), cwd) || isSameOrDescendant(cwd, path7.resolve(record.cwd)));
3789
+ return rows.map(fromExecutionRow).filter((record) => !input.kind || record.kind === input.kind).filter((record) => kinds.size === 0 || kinds.has(record.kind)).filter((record) => statuses.size === 0 || statuses.has(record.status)).filter((record) => !cwd || isSameOrDescendant(path9.resolve(record.cwd), cwd) || isSameOrDescendant(cwd, path9.resolve(record.cwd)));
2625
3790
  }
2626
3791
  markRunning(id, input) {
2627
3792
  const current = requireExecution(this.load(id), id);
@@ -2699,8 +3864,8 @@ function requireExecution(record, id) {
2699
3864
  return record;
2700
3865
  }
2701
3866
  function isSameOrDescendant(targetPath, possibleAncestor) {
2702
- const relative = path7.relative(possibleAncestor, targetPath);
2703
- return relative === "" || !relative.startsWith("..") && !path7.isAbsolute(relative);
3867
+ const relative = path9.relative(possibleAncestor, targetPath);
3868
+ return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
2704
3869
  }
2705
3870
 
2706
3871
  // src/control/schema.ts
@@ -2814,7 +3979,7 @@ var TaskLifecycleLedgerRepo = class {
2814
3979
  stage: existing?.stage === "completed" || !existing ? "normal_work" : existing.stage,
2815
3980
  scope: existing?.scope,
2816
3981
  boundary: existing?.boundary,
2817
- reason: normalizeText2(input.reason) ?? existing?.reason ?? "turn_started",
3982
+ reason: normalizeText3(input.reason) ?? existing?.reason ?? "turn_started",
2818
3983
  activeExecutionIds: existing?.activeExecutionIds ?? [],
2819
3984
  activeTodoIds: existing?.activeTodoIds ?? [],
2820
3985
  verificationFacts: existing?.verificationFacts ?? [],
@@ -2843,9 +4008,9 @@ var TaskLifecycleLedgerRepo = class {
2843
4008
  return this.save({
2844
4009
  ...current,
2845
4010
  stage: input.stage ?? current.stage,
2846
- scope: normalizeText2(input.scope) ?? current.scope,
2847
- boundary: normalizeText2(input.boundary) ?? current.boundary,
2848
- reason: normalizeText2(input.reason) ?? current.reason,
4011
+ scope: normalizeText3(input.scope) ?? current.scope,
4012
+ boundary: normalizeText3(input.boundary) ?? current.boundary,
4013
+ reason: normalizeText3(input.reason) ?? current.reason,
2849
4014
  activeExecutionIds: normalizeStringList2(input.activeExecutionIds ?? current.activeExecutionIds),
2850
4015
  activeTodoIds: normalizeStringList2(input.activeTodoIds ?? current.activeTodoIds),
2851
4016
  verificationFacts: normalizeStringList2(input.verificationFacts ?? current.verificationFacts),
@@ -2961,7 +4126,7 @@ function normalizeStringList2(value) {
2961
4126
  const seen = /* @__PURE__ */ new Set();
2962
4127
  const items = [];
2963
4128
  for (const item of value) {
2964
- const text = normalizeText2(item);
4129
+ const text = normalizeText3(item);
2965
4130
  if (!text || seen.has(text)) {
2966
4131
  continue;
2967
4132
  }
@@ -2970,7 +4135,7 @@ function normalizeStringList2(value) {
2970
4135
  }
2971
4136
  return items;
2972
4137
  }
2973
- function normalizeText2(value) {
4138
+ function normalizeText3(value) {
2974
4139
  return typeof value === "string" && value.trim() ? value.replace(/\s+/g, " ").trim() : void 0;
2975
4140
  }
2976
4141
 
@@ -3010,7 +4175,7 @@ var ControlPlaneLedger = class {
3010
4175
  db;
3011
4176
  constructor(rootDir) {
3012
4177
  const statePaths = getProjectStatePaths(rootDir);
3013
- fs6.mkdirSync(statePaths.kittyDir, { recursive: true });
4178
+ fs8.mkdirSync(statePaths.kittyDir, { recursive: true });
3014
4179
  this.db = new Database(statePaths.controlPlaneLedgerFile);
3015
4180
  this.db.pragma("journal_mode = WAL");
3016
4181
  this.db.pragma("foreign_keys = ON");
@@ -3177,11 +4342,11 @@ function truncateWakeFact(value) {
3177
4342
  }
3178
4343
 
3179
4344
  // src/utils/fs.ts
3180
- import fs7 from "fs/promises";
3181
- import path8 from "path";
4345
+ import fs9 from "fs/promises";
4346
+ import path10 from "path";
3182
4347
  async function fileExists(targetPath) {
3183
4348
  try {
3184
- await fs7.access(targetPath);
4349
+ await fs9.access(targetPath);
3185
4350
  return true;
3186
4351
  } catch {
3187
4352
  return false;
@@ -3189,10 +4354,10 @@ async function fileExists(targetPath) {
3189
4354
  }
3190
4355
  function resolveUserPath(inputPath, cwd) {
3191
4356
  const cleanPath = normalizeUserPathInput(inputPath);
3192
- if (path8.isAbsolute(cleanPath)) {
3193
- return path8.normalize(cleanPath);
4357
+ if (path10.isAbsolute(cleanPath)) {
4358
+ return path10.normalize(cleanPath);
3194
4359
  }
3195
- return path8.resolve(cwd, cleanPath);
4360
+ return path10.resolve(cwd, cleanPath);
3196
4361
  }
3197
4362
  function normalizeUserPathInput(inputPath) {
3198
4363
  const trimmed = inputPath.trim();
@@ -3206,7 +4371,7 @@ function normalizeUserPathInput(inputPath) {
3206
4371
  return trimmed;
3207
4372
  }
3208
4373
  async function ensureParentDirectory(filePath) {
3209
- await fs7.mkdir(path8.dirname(filePath), { recursive: true });
4374
+ await fs9.mkdir(path10.dirname(filePath), { recursive: true });
3210
4375
  }
3211
4376
  function truncateText(input, maxChars) {
3212
4377
  if (input.length <= maxChars) {
@@ -3258,7 +4423,7 @@ function projectToolResultForModel(input) {
3258
4423
  }
3259
4424
  }
3260
4425
  function projectExecutionAction(payload) {
3261
- const execution = readObject(payload.execution) ?? payload;
4426
+ const execution = readObject2(payload.execution) ?? payload;
3262
4427
  return joinLines([
3263
4428
  readString(execution.id) ?? "execution",
3264
4429
  readString(execution.kind),
@@ -3266,7 +4431,7 @@ function projectExecutionAction(payload) {
3266
4431
  readString(execution.command),
3267
4432
  readString(execution.summary),
3268
4433
  readString(execution.outputPreview),
3269
- readObject(execution.health) ? readString(readObject(execution.health)?.message) : void 0,
4434
+ readObject2(execution.health) ? readString(readObject2(execution.health)?.message) : void 0,
3270
4435
  readString(execution.error)
3271
4436
  ]);
3272
4437
  }
@@ -3293,10 +4458,10 @@ function projectExecutionCheck(payload) {
3293
4458
  ]);
3294
4459
  }
3295
4460
  function projectRead(payload) {
3296
- const path27 = readString(payload.path) ?? readString(payload.requestedPath) ?? "file";
4461
+ const path29 = readString(payload.path) ?? readString(payload.requestedPath) ?? "file";
3297
4462
  if (payload.readable === false) {
3298
4463
  return joinLines([
3299
- `${path27}: not readable`,
4464
+ `${path29}: not readable`,
3300
4465
  readString(payload.reason),
3301
4466
  readString(payload.detectedCapability) ? `capability: ${readString(payload.detectedCapability)}` : void 0
3302
4467
  ]);
@@ -3304,35 +4469,35 @@ function projectRead(payload) {
3304
4469
  const startLine = readNumber(payload.startLine);
3305
4470
  const endLine = readNumber(payload.endLine);
3306
4471
  const content = readString(payload.content) ?? "";
3307
- const continuation = readObject(payload.continuation);
3308
- const continuationArgs = readObject(continuation?.continuationArgs);
4472
+ const continuation = readObject2(payload.continuation);
4473
+ const continuationArgs = readObject2(continuation?.continuationArgs);
3309
4474
  return joinLines([
3310
- `${path27}${startLine && endLine ? `:${startLine}-${endLine}` : ""}`,
4475
+ `${path29}${startLine && endLine ? `:${startLine}-${endLine}` : ""}`,
3311
4476
  truncateText(content, DEFAULT_MAX_CHARS),
3312
4477
  continuationArgs ? `next: read ${JSON.stringify(continuationArgs)}` : void 0
3313
4478
  ]);
3314
4479
  }
3315
4480
  function projectEdit(payload) {
3316
- const path27 = readString(payload.path) ?? "file";
4481
+ const path29 = readString(payload.path) ?? "file";
3317
4482
  const applied = readNumber(payload.appliedEdits) ?? readNumber(payload.requestedEdits);
3318
4483
  const diff = readString(payload.diff) ?? readString(payload.preview);
3319
4484
  return joinLines([
3320
- `edited ${path27}${applied ? ` (${applied} replacement${applied === 1 ? "" : "s"})` : ""}`,
4485
+ `edited ${path29}${applied ? ` (${applied} replacement${applied === 1 ? "" : "s"})` : ""}`,
3321
4486
  diff ? truncateText(diff, DIFF_MAX_CHARS) : void 0
3322
4487
  ]);
3323
4488
  }
3324
4489
  function projectWrite(payload) {
3325
- const path27 = readString(payload.path) ?? "file";
4490
+ const path29 = readString(payload.path) ?? "file";
3326
4491
  const bytes = readNumber(payload.bytes);
3327
4492
  const existed = payload.existed === true;
3328
4493
  const diff = readString(payload.diff) ?? readString(payload.preview);
3329
4494
  return joinLines([
3330
- `${existed ? "wrote" : "created"} ${path27}${bytes !== void 0 ? ` (${bytes} bytes)` : ""}`,
4495
+ `${existed ? "wrote" : "created"} ${path29}${bytes !== void 0 ? ` (${bytes} bytes)` : ""}`,
3331
4496
  diff ? truncateText(diff, DIFF_MAX_CHARS) : void 0
3332
4497
  ]);
3333
4498
  }
3334
4499
  function projectBash(payload) {
3335
- const governance = readObject(payload.outputGovernance);
4500
+ const governance = readObject2(payload.outputGovernance);
3336
4501
  const projection = readString(governance?.projection);
3337
4502
  if (projection) {
3338
4503
  return projection;
@@ -3353,13 +4518,13 @@ function projectBash(payload) {
3353
4518
  return joinLines(lines);
3354
4519
  }
3355
4520
  function projectSkillLoad(payload) {
3356
- const skill = readObject(payload.skill);
4521
+ const skill = readObject2(payload.skill);
3357
4522
  const name = readString(skill?.name) ?? "skill";
3358
4523
  const description = readString(skill?.description);
3359
- const path27 = readString(skill?.path);
4524
+ const path29 = readString(skill?.path);
3360
4525
  const body = readString(payload.body) ?? "";
3361
4526
  return joinLines([
3362
- `loaded skill: ${name}${path27 ? ` (${path27})` : ""}`,
4527
+ `loaded skill: ${name}${path29 ? ` (${path29})` : ""}`,
3363
4528
  description,
3364
4529
  truncateText(body, SKILL_BODY_MAX_CHARS)
3365
4530
  ]);
@@ -3389,8 +4554,8 @@ function projectFailure(toolName, rawOutput, payload) {
3389
4554
  if (!payload) {
3390
4555
  return truncateText(rawOutput.trim(), DEFAULT_MAX_CHARS);
3391
4556
  }
3392
- const details = readObject(payload.details);
3393
- const readArgs = readObject(details?.readArgs);
4557
+ const details = readObject2(payload.details);
4558
+ const readArgs = readObject2(details?.readArgs);
3394
4559
  const suggestions = readArray(details?.suggestions);
3395
4560
  const lines = [
3396
4561
  `${toolName} failed: ${readString(payload.error) ?? "unknown error"}`,
@@ -3412,7 +4577,7 @@ function parseObject(raw) {
3412
4577
  return null;
3413
4578
  }
3414
4579
  }
3415
- function readObject(value) {
4580
+ function readObject2(value) {
3416
4581
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3417
4582
  }
3418
4583
  function readArray(value) {
@@ -3447,14 +4612,14 @@ var ToolExecutionError = class extends Error {
3447
4612
  };
3448
4613
 
3449
4614
  // src/tools/outputCapture.ts
3450
- import crypto from "crypto";
3451
- import fs8 from "fs/promises";
3452
- import path9 from "path";
4615
+ import crypto2 from "crypto";
4616
+ import fs10 from "fs/promises";
4617
+ import path11 from "path";
3453
4618
  var DEFAULT_BASH_OUTPUT_PREVIEW_CHARS = 12e3;
3454
4619
  async function createBashOutputCapture(input) {
3455
4620
  const maxPreviewChars = typeof input.maxPreviewChars === "number" && Number.isFinite(input.maxPreviewChars) && input.maxPreviewChars > 0 ? Math.trunc(input.maxPreviewChars) : DEFAULT_BASH_OUTPUT_PREVIEW_CHARS;
3456
4621
  const absoluteOutputPath = input.stateRootDir && input.sessionId ? await createAbsoluteOutputPath(input.stateRootDir, input.sessionId) : void 0;
3457
- const outputPath = absoluteOutputPath && input.stateRootDir ? path9.relative(input.stateRootDir, absoluteOutputPath) || void 0 : void 0;
4622
+ const outputPath = absoluteOutputPath && input.stateRootDir ? path11.relative(input.stateRootDir, absoluteOutputPath) || void 0 : void 0;
3458
4623
  let preview = "";
3459
4624
  let bufferedOutput = "";
3460
4625
  let totalChars = 0;
@@ -3492,507 +4657,125 @@ async function createBashOutputCapture(input) {
3492
4657
  truncated,
3493
4658
  outputChars: totalChars,
3494
4659
  outputBytes: totalBytes
3495
- };
3496
- }
3497
- function queueWrite(chunk) {
3498
- if (!absoluteOutputPath) {
3499
- return;
3500
- }
3501
- pendingWrite = pendingWrite.then(() => fs8.appendFile(absoluteOutputPath, chunk, "utf8"));
3502
- }
3503
- return {
3504
- append,
3505
- finalize
3506
- };
3507
- }
3508
- async function createAbsoluteOutputPath(stateRootDir, sessionId) {
3509
- const paths = getProjectStatePaths(stateRootDir);
3510
- const sessionDir = path9.join(paths.observabilityDir, "command-output", sessionId);
3511
- await fs8.mkdir(sessionDir, { recursive: true });
3512
- return path9.join(sessionDir, `${Date.now()}-bash-output-${crypto.randomUUID().slice(0, 8)}.txt`);
3513
- }
3514
-
3515
- // src/utils/commandRunner/shellRuntime.ts
3516
- function getShellRuntimeInfo(platform = process.platform) {
3517
- if (platform === "win32") {
3518
- return {
3519
- platform,
3520
- shell: "powershell",
3521
- executable: "powershell.exe",
3522
- invocation: "powershell.exe -NoLogo -NoProfile -EncodedCommand <command>",
3523
- guidance: "Windows runs commands through PowerShell; use PowerShell syntax, semicolon-separated commands, node -e, python -c, or an explicit bash invocation for Bash/POSIX syntax."
3524
- };
3525
- }
3526
- return {
3527
- platform,
3528
- shell: "bash",
3529
- executable: "/bin/bash",
3530
- invocation: "/bin/bash -lc <command>",
3531
- guidance: "Unix-like platforms run commands through bash -lc; use Bash syntax."
3532
- };
3533
- }
3534
-
3535
- // src/utils/commandRunner/launch.ts
3536
- async function launchCommand(command, cwd, timeoutMs, abortSignal) {
3537
- const execa4 = await loadExeca();
3538
- const shell = getShellRuntimeInfo();
3539
- const subprocess = shell.shell === "powershell" ? execa4(shell.executable, ["-NoLogo", "-NoProfile", "-EncodedCommand", encodePowerShellCommand(command)], {
3540
- cwd,
3541
- timeout: timeoutMs,
3542
- cancelSignal: abortSignal,
3543
- all: true,
3544
- buffer: false,
3545
- reject: false,
3546
- env: buildCommandEnvironment()
3547
- }) : execa4(shell.executable, ["-lc", command], {
3548
- cwd,
3549
- timeout: timeoutMs,
3550
- cancelSignal: abortSignal,
3551
- all: true,
3552
- buffer: false,
3553
- reject: false,
3554
- env: buildCommandEnvironment()
3555
- });
3556
- return { subprocess };
3557
- }
3558
- function encodePowerShellCommand(command) {
3559
- const wrapped = [
3560
- "$ProgressPreference = 'SilentlyContinue'",
3561
- "[Console]::InputEncoding = [System.Text.Encoding]::UTF8",
3562
- "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
3563
- "$OutputEncoding = [System.Text.Encoding]::UTF8",
3564
- "try { chcp 65001 > $null } catch { }",
3565
- `& { ${command} }`,
3566
- "$code = if ($null -ne $LASTEXITCODE) { [int]$LASTEXITCODE } elseif ($?) { 0 } else { 1 }",
3567
- "exit $code"
3568
- ].join("; ");
3569
- return Buffer.from(wrapped, "utf16le").toString("base64");
3570
- }
3571
- function buildCommandEnvironment() {
3572
- return {
3573
- ...process.env,
3574
- LANG: process.env.LANG || "C.UTF-8",
3575
- LC_ALL: process.env.LC_ALL || "C.UTF-8",
3576
- PYTHONIOENCODING: process.env.PYTHONIOENCODING || "utf-8"
3577
- };
3578
- }
3579
-
3580
- // src/utils/commandRunner/platformArgs.ts
3581
- function splitByAndAnd(command) {
3582
- const segments = [];
3583
- let current = "";
3584
- let inSingle = false;
3585
- let inDouble = false;
3586
- for (let index = 0; index < command.length; index += 1) {
3587
- const char = command.charAt(index);
3588
- if (char === "'" && !inDouble) {
3589
- inSingle = !inSingle;
3590
- current += char;
3591
- continue;
3592
- }
3593
- if (char === '"' && !inSingle) {
3594
- inDouble = !inDouble;
3595
- current += char;
3596
- continue;
3597
- }
3598
- if (!inSingle && !inDouble && char === "&" && command.charAt(index + 1) === "&") {
3599
- if (current.trim()) {
3600
- segments.push(current.trim());
3601
- }
3602
- current = "";
3603
- index += 1;
3604
- continue;
3605
- }
3606
- current += char;
3607
- }
3608
- if (current.trim()) {
3609
- segments.push(current.trim());
3610
- }
3611
- return segments.length > 0 ? segments : [command];
3612
- }
3613
- function joinWithAndSemantics(segments) {
3614
- if (segments.length <= 1) {
3615
- return segments[0] ?? "";
3616
- }
3617
- let script = segments[0] ?? "";
3618
- for (let index = 1; index < segments.length; index += 1) {
3619
- const segment = segments[index];
3620
- script += `; if ($?) { ${segment} }`;
3621
- }
3622
- return script;
3623
- }
3624
- function splitArgs(command) {
3625
- const args = [];
3626
- let current = "";
3627
- let inSingle = false;
3628
- let inDouble = false;
3629
- for (let index = 0; index < command.length; index += 1) {
3630
- const char = command.charAt(index);
3631
- if (char === "'" && !inDouble) {
3632
- inSingle = !inSingle;
3633
- continue;
3634
- }
3635
- if (char === '"' && !inSingle) {
3636
- inDouble = !inDouble;
3637
- continue;
3638
- }
3639
- if (!inSingle && !inDouble && /\s/.test(char)) {
3640
- if (current) {
3641
- args.push(current);
3642
- current = "";
3643
- }
3644
- continue;
3645
- }
3646
- current += char;
3647
- }
3648
- if (current) {
3649
- args.push(current);
3650
- }
3651
- return args;
3652
- }
3653
- function expandPaths(paths) {
3654
- return paths.flatMap((targetPath) => expandBraces(targetPath));
3655
- }
3656
- function normalizeWindowsPath(value) {
3657
- if (value.includes("://")) {
3658
- return value;
3659
- }
3660
- return value.replace(/\//g, "\\");
3661
- }
3662
- function quotePowerShell(value) {
3663
- const escaped = value.replace(/'/g, "''");
3664
- return `'${escaped}'`;
3665
- }
3666
- function expandBraces(input) {
3667
- const start = findBraceStart(input);
3668
- if (start === -1) {
3669
- return [input];
3670
- }
3671
- const end = findMatchingBrace(input, start);
3672
- if (end === -1) {
3673
- return [input];
3674
- }
3675
- const prefix = input.slice(0, start);
3676
- const suffix = input.slice(end + 1);
3677
- const body = input.slice(start + 1, end);
3678
- const parts = splitBraceParts(body);
3679
- const expandedSuffix = expandBraces(suffix);
3680
- const results = [];
3681
- for (const part of parts) {
3682
- for (const expandedPart of expandBraces(part)) {
3683
- for (const tail of expandedSuffix) {
3684
- results.push(`${prefix}${expandedPart}${tail}`);
3685
- }
3686
- }
3687
- }
3688
- return results;
3689
- }
3690
- function findBraceStart(input) {
3691
- let inSingle = false;
3692
- let inDouble = false;
3693
- for (let index = 0; index < input.length; index += 1) {
3694
- const char = input.charAt(index);
3695
- if (char === "'" && !inDouble) {
3696
- inSingle = !inSingle;
3697
- continue;
3698
- }
3699
- if (char === '"' && !inSingle) {
3700
- inDouble = !inDouble;
3701
- continue;
3702
- }
3703
- if (!inSingle && !inDouble && char === "{") {
3704
- return index;
3705
- }
3706
- }
3707
- return -1;
3708
- }
3709
- function findMatchingBrace(input, start) {
3710
- let depth = 0;
3711
- let inSingle = false;
3712
- let inDouble = false;
3713
- for (let index = start; index < input.length; index += 1) {
3714
- const char = input.charAt(index);
3715
- if (char === "'" && !inDouble) {
3716
- inSingle = !inSingle;
3717
- continue;
3718
- }
3719
- if (char === '"' && !inSingle) {
3720
- inDouble = !inDouble;
3721
- continue;
3722
- }
3723
- if (inSingle || inDouble) {
3724
- continue;
3725
- }
3726
- if (char === "{") {
3727
- depth += 1;
3728
- } else if (char === "}") {
3729
- depth -= 1;
3730
- if (depth === 0) {
3731
- return index;
3732
- }
3733
- }
3734
- }
3735
- return -1;
3736
- }
3737
- function splitBraceParts(input) {
3738
- const parts = [];
3739
- let current = "";
3740
- let depth = 0;
3741
- for (let index = 0; index < input.length; index += 1) {
3742
- const char = input.charAt(index);
3743
- if (char === "{") {
3744
- depth += 1;
3745
- current += char;
3746
- continue;
3747
- }
3748
- if (char === "}") {
3749
- depth -= 1;
3750
- current += char;
3751
- continue;
3752
- }
3753
- if (char === "," && depth === 0) {
3754
- parts.push(current);
3755
- current = "";
3756
- continue;
3757
- }
3758
- current += char;
3759
- }
3760
- if (current) {
3761
- parts.push(current);
3762
- }
3763
- return parts.length > 0 ? parts : [input];
3764
- }
3765
-
3766
- // src/utils/commandRunner/platformTransforms.ts
3767
- function startsWithExplicitShell(command) {
3768
- return /^\s*(cmd(?:\.exe)?\s+\/c|powershell(?:\.exe)?\b|pwsh\b|bash\b)/i.test(command);
3769
- }
3770
- function normalizeWindowsSegment(segment) {
3771
- const trimmed = segment.trim();
3772
- if (!trimmed) {
3773
- return segment;
3774
- }
3775
- const lowered = trimmed.toLowerCase();
3776
- if (lowered.startsWith("get-childitem") || lowered.startsWith("new-item")) {
3777
- return segment;
3778
- }
3779
- if (lowered.startsWith("ls")) {
3780
- return normalizeLsSegment(trimmed);
3781
- }
3782
- if (lowered.startsWith("mkdir") || lowered.startsWith("md ")) {
3783
- return normalizeMkdirSegment(trimmed);
3784
- }
3785
- if (lowered.startsWith("rm ")) {
3786
- return normalizeRemoveSegment(trimmed);
3787
- }
3788
- if (lowered.startsWith("cp ")) {
3789
- return normalizeCopySegment(trimmed);
3790
- }
3791
- if (lowered.startsWith("mv ")) {
3792
- return normalizeMoveSegment(trimmed);
3793
- }
3794
- if (lowered.startsWith("touch ")) {
3795
- return normalizeTouchSegment(trimmed);
4660
+ };
3796
4661
  }
3797
- if (lowered.startsWith("cat ")) {
3798
- return normalizeCatSegment(trimmed);
4662
+ function queueWrite(chunk) {
4663
+ if (!absoluteOutputPath) {
4664
+ return;
4665
+ }
4666
+ pendingWrite = pendingWrite.then(() => fs10.appendFile(absoluteOutputPath, chunk, "utf8"));
3799
4667
  }
3800
- return segment;
4668
+ return {
4669
+ append,
4670
+ finalize
4671
+ };
3801
4672
  }
3802
- function normalizeLsSegment(segment) {
3803
- const args = splitArgs(segment).slice(1);
3804
- const flags = args.filter((arg) => arg.startsWith("-"));
3805
- const paths = args.filter((arg) => !arg.startsWith("-"));
3806
- const force = flags.some((flag) => flag.includes("a"));
3807
- const targetPath = paths[0];
3808
- let command = "Get-ChildItem";
3809
- if (force) {
3810
- command += " -Force";
3811
- }
3812
- if (targetPath) {
3813
- command += ` -LiteralPath ${quotePowerShell(normalizeWindowsPath(targetPath))}`;
3814
- }
3815
- return command;
4673
+ async function createAbsoluteOutputPath(stateRootDir, sessionId) {
4674
+ const paths = getProjectStatePaths(stateRootDir);
4675
+ const sessionDir = path11.join(paths.observabilityDir, "command-output", sessionId);
4676
+ await fs10.mkdir(sessionDir, { recursive: true });
4677
+ return path11.join(sessionDir, `${Date.now()}-bash-output-${crypto2.randomUUID().slice(0, 8)}.txt`);
3816
4678
  }
3817
- function normalizeMkdirSegment(segment) {
3818
- const args = splitArgs(segment);
3819
- if (args.length <= 1) {
3820
- return segment;
4679
+
4680
+ // src/utils/commandRunner/shellRuntime.ts
4681
+ function getShellRuntimeInfo(platform = process.platform) {
4682
+ if (platform === "win32") {
4683
+ return {
4684
+ platform,
4685
+ shell: "powershell",
4686
+ executable: "powershell.exe",
4687
+ invocation: "powershell.exe -NoLogo -NoProfile -EncodedCommand <command>",
4688
+ guidance: "Windows runs commands through PowerShell; use PowerShell syntax, semicolon-separated commands, node -e, python -c, or an explicit bash invocation for Bash/POSIX syntax."
4689
+ };
3821
4690
  }
3822
- const rest = args.slice(1);
3823
- let hasParents = false;
3824
- const paths = rest.filter((arg) => {
3825
- const lowered = arg.toLowerCase();
3826
- if (lowered === "-p" || lowered === "--parents") {
3827
- hasParents = true;
3828
- return false;
3829
- }
3830
- return true;
4691
+ return {
4692
+ platform,
4693
+ shell: "bash",
4694
+ executable: "/bin/bash",
4695
+ invocation: "/bin/bash -lc <command>",
4696
+ guidance: "Unix-like platforms run commands through bash -lc; use Bash syntax."
4697
+ };
4698
+ }
4699
+
4700
+ // src/utils/commandRunner/launch.ts
4701
+ async function launchCommand(command, cwd, timeoutMs, abortSignal) {
4702
+ const execa4 = await loadExeca();
4703
+ const shell = getShellRuntimeInfo();
4704
+ const subprocess = shell.shell === "powershell" ? execa4(shell.executable, ["-NoLogo", "-NoProfile", "-EncodedCommand", encodePowerShellCommand(command)], {
4705
+ cwd,
4706
+ timeout: timeoutMs,
4707
+ cancelSignal: abortSignal,
4708
+ all: true,
4709
+ buffer: false,
4710
+ reject: false,
4711
+ env: buildCommandEnvironment()
4712
+ }) : execa4(shell.executable, ["-lc", command], {
4713
+ cwd,
4714
+ timeout: timeoutMs,
4715
+ cancelSignal: abortSignal,
4716
+ all: true,
4717
+ buffer: false,
4718
+ reject: false,
4719
+ env: buildCommandEnvironment()
3831
4720
  });
3832
- const needsNormalization = hasParents || paths.some((targetPath) => targetPath.includes("{"));
3833
- if (!needsNormalization) {
3834
- return segment;
3835
- }
3836
- const expanded = expandPaths(paths);
3837
- if (expanded.length === 0) {
3838
- return segment;
3839
- }
3840
- const normalizedPaths = expanded.map((targetPath) => quotePowerShell(normalizeWindowsPath(targetPath)));
3841
- return `New-Item -ItemType Directory -Force -Path ${normalizedPaths.join(", ")}`;
3842
- }
3843
- function normalizeRemoveSegment(segment) {
3844
- const args = splitArgs(segment);
3845
- if (args.length <= 1) {
3846
- return segment;
3847
- }
3848
- const flags = args.slice(1).filter((arg) => arg.startsWith("-"));
3849
- const paths = args.slice(1).filter((arg) => !arg.startsWith("-"));
3850
- if (paths.length === 0) {
3851
- return segment;
3852
- }
3853
- const recurse = flags.some((flag) => /r/i.test(flag));
3854
- const force = flags.some((flag) => /f/i.test(flag));
3855
- let command = "Remove-Item";
3856
- if (recurse) {
3857
- command += " -Recurse";
3858
- }
3859
- if (force) {
3860
- command += " -Force";
3861
- }
3862
- command += ` -LiteralPath ${paths.map((targetPath) => quotePowerShell(normalizeWindowsPath(targetPath))).join(", ")}`;
3863
- return command;
3864
- }
3865
- function normalizeCopySegment(segment) {
3866
- const args = splitArgs(segment);
3867
- if (args.length < 3) {
3868
- return segment;
3869
- }
3870
- const flags = args.slice(1).filter((arg) => arg.startsWith("-"));
3871
- const paths = args.slice(1).filter((arg) => !arg.startsWith("-"));
3872
- if (paths.length < 2) {
3873
- return segment;
3874
- }
3875
- const recurse = flags.some((flag) => /r/i.test(flag));
3876
- const force = flags.some((flag) => /f/i.test(flag));
3877
- const destination = paths[paths.length - 1];
3878
- if (!destination) {
3879
- return segment;
3880
- }
3881
- const sources = paths.slice(0, -1);
3882
- let command = "Copy-Item";
3883
- if (recurse) {
3884
- command += " -Recurse";
3885
- }
3886
- if (force) {
3887
- command += " -Force";
3888
- }
3889
- command += ` -Path ${sources.map((targetPath) => quotePowerShell(normalizeWindowsPath(targetPath))).join(", ")}`;
3890
- command += ` -Destination ${quotePowerShell(normalizeWindowsPath(destination))}`;
3891
- return command;
3892
- }
3893
- function normalizeMoveSegment(segment) {
3894
- const args = splitArgs(segment);
3895
- if (args.length < 3) {
3896
- return segment;
3897
- }
3898
- const flags = args.slice(1).filter((arg) => arg.startsWith("-"));
3899
- const paths = args.slice(1).filter((arg) => !arg.startsWith("-"));
3900
- if (paths.length < 2) {
3901
- return segment;
3902
- }
3903
- const force = flags.some((flag) => /f/i.test(flag));
3904
- const destination = paths[paths.length - 1];
3905
- if (!destination) {
3906
- return segment;
3907
- }
3908
- const sources = paths.slice(0, -1);
3909
- let command = "Move-Item";
3910
- if (force) {
3911
- command += " -Force";
3912
- }
3913
- command += ` -Path ${sources.map((targetPath) => quotePowerShell(normalizeWindowsPath(targetPath))).join(", ")}`;
3914
- command += ` -Destination ${quotePowerShell(normalizeWindowsPath(destination))}`;
3915
- return command;
3916
- }
3917
- function normalizeTouchSegment(segment) {
3918
- const args = splitArgs(segment).slice(1);
3919
- if (args.length === 0) {
3920
- return segment;
3921
- }
3922
- const expanded = expandPaths(args);
3923
- if (expanded.length === 0) {
3924
- return segment;
3925
- }
3926
- const paths = expanded.map((targetPath) => quotePowerShell(normalizeWindowsPath(targetPath)));
3927
- return `New-Item -ItemType File -Force -Path ${paths.join(", ")}`;
3928
- }
3929
- function normalizeCatSegment(segment) {
3930
- const args = splitArgs(segment).slice(1);
3931
- if (args.length === 0) {
3932
- return segment;
3933
- }
3934
- const targetPath = args[0];
3935
- if (!targetPath) {
3936
- return segment;
3937
- }
3938
- return `Get-Content -LiteralPath ${quotePowerShell(normalizeWindowsPath(targetPath))}`;
3939
- }
3940
-
3941
- // src/utils/commandRunner/platform.ts
3942
- function normalizeCommandForPlatform(command) {
3943
- if (process.platform !== "win32") {
3944
- return command;
3945
- }
3946
- const trimmed = command.trim();
3947
- if (!trimmed) {
3948
- return command;
3949
- }
3950
- const normalized = normalizeWindowsCommand(trimmed);
3951
- return normalizeNpmCommandNames(normalized);
4721
+ return { subprocess };
3952
4722
  }
3953
- function normalizeWindowsCommand(command) {
3954
- if (startsWithExplicitShell(command)) {
3955
- return command;
3956
- }
3957
- const segments = splitByAndAnd(command);
3958
- const normalizedSegments = segments.map((segment) => normalizeWindowsSegment(segment));
3959
- return joinWithAndSemantics(normalizedSegments);
4723
+ function encodePowerShellCommand(command) {
4724
+ const wrapped = [
4725
+ "$ProgressPreference = 'SilentlyContinue'",
4726
+ "$ErrorActionPreference = 'Stop'",
4727
+ "[Console]::InputEncoding = [System.Text.Encoding]::UTF8",
4728
+ "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
4729
+ "$OutputEncoding = [System.Text.Encoding]::UTF8",
4730
+ "try { chcp 65001 > $null } catch { }",
4731
+ "$code = 0",
4732
+ "try {",
4733
+ `& { ${command} }`,
4734
+ "$code = if ($null -ne $LASTEXITCODE) { [int]$LASTEXITCODE } elseif ($?) { 0 } else { 1 }",
4735
+ "} catch {",
4736
+ "[Console]::Error.WriteLine($_.Exception.Message)",
4737
+ "$code = 1",
4738
+ "}",
4739
+ "exit $code"
4740
+ ].join("\n");
4741
+ return Buffer.from(wrapped, "utf16le").toString("base64");
3960
4742
  }
3961
- function normalizeNpmCommandNames(command) {
3962
- const commandNames = {
3963
- npm: "npm.cmd",
3964
- npx: "npx.cmd",
3965
- pnpm: "pnpm.cmd",
3966
- yarn: "yarn.cmd"
4743
+ function buildCommandEnvironment() {
4744
+ return {
4745
+ ...process.env,
4746
+ LANG: process.env.LANG || "C.UTF-8",
4747
+ LC_ALL: process.env.LC_ALL || "C.UTF-8",
4748
+ PYTHONIOENCODING: process.env.PYTHONIOENCODING || "utf-8"
3967
4749
  };
3968
- const pattern = /(^|[;&|]|\&\&)\s*(npm|npx|pnpm|yarn)(?=\s|$)/gi;
3969
- return command.replace(pattern, (match, prefix, tool) => {
3970
- const replacement = commandNames[String(tool).toLowerCase()];
3971
- if (!replacement) {
3972
- return match;
3973
- }
3974
- if (!prefix) {
3975
- return replacement;
3976
- }
3977
- return `${prefix} ${replacement}`;
3978
- });
4750
+ }
4751
+
4752
+ // src/utils/commandRunner/output.ts
4753
+ function normalizeCommandOutput(output) {
4754
+ if (!output.includes("#< CLIXML")) {
4755
+ return output;
4756
+ }
4757
+ const errors = [...output.matchAll(/<S\s+S="Error">([\s\S]*?)<\/S>/g)].map((match) => decodePowerShellText(match[1] ?? "")).map((line) => line.trimEnd()).filter(Boolean);
4758
+ if (errors.length === 0) {
4759
+ return output;
4760
+ }
4761
+ return errors.join("\n").replace(/\n{3,}/g, "\n\n").trim();
4762
+ }
4763
+ function decodePowerShellText(value) {
4764
+ return value.replace(/_x000D__x000A_/g, "\n").replace(/_x000D_/g, "\r").replace(/_x000A_/g, "\n").replace(/_x0009_/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
3979
4765
  }
3980
4766
 
3981
4767
  // src/utils/commandRunner/run.ts
3982
4768
  var STALL_KILL_TIMEOUT_MS = 5e3;
3983
4769
  async function runCommandWithPolicy(options) {
3984
- const normalizedCommand = normalizeCommandForPlatform(options.command);
3985
- return runCommandOnce({
3986
- ...options,
3987
- command: normalizedCommand
3988
- });
4770
+ return runCommandOnce(options);
3989
4771
  }
3990
4772
  async function runCommandOnce(options) {
3991
4773
  const start = Date.now();
3992
4774
  let stalled = false;
3993
4775
  let stallTimer = null;
3994
4776
  let forceKillTimer = null;
3995
- const { subprocess } = await launchCommand(options.command, options.cwd, options.timeoutMs, options.abortSignal);
4777
+ const launched = await launchCommand(options.command, options.cwd, options.timeoutMs, options.abortSignal);
4778
+ const { subprocess } = launched;
3996
4779
  const outputCapture = await createBashOutputCapture(options.outputCapture ?? {});
3997
4780
  const clearTimers = () => {
3998
4781
  if (stallTimer) {
@@ -4045,9 +4828,11 @@ async function runCommandOnce(options) {
4045
4828
  const result = await subprocess;
4046
4829
  clearTimers();
4047
4830
  const shellOutput = await outputCapture.finalize();
4831
+ const output = normalizeCommandOutput(shellOutput.outputPreview);
4048
4832
  return {
4833
+ command: options.command,
4049
4834
  exitCode: typeof result.exitCode === "number" ? result.exitCode : null,
4050
- output: shellOutput.outputPreview,
4835
+ output,
4051
4836
  outputPath: shellOutput.outputPath,
4052
4837
  truncated: shellOutput.truncated,
4053
4838
  outputChars: shellOutput.outputChars,
@@ -4062,9 +4847,12 @@ async function runCommandOnce(options) {
4062
4847
  const timedOut = isTimedOutError(error);
4063
4848
  clearTimers();
4064
4849
  const shellOutput = await outputCapture.finalize();
4850
+ const fallbackOutput = shellOutput.outputChars > 0 ? shellOutput.outputPreview : readProcessOutput(error);
4851
+ const output = normalizeCommandOutput(fallbackOutput);
4065
4852
  return {
4853
+ command: options.command,
4066
4854
  exitCode: readExitCode(error),
4067
- output: shellOutput.outputChars > 0 ? shellOutput.outputPreview : readProcessOutput(error),
4855
+ output,
4068
4856
  outputPath: shellOutput.outputPath,
4069
4857
  truncated: shellOutput.truncated,
4070
4858
  outputChars: shellOutput.outputChars,
@@ -4100,8 +4888,8 @@ function isAbortedProcessResult(value, signal) {
4100
4888
  }
4101
4889
 
4102
4890
  // src/tools/core/shared.ts
4103
- import fs9 from "fs/promises";
4104
- import path10 from "path";
4891
+ import fs11 from "fs/promises";
4892
+ import path12 from "path";
4105
4893
  import { diffLines } from "diff";
4106
4894
 
4107
4895
  // src/utils/text.ts
@@ -4315,15 +5103,15 @@ function comparePathForDiscovery(root, left, right) {
4315
5103
  if (leftDepth !== rightDepth) {
4316
5104
  return leftDepth - rightDepth;
4317
5105
  }
4318
- const leftName = path10.posix.basename(leftRelative).toLowerCase();
4319
- const rightName = path10.posix.basename(rightRelative).toLowerCase();
5106
+ const leftName = path12.posix.basename(leftRelative).toLowerCase();
5107
+ const rightName = path12.posix.basename(rightRelative).toLowerCase();
4320
5108
  if (leftName !== rightName) {
4321
5109
  return leftName.localeCompare(rightName);
4322
5110
  }
4323
5111
  return leftRelative.localeCompare(rightRelative);
4324
5112
  }
4325
5113
  function toPosixRelative(root, targetPath) {
4326
- return (path10.relative(root, targetPath) || path10.basename(targetPath)).replace(/\\/g, "/");
5114
+ return (path12.relative(root, targetPath) || path12.basename(targetPath)).replace(/\\/g, "/");
4327
5115
  }
4328
5116
  function pathDepth(relativePath) {
4329
5117
  return relativePath.split("/").filter(Boolean).length;
@@ -4625,7 +5413,7 @@ var bashToolDefinition = {
4625
5413
  const status = result.aborted ? "aborted" : result.stalled ? "stalled" : result.timedOut ? "timed_out" : result.exitCode === 0 ? "completed" : "failed";
4626
5414
  const outputGovernance = governToolOutput({
4627
5415
  toolName: "bash",
4628
- command,
5416
+ command: result.command,
4629
5417
  status,
4630
5418
  exitCode: result.exitCode,
4631
5419
  durationMs: result.durationMs,
@@ -4653,7 +5441,7 @@ var bashToolDefinition = {
4653
5441
  return okResult(
4654
5442
  JSON.stringify(
4655
5443
  {
4656
- command,
5444
+ command: result.command,
4657
5445
  cwd: resolvedCwd,
4658
5446
  exitCode: result.exitCode,
4659
5447
  status,
@@ -4684,7 +5472,7 @@ var bashToolDefinition = {
4684
5472
  };
4685
5473
 
4686
5474
  // src/tools/edit.ts
4687
- import fs11 from "fs/promises";
5475
+ import fs13 from "fs/promises";
4688
5476
 
4689
5477
  // src/tools/core/changeTracking.ts
4690
5478
  async function recordToolChange(context, input) {
@@ -4725,13 +5513,13 @@ function encodeUtf8(value) {
4725
5513
  }
4726
5514
 
4727
5515
  // src/tools/core/pathDisplay.ts
4728
- import path11 from "path";
5516
+ import path13 from "path";
4729
5517
  function toToolRelativePath(cwd, targetPath) {
4730
- const relative = path11.relative(cwd, targetPath);
5518
+ const relative = path13.relative(cwd, targetPath);
4731
5519
  if (!relative || relative === "") {
4732
5520
  return ".";
4733
5521
  }
4734
- if (relative.startsWith("..") || path11.isAbsolute(relative)) {
5522
+ if (relative.startsWith("..") || path13.isAbsolute(relative)) {
4735
5523
  return targetPath;
4736
5524
  }
4737
5525
  return relative.replace(/\\/g, "/");
@@ -4758,8 +5546,8 @@ function buildToolChangeFeedback(input) {
4758
5546
  }
4759
5547
 
4760
5548
  // src/tools/writeDiagnostics.ts
4761
- import fs10 from "fs/promises";
4762
- import path12 from "path";
5549
+ import fs12 from "fs/promises";
5550
+ import path14 from "path";
4763
5551
  var TYPE_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
4764
5552
  async function collectWriteDiagnostics(paths) {
4765
5553
  const uniquePaths2 = takeUniquePaths(paths);
@@ -4806,7 +5594,7 @@ function createEmptyDiagnosticsReport(status) {
4806
5594
  }
4807
5595
  async function collectFileDiagnostics(targetPath) {
4808
5596
  try {
4809
- const extension = path12.extname(targetPath).toLowerCase();
5597
+ const extension = path14.extname(targetPath).toLowerCase();
4810
5598
  if (extension === ".json") {
4811
5599
  return await collectJsonDiagnostics(targetPath);
4812
5600
  }
@@ -4822,7 +5610,7 @@ async function collectFileDiagnostics(targetPath) {
4822
5610
  return null;
4823
5611
  }
4824
5612
  async function collectJsonDiagnostics(targetPath) {
4825
- const content = await fs10.readFile(targetPath, "utf8");
5613
+ const content = await fs12.readFile(targetPath, "utf8");
4826
5614
  const diagnostics = [];
4827
5615
  try {
4828
5616
  JSON.parse(content);
@@ -4843,7 +5631,7 @@ async function collectJsonDiagnostics(targetPath) {
4843
5631
  async function collectTypeScriptDiagnostics(targetPath) {
4844
5632
  const diagnostics = [];
4845
5633
  const TypeScript = await import("typescript");
4846
- const content = await fs10.readFile(targetPath, "utf8");
5634
+ const content = await fs12.readFile(targetPath, "utf8");
4847
5635
  const transpiled = TypeScript.transpileModule(content, {
4848
5636
  fileName: targetPath,
4849
5637
  compilerOptions: {
@@ -4960,7 +5748,7 @@ var editToolDefinition = {
4960
5748
  const resolved = resolveUserPath(targetPath, context.cwd);
4961
5749
  const displayPath = toToolRelativePath(context.cwd, resolved);
4962
5750
  return withFileEditLock(resolved, async () => {
4963
- const beforeBuffer = await fs11.readFile(resolved);
5751
+ const beforeBuffer = await fs13.readFile(resolved);
4964
5752
  const beforeEnvelope = decodeTextFileEnvelope(beforeBuffer);
4965
5753
  if (!beforeEnvelope) {
4966
5754
  throw new ToolExecutionError(`edit cannot edit binary or unsupported text encoding for ${displayPath}`, {
@@ -4978,7 +5766,7 @@ var editToolDefinition = {
4978
5766
  });
4979
5767
  }
4980
5768
  const diff = buildDiffPreview(before, after);
4981
- await fs11.writeFile(resolved, encodeTextFileEnvelope(after, beforeEnvelope));
5769
+ await fs13.writeFile(resolved, encodeTextFileEnvelope(after, beforeEnvelope));
4982
5770
  const changeRecord = await recordToolChange(context, {
4983
5771
  toolName: "edit",
4984
5772
  summary: `edit ${displayPath}`,
@@ -5132,9 +5920,9 @@ function lineForOffset(input, offset) {
5132
5920
  }
5133
5921
  return line;
5134
5922
  }
5135
- function buildReadArgs(path27, line) {
5923
+ function buildReadArgs(path29, line) {
5136
5924
  return {
5137
- path: path27,
5925
+ path: path29,
5138
5926
  offset: Math.max(1, (line ?? 1) - 20),
5139
5927
  limit: 60
5140
5928
  };
@@ -5188,8 +5976,8 @@ async function withFileEditLock(filePath, action) {
5188
5976
  }
5189
5977
 
5190
5978
  // src/tools/core/fileIntrospection.ts
5191
- import fs12 from "fs/promises";
5192
- import path13 from "path";
5979
+ import fs14 from "fs/promises";
5980
+ import path15 from "path";
5193
5981
  var KNOWN_BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
5194
5982
  ".epub",
5195
5983
  ".mobi",
@@ -5207,8 +5995,8 @@ var KNOWN_BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
5207
5995
  ".bin"
5208
5996
  ]);
5209
5997
  async function inspectTextFile(filePath, _maxBytes) {
5210
- const stat = await fs12.stat(filePath);
5211
- const extension = path13.extname(filePath).toLowerCase();
5998
+ const stat = await fs14.stat(filePath);
5999
+ const extension = path15.extname(filePath).toLowerCase();
5212
6000
  if (KNOWN_BINARY_EXTENSIONS.has(extension)) {
5213
6001
  return {
5214
6002
  readable: false,
@@ -5218,7 +6006,7 @@ async function inspectTextFile(filePath, _maxBytes) {
5218
6006
  extension
5219
6007
  };
5220
6008
  }
5221
- const buffer = await fs12.readFile(filePath);
6009
+ const buffer = await fs14.readFile(filePath);
5222
6010
  const decoded = decodeTextFileEnvelope(buffer);
5223
6011
  if (!decoded) {
5224
6012
  return {
@@ -5242,11 +6030,11 @@ async function inspectTextFile(filePath, _maxBytes) {
5242
6030
  }
5243
6031
 
5244
6032
  // src/tools/core/pathSuggestions.ts
5245
- import path14 from "path";
6033
+ import path16 from "path";
5246
6034
  import fg3 from "fast-glob";
5247
6035
  async function findPathSuggestions(cwd, requestedPath, projectContext, limit = 8) {
5248
6036
  const normalized = normalizeUserPathInput(requestedPath).replace(/\\/g, "/");
5249
- const baseName = path14.basename(normalized).trim();
6037
+ const baseName = path16.basename(normalized).trim();
5250
6038
  const needle = baseName.length > 0 ? baseName : normalized.trim();
5251
6039
  if (!needle) {
5252
6040
  return [];
@@ -5263,8 +6051,8 @@ async function findPathSuggestions(cwd, requestedPath, projectContext, limit = 8
5263
6051
  suppressErrors: true,
5264
6052
  ignore: buildFastGlobIgnorePatterns(cwd, projectContext.ignoreRules)
5265
6053
  });
5266
- for (const entry of entries.sort((left, right) => comparePathForDiscovery(cwd, path14.resolve(cwd, left), path14.resolve(cwd, right)))) {
5267
- const absolutePath = path14.resolve(cwd, entry);
6054
+ for (const entry of entries.sort((left, right) => comparePathForDiscovery(cwd, path16.resolve(cwd, left), path16.resolve(cwd, right)))) {
6055
+ const absolutePath = path16.resolve(cwd, entry);
5268
6056
  const isDirectory3 = entry.endsWith("/");
5269
6057
  if (isPathIgnored(absolutePath, projectContext.ignoreRules, isDirectory3)) {
5270
6058
  continue;
@@ -5429,7 +6217,7 @@ function fitWindowWithinBudget(lines, start, requestedEndExclusive, maxChars) {
5429
6217
  }
5430
6218
 
5431
6219
  // src/tools/write.ts
5432
- import fs13 from "fs/promises";
6220
+ import fs15 from "fs/promises";
5433
6221
  var writeToolDefinition = {
5434
6222
  definition: {
5435
6223
  type: "function",
@@ -5465,12 +6253,12 @@ var writeToolDefinition = {
5465
6253
  const resolved = resolveUserPath(targetPath, context.cwd);
5466
6254
  const displayPath = toToolRelativePath(context.cwd, resolved);
5467
6255
  const existed = await fileExists(resolved);
5468
- const before = existed ? await fs13.readFile(resolved, "utf8") : "";
6256
+ const before = existed ? await fs15.readFile(resolved, "utf8") : "";
5469
6257
  const preview = buildDiffPreview(before, content);
5470
6258
  if (createDirectories) {
5471
6259
  await ensureParentDirectory(resolved);
5472
6260
  }
5473
- await fs13.writeFile(resolved, content, "utf8");
6261
+ await fs15.writeFile(resolved, content, "utf8");
5474
6262
  const changeRecord = await recordToolChange(context, {
5475
6263
  toolName: "write",
5476
6264
  summary: `write ${displayPath}`,
@@ -5525,7 +6313,7 @@ var writeToolDefinition = {
5525
6313
  };
5526
6314
 
5527
6315
  // src/tools/sendFile.ts
5528
- import fs14 from "fs/promises";
6316
+ import fs16 from "fs/promises";
5529
6317
  var sendFileToolDefinition = {
5530
6318
  definition: {
5531
6319
  type: "function",
@@ -5568,7 +6356,7 @@ var sendFileToolDefinition = {
5568
6356
  };
5569
6357
  }
5570
6358
  try {
5571
- await fs14.access(filePath);
6359
+ await fs16.access(filePath);
5572
6360
  } catch {
5573
6361
  return {
5574
6362
  ok: false,
@@ -6100,6 +6888,53 @@ async function executeToolBatch(params) {
6100
6888
  };
6101
6889
  }
6102
6890
 
6891
+ // src/session/events.ts
6892
+ import fs17 from "fs/promises";
6893
+ import path17 from "path";
6894
+ var SessionEventStore = class {
6895
+ constructor(eventsDir) {
6896
+ this.eventsDir = eventsDir;
6897
+ }
6898
+ async append(event) {
6899
+ const record = {
6900
+ id: createEventId(),
6901
+ createdAt: event.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
6902
+ type: event.type,
6903
+ sessionId: event.sessionId,
6904
+ cwd: event.cwd,
6905
+ host: event.host,
6906
+ message: event.message,
6907
+ details: event.details
6908
+ };
6909
+ await fs17.mkdir(this.eventsDir, { recursive: true });
6910
+ await fs17.appendFile(this.getSessionEventPath(event.sessionId), `${JSON.stringify(record)}
6911
+ `, "utf8");
6912
+ return record;
6913
+ }
6914
+ async list(sessionId, limit = 100) {
6915
+ const filePath = this.getSessionEventPath(sessionId);
6916
+ let raw = "";
6917
+ try {
6918
+ raw = await fs17.readFile(filePath, "utf8");
6919
+ } catch (error) {
6920
+ if (error.code === "ENOENT") {
6921
+ return [];
6922
+ }
6923
+ throw error;
6924
+ }
6925
+ return raw.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line)).slice(-limit);
6926
+ }
6927
+ getSessionEventPath(sessionId) {
6928
+ return path17.join(this.eventsDir, `${sanitizeSessionId(sessionId)}.jsonl`);
6929
+ }
6930
+ };
6931
+ function createEventId() {
6932
+ return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}-${Math.random().toString(16).slice(2, 10)}`;
6933
+ }
6934
+ function sanitizeSessionId(sessionId) {
6935
+ return sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_");
6936
+ }
6937
+
6103
6938
  // src/agent/turn/toolFailure.ts
6104
6939
  function readToolFailureError(output) {
6105
6940
  try {
@@ -6135,9 +6970,16 @@ async function processToolCallBatch(input) {
6135
6970
  const batchToolMessages = [];
6136
6971
  const batchModelOutputs = [];
6137
6972
  const batchChangedPaths = /* @__PURE__ */ new Set();
6973
+ const sessionEvents = new SessionEventStore(options.config.paths.eventsDir);
6138
6974
  const leadWaitExecutionsBefore = identity.kind === "lead" ? listLeadWaitExecutions(projectContext.stateRootDir) : [];
6139
6975
  for (const toolCall of response.toolCalls) {
6140
6976
  throwIfAborted(options.abortSignal, "Turn aborted by user.");
6977
+ await sessionEvents.append({
6978
+ type: "tool.started",
6979
+ sessionId: session.id,
6980
+ cwd: options.cwd,
6981
+ details: buildToolStartedEventDetails(toolCall, identity)
6982
+ });
6141
6983
  options.callbacks?.onToolCall?.(toolCall.function.name, toolCall.function.arguments);
6142
6984
  await recordObservabilityEvent(projectContext.stateRootDir, {
6143
6985
  event: "tool.execution",
@@ -6171,6 +7013,7 @@ async function processToolCallBatch(input) {
6171
7013
  } else if (metadata?.sessionDiff) {
6172
7014
  session = await options.sessionStore.save(noteSessionDiff(session, metadata.sessionDiff));
6173
7015
  }
7016
+ const failureError = result.ok ? void 0 : readToolFailureError(result.output);
6174
7017
  await recordObservabilityEvent(projectContext.stateRootDir, {
6175
7018
  event: "tool.execution",
6176
7019
  status: result.ok ? "completed" : "failed",
@@ -6179,11 +7022,23 @@ async function processToolCallBatch(input) {
6179
7022
  identityName: identity.name,
6180
7023
  toolName: toolCall.function.name,
6181
7024
  durationMs,
6182
- error: result.ok ? void 0 : readToolFailureError(result.output),
7025
+ error: failureError,
6183
7026
  details: {
6184
7027
  changedPathCount: metadata?.changedPaths?.length ?? 0
6185
7028
  }
6186
7029
  });
7030
+ await sessionEvents.append({
7031
+ type: result.ok ? "tool.completed" : "tool.failed",
7032
+ sessionId: session.id,
7033
+ cwd: options.cwd,
7034
+ details: buildToolFinishedEventDetails({
7035
+ toolCall,
7036
+ identity,
7037
+ durationMs,
7038
+ changedPathCount: metadata?.changedPaths?.length ?? 0,
7039
+ error: failureError ? formatToolFailureError(failureError) : void 0
7040
+ })
7041
+ });
6187
7042
  if (metadata?.outputGovernance) {
6188
7043
  await recordObservabilityEvent(projectContext.stateRootDir, {
6189
7044
  event: "tool.output",
@@ -6277,6 +7132,33 @@ async function processToolCallBatch(input) {
6277
7132
  }
6278
7133
  };
6279
7134
  }
7135
+ function buildToolStartedEventDetails(toolCall, identity) {
7136
+ return {
7137
+ toolName: toolCall.function.name,
7138
+ toolCallId: toolCall.id,
7139
+ identityKind: identity.kind,
7140
+ identityName: identity.name,
7141
+ argumentsPreview: previewToolArguments(toolCall.function.arguments)
7142
+ };
7143
+ }
7144
+ function buildToolFinishedEventDetails(input) {
7145
+ return {
7146
+ toolName: input.toolCall.function.name,
7147
+ toolCallId: input.toolCall.id,
7148
+ identityKind: input.identity.kind,
7149
+ identityName: input.identity.name,
7150
+ durationMs: input.durationMs,
7151
+ changedPathCount: input.changedPathCount,
7152
+ error: input.error
7153
+ };
7154
+ }
7155
+ function previewToolArguments(rawArgs) {
7156
+ const normalized = rawArgs.replace(/\s+/g, " ").trim();
7157
+ return normalized.length > 240 ? `${normalized.slice(0, 237)}...` : normalized;
7158
+ }
7159
+ function formatToolFailureError(error) {
7160
+ return error.code ? `${error.code}: ${error.message}` : error.message;
7161
+ }
6280
7162
 
6281
7163
  // src/agent/turn/toolless.ts
6282
7164
  async function resolveToollessTurn(params) {
@@ -6362,9 +7244,9 @@ function normalizeToolArguments(raw) {
6362
7244
  }
6363
7245
 
6364
7246
  // src/agent/changes/store.ts
6365
- import crypto2 from "crypto";
6366
- import fs15 from "fs/promises";
6367
- import path15 from "path";
7247
+ import crypto3 from "crypto";
7248
+ import fs18 from "fs/promises";
7249
+ import path18 from "path";
6368
7250
  var ChangeStore = class {
6369
7251
  constructor(changesDir) {
6370
7252
  this.changesDir = changesDir;
@@ -6372,8 +7254,8 @@ var ChangeStore = class {
6372
7254
  async record(input) {
6373
7255
  const id = createChangeId();
6374
7256
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6375
- const blobDir = path15.join(this.changesDir, id);
6376
- await fs15.mkdir(blobDir, { recursive: true });
7257
+ const blobDir = path18.join(this.changesDir, id);
7258
+ await fs18.mkdir(blobDir, { recursive: true });
6377
7259
  const operations = await Promise.all(
6378
7260
  input.operations.map(async (operation, index) => {
6379
7261
  const beforeSnapshotPath = await this.writeSnapshot(
@@ -6413,21 +7295,21 @@ var ChangeStore = class {
6413
7295
  preview: input.preview,
6414
7296
  operations
6415
7297
  };
6416
- await fs15.mkdir(this.changesDir, { recursive: true });
6417
- await fs15.writeFile(this.getMetadataPath(id), `${JSON.stringify(record, null, 2)}
7298
+ await fs18.mkdir(this.changesDir, { recursive: true });
7299
+ await fs18.writeFile(this.getMetadataPath(id), `${JSON.stringify(record, null, 2)}
6418
7300
  `, "utf8");
6419
7301
  return record;
6420
7302
  }
6421
7303
  async list(limit = 20) {
6422
- await fs15.mkdir(this.changesDir, { recursive: true });
6423
- const entries = await fs15.readdir(this.changesDir, { withFileTypes: true });
7304
+ await fs18.mkdir(this.changesDir, { recursive: true });
7305
+ const entries = await fs18.readdir(this.changesDir, { withFileTypes: true });
6424
7306
  const changes = await Promise.all(
6425
- entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => this.load(path15.basename(entry.name, ".json")))
7307
+ entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => this.load(path18.basename(entry.name, ".json")))
6426
7308
  );
6427
7309
  return changes.sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, limit);
6428
7310
  }
6429
7311
  async load(id) {
6430
- const raw = await fs15.readFile(this.getMetadataPath(id), "utf8");
7312
+ const raw = await fs18.readFile(this.getMetadataPath(id), "utf8");
6431
7313
  return JSON.parse(raw);
6432
7314
  }
6433
7315
  async loadLatestUndoable() {
@@ -6451,17 +7333,17 @@ var ChangeStore = class {
6451
7333
  restoredPaths.push(operation.path);
6452
7334
  if (operation.beforeSnapshotPath) {
6453
7335
  const buffer = await this.readSnapshot(operation.beforeSnapshotPath);
6454
- await fs15.mkdir(path15.dirname(operation.path), { recursive: true });
6455
- await fs15.writeFile(operation.path, buffer);
7336
+ await fs18.mkdir(path18.dirname(operation.path), { recursive: true });
7337
+ await fs18.writeFile(operation.path, buffer);
6456
7338
  continue;
6457
7339
  }
6458
- await fs15.rm(operation.path, { force: true });
7340
+ await fs18.rm(operation.path, { force: true });
6459
7341
  }
6460
7342
  const updated = {
6461
7343
  ...record,
6462
7344
  undoneAt: (/* @__PURE__ */ new Date()).toISOString()
6463
7345
  };
6464
- await fs15.writeFile(this.getMetadataPath(updated.id), `${JSON.stringify(updated, null, 2)}
7346
+ await fs18.writeFile(this.getMetadataPath(updated.id), `${JSON.stringify(updated, null, 2)}
6465
7347
  `, "utf8");
6466
7348
  return {
6467
7349
  record: updated,
@@ -6469,24 +7351,24 @@ var ChangeStore = class {
6469
7351
  };
6470
7352
  }
6471
7353
  getMetadataPath(id) {
6472
- return path15.join(this.changesDir, `${id}.json`);
7354
+ return path18.join(this.changesDir, `${id}.json`);
6473
7355
  }
6474
7356
  async writeSnapshot(blobDir, label, buffer) {
6475
7357
  if (!buffer) {
6476
7358
  return void 0;
6477
7359
  }
6478
7360
  const fileName = `${label}.bin`;
6479
- const absolutePath = path15.join(blobDir, fileName);
6480
- await fs15.writeFile(absolutePath, buffer);
6481
- return path15.relative(this.changesDir, absolutePath);
7361
+ const absolutePath = path18.join(blobDir, fileName);
7362
+ await fs18.writeFile(absolutePath, buffer);
7363
+ return path18.relative(this.changesDir, absolutePath);
6482
7364
  }
6483
7365
  async readSnapshot(relativePath) {
6484
- return fs15.readFile(path15.join(this.changesDir, relativePath));
7366
+ return fs18.readFile(path18.join(this.changesDir, relativePath));
6485
7367
  }
6486
7368
  };
6487
7369
  function createChangeId() {
6488
7370
  const date = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
6489
- const random = crypto2.randomUUID().slice(0, 8);
7371
+ const random = crypto3.randomUUID().slice(0, 8);
6490
7372
  return `${date}-${random}`;
6491
7373
  }
6492
7374
 
@@ -6867,9 +7749,10 @@ var backgroundRunTool = {
6867
7749
  registerBackgroundProcess(job.id, subprocess);
6868
7750
  store.markRunning(job.id, { pid: subprocess.pid ?? 0 });
6869
7751
  const outputTracker = createBackgroundOutputTracker((output) => {
7752
+ const normalizedOutput = normalizeCommandOutput(output);
6870
7753
  store.updateRunningOutput(job.id, {
6871
- output,
6872
- summary: summarizeBackgroundOutput(output),
7754
+ output: normalizedOutput,
7755
+ summary: summarizeBackgroundOutput(normalizedOutput),
6873
7756
  lastOutputAt: (/* @__PURE__ */ new Date()).toISOString()
6874
7757
  });
6875
7758
  });
@@ -6879,7 +7762,7 @@ var backgroundRunTool = {
6879
7762
  void subprocess.then(async (result) => {
6880
7763
  outputTracker.flush();
6881
7764
  const running2 = store.load(job.id);
6882
- const resultOutput = typeof result.all === "string" ? result.all : "";
7765
+ const resultOutput = normalizeCommandOutput(typeof result.all === "string" ? result.all : "");
6883
7766
  const output = resultOutput || running2?.output || "";
6884
7767
  store.close(job.id, {
6885
7768
  status: result.exitCode === 0 ? "completed" : "failed",
@@ -6891,7 +7774,7 @@ var backgroundRunTool = {
6891
7774
  }, async (error) => {
6892
7775
  outputTracker.flush();
6893
7776
  const running2 = store.load(job.id);
6894
- const errorOutput = typeof error.all === "string" ? error.all : "";
7777
+ const errorOutput = normalizeCommandOutput(typeof error.all === "string" ? error.all : "");
6895
7778
  const output = errorOutput || running2?.output || String(error.message);
6896
7779
  store.close(job.id, {
6897
7780
  status: "failed",
@@ -7059,20 +7942,20 @@ function createBackgroundTools() {
7059
7942
  }
7060
7943
 
7061
7944
  // src/extensions/tools/network/tools/downloadUrl.ts
7062
- import fs17 from "fs/promises";
7945
+ import fs20 from "fs/promises";
7063
7946
 
7064
7947
  // src/extensions/shared.ts
7065
- import fs16 from "fs/promises";
7066
- import path16 from "path";
7948
+ import fs19 from "fs/promises";
7949
+ import path19 from "path";
7067
7950
  async function ensureExtensionDir(rootDir, extensionId) {
7068
7951
  const paths = await ensureProjectStateDirectories(rootDir);
7069
- const dir = path16.join(paths.extensionsDir, extensionId);
7070
- await fs16.mkdir(dir, { recursive: true });
7952
+ const dir = path19.join(paths.extensionsDir, extensionId);
7953
+ await fs19.mkdir(dir, { recursive: true });
7071
7954
  return dir;
7072
7955
  }
7073
7956
  async function readJsonFile(filePath, fallback) {
7074
7957
  try {
7075
- return JSON.parse(await fs16.readFile(filePath, "utf8"));
7958
+ return JSON.parse(await fs19.readFile(filePath, "utf8"));
7076
7959
  } catch (error) {
7077
7960
  if (error.code === "ENOENT") {
7078
7961
  return fallback;
@@ -7081,8 +7964,8 @@ async function readJsonFile(filePath, fallback) {
7081
7964
  }
7082
7965
  }
7083
7966
  async function writeJsonFile(filePath, value) {
7084
- await fs16.mkdir(path16.dirname(filePath), { recursive: true });
7085
- await fs16.writeFile(filePath, `${JSON.stringify(value, null, 2)}
7967
+ await fs19.mkdir(path19.dirname(filePath), { recursive: true });
7968
+ await fs19.writeFile(filePath, `${JSON.stringify(value, null, 2)}
7086
7969
  `, "utf8");
7087
7970
  }
7088
7971
  function jsonResult(value) {
@@ -7098,7 +7981,7 @@ function sanitizeStateSegment(value) {
7098
7981
  }
7099
7982
 
7100
7983
  // src/extensions/tools/network/session.ts
7101
- import path17 from "path";
7984
+ import path20 from "path";
7102
7985
  async function listHttpSessions(rootDir) {
7103
7986
  const state = await readJsonFile(await sessionFile(rootDir), { sessions: [] });
7104
7987
  return Array.isArray(state.sessions) ? state.sessions.map(normalizeSession) : [];
@@ -7123,7 +8006,7 @@ async function getHttpSessionStateFile(rootDir) {
7123
8006
  return sessionFile(rootDir);
7124
8007
  }
7125
8008
  async function sessionFile(rootDir) {
7126
- return path17.join(await ensureExtensionDir(rootDir, "network"), "http-sessions.json");
8009
+ return path20.join(await ensureExtensionDir(rootDir, "network"), "http-sessions.json");
7127
8010
  }
7128
8011
  function normalizeSession(value) {
7129
8012
  return {
@@ -7352,7 +8235,7 @@ var downloadUrlTool = {
7352
8235
  }
7353
8236
  const bytes = Buffer.from(await response.arrayBuffer());
7354
8237
  await ensureParentDirectory(targetPath);
7355
- await fs17.writeFile(targetPath, bytes);
8238
+ await fs20.writeFile(targetPath, bytes);
7356
8239
  return changedJsonResult({
7357
8240
  ok: response.ok,
7358
8241
  url,
@@ -7417,17 +8300,17 @@ var httpProbeTool = {
7417
8300
  };
7418
8301
 
7419
8302
  // src/extensions/tools/network/traceStore.ts
7420
- import fs18 from "fs/promises";
7421
- import path18 from "path";
8303
+ import fs21 from "fs/promises";
8304
+ import path21 from "path";
7422
8305
  async function writeNetworkTrace(rootDir, traceId, record) {
7423
8306
  const filePath = await networkTraceFilePath(rootDir, traceId);
7424
- await fs18.mkdir(path18.dirname(filePath), { recursive: true });
7425
- await fs18.writeFile(filePath, `${JSON.stringify(record, null, 2)}
8307
+ await fs21.mkdir(path21.dirname(filePath), { recursive: true });
8308
+ await fs21.writeFile(filePath, `${JSON.stringify(record, null, 2)}
7426
8309
  `, "utf8");
7427
8310
  return filePath;
7428
8311
  }
7429
8312
  async function networkTraceFilePath(rootDir, traceId) {
7430
- return path18.join(await ensureExtensionDir(rootDir, "network"), "traces", `${sanitizeStateSegment(traceId)}.json`);
8313
+ return path21.join(await ensureExtensionDir(rootDir, "network"), "traces", `${sanitizeStateSegment(traceId)}.json`);
7431
8314
  }
7432
8315
 
7433
8316
  // src/extensions/tools/network/tools/httpRequest.ts
@@ -7827,14 +8710,14 @@ function readStringMap2(value) {
7827
8710
  }
7828
8711
 
7829
8712
  // src/extensions/tools/network/openapi.ts
7830
- import fs19 from "fs/promises";
8713
+ import fs22 from "fs/promises";
7831
8714
  async function loadOpenApiDocument(source, context) {
7832
8715
  const normalizedSource = source.trim();
7833
8716
  if (!normalizedSource) {
7834
8717
  throw new ToolExecutionError("OpenAPI source is required.", { code: "OPENAPI_SOURCE_INVALID" });
7835
8718
  }
7836
8719
  const resolvedSource = /^https?:\/\//i.test(normalizedSource) ? normalizedSource : resolveUserPath(normalizedSource, context.cwd);
7837
- const raw = /^https?:\/\//i.test(normalizedSource) ? await (await fetchWithTimeout(normalizedSource, { method: "GET" }, 2e4, context.abortSignal)).text() : stripBom(await fs19.readFile(resolvedSource, "utf8"));
8720
+ const raw = /^https?:\/\//i.test(normalizedSource) ? await (await fetchWithTimeout(normalizedSource, { method: "GET" }, 2e4, context.abortSignal)).text() : stripBom(await fs22.readFile(resolvedSource, "utf8"));
7838
8721
  const parsed = parseOpenApiDocument(raw, normalizedSource);
7839
8722
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
7840
8723
  throw new ToolExecutionError("OpenAPI document root must be an object.", { code: "OPENAPI_ROOT_INVALID" });
@@ -8182,8 +9065,8 @@ async function recordSkillUse(rootDir, input) {
8182
9065
  }
8183
9066
 
8184
9067
  // src/extensions/tools/skills/tools/skillReadResource.ts
8185
- import fs20 from "fs/promises";
8186
- import path19 from "path";
9068
+ import fs23 from "fs/promises";
9069
+ import path22 from "path";
8187
9070
  var MAX_RESOURCE_CHARS = 24e3;
8188
9071
  var skillReadResourceTool = {
8189
9072
  definition: {
@@ -8220,8 +9103,8 @@ var skillReadResourceTool = {
8220
9103
  if (!resource) {
8221
9104
  throw new Error(`Skill "${name}" does not declare resource: ${requestedPath}`);
8222
9105
  }
8223
- const absolutePath = path19.resolve(context.projectContext.rootDir, resource.path);
8224
- const content = await fs20.readFile(absolutePath, "utf8");
9106
+ const absolutePath = path22.resolve(context.projectContext.rootDir, resource.path);
9107
+ const content = await fs23.readFile(absolutePath, "utf8");
8225
9108
  return jsonResult({
8226
9109
  ok: true,
8227
9110
  skill: {
@@ -8239,7 +9122,7 @@ function normalizeResourcePath(value) {
8239
9122
  }
8240
9123
 
8241
9124
  // src/extensions/tools/skills/tools/skillRunScript.ts
8242
- import path20 from "path";
9125
+ import path23 from "path";
8243
9126
  var skillRunScriptTool = {
8244
9127
  definition: {
8245
9128
  type: "function",
@@ -8283,12 +9166,12 @@ var skillRunScriptTool = {
8283
9166
  if (!resource) {
8284
9167
  throw new Error(`Skill "${name}" does not declare script resource: ${requestedPath}`);
8285
9168
  }
8286
- const skillDir = path20.dirname(skill.path);
8287
- const relativeToSkill = normalizeResourcePath2(path20.relative(skillDir, resource.path));
9169
+ const skillDir = path23.dirname(skill.path);
9170
+ const relativeToSkill = normalizeResourcePath2(path23.relative(skillDir, resource.path));
8288
9171
  if (!relativeToSkill.startsWith("scripts/")) {
8289
9172
  throw new Error(`Skill "${name}" resource is not executable because it is outside scripts/: ${requestedPath}`);
8290
9173
  }
8291
- const scriptPath = path20.resolve(context.projectContext.rootDir, resource.path);
9174
+ const scriptPath = path23.resolve(context.projectContext.rootDir, resource.path);
8292
9175
  const argumentText = typeof args.args === "string" ? args.args.trim() : "";
8293
9176
  const command = buildScriptCommand(scriptPath, argumentText);
8294
9177
  const result = await runCommandWithPolicy({
@@ -8363,7 +9246,7 @@ function quotePath(value) {
8363
9246
  return `"${value.replace(/"/g, '\\"')}"`;
8364
9247
  }
8365
9248
  function buildScriptCommand(scriptPath, argumentText) {
8366
- const extension = path20.extname(scriptPath).toLowerCase();
9249
+ const extension = path23.extname(scriptPath).toLowerCase();
8367
9250
  const quoted = quotePath(scriptPath);
8368
9251
  const suffix = argumentText ? ` ${argumentText}` : "";
8369
9252
  if (extension === ".js" || extension === ".mjs" || extension === ".cjs") {
@@ -8417,12 +9300,12 @@ var subagentCheckTool = {
8417
9300
 
8418
9301
  // src/execution/launch.ts
8419
9302
  import { spawn } from "child_process";
8420
- import path21 from "path";
9303
+ import path24 from "path";
8421
9304
  function spawnExecutionWorker(input) {
8422
9305
  if (process.env.KITTY_TEST_WORKER_MODE === "stub") {
8423
9306
  return process.pid;
8424
9307
  }
8425
- const cliEntry = path21.resolve(process.argv[1] ?? "");
9308
+ const cliEntry = path24.resolve(process.argv[1] ?? "");
8426
9309
  if (!cliEntry) {
8427
9310
  throw new Error("Unable to locate Kitty CLI entrypoint for execution worker.");
8428
9311
  }
@@ -8628,7 +9511,7 @@ function parseWorktreeBlock(block) {
8628
9511
  }
8629
9512
 
8630
9513
  // src/extensions/tools/worktree/state.ts
8631
- import path22 from "path";
9514
+ import path25 from "path";
8632
9515
  async function readWorktreeState(rootDir) {
8633
9516
  return normalizeWorktreeState(await readJsonFile(await stateFile(rootDir), {
8634
9517
  schemaVersion: 1,
@@ -8651,7 +9534,7 @@ async function recordWorktreeEvent(rootDir, event) {
8651
9534
  return writeWorktreeState(rootDir, state);
8652
9535
  }
8653
9536
  async function stateFile(rootDir) {
8654
- return path22.join(await ensureExtensionDir(rootDir, "worktree"), "state.json");
9537
+ return path25.join(await ensureExtensionDir(rootDir, "worktree"), "state.json");
8655
9538
  }
8656
9539
  function normalizeWorktreeState(value) {
8657
9540
  return {
@@ -9274,8 +10157,8 @@ function looksLikeToolProtocolText(content) {
9274
10157
  }
9275
10158
 
9276
10159
  // src/observability/crashRecorder.ts
9277
- import fs21 from "fs";
9278
- import path23 from "path";
10160
+ import fs24 from "fs";
10161
+ import path26 from "path";
9279
10162
  var activeCrashContexts = /* @__PURE__ */ new Map();
9280
10163
  var nextCrashContextId = 0;
9281
10164
  function enterCrashContext(context) {
@@ -9287,7 +10170,7 @@ function enterCrashContext(context) {
9287
10170
  }
9288
10171
 
9289
10172
  // src/observability/hostEvents.ts
9290
- import path24 from "path";
10173
+ import path27 from "path";
9291
10174
  async function recordHostTurnStarted(rootDir, input) {
9292
10175
  await recordObservabilityEvent(rootDir, {
9293
10176
  event: "host.turn",
@@ -9318,53 +10201,6 @@ async function recordHostTurnFinished(rootDir, input) {
9318
10201
  });
9319
10202
  }
9320
10203
 
9321
- // src/session/events.ts
9322
- import fs22 from "fs/promises";
9323
- import path25 from "path";
9324
- var SessionEventStore = class {
9325
- constructor(eventsDir) {
9326
- this.eventsDir = eventsDir;
9327
- }
9328
- async append(event) {
9329
- const record = {
9330
- id: createEventId(),
9331
- createdAt: event.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
9332
- type: event.type,
9333
- sessionId: event.sessionId,
9334
- cwd: event.cwd,
9335
- host: event.host,
9336
- message: event.message,
9337
- details: event.details
9338
- };
9339
- await fs22.mkdir(this.eventsDir, { recursive: true });
9340
- await fs22.appendFile(this.getSessionEventPath(event.sessionId), `${JSON.stringify(record)}
9341
- `, "utf8");
9342
- return record;
9343
- }
9344
- async list(sessionId, limit = 100) {
9345
- const filePath = this.getSessionEventPath(sessionId);
9346
- let raw = "";
9347
- try {
9348
- raw = await fs22.readFile(filePath, "utf8");
9349
- } catch (error) {
9350
- if (error.code === "ENOENT") {
9351
- return [];
9352
- }
9353
- throw error;
9354
- }
9355
- return raw.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line)).slice(-limit);
9356
- }
9357
- getSessionEventPath(sessionId) {
9358
- return path25.join(this.eventsDir, `${sanitizeSessionId(sessionId)}.jsonl`);
9359
- }
9360
- };
9361
- function createEventId() {
9362
- return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}-${Math.random().toString(16).slice(2, 10)}`;
9363
- }
9364
- function sanitizeSessionId(sessionId) {
9365
- return sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_");
9366
- }
9367
-
9368
10204
  // src/host/toolRegistry.ts
9369
10205
  async function createHostToolRegistry(config, options = {}) {
9370
10206
  const extraTools = options.extraTools ?? [];
@@ -9685,7 +10521,7 @@ function tryParseJson(input) {
9685
10521
  }
9686
10522
 
9687
10523
  // src/runtime-ui/pathDisplay.ts
9688
- import path26 from "path";
10524
+ import path28 from "path";
9689
10525
  function normalizeDisplayPath(value, cwd) {
9690
10526
  if (!value) {
9691
10527
  return value;
@@ -9693,10 +10529,10 @@ function normalizeDisplayPath(value, cwd) {
9693
10529
  if (!cwd) {
9694
10530
  return value;
9695
10531
  }
9696
- const normalizedCwd = path26.resolve(cwd);
9697
- const normalizedValue = path26.resolve(value);
9698
- if (normalizedValue === normalizedCwd || normalizedValue.startsWith(`${normalizedCwd}${path26.sep}`)) {
9699
- return path26.relative(normalizedCwd, normalizedValue) || ".";
10532
+ const normalizedCwd = path28.resolve(cwd);
10533
+ const normalizedValue = path28.resolve(value);
10534
+ if (normalizedValue === normalizedCwd || normalizedValue.startsWith(`${normalizedCwd}${path28.sep}`)) {
10535
+ return path28.relative(normalizedCwd, normalizedValue) || ".";
9700
10536
  }
9701
10537
  return value;
9702
10538
  }
@@ -9704,7 +10540,7 @@ function rewriteAbsolutePaths(value, cwd) {
9704
10540
  if (!cwd) {
9705
10541
  return value;
9706
10542
  }
9707
- const normalizedCwd = path26.resolve(cwd).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10543
+ const normalizedCwd = path28.resolve(cwd).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9708
10544
  const pattern = new RegExp(`${normalizedCwd}(?:\\\\[^\\s"']*|/[^\\s"']*)*`, "g");
9709
10545
  return value.replace(pattern, (match) => normalizeDisplayPath(match, cwd) ?? match);
9710
10546
  }
@@ -9764,24 +10600,24 @@ function buildToolCallDisplay(name, rawArgs, maxChars, cwd) {
9764
10600
  };
9765
10601
  }
9766
10602
  const args = parsed;
9767
- const path27 = normalizeDisplayPath(readStringField(args, "path"), cwd);
10603
+ const path29 = normalizeDisplayPath(readStringField(args, "path"), cwd);
9768
10604
  switch (name) {
9769
10605
  case "read": {
9770
10606
  const offset = typeof args.offset === "number" ? Math.trunc(args.offset) : void 0;
9771
10607
  const limit = typeof args.limit === "number" ? Math.trunc(args.limit) : void 0;
9772
10608
  const range = offset === void 0 ? "" : limit === void 0 ? `:${offset}` : `:${offset}-${Math.max(offset, offset + limit - 1)}`;
9773
10609
  return {
9774
- summary: `${name} ${path27 ?? "(missing path)"}${range}`
10610
+ summary: `${name} ${path29 ?? "(missing path)"}${range}`
9775
10611
  };
9776
10612
  }
9777
10613
  case "write":
9778
10614
  return {
9779
- summary: `${name} ${path27 ?? "(missing path)"}`
10615
+ summary: `${name} ${path29 ?? "(missing path)"}`
9780
10616
  };
9781
10617
  case "edit": {
9782
10618
  const edits = Array.isArray(args.edits) ? args.edits : [];
9783
10619
  return {
9784
- summary: `${name} ${path27 ?? "(missing path)"}` + (edits.length > 0 ? ` edits=${edits.length}` : "")
10620
+ summary: `${name} ${path29 ?? "(missing path)"}` + (edits.length > 0 ? ` edits=${edits.length}` : "")
9785
10621
  };
9786
10622
  }
9787
10623
  case "bash": {
@@ -9793,7 +10629,7 @@ function buildToolCallDisplay(name, rawArgs, maxChars, cwd) {
9793
10629
  }
9794
10630
  case "download_url":
9795
10631
  return {
9796
- summary: `${name} ${readStringField(args, "url") ?? "(missing url)"} -> ${path27 ?? "(missing path)"}`
10632
+ summary: `${name} ${readStringField(args, "url") ?? "(missing url)"} -> ${path29 ?? "(missing path)"}`
9797
10633
  };
9798
10634
  case "http_probe": {
9799
10635
  const method = readStringField(args, "method") ?? "HEAD";
@@ -9834,14 +10670,14 @@ function buildToolCallDisplay(name, rawArgs, maxChars, cwd) {
9834
10670
  case "worktree_create": {
9835
10671
  const branch = readStringField(args, "branch");
9836
10672
  return {
9837
- summary: `${name} ${path27 ?? "(missing path)"}${branch ? ` branch=${branch}` : ""}`
10673
+ summary: `${name} ${path29 ?? "(missing path)"}${branch ? ` branch=${branch}` : ""}`
9838
10674
  };
9839
10675
  }
9840
10676
  case "worktree_get":
9841
10677
  case "worktree_keep":
9842
10678
  case "worktree_remove":
9843
10679
  return {
9844
- summary: `${name} ${path27 ?? "(missing path)"}`
10680
+ summary: `${name} ${path29 ?? "(missing path)"}`
9845
10681
  };
9846
10682
  case "worktree_events": {
9847
10683
  const limit = typeof args.limit === "number" ? Math.trunc(args.limit) : void 0;
@@ -9980,7 +10816,7 @@ function colorizeMarker(marker) {
9980
10816
  }
9981
10817
 
9982
10818
  // src/utils/stdio.ts
9983
- import fs23 from "fs";
10819
+ import fs25 from "fs";
9984
10820
  var stdoutBroken = false;
9985
10821
  var stderrBroken = false;
9986
10822
  function writeStdout(text) {
@@ -10006,7 +10842,7 @@ function writeToFd(fd, text, stream) {
10006
10842
  if (target.isTTY) {
10007
10843
  return target.write(text);
10008
10844
  }
10009
- fs23.writeSync(fd, text, void 0, "utf8");
10845
+ fs25.writeSync(fd, text, void 0, "utf8");
10010
10846
  return true;
10011
10847
  } catch (error) {
10012
10848
  if (isIgnorableStreamError(error)) {
@@ -10289,10 +11125,17 @@ function formatRuntimeUiMessage(prefix, summary, detail) {
10289
11125
  export {
10290
11126
  resolveProjectRoots,
10291
11127
  buildProjectMap,
11128
+ PROJECT_STATE_DIR_NAME,
11129
+ PROJECT_STATE_ENV_FILE_NAME,
11130
+ PROJECT_STATE_ENV_EXAMPLE_FILE_NAME,
11131
+ PROJECT_STATE_IGNORE_FILE_NAME,
11132
+ PRESERVED_PROJECT_STATE_ENTRY_NAMES,
11133
+ getProjectStatePaths,
10292
11134
  loadProjectContext,
10293
11135
  getErrorMessage,
10294
11136
  ControlPlaneLedger,
10295
11137
  ExecutionStore,
11138
+ SessionEventStore,
10296
11139
  isProcessAlive,
10297
11140
  terminatePid,
10298
11141
  BackgroundExecutionStore,
@@ -10301,12 +11144,14 @@ export {
10301
11144
  summarizeExecution,
10302
11145
  summarizeExecutionSet,
10303
11146
  EXTENSION_ENV_KEYS,
10304
- SessionEventStore,
10305
11147
  runHostTurn,
10306
11148
  writeStdout,
10307
11149
  writeStdoutLine,
10308
11150
  writeStderrLine,
10309
11151
  createRuntimeUiEvent,
11152
+ tryParseJson,
11153
+ buildToolCallDisplay,
11154
+ buildToolResultDisplay,
10310
11155
  colorRuntimeUiText,
10311
11156
  createRuntimeUiTerminalRenderer,
10312
11157
  formatRuntimeUiEventLine