@jun133/kitty 0.0.11 → 0.0.13

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.
@@ -23,7 +23,7 @@ import {
23
23
  terminatePid,
24
24
  writeStderrLine,
25
25
  writeStdoutLine
26
- } from "./chunk-ELBEXOR7.mjs";
26
+ } from "./chunk-MMIH75OY.mjs";
27
27
  import {
28
28
  PRESERVED_PROJECT_STATE_ENTRY_NAMES,
29
29
  PROJECT_STATE_DIR_NAME,
@@ -315,7 +315,7 @@ async function terminateProcesses(processes, cwd) {
315
315
 
316
316
  // src/runtime/scene.ts
317
317
  function buildRuntimeScene(status) {
318
- const executions = status.executions.active.map(buildExecutionScene);
318
+ const executions = readSceneExecutions(status);
319
319
  const blockedExecutions = executions.filter((execution) => execution.risk === "blocked");
320
320
  const watchExecutions = executions.filter((execution) => execution.risk === "watch");
321
321
  const activeBackground = executions.filter((execution) => execution.kind === "background");
@@ -326,6 +326,7 @@ function buildRuntimeScene(status) {
326
326
  nextAction: readNextAction(status, blockedExecutions, watchExecutions),
327
327
  blocked: readBlocked(blockedExecutions),
328
328
  cost: readCost(status),
329
+ toolOutputs: readToolOutputs(status),
329
330
  recovery: readRecovery(status, executions),
330
331
  skills: {
331
332
  ready: status.skills.ready,
@@ -345,6 +346,19 @@ function buildRuntimeScene(status) {
345
346
  executions
346
347
  };
347
348
  }
349
+ function readSceneExecutions(status) {
350
+ const byId = /* @__PURE__ */ new Map();
351
+ for (const execution of status.executions.active) {
352
+ byId.set(execution.id, buildExecutionScene(execution));
353
+ }
354
+ for (const execution of status.executions.recent) {
355
+ const scene = buildExecutionScene(execution);
356
+ if (scene.risk !== "none" && !byId.has(scene.id)) {
357
+ byId.set(scene.id, scene);
358
+ }
359
+ }
360
+ return [...byId.values()];
361
+ }
348
362
  function buildExecutionScene(execution) {
349
363
  const risk = readExecutionRisk(execution);
350
364
  return {
@@ -359,9 +373,6 @@ function buildExecutionScene(execution) {
359
373
  };
360
374
  }
361
375
  function buildHeadline(status, blockedExecutions, watchExecutions) {
362
- if (!status.sessions.latest) {
363
- return "No active session yet.";
364
- }
365
376
  if (blockedExecutions.length > 0) {
366
377
  return `${blockedExecutions.length} execution(s) need attention.`;
367
378
  }
@@ -371,6 +382,9 @@ function buildHeadline(status, blockedExecutions, watchExecutions) {
371
382
  if (status.executions.active.length > 0) {
372
383
  return `${status.executions.active.length} execution(s) are running.`;
373
384
  }
385
+ if (!status.sessions.latest) {
386
+ return "No active session yet.";
387
+ }
374
388
  return "Ready to continue the latest session.";
375
389
  }
376
390
  function readFocus(status) {
@@ -412,6 +426,23 @@ function readCost(status) {
412
426
  const usageText = latest?.usage ? readUsageCost(latest.usage) : latest ? "provider usage unavailable" : "no model request yet";
413
427
  return `${budgetText}; ${layoutText}; ${usageText}`;
414
428
  }
429
+ function readToolOutputs(status) {
430
+ const recent = status.toolOutputs.recent;
431
+ if (recent.length === 0) {
432
+ return "no tool output governance yet";
433
+ }
434
+ const saved = recent.reduce((total, item) => total + (item.savedTokens ?? 0), 0);
435
+ const truncated = recent.filter((item) => item.truncated).length;
436
+ const degraded = recent.filter((item) => item.degraded).length;
437
+ const best = recent.filter((item) => typeof item.savedTokens === "number").sort((a, b) => (b.savedTokens ?? 0) - (a.savedTokens ?? 0))[0];
438
+ return [
439
+ `${recent.length} recent`,
440
+ `${saved} tokens saved est.`,
441
+ truncated > 0 ? `${truncated} recoverable` : void 0,
442
+ degraded > 0 ? `${degraded} degraded` : void 0,
443
+ best ? `top=${best.toolName ?? "tool"}:${best.kind ?? "output"}` : void 0
444
+ ].filter(Boolean).join("; ");
445
+ }
415
446
  function readStableRatio(stableChars, volatileChars) {
416
447
  const total = stableChars + volatileChars;
417
448
  return total > 0 ? `${Math.round(stableChars / total * 100)}%` : "unknown";
@@ -825,13 +856,14 @@ async function buildRuntimeStatus(rootDir) {
825
856
  const sessionStore = new SessionStore(paths.sessionsDir, {
826
857
  memorySessionsDir: paths.sessionMemoryDir
827
858
  });
828
- const [sessionRead, memoryAssets, control, projectMap, projectContext, modelRequests] = await Promise.all([
859
+ const [sessionRead, memoryAssets, control, projectMap, projectContext, modelRequests, toolOutputs] = await Promise.all([
829
860
  sessionStore.listReadable?.(DEFAULT_RECENT_LIMIT) ?? sessionStore.list(DEFAULT_RECENT_LIMIT).then((sessions2) => ({ sessions: sessions2, skipped: [] })),
830
861
  listRuntimeMemoryAssets(paths.rootDir),
831
862
  readControlPlaneStatus(paths.rootDir),
832
863
  buildProjectMap(paths.rootDir),
833
864
  loadProjectContext(paths.rootDir, { projectDocMaxBytes: 24576 }),
834
- readRecentModelRequests(paths.observabilityEventsDir)
865
+ readRecentModelRequests(paths.observabilityEventsDir),
866
+ readRecentToolOutputs(paths.observabilityEventsDir)
835
867
  ]);
836
868
  const sessions = sessionRead.sessions.map(summarizeSession);
837
869
  const taskLifecycle = sessions[0] ? readTaskLifecycleStatus(paths.rootDir, sessions[0].id) : void 0;
@@ -852,6 +884,9 @@ async function buildRuntimeStatus(rootDir) {
852
884
  modelRequests: {
853
885
  recent: modelRequests
854
886
  },
887
+ toolOutputs: {
888
+ recent: toolOutputs
889
+ },
855
890
  taskLifecycle,
856
891
  executions: control.executions,
857
892
  wakeSignals: control.wakeSignals
@@ -926,6 +961,25 @@ async function readRecentModelRequests(eventsDir) {
926
961
  }
927
962
  return records.slice(-DEFAULT_RECENT_LIMIT).reverse();
928
963
  }
964
+ async function readRecentToolOutputs(eventsDir) {
965
+ const files = await fs7.readdir(eventsDir).catch(() => []);
966
+ const jsonlFiles = files.filter((file) => file.endsWith(".jsonl")).sort().slice(-3);
967
+ const records = [];
968
+ for (const file of jsonlFiles) {
969
+ const content = await fs7.readFile(path6.join(eventsDir, file), "utf8").catch(() => "");
970
+ for (const line of content.split(/\r?\n/)) {
971
+ if (!line.trim()) {
972
+ continue;
973
+ }
974
+ const record = parseObservabilityRecord(line);
975
+ if (!record || record.event !== "tool.output") {
976
+ continue;
977
+ }
978
+ records.push(summarizeToolOutput(record));
979
+ }
980
+ }
981
+ return records.slice(-DEFAULT_RECENT_LIMIT).reverse();
982
+ }
929
983
  function parseObservabilityRecord(line) {
930
984
  try {
931
985
  const parsed = JSON.parse(line);
@@ -946,6 +1000,25 @@ function summarizeModelRequest(record) {
946
1000
  usage
947
1001
  };
948
1002
  }
1003
+ function summarizeToolOutput(record) {
1004
+ const details = record.details ?? {};
1005
+ return {
1006
+ timestamp: record.timestamp,
1007
+ toolName: record.toolName,
1008
+ kind: readString(details.kind),
1009
+ mode: readString(details.mode),
1010
+ rawChars: readNumber(details.rawChars),
1011
+ projectedChars: readNumber(details.projectedChars),
1012
+ rawTokens: readNumber(details.rawTokens),
1013
+ projectedTokens: readNumber(details.projectedTokens),
1014
+ savedTokens: readNumber(details.savedTokens),
1015
+ savingsRatio: readNumber(details.savingsRatio),
1016
+ truncated: details.truncated === true,
1017
+ outputPath: readString(details.outputPath),
1018
+ degraded: details.degraded === true,
1019
+ reason: readString(details.reason)
1020
+ };
1021
+ }
949
1022
  function readUsageSummary(value) {
950
1023
  if (!value || typeof value !== "object") {
951
1024
  return void 0;
@@ -967,6 +1040,9 @@ function readUsageSummary(value) {
967
1040
  function readNumber(value) {
968
1041
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
969
1042
  }
1043
+ function readString(value) {
1044
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1045
+ }
970
1046
  function summarizeSkills(skills) {
971
1047
  const summaries = skills.map((skill) => ({
972
1048
  name: skill.name,
@@ -1062,11 +1138,15 @@ function formatRuntimeStatusText(status) {
1062
1138
  lines.push(`- Skills: ${status.scene.skills.ready}/${status.scene.skills.total} ready; ${status.scene.skills.nextAction}`);
1063
1139
  lines.push(`- Memory: ${status.scene.memory.assets} asset(s), session=${status.scene.memory.latestSessionMemory ? "yes" : "no"}; ${status.scene.memory.nextAction}`);
1064
1140
  lines.push(`- Cost: ${status.scene.cost}`);
1141
+ lines.push(`- Tool output: ${status.scene.toolOutputs}`);
1065
1142
  lines.push(`- Recovery: ${status.scene.recovery}`);
1066
1143
  lines.push("");
1067
1144
  lines.push("Current workspace:");
1068
1145
  lines.push(`- Focus: ${status.scene.focus}`);
1069
1146
  lines.push(`- Session: ${readSessionLine(status)}`);
1147
+ if (status.sessions.skipped > 0) {
1148
+ lines.push(`- Sessions: ${status.sessions.total} total, ${status.sessions.skipped} skipped`);
1149
+ }
1070
1150
  lines.push(`- Next: ${status.scene.nextAction}`);
1071
1151
  lines.push(`- Blocked: ${status.scene.blocked}`);
1072
1152
  lines.push(`- Context budget: ${readContextBudgetLine(status)}`);
@@ -1173,9 +1253,27 @@ function formatRuntimeStatusText(status) {
1173
1253
  ].filter(Boolean).join(" "));
1174
1254
  }
1175
1255
  }
1176
- if (status.executions.active.length > 0) {
1256
+ if (status.toolOutputs.recent.length > 0) {
1257
+ lines.push("");
1258
+ lines.push("Recent tool output:");
1259
+ for (const output of status.toolOutputs.recent.slice(0, 5)) {
1260
+ lines.push([
1261
+ output.toolName ?? "tool",
1262
+ output.kind ? `kind=${output.kind}` : void 0,
1263
+ output.mode ? `mode=${output.mode}` : void 0,
1264
+ output.rawTokens === void 0 ? void 0 : `raw=${output.rawTokens}`,
1265
+ output.projectedTokens === void 0 ? void 0 : `projected=${output.projectedTokens}`,
1266
+ output.savedTokens === void 0 ? void 0 : `saved=${output.savedTokens}`,
1267
+ output.savingsRatio === void 0 ? void 0 : `savedRatio=${Math.round(output.savingsRatio * 100)}%`,
1268
+ output.truncated ? "recoverable=yes" : void 0,
1269
+ output.degraded ? "degraded=yes" : void 0,
1270
+ output.outputPath ? `full=${truncateCliValue(output.outputPath, 80)}` : void 0
1271
+ ].filter(Boolean).join(" "));
1272
+ }
1273
+ }
1274
+ if (status.scene.executions.length > 0) {
1177
1275
  lines.push("");
1178
- lines.push("Active executions:");
1276
+ lines.push("Scene executions:");
1179
1277
  for (const execution of status.scene.executions) {
1180
1278
  lines.push([
1181
1279
  execution.id,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuntimeUiEvent,
3
3
  createRuntimeUiTerminalRenderer
4
- } from "./chunk-ELBEXOR7.mjs";
4
+ } from "./chunk-MMIH75OY.mjs";
5
5
 
6
6
  // src/runtime-ui/agentCallbacks.ts
7
7
  function createRuntimeUiAgentCallbacks(input) {
@@ -1034,6 +1034,7 @@ function buildToolBlock() {
1034
1034
  function buildCommunicationBlock() {
1035
1035
  return [
1036
1036
  "Always reply in Simplified Chinese.",
1037
+ "No Markdown in user-facing conversational replies; prefer plain text whenever possible.",
1037
1038
  "Provide concise progress updates during multi-step work.",
1038
1039
  "Make every sentence carry decision, execution, evidence, or understanding.",
1039
1040
  "Claim changed files, passed commands, and successful tools only when tool evidence supports them.",
@@ -3225,6 +3226,9 @@ var DIFF_MAX_CHARS = 3e3;
3225
3226
  var OUTPUT_MAX_CHARS = 1500;
3226
3227
  var SKILL_BODY_MAX_CHARS = 16e3;
3227
3228
  function projectToolResultForModel(input) {
3229
+ if (input.result.metadata?.outputGovernance) {
3230
+ return input.result.metadata.outputGovernance.projection;
3231
+ }
3228
3232
  const parsed = parseObject(input.result.output);
3229
3233
  if (!input.result.ok) {
3230
3234
  return projectFailure(input.toolName, input.result.output, parsed);
@@ -3328,6 +3332,11 @@ function projectWrite(payload) {
3328
3332
  ]);
3329
3333
  }
3330
3334
  function projectBash(payload) {
3335
+ const governance = readObject(payload.outputGovernance);
3336
+ const projection = readString(governance?.projection);
3337
+ if (projection) {
3338
+ return projection;
3339
+ }
3331
3340
  const exitCode = readNumber(payload.exitCode);
3332
3341
  const durationMs = readNumber(payload.durationMs);
3333
3342
  const status = readString(payload.status);
@@ -4320,6 +4329,251 @@ function pathDepth(relativePath) {
4320
4329
  return relativePath.split("/").filter(Boolean).length;
4321
4330
  }
4322
4331
 
4332
+ // src/tools/outputKernel/classifier.ts
4333
+ function classifyToolOutput(source) {
4334
+ const output = source.output.trim();
4335
+ if (!output) {
4336
+ return "empty";
4337
+ }
4338
+ const command = (source.command ?? "").toLowerCase();
4339
+ const text = output.toLowerCase();
4340
+ if (looksLikeGitDiff(command, text)) {
4341
+ return "git_diff";
4342
+ }
4343
+ if (looksLikeSearch(command)) {
4344
+ return "search";
4345
+ }
4346
+ if (looksLikeTypecheck(command, text)) {
4347
+ return "typecheck";
4348
+ }
4349
+ if (looksLikeTest(command, text)) {
4350
+ return "test";
4351
+ }
4352
+ if (looksLikeBuild(command, text)) {
4353
+ return "build";
4354
+ }
4355
+ return "generic";
4356
+ }
4357
+ function looksLikeGitDiff(command, text) {
4358
+ return /\bgit\s+(diff|show)\b/.test(command) || text.includes("diff --git ");
4359
+ }
4360
+ function looksLikeSearch(command) {
4361
+ return /(^|\s)(rg|grep)(\s|$)/.test(command);
4362
+ }
4363
+ function looksLikeTypecheck(command, text) {
4364
+ return /\b(tsc|typecheck|mypy)\b/.test(command) || /\b(error|warning)\s+ts\d+:/i.test(text);
4365
+ }
4366
+ function looksLikeTest(command, text) {
4367
+ return /\b(test|vitest|jest|pytest|playwright|node --test)\b/.test(command) || /\b(test result|tests? failed|tests? passed|failing tests?|failures?)\b/.test(text);
4368
+ }
4369
+ function looksLikeBuild(command, text) {
4370
+ return /\b(build|compile|cargo check|cargo clippy|npm run build|pnpm build)\b/.test(command) || /\b(compilation failed|build failed|compiled successfully|error\[e\d+\])\b/.test(text);
4371
+ }
4372
+
4373
+ // src/tools/outputKernel/metrics.ts
4374
+ function estimateTextTokens(value) {
4375
+ const trimmed = value.trim();
4376
+ if (!trimmed) {
4377
+ return 0;
4378
+ }
4379
+ return Math.max(1, Math.ceil(trimmed.length / 4));
4380
+ }
4381
+ function computeSavings(input) {
4382
+ const rawChars = input.raw.length;
4383
+ const projectedChars = input.projected.length;
4384
+ const rawTokens = estimateTextTokens(input.raw);
4385
+ const projectedTokens = estimateTextTokens(input.projected);
4386
+ const savedTokens = Math.max(0, rawTokens - projectedTokens);
4387
+ const savingsRatio = rawTokens > 0 ? Math.round(savedTokens / rawTokens * 1e4) / 1e4 : 0;
4388
+ return {
4389
+ rawChars,
4390
+ projectedChars,
4391
+ rawTokens,
4392
+ projectedTokens,
4393
+ savedTokens,
4394
+ savingsRatio
4395
+ };
4396
+ }
4397
+
4398
+ // src/tools/outputKernel/projectors/shared.ts
4399
+ function buildHeader(source, label) {
4400
+ return [
4401
+ `${source.toolName}: ${label}`,
4402
+ source.exitCode === void 0 ? void 0 : `exit=${source.exitCode ?? "null"}`,
4403
+ source.durationMs === void 0 ? void 0 : `duration=${source.durationMs}ms`,
4404
+ source.status ? `status=${source.status}` : void 0
4405
+ ].filter(Boolean).join(" ");
4406
+ }
4407
+ function splitOutputLines(value) {
4408
+ return value.split(/\r?\n/).map((line) => line.trimEnd());
4409
+ }
4410
+ function dedupeProjectedLines(lines) {
4411
+ const seen = /* @__PURE__ */ new Set();
4412
+ const result = [];
4413
+ for (const rawLine of lines) {
4414
+ const line = truncateText(rawLine.trim(), 260);
4415
+ if (!line || seen.has(line)) {
4416
+ continue;
4417
+ }
4418
+ seen.add(line);
4419
+ result.push(line);
4420
+ }
4421
+ return result;
4422
+ }
4423
+
4424
+ // src/tools/outputKernel/projectors/diagnostic.ts
4425
+ var STRUCTURED_MAX_LINES = 28;
4426
+ function buildDiagnosticProjection(source, label) {
4427
+ const lines = splitOutputLines(source.output);
4428
+ const evidence = lines.filter(isDiagnosticEvidenceLine).slice(0, STRUCTURED_MAX_LINES);
4429
+ const summaryLines = lines.filter(isSummaryLine).slice(0, 8);
4430
+ return dedupeProjectedLines([
4431
+ buildHeader(source, label),
4432
+ ...summaryLines,
4433
+ ...evidence
4434
+ ]).join("\n");
4435
+ }
4436
+ function isDiagnosticEvidenceLine(line) {
4437
+ return /(^|\b)(error|warning|fail|failed|failure|panic|exception|traceback|expected|received|cannot find|not assignable|mismatched|undefined|denied)(\b|:)/i.test(line) || /^\s*(at\s|file\s|src\/|tests?\/|[A-Za-z]:\\|\.\/)/.test(line) || /\(\d+,\d+\):\s+(error|warning)\s+/i.test(line) || /error\[e\d+\]/i.test(line);
4438
+ }
4439
+ function isSummaryLine(line) {
4440
+ return /\b(\d+\s+(passed|failed|skipped|errors?|warnings?)|test result|found \d+ errors?|failed tests?|build failed|compiled successfully)\b/i.test(line);
4441
+ }
4442
+
4443
+ // src/tools/outputKernel/projectors/gitDiff.ts
4444
+ var DIFF_MAX_FILES = 24;
4445
+ function buildGitDiffProjection(source) {
4446
+ const lines = splitOutputLines(source.output);
4447
+ const files = lines.filter((line) => line.startsWith("diff --git ")).map((line) => line.replace(/^diff --git a\//, "").replace(/ b\//, " -> ")).slice(0, DIFF_MAX_FILES);
4448
+ const stats = lines.filter((line) => /(\d+ files? changed|\d+ insertions?\(\+\)|\d+ deletions?\(-\))/.test(line)).slice(0, 8);
4449
+ const hunks = lines.filter((line) => line.startsWith("@@") || line.startsWith("+++ ") || line.startsWith("--- ")).slice(0, 18).map((line) => truncateText(line, 220));
4450
+ return [
4451
+ buildHeader(source, "git diff"),
4452
+ files.length > 0 ? `files: ${files.join(", ")}` : void 0,
4453
+ ...stats,
4454
+ ...hunks
4455
+ ].filter((line) => Boolean(line)).join("\n");
4456
+ }
4457
+
4458
+ // src/tools/outputKernel/projectors/generic.ts
4459
+ var GENERIC_MAX_CHARS = 1500;
4460
+ function projectEmptyOutput(source) {
4461
+ return {
4462
+ mode: "empty",
4463
+ projection: buildHeader(source, "no output"),
4464
+ degraded: false,
4465
+ reason: "empty_output"
4466
+ };
4467
+ }
4468
+ function projectGenericOutput(source, reason = "generic_output") {
4469
+ return {
4470
+ mode: "generic",
4471
+ projection: buildGenericPreview(source),
4472
+ degraded: false,
4473
+ reason
4474
+ };
4475
+ }
4476
+ function projectStructuredOutput(source, body) {
4477
+ const trimmed = body.trim();
4478
+ if (!trimmed) {
4479
+ return {
4480
+ mode: "generic",
4481
+ projection: buildGenericPreview(source),
4482
+ degraded: true,
4483
+ reason: "structured_projection_empty"
4484
+ };
4485
+ }
4486
+ return {
4487
+ mode: "structured",
4488
+ projection: trimmed,
4489
+ degraded: false,
4490
+ reason: "structured_projection"
4491
+ };
4492
+ }
4493
+ function buildGenericPreview(source) {
4494
+ return [
4495
+ buildHeader(source, "output"),
4496
+ truncateText(source.output.trim(), GENERIC_MAX_CHARS)
4497
+ ].filter(Boolean).join("\n");
4498
+ }
4499
+
4500
+ // src/tools/outputKernel/projectors/search.ts
4501
+ var SEARCH_MAX_MATCHES = 24;
4502
+ function buildSearchProjection(source) {
4503
+ const nonEmptyLines = splitOutputLines(source.output).filter((line) => line.trim().length > 0);
4504
+ const matches = nonEmptyLines.slice(0, SEARCH_MAX_MATCHES).map((line) => truncateText(line, 220));
4505
+ const omitted = Math.max(0, nonEmptyLines.length - matches.length);
4506
+ return [
4507
+ buildHeader(source, "search"),
4508
+ `matches shown: ${matches.length}${omitted > 0 ? `, omitted: ${omitted}` : ""}`,
4509
+ ...matches
4510
+ ].join("\n");
4511
+ }
4512
+
4513
+ // src/tools/outputKernel/projectors/recovery.ts
4514
+ function appendRecoveryHint(projection, governance) {
4515
+ if (!governance.recoveryHint) {
4516
+ return projection;
4517
+ }
4518
+ if (projection.includes(governance.recoveryHint)) {
4519
+ return projection;
4520
+ }
4521
+ return `${projection.trimEnd()}
4522
+ ${governance.recoveryHint}`;
4523
+ }
4524
+
4525
+ // src/tools/outputKernel/projectors.ts
4526
+ function projectOutputByKind(kind, source) {
4527
+ switch (kind) {
4528
+ case "empty":
4529
+ return projectEmptyOutput(source);
4530
+ case "test":
4531
+ return projectStructuredOutput(source, buildDiagnosticProjection(source, "test"));
4532
+ case "build":
4533
+ return projectStructuredOutput(source, buildDiagnosticProjection(source, "build"));
4534
+ case "typecheck":
4535
+ return projectStructuredOutput(source, buildDiagnosticProjection(source, "typecheck"));
4536
+ case "search":
4537
+ return projectStructuredOutput(source, buildSearchProjection(source));
4538
+ case "git_diff":
4539
+ return projectStructuredOutput(source, buildGitDiffProjection(source));
4540
+ case "generic":
4541
+ return projectGenericOutput(source);
4542
+ }
4543
+ }
4544
+
4545
+ // src/tools/outputKernel/index.ts
4546
+ function governToolOutput(source) {
4547
+ const kind = classifyToolOutput(source);
4548
+ const projected = projectOutputByKind(kind, source);
4549
+ const recoveryHint = source.outputPath ? `[full output: ${source.outputPath}]` : void 0;
4550
+ const projection = appendRecoveryHint(projected.projection, {
4551
+ outputPath: source.outputPath,
4552
+ recoveryHint
4553
+ });
4554
+ const metrics = computeSavings({
4555
+ raw: source.output,
4556
+ projected: projection
4557
+ });
4558
+ return {
4559
+ version: 1,
4560
+ kind,
4561
+ mode: projected.mode,
4562
+ projection,
4563
+ rawChars: source.outputChars ?? metrics.rawChars,
4564
+ projectedChars: metrics.projectedChars,
4565
+ rawTokens: metrics.rawTokens,
4566
+ projectedTokens: metrics.projectedTokens,
4567
+ savedTokens: metrics.savedTokens,
4568
+ savingsRatio: metrics.savingsRatio,
4569
+ truncated: Boolean(source.truncated),
4570
+ outputPath: source.outputPath,
4571
+ recoveryHint,
4572
+ degraded: projected.degraded,
4573
+ reason: projected.reason
4574
+ };
4575
+ }
4576
+
4323
4577
  // src/tools/bash.ts
4324
4578
  var SHELL_RUNTIME = getShellRuntimeInfo();
4325
4579
  var bashToolDefinition = {
@@ -4369,6 +4623,18 @@ var bashToolDefinition = {
4369
4623
  }
4370
4624
  });
4371
4625
  const status = result.aborted ? "aborted" : result.stalled ? "stalled" : result.timedOut ? "timed_out" : result.exitCode === 0 ? "completed" : "failed";
4626
+ const outputGovernance = governToolOutput({
4627
+ toolName: "bash",
4628
+ command,
4629
+ status,
4630
+ exitCode: result.exitCode,
4631
+ durationMs: result.durationMs,
4632
+ output: result.output,
4633
+ outputPath: result.outputPath,
4634
+ truncated: result.truncated,
4635
+ outputChars: result.outputChars,
4636
+ outputBytes: result.outputBytes
4637
+ });
4372
4638
  const metadata = {
4373
4639
  runtime: {
4374
4640
  status,
@@ -4381,7 +4647,8 @@ var bashToolDefinition = {
4381
4647
  truncated: result.truncated,
4382
4648
  outputPath: result.outputPath,
4383
4649
  outputPreview: result.output
4384
- }
4650
+ },
4651
+ outputGovernance
4385
4652
  };
4386
4653
  return okResult(
4387
4654
  JSON.stringify(
@@ -4396,6 +4663,7 @@ var bashToolDefinition = {
4396
4663
  outputPath: result.outputPath,
4397
4664
  outputChars: result.outputChars,
4398
4665
  outputBytes: result.outputBytes,
4666
+ outputGovernance,
4399
4667
  output: truncateText(result.output, 4e3),
4400
4668
  ...status === "completed" ? {} : {
4401
4669
  shell: shell.shell,
@@ -5916,6 +6184,31 @@ async function processToolCallBatch(input) {
5916
6184
  changedPathCount: metadata?.changedPaths?.length ?? 0
5917
6185
  }
5918
6186
  });
6187
+ if (metadata?.outputGovernance) {
6188
+ await recordObservabilityEvent(projectContext.stateRootDir, {
6189
+ event: "tool.output",
6190
+ status: result.ok ? "completed" : "failed",
6191
+ sessionId: session.id,
6192
+ identityKind: identity.kind,
6193
+ identityName: identity.name,
6194
+ toolName: toolCall.function.name,
6195
+ durationMs,
6196
+ details: {
6197
+ kind: metadata.outputGovernance.kind,
6198
+ mode: metadata.outputGovernance.mode,
6199
+ rawChars: metadata.outputGovernance.rawChars,
6200
+ projectedChars: metadata.outputGovernance.projectedChars,
6201
+ rawTokens: metadata.outputGovernance.rawTokens,
6202
+ projectedTokens: metadata.outputGovernance.projectedTokens,
6203
+ savedTokens: metadata.outputGovernance.savedTokens,
6204
+ savingsRatio: metadata.outputGovernance.savingsRatio,
6205
+ truncated: metadata.outputGovernance.truncated,
6206
+ outputPath: metadata.outputGovernance.outputPath,
6207
+ degraded: metadata.outputGovernance.degraded,
6208
+ reason: metadata.outputGovernance.reason
6209
+ }
6210
+ });
6211
+ }
5919
6212
  if (result.ok) {
5920
6213
  options.callbacks?.onToolResult?.(toolCall.function.name, result.output);
5921
6214
  } else {