laohuang 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/bin.js +2463 -190
  2. package/dist/bin.js.map +4 -4
  3. package/package.json +3 -1
package/dist/bin.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
5
- import { dirname as dirname4, join as join4 } from "node:path";
4
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
5
+ import { dirname as dirname4, join as join6 } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  // ../../packages/core/agent-runtime/dist/agent.js
@@ -62,8 +62,8 @@ var CancelToken = class {
62
62
  continue;
63
63
  }
64
64
  }
65
- for (const resolve of this.#waiters) {
66
- resolve();
65
+ for (const resolve2 of this.#waiters) {
66
+ resolve2();
67
67
  }
68
68
  this.#waiters.clear();
69
69
  return true;
@@ -76,20 +76,20 @@ var CancelToken = class {
76
76
  if (this.#cancelled) {
77
77
  return Promise.resolve(true);
78
78
  }
79
- return new Promise((resolve) => {
79
+ return new Promise((resolve2) => {
80
80
  let timer;
81
81
  const onCancel = () => {
82
82
  if (timer !== void 0) {
83
83
  clearTimeout(timer);
84
84
  }
85
85
  this.#waiters.delete(onCancel);
86
- resolve(true);
86
+ resolve2(true);
87
87
  };
88
88
  this.#waiters.add(onCancel);
89
89
  if (timeoutMs !== void 0) {
90
90
  timer = setTimeout(() => {
91
91
  this.#waiters.delete(onCancel);
92
- resolve(false);
92
+ resolve2(false);
93
93
  }, timeoutMs);
94
94
  }
95
95
  });
@@ -329,7 +329,11 @@ var EVENT_SPECS = new Map([
329
329
  spec(EventKind.ModelRequestStarted, {
330
330
  sources: [EventSource.Model],
331
331
  required_payload: ["request_id"],
332
- payload_types: { request_id: "string" },
332
+ payload_types: {
333
+ request_id: "string",
334
+ context_tokens: "integer",
335
+ context_window: "integer"
336
+ },
333
337
  require_task_id: true,
334
338
  require_correlation_id: true,
335
339
  max_payload_chars: 1e5
@@ -646,8 +650,8 @@ var SubscriberMailbox = class _SubscriberMailbox {
646
650
  const deadline = timeoutMs === void 0 ? void 0 : Date.now() + timeoutMs;
647
651
  while (this.unfinished > 0) {
648
652
  if (deadline === void 0) {
649
- await new Promise((resolve) => {
650
- this.flushWaiters.push(resolve);
653
+ await new Promise((resolve2) => {
654
+ this.flushWaiters.push(resolve2);
651
655
  });
652
656
  continue;
653
657
  }
@@ -655,12 +659,12 @@ var SubscriberMailbox = class _SubscriberMailbox {
655
659
  if (remaining <= 0) {
656
660
  return false;
657
661
  }
658
- const waiter = new Promise((resolve) => {
659
- this.flushWaiters.push(resolve);
662
+ const waiter = new Promise((resolve2) => {
663
+ this.flushWaiters.push(resolve2);
660
664
  });
661
665
  let timer;
662
- const timeout = new Promise((resolve) => {
663
- timer = setTimeout(resolve, remaining);
666
+ const timeout = new Promise((resolve2) => {
667
+ timer = setTimeout(resolve2, remaining);
664
668
  });
665
669
  await Promise.race([waiter, timeout]);
666
670
  clearTimeout(timer);
@@ -689,22 +693,22 @@ var SubscriberMailbox = class _SubscriberMailbox {
689
693
  wake() {
690
694
  const waiters = this.wakeWaiters;
691
695
  this.wakeWaiters = [];
692
- for (const resolve of waiters) {
693
- resolve();
696
+ for (const resolve2 of waiters) {
697
+ resolve2();
694
698
  }
695
699
  }
696
700
  notifyFlushWaiters() {
697
701
  const waiters = this.flushWaiters;
698
702
  this.flushWaiters = [];
699
- for (const resolve of waiters) {
700
- resolve();
703
+ for (const resolve2 of waiters) {
704
+ resolve2();
701
705
  }
702
706
  }
703
707
  async run() {
704
708
  for (; ; ) {
705
709
  while (this.items.length === 0 && !this.closed) {
706
- await new Promise((resolve) => {
707
- this.wakeWaiters.push(resolve);
710
+ await new Promise((resolve2) => {
711
+ this.wakeWaiters.push(resolve2);
708
712
  });
709
713
  }
710
714
  if (this.items.length === 0 && this.closed) {
@@ -847,8 +851,8 @@ var EventBus = class {
847
851
  if (this.closed) {
848
852
  return Promise.reject(new EventBusClosedError());
849
853
  }
850
- return new Promise((resolve, reject) => {
851
- const waiter = { resolve, reject };
854
+ return new Promise((resolve2, reject) => {
855
+ const waiter = { resolve: resolve2, reject };
852
856
  if (timeoutMs !== void 0) {
853
857
  waiter.timer = setTimeout(() => {
854
858
  const index = this.bufferWaiters.indexOf(waiter);
@@ -1085,6 +1089,7 @@ var ModelRuntime = class {
1085
1089
  tools: request.tools,
1086
1090
  ...request.reasoningEffort === void 0 ? {} : { reasoningEffort: request.reasoningEffort },
1087
1091
  ...request.temperature === void 0 ? {} : { temperature: request.temperature },
1092
+ ...request.maxOutputTokens === void 0 ? {} : { maxOutputTokens: request.maxOutputTokens },
1088
1093
  ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
1089
1094
  ...request.requestId === void 0 ? {} : { requestId: request.requestId },
1090
1095
  ...request.cancelToken == null ? {} : { cancelToken: request.cancelToken },
@@ -1185,7 +1190,7 @@ async function defaultSleep(delayMs, cancelToken) {
1185
1190
  throw new ModelStreamCancelled(cancelToken?.reason || "cancelled");
1186
1191
  }
1187
1192
  if (cancelToken === null) {
1188
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1193
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
1189
1194
  }
1190
1195
  }
1191
1196
  function errorMessage(error) {
@@ -1193,7 +1198,7 @@ function errorMessage(error) {
1193
1198
  }
1194
1199
 
1195
1200
  // ../../packages/core/agent-runtime/dist/system-prompt.js
1196
- var TITLE = "You are laoHuangCode, a coding agent.";
1201
+ var TITLE = "You are a coding agent operating inside LaoHuang, the laohuang CLI harness.";
1197
1202
  var GENERAL_GUIDELINES = [
1198
1203
  "- Follow direct user instructions. Project instructions may provide additional guidance.",
1199
1204
  "- Inspect relevant files before changing them.",
@@ -1202,7 +1207,7 @@ var GENERAL_GUIDELINES = [
1202
1207
  "- Keep the final response concise and state what changed."
1203
1208
  ];
1204
1209
  var SECTION_ORDER = ["read", "write", "edit", "bash"];
1205
- function buildSystemPrompt(registry) {
1210
+ function buildSystemPrompt(registry, options = {}) {
1206
1211
  const specsByName = new Map(registry.orderedSpecs.map((spec2) => [spec2.name, spec2]));
1207
1212
  const sections = SECTION_ORDER.map((name) => {
1208
1213
  const spec2 = specsByName.get(name);
@@ -1210,6 +1215,7 @@ function buildSystemPrompt(registry) {
1210
1215
  return `## ${name}
1211
1216
  ${body}`;
1212
1217
  });
1218
+ const runtimeFacts = runtimeFactLines(options);
1213
1219
  return [
1214
1220
  TITLE,
1215
1221
  "",
@@ -1218,9 +1224,29 @@ ${body}`;
1218
1224
  "",
1219
1225
  "Tool guidelines:",
1220
1226
  "",
1221
- sections.join("\n\n")
1227
+ sections.join("\n\n"),
1228
+ ...runtimeFacts.length === 0 ? [] : ["", "Runtime facts:", ...runtimeFacts]
1222
1229
  ].join("\n");
1223
1230
  }
1231
+ function runtimeFactLines(options) {
1232
+ const lines = [];
1233
+ if (hasText(options.cliName)) {
1234
+ lines.push(`- CLI: ${options.cliName}`);
1235
+ }
1236
+ if (hasText(options.cliVersion)) {
1237
+ lines.push(`- CLI version: ${options.cliVersion}`);
1238
+ }
1239
+ if (hasText(options.provider) && hasText(options.model)) {
1240
+ lines.push(`- Provider/model: ${options.provider}/${options.model}`);
1241
+ }
1242
+ if (hasText(options.promptCwd)) {
1243
+ lines.push(`- Current working directory: ${options.promptCwd}`);
1244
+ }
1245
+ return lines;
1246
+ }
1247
+ function hasText(value) {
1248
+ return value !== null && value !== void 0 && value.length > 0;
1249
+ }
1224
1250
 
1225
1251
  // ../../packages/context/project-instructions/dist/project-instructions.js
1226
1252
  import { createHash } from "node:crypto";
@@ -1236,7 +1262,7 @@ var WRAPPER_INTRO = "The following workspace instructions may be relevant to you
1236
1262
  var OMISSION_NOTICE = "[omitted to stay within the total instruction budget]";
1237
1263
  var TRUNCATION_NOTICE = "[truncated to stay within the total instruction budget]";
1238
1264
  var PER_FILE_CAP_NOTICE = "[truncated: file exceeds the per-file instruction cap]";
1239
- var ProjectInstructionState = class {
1265
+ var ProjectInstructionState = class _ProjectInstructionState {
1240
1266
  records = /* @__PURE__ */ new Map();
1241
1267
  /** True when any instruction file from this directory scope was loaded. */
1242
1268
  hasScope(scope) {
@@ -1255,6 +1281,17 @@ var ProjectInstructionState = class {
1255
1281
  entry(displayPath) {
1256
1282
  return this.records.get(displayPath);
1257
1283
  }
1284
+ /** Stable recovery snapshot of all loaded instruction records. */
1285
+ snapshot() {
1286
+ return [...this.records.values()];
1287
+ }
1288
+ static fromSnapshot(records) {
1289
+ const state = new _ProjectInstructionState();
1290
+ for (const record of records) {
1291
+ state.record(record);
1292
+ }
1293
+ return state;
1294
+ }
1258
1295
  get loadedPaths() {
1259
1296
  return [...this.records.keys()];
1260
1297
  }
@@ -1779,15 +1816,24 @@ var AgentStepRunner = class {
1779
1816
  modelRequests += 1;
1780
1817
  const requestId = modelRound === 1 && this.options.requestId !== null ? this.options.requestId : randomUUID3();
1781
1818
  this.options.onRequestId(requestId);
1819
+ const candidateMessages = history.snapshot();
1820
+ const prepared = this.options.contextGovernor === null || this.options.contextGovernor === void 0 ? { messages: candidateMessages } : await this.options.contextGovernor.prepare({
1821
+ messages: candidateMessages,
1822
+ tools: this.options.toolDefinitions,
1823
+ provider: this.options.provider,
1824
+ model: this.options.model
1825
+ });
1826
+ const requestMessages = [...prepared.messages];
1782
1827
  this.emit("model_request", {
1783
1828
  round: modelRound,
1784
1829
  request_id: requestId,
1785
- message_count: history.snapshot().length,
1830
+ message_count: requestMessages.length,
1786
1831
  tool_rounds: toolRounds,
1787
1832
  model_requests: modelRequests,
1788
- total_tokens: totalTokens
1833
+ total_tokens: totalTokens,
1834
+ ...prepared.contextTokens === void 0 ? {} : { context_tokens: prepared.contextTokens },
1835
+ ...prepared.contextWindow === void 0 ? {} : { context_window: prepared.contextWindow }
1789
1836
  });
1790
- const requestMessages = history.snapshot();
1791
1837
  let result;
1792
1838
  try {
1793
1839
  const modelRequestOpened = context?.modelRequestOpened;
@@ -1854,7 +1900,10 @@ Authentication failed for ${this.options.provider}. Run /login ${this.options.pr
1854
1900
  tool_rounds: toolRounds,
1855
1901
  model_requests: modelRequests
1856
1902
  });
1857
- if (!history.commitAssistant(result.message)) {
1903
+ if (!history.commitAssistant(result.message, {
1904
+ requestId,
1905
+ finishReason: result.finishReason
1906
+ })) {
1858
1907
  this.emit("model_response_aborted", {
1859
1908
  round: modelRound,
1860
1909
  request_id: requestId,
@@ -2003,11 +2052,13 @@ var HistoryCommitter = class {
2003
2052
  context;
2004
2053
  cancelToken;
2005
2054
  createCancelled;
2055
+ conversationHistory;
2006
2056
  constructor(options) {
2007
2057
  this.messages = options.messages;
2008
2058
  this.context = options.context;
2009
2059
  this.cancelToken = options.cancelToken;
2010
2060
  this.createCancelled = options.createCancelled;
2061
+ this.conversationHistory = options.conversationHistory ?? null;
2011
2062
  }
2012
2063
  commitInput(content) {
2013
2064
  const message = { role: "user", content };
@@ -2016,6 +2067,11 @@ var HistoryCommitter = class {
2016
2067
  return this.commitContextMessage((append, rollback) => commitInput.call(this.context, append, rollback), message);
2017
2068
  }
2018
2069
  this.raiseIfCancelled();
2070
+ this.conversationHistory?.appendUser({
2071
+ message,
2072
+ inputEventIds: [],
2073
+ source: "direct"
2074
+ });
2019
2075
  this.messages.push(message);
2020
2076
  return true;
2021
2077
  }
@@ -2030,45 +2086,81 @@ var HistoryCommitter = class {
2030
2086
  return this.commitContextMessage((append, rollback) => commitPending.call(this.context, batch, append, rollback), message);
2031
2087
  }
2032
2088
  this.raiseIfCancelled();
2089
+ this.conversationHistory?.appendUser({
2090
+ message,
2091
+ inputEventIds: [...batch.eventIds ?? []],
2092
+ source: "pending"
2093
+ });
2033
2094
  this.messages.push(message);
2034
2095
  return true;
2035
2096
  }
2036
- commitAssistant(message) {
2097
+ commitAssistant(message, metadata) {
2037
2098
  const commitIfActive = this.context?.commitIfActive;
2038
2099
  if (typeof commitIfActive === "function") {
2039
2100
  return commitIfActive.call(this.context, () => {
2101
+ if (message.role === "assistant" && metadata !== void 0) {
2102
+ this.conversationHistory?.appendAssistant({
2103
+ message,
2104
+ requestId: metadata.requestId,
2105
+ finishReason: metadata.finishReason
2106
+ });
2107
+ }
2040
2108
  this.messages.push(message);
2041
2109
  });
2042
2110
  }
2043
2111
  this.raiseIfCancelled();
2112
+ if (message.role === "assistant" && metadata !== void 0) {
2113
+ this.conversationHistory?.appendAssistant({
2114
+ message,
2115
+ requestId: metadata.requestId,
2116
+ finishReason: metadata.finishReason
2117
+ });
2118
+ }
2044
2119
  this.messages.push(message);
2045
2120
  return true;
2046
2121
  }
2047
2122
  commitToolResults(toolCalls, toolResults) {
2123
+ const messages = [];
2048
2124
  for (let index = 0; index < toolCalls.length; index += 1) {
2049
2125
  const call = toolCalls[index];
2050
2126
  const result = toolResults[index];
2051
2127
  if (call === void 0 || result === void 0) {
2052
2128
  continue;
2053
2129
  }
2054
- this.messages.push({
2130
+ const message = {
2055
2131
  role: "tool-result",
2056
2132
  toolCallId: call.id,
2057
2133
  toolName: call.name,
2058
2134
  content: JSON.stringify(result),
2059
2135
  isError: result.ok !== true
2060
- });
2136
+ };
2137
+ messages.push(message);
2138
+ this.messages.push(message);
2061
2139
  }
2140
+ this.conversationHistory?.appendToolResults({
2141
+ requestId: "tool-results",
2142
+ messages,
2143
+ recovered: false
2144
+ });
2062
2145
  }
2063
2146
  commitReminder(content) {
2064
2147
  this.raiseIfCancelled();
2065
- this.messages.push({ role: "user", content });
2148
+ const message = { role: "user", content };
2149
+ this.conversationHistory?.appendReminder({ message, reason: "repeat_tool" });
2150
+ this.messages.push(message);
2066
2151
  }
2067
2152
  snapshot() {
2068
2153
  return [...this.messages];
2069
2154
  }
2070
2155
  commitContextMessage(commit, message) {
2071
2156
  const append = () => {
2157
+ if (message.role === "user") {
2158
+ this.conversationHistory?.appendUser({
2159
+ message,
2160
+ inputEventIds: [],
2161
+ source: "direct"
2162
+ });
2163
+ }
2072
2164
  this.messages.push(message);
2073
2165
  };
2074
2166
  const rollback = () => {
@@ -2129,6 +2221,8 @@ var CodingAgent = class {
2129
2221
  messages;
2130
2222
  onToolEvent;
2131
2223
  onAgentEvent;
2224
+ cliName;
2225
+ cliVersion;
2132
2226
  instructionRoot;
2133
2227
  startupCwd;
2134
2228
  baselineInstructionsLoaded = false;
@@ -2136,16 +2230,22 @@ var CodingAgent = class {
2136
2230
  turn = 0;
2137
2231
  activeContext = null;
2138
2232
  activeRequestId = null;
2233
+ conversationHistory;
2234
+ contextGovernor;
2139
2235
  constructor(options) {
2140
2236
  this.repeatToolReminderThresholds = normalizeReminderThresholds(options.repeatToolReminderThresholds ?? [3, 5, 8]);
2141
2237
  this.model = options.model;
2142
2238
  this.tools = options.tools;
2143
2239
  this.onToolEvent = options.onToolEvent ?? null;
2144
2240
  this.onAgentEvent = options.onAgentEvent ?? null;
2241
+ this.cliName = options.cliName ?? null;
2242
+ this.cliVersion = options.cliVersion ?? null;
2145
2243
  this.provider = options.provider;
2146
2244
  this.baseUrl = options.baseUrl ?? null;
2147
2245
  this.reasoningEffort = options.reasoningEffort ?? "high";
2148
2246
  this.adapter = options.modelAdapter;
2247
+ this.conversationHistory = options.conversationHistory ?? null;
2248
+ this.contextGovernor = options.contextGovernor ?? null;
2149
2249
  this.modelRuntime = new ModelRuntime(this.adapter);
2150
2250
  this.toolExecution = options.toolExecution ?? "parallel";
2151
2251
  this.toolRuntime = new ToolRuntime(this.tools, {
@@ -2153,7 +2253,12 @@ var CodingAgent = class {
2153
2253
  });
2154
2254
  this.instructionRoot = options.projectRoot == null ? null : realpathOrSelf(options.projectRoot);
2155
2255
  this.startupCwd = options.startupCwd == null ? null : realpathOrSelf(options.startupCwd);
2156
- this.messages = [{ role: "system", content: buildSystemPrompt(this.tools) }];
2256
+ this.messages = [
2257
+ {
2258
+ role: "system",
2259
+ content: this.buildSystemPrompt()
2260
+ }
2261
+ ];
2157
2262
  }
2158
2263
  /** Bookkeeping for loaded project instructions (never model-visible). */
2159
2264
  get projectInstructionState() {
@@ -2173,6 +2278,7 @@ var CodingAgent = class {
2173
2278
  this.provider = options.provider;
2174
2279
  this.model = options.model;
2175
2280
  this.baseUrl = options.baseUrl;
2281
+ this.refreshSystemPrompt();
2176
2282
  this.emit("model_switched", {
2177
2283
  provider: options.provider,
2178
2284
  model: options.model,
@@ -2201,8 +2307,10 @@ var CodingAgent = class {
2201
2307
  messages: this.messages,
2202
2308
  context,
2203
2309
  cancelToken,
2204
- createCancelled: (message) => new AgentCancelled(message)
2310
+ createCancelled: (message) => new AgentCancelled(message),
2311
+ conversationHistory: this.conversationHistory
2205
2312
  }),
2313
+ contextGovernor: this.contextGovernor,
2206
2314
  repeatToolPolicy: new RepeatToolPolicy(this.repeatToolReminderThresholds),
2207
2315
  userInput,
2208
2316
  context,
@@ -2250,6 +2358,11 @@ var CodingAgent = class {
2250
2358
  return;
2251
2359
  }
2252
2360
  raiseIfCancelled(cancelToken);
2361
+ this.conversationHistory?.appendUser({
2362
+ message: { role: "user", content: baseline.rendered },
2363
+ inputEventIds: [],
2364
+ source: "direct"
2365
+ });
2253
2366
  this.messages.push({ role: "user", content: baseline.rendered });
2254
2367
  }
2255
2368
  /**
@@ -2288,8 +2401,32 @@ var CodingAgent = class {
2288
2401
  if (rendered === "") {
2289
2402
  return;
2290
2403
  }
2404
+ this.conversationHistory?.appendUser({
2405
+ message: { role: "user", content: rendered },
2406
+ inputEventIds: [],
2407
+ source: "direct"
2408
+ });
2291
2409
  this.messages.push({ role: "user", content: rendered });
2292
2410
  }
2411
+ buildSystemPrompt() {
2412
+ return buildSystemPrompt(this.tools, {
2413
+ cliName: this.cliName,
2414
+ cliVersion: this.cliVersion,
2415
+ provider: this.provider,
2416
+ model: this.model,
2417
+ promptCwd: this.startupCwd
2418
+ });
2419
+ }
2420
+ refreshSystemPrompt() {
2421
+ const first = this.messages[0];
2422
+ if (first?.role !== "system") {
2423
+ return;
2424
+ }
2425
+ this.messages[0] = {
2426
+ role: "system",
2427
+ content: this.buildSystemPrompt()
2428
+ };
2429
+ }
2293
2430
  // --- Events --------------------------------------------------------------------
2294
2431
  emit(eventType, payload) {
2295
2432
  const fullPayload = { turn: this.turn, ...payload };
@@ -2385,6 +2522,1273 @@ function normalizeReminderThresholds(values) {
2385
2522
  return [...unique].sort((left, right) => left - right);
2386
2523
  }
2387
2524
 
2525
+ // ../../packages/session/session-context/dist/conversation-history.js
2526
+ import { randomUUID as randomUUID4 } from "node:crypto";
2527
+ var ConversationHistory = class _ConversationHistory {
2528
+ #journal;
2529
+ #entries;
2530
+ #staged = /* @__PURE__ */ new Map();
2531
+ constructor(entries, journal) {
2532
+ this.#entries = [...entries];
2533
+ this.#journal = journal;
2534
+ }
2535
+ static fromReplay(replay, journal) {
2536
+ const history = new _ConversationHistory(replay.items.filter((item) => item.kind === "entry"), journal);
2537
+ for (const open of replay.openToolCalls) {
2538
+ const entry = history.appendToolResults({
2539
+ requestId: "recovered",
2540
+ recovered: true,
2541
+ messages: [{
2542
+ role: "tool-result",
2543
+ toolCallId: open.toolCallId,
2544
+ toolName: open.toolName,
2545
+ content: JSON.stringify({
2546
+ ok: false,
2547
+ status: "interrupted",
2548
+ error: "Tool execution was interrupted before a result was recorded."
2549
+ }),
2550
+ isError: true
2551
+ }]
2552
+ })[0];
2553
+ journal.appendRecord({
2554
+ recordType: "error",
2555
+ payload: {
2556
+ type: "interrupted_tool_call",
2557
+ toolCallId: open.toolCallId,
2558
+ repairedEntryId: entry?.id ?? null
2559
+ }
2560
+ });
2561
+ }
2562
+ return history;
2563
+ }
2564
+ appendSystemContext(input) {
2565
+ return this.append({
2566
+ entryType: "system_context",
2567
+ payload: input
2568
+ });
2569
+ }
2570
+ appendProjectInstructions(input) {
2571
+ if (input.message.content === "") {
2572
+ return null;
2573
+ }
2574
+ return this.append({
2575
+ entryType: "project_instructions",
2576
+ payload: {
2577
+ message: input.message,
2578
+ files: input.files,
2579
+ supersedesEntryIds: input.supersedesEntryIds ?? []
2580
+ }
2581
+ });
2582
+ }
2583
+ appendUser(input) {
2584
+ return this.append({
2585
+ entryType: "user_message",
2586
+ payload: input
2587
+ });
2588
+ }
2589
+ stageUser(input) {
2590
+ const token = randomUUID4().replaceAll("-", "");
2591
+ this.#staged.set(token, input);
2592
+ return {
2593
+ token,
2594
+ message: input.message,
2595
+ inputEventIds: input.inputEventIds
2596
+ };
2597
+ }
2598
+ commitStagedUser(token) {
2599
+ const staged = this.#staged.get(token);
2600
+ if (staged === void 0) {
2601
+ throw new Error(`unknown staged user message: ${token}`);
2602
+ }
2603
+ this.#staged.delete(token);
2604
+ return this.appendUser(staged);
2605
+ }
2606
+ discardStagedUser(token) {
2607
+ this.#staged.delete(token);
2608
+ }
2609
+ appendAssistant(input) {
2610
+ return this.append({
2611
+ entryType: "assistant_message",
2612
+ payload: input
2613
+ });
2614
+ }
2615
+ appendToolResults(input) {
2616
+ return input.messages.map((message) => this.append({
2617
+ entryType: "tool_result",
2618
+ payload: {
2619
+ message,
2620
+ requestId: input.requestId,
2621
+ recovered: input.recovered
2622
+ }
2623
+ }));
2624
+ }
2625
+ appendReminder(input) {
2626
+ return this.append({
2627
+ entryType: "reminder",
2628
+ payload: input
2629
+ });
2630
+ }
2631
+ appendCompaction(input) {
2632
+ return this.append({
2633
+ entryType: "compaction",
2634
+ payload: input
2635
+ });
2636
+ }
2637
+ entries() {
2638
+ return [...this.#entries];
2639
+ }
2640
+ append(input) {
2641
+ const entry = this.#journal.appendEntry(input);
2642
+ this.#entries.push(entry);
2643
+ return entry;
2644
+ }
2645
+ };
2646
+
2647
+ // ../../packages/session/session-context/dist/context-builder.js
2648
+ var ContextBuildError = class extends Error {
2649
+ entryId;
2650
+ constructor(entryId, message) {
2651
+ super(`${entryId}: ${message}`);
2652
+ this.name = "ContextBuildError";
2653
+ this.entryId = entryId;
2654
+ }
2655
+ };
2656
+ var ContextBuilder = class {
2657
+ build(input) {
2658
+ const sorted = [...input.entries].sort((left, right) => left.seq - right.seq);
2659
+ const superseded = supersededEntryIds(sorted);
2660
+ const activeCompaction = latestCompaction(sorted);
2661
+ const selected = [];
2662
+ const system = latestActive(sorted, "system_context", superseded);
2663
+ if (system !== null) {
2664
+ selected.push({ entryId: system.id, message: system.payload.message });
2665
+ }
2666
+ for (const entry of sorted) {
2667
+ if (entry.entryType === "project_instructions" && !superseded.has(entry.id)) {
2668
+ selected.push({ entryId: entry.id, message: entry.payload.message });
2669
+ }
2670
+ }
2671
+ if (activeCompaction !== null) {
2672
+ selected.push({
2673
+ entryId: activeCompaction.id,
2674
+ message: {
2675
+ role: "user",
2676
+ content: `<conversation_summary>
2677
+ ${activeCompaction.payload.summary}
2678
+ </conversation_summary>`
2679
+ }
2680
+ });
2681
+ }
2682
+ for (const entry of conversationTail(sorted, activeCompaction)) {
2683
+ const message = modelMessageForEntry(entry, input.currentProvider, input.currentModel);
2684
+ if (message !== null) {
2685
+ selected.push({ entryId: entry.id, message });
2686
+ }
2687
+ }
2688
+ validateToolPairs(selected);
2689
+ return {
2690
+ messages: selected.map((item) => item.message),
2691
+ sourceEntryIds: selected.map((item) => item.entryId),
2692
+ activeCompactionId: activeCompaction?.id ?? null
2693
+ };
2694
+ }
2695
+ };
2696
+ function supersededEntryIds(entries) {
2697
+ const superseded = /* @__PURE__ */ new Set();
2698
+ for (const entry of entries) {
2699
+ if (entry.entryType === "system_context" && entry.payload.supersedesEntryId !== void 0) {
2700
+ superseded.add(entry.payload.supersedesEntryId);
2701
+ }
2702
+ if (entry.entryType === "project_instructions") {
2703
+ for (const id of entry.payload.supersedesEntryIds) {
2704
+ superseded.add(id);
2705
+ }
2706
+ }
2707
+ }
2708
+ return superseded;
2709
+ }
2710
+ function latestActive(entries, entryType, superseded) {
2711
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
2712
+ const entry = entries[index];
2713
+ if (entry.entryType === entryType && !superseded.has(entry.id)) {
2714
+ return entry;
2715
+ }
2716
+ }
2717
+ return null;
2718
+ }
2719
+ function latestCompaction(entries) {
2720
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
2721
+ const entry = entries[index];
2722
+ if (entry.entryType === "compaction") {
2723
+ return entry;
2724
+ }
2725
+ }
2726
+ return null;
2727
+ }
2728
+ function conversationTail(entries, activeCompaction) {
2729
+ const boundary = activeCompaction?.payload.retainedFromSeq ?? 1;
2730
+ return entries.filter((entry) => entry.seq >= boundary && entry.entryType !== "system_context" && entry.entryType !== "project_instructions" && entry.entryType !== "compaction");
2731
+ }
2732
+ function modelMessageForEntry(entry, currentProvider, currentModel) {
2733
+ if (entry.entryType === "user_message" || entry.entryType === "reminder") {
2734
+ return entry.payload.message;
2735
+ }
2736
+ if (entry.entryType === "assistant_message") {
2737
+ const message = entry.payload.message;
2738
+ if (message.provider !== currentProvider || message.model !== currentModel) {
2739
+ return portableModelMessage(message);
2740
+ }
2741
+ return message;
2742
+ }
2743
+ if (entry.entryType === "tool_result") {
2744
+ return entry.payload.message;
2745
+ }
2746
+ return null;
2747
+ }
2748
+ function validateToolPairs(selected) {
2749
+ let expected = [];
2750
+ let ownerEntryId = null;
2751
+ for (const item of selected) {
2752
+ if (item.message.role === "assistant") {
2753
+ if (expected.length > 0) {
2754
+ throw new ContextBuildError(ownerEntryId ?? item.entryId, "missing tool result");
2755
+ }
2756
+ expected = item.message.content.filter((block) => block.type === "tool-call").map((block) => block.call.id);
2757
+ ownerEntryId = expected.length === 0 ? null : item.entryId;
2758
+ continue;
2759
+ }
2760
+ if (item.message.role === "tool-result") {
2761
+ const [next, ...rest] = expected;
2762
+ if (next === void 0) {
2763
+ throw new ContextBuildError(item.entryId, "orphan tool result");
2764
+ }
2765
+ if (item.message.toolCallId !== next) {
2766
+ throw new ContextBuildError(item.entryId, "tool result order does not match tool call order");
2767
+ }
2768
+ expected = rest;
2769
+ ownerEntryId = expected.length === 0 ? null : ownerEntryId;
2770
+ continue;
2771
+ }
2772
+ if (expected.length > 0) {
2773
+ throw new ContextBuildError(ownerEntryId ?? item.entryId, "missing tool result");
2774
+ }
2775
+ }
2776
+ if (expected.length > 0) {
2777
+ throw new ContextBuildError(ownerEntryId ?? "unknown", "missing tool result");
2778
+ }
2779
+ }
2780
+
2781
+ // ../../packages/session/session-context/dist/token-estimator.js
2782
+ import { createHash as createHash2 } from "node:crypto";
2783
+ var TEXT_BYTES_PER_TOKEN = 4;
2784
+ var BLOCK_OVERHEAD = 4;
2785
+ var MESSAGE_OVERHEAD = 4;
2786
+ var DefaultTokenEstimator = class {
2787
+ estimateMessages(messages) {
2788
+ return messages.reduce((total, message) => total + this.estimateMessage(message), 0);
2789
+ }
2790
+ estimateTools(tools) {
2791
+ if (tools.length === 0) {
2792
+ return 0;
2793
+ }
2794
+ return this.estimateText(canonicalJson(tools)) + BLOCK_OVERHEAD;
2795
+ }
2796
+ estimateText(text) {
2797
+ return Math.ceil(Buffer.byteLength(text, "utf8") / TEXT_BYTES_PER_TOKEN);
2798
+ }
2799
+ measure(input) {
2800
+ const anchor = input.anchor ?? null;
2801
+ if (anchor !== null && this.isValidAnchor(input, anchor)) {
2802
+ const trailingEntries = input.entries.filter((entry) => entry.seq > anchor.throughSeq);
2803
+ const trailingTokens = this.estimateMessages(messagesForEntries(trailingEntries));
2804
+ return {
2805
+ totalTokens: anchor.contextTokens + trailingTokens,
2806
+ anchorTokens: anchor.contextTokens,
2807
+ trailingTokens,
2808
+ source: "provider_usage"
2809
+ };
2810
+ }
2811
+ const totalTokens = this.estimateMessages(input.messages) + this.estimateTools(input.tools);
2812
+ return {
2813
+ totalTokens,
2814
+ anchorTokens: 0,
2815
+ trailingTokens: totalTokens,
2816
+ source: "estimated"
2817
+ };
2818
+ }
2819
+ estimateMessage(message) {
2820
+ if (message.role === "system" || message.role === "user") {
2821
+ return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content);
2822
+ }
2823
+ if (message.role === "tool-result") {
2824
+ return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content);
2825
+ }
2826
+ return MESSAGE_OVERHEAD + message.content.reduce((total, block) => {
2827
+ if (block.type === "text" || block.type === "reasoning") {
2828
+ return total + BLOCK_OVERHEAD + this.estimateText(block.text);
2829
+ }
2830
+ return total + BLOCK_OVERHEAD + this.estimateText(`${block.call.name}${canonicalJson(block.call.arguments)}`);
2831
+ }, 0);
2832
+ }
2833
+ isValidAnchor(input, anchor) {
2834
+ return anchor.provider === input.provider && anchor.model === input.model && anchor.systemFingerprint === input.systemFingerprint && anchor.projectInstructionsFingerprint === input.projectInstructionsFingerprint && anchor.toolsFingerprint === input.toolsFingerprint && input.entries.some((entry) => entry.id === anchor.throughEntryId && entry.seq === anchor.throughSeq);
2835
+ }
2836
+ };
2837
+ function fingerprintContextPart(value) {
2838
+ return createHash2("sha256").update(canonicalJson(value)).digest("hex");
2839
+ }
2840
+ function canonicalJson(value) {
2841
+ return JSON.stringify(sortStable(value));
2842
+ }
2843
+ function sortStable(value) {
2844
+ if (Array.isArray(value)) {
2845
+ return value.map(sortStable);
2846
+ }
2847
+ if (value !== null && typeof value === "object") {
2848
+ const record = value;
2849
+ const sorted = {};
2850
+ for (const key of Object.keys(record).sort()) {
2851
+ sorted[key] = sortStable(record[key]);
2852
+ }
2853
+ return sorted;
2854
+ }
2855
+ return value;
2856
+ }
2857
+ function messagesForEntries(entries) {
2858
+ return entries.flatMap((entry) => {
2859
+ if (entry.entryType === "system_context") {
2860
+ return [entry.payload.message];
2861
+ }
2862
+ if (entry.entryType === "project_instructions" || entry.entryType === "user_message" || entry.entryType === "reminder") {
2863
+ return [entry.payload.message];
2864
+ }
2865
+ if (entry.entryType === "assistant_message") {
2866
+ return [entry.payload.message];
2867
+ }
2868
+ if (entry.entryType === "tool_result") {
2869
+ return [entry.payload.message];
2870
+ }
2871
+ return [];
2872
+ });
2873
+ }
2874
+
2875
+ // ../../packages/session/session-context/dist/summary-prompt.js
2876
+ var COMPACTION_SYSTEM_PROMPT = "Summarize the conversation for continuation. Preserve user goals, explicit constraints, completed work, modified files, architectural decisions, tool outcomes, failures, current TODOs, and concrete state needed to continue. Do not include credentials. Do not invent results.";
2877
+
2878
+ // ../../packages/session/session-context/dist/compaction.js
2879
+ function selectCompactionPlan(input) {
2880
+ const units = semanticUnits(input.entries);
2881
+ const retained = [];
2882
+ let tokens = 0;
2883
+ for (let index = units.length - 1; index >= 0; index -= 1) {
2884
+ const unit = units[index];
2885
+ const unitTokens = input.estimator.estimateMessages(unit.messages);
2886
+ retained.unshift(unit);
2887
+ tokens += unitTokens;
2888
+ if (tokens >= input.retainTokens) {
2889
+ break;
2890
+ }
2891
+ }
2892
+ if (retained.length === 0 && units.length > 0) {
2893
+ retained.push(units.at(-1));
2894
+ }
2895
+ const retainedFirst = retained[0]?.entries[0]?.seq ?? Number.MAX_SAFE_INTEGER;
2896
+ const summarized = units.filter((unit) => unit.entries[0].seq < retainedFirst);
2897
+ return {
2898
+ summarizedEntries: summarized.flatMap((unit) => unit.entries),
2899
+ retainedEntries: retained.flatMap((unit) => unit.entries),
2900
+ retainedFromSeq: retained[0]?.entries[0]?.seq ?? 1
2901
+ };
2902
+ }
2903
+ function serializeConversation(entries) {
2904
+ const lines = ["<conversation>"];
2905
+ for (const entry of entries) {
2906
+ lines.push(`<entry id="${entry.id}" seq="${entry.seq}" type="${entry.entryType}">`);
2907
+ if (entry.entryType === "assistant_message") {
2908
+ for (const block of entry.payload.message.content) {
2909
+ if (block.type === "tool-call") {
2910
+ lines.push(`[assistant tool-call ${block.call.id} ${block.call.name}] ${JSON.stringify(block.call.arguments)}`);
2911
+ } else {
2912
+ lines.push(`[assistant ${block.type}] ${block.text}`);
2913
+ }
2914
+ }
2915
+ } else if (entry.entryType === "tool_result") {
2916
+ lines.push(`[tool-result ${entry.payload.message.toolCallId} ${entry.payload.message.toolName}] ${entry.payload.message.content}`);
2917
+ } else if (entry.entryType === "user_message" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
2918
+ lines.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
2919
+ }
2920
+ lines.push("</entry>");
2921
+ }
2922
+ lines.push("</conversation>");
2923
+ return lines.join("\n");
2924
+ }
2925
+ function semanticUnits(entries) {
2926
+ const units = [];
2927
+ for (let index = 0; index < entries.length; index += 1) {
2928
+ const entry = entries[index];
2929
+ if (entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
2930
+ continue;
2931
+ }
2932
+ if (entry.entryType === "assistant_message") {
2933
+ const toolCallIds = entry.payload.message.content.filter((block) => block.type === "tool-call").map((block) => block.call.id);
2934
+ if (toolCallIds.length > 0) {
2935
+ const grouped = [entry];
2936
+ for (const id of toolCallIds) {
2937
+ const resultIndex = entries.findIndex((candidate) => candidate.entryType === "tool_result" && candidate.payload.message.toolCallId === id);
2938
+ if (resultIndex < 0) {
2939
+ throw new Error(`cannot compact incomplete tool call: ${id}`);
2940
+ }
2941
+ grouped.push(entries[resultIndex]);
2942
+ }
2943
+ units.push({ entries: grouped, messages: grouped.map(messageForEntry) });
2944
+ continue;
2945
+ }
2946
+ }
2947
+ if (entry.entryType === "tool_result") {
2948
+ continue;
2949
+ }
2950
+ units.push({ entries: [entry], messages: [messageForEntry(entry)] });
2951
+ }
2952
+ return units;
2953
+ }
2954
+ function messageForEntry(entry) {
2955
+ if (entry.entryType === "assistant_message") {
2956
+ return entry.payload.message;
2957
+ }
2958
+ if (entry.entryType === "tool_result") {
2959
+ return entry.payload.message;
2960
+ }
2961
+ if (entry.entryType === "user_message" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
2962
+ return entry.payload.message;
2963
+ }
2964
+ throw new Error(`entry has no model message: ${entry.entryType}`);
2965
+ }
2966
+
2967
+ // ../../packages/session/session-context/dist/context-governor.js
2968
+ var ContextBudgetError = class extends Error {
2969
+ constructor(message) {
2970
+ super(message);
2971
+ this.name = "ContextBudgetError";
2972
+ }
2973
+ };
2974
+ var ContextGovernor = class {
2975
+ #builder;
2976
+ #estimator;
2977
+ #summarize;
2978
+ #appendCompaction;
2979
+ constructor(options) {
2980
+ this.#builder = options.builder ?? new ContextBuilder();
2981
+ this.#estimator = options.estimator ?? new DefaultTokenEstimator();
2982
+ this.#summarize = options.summarize;
2983
+ this.#appendCompaction = options.appendCompaction;
2984
+ }
2985
+ async prepare(input) {
2986
+ let built = this.#builder.build(input);
2987
+ let tokens = this.measure(input, built);
2988
+ const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
2989
+ if (!input.policy.auto || tokens <= calculated.autoTrigger) {
2990
+ return { built, messages: built.messages, compacted: false, tokens };
2991
+ }
2992
+ const result = await this.compact({ ...input, trigger: "automatic" });
2993
+ const entries = [...input.entries, result.entry];
2994
+ built = this.#builder.build({ ...input, entries });
2995
+ tokens = this.measure({ ...input, entries }, built);
2996
+ if (tokens > calculated.hardInputLimit) {
2997
+ throw new ContextBudgetError("context remains over hard input limit after compaction");
2998
+ }
2999
+ return { built, messages: built.messages, compacted: true, tokens };
3000
+ }
3001
+ async compact(input) {
3002
+ const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
3003
+ const plan = selectCompactionPlan({
3004
+ entries: input.entries,
3005
+ retainTokens: calculated.retainTokens,
3006
+ estimator: this.#estimator
3007
+ });
3008
+ if (input.trigger === "manual" && plan.summarizedEntries.length === 0) {
3009
+ throw new Error("No messages to compact in current history.");
3010
+ }
3011
+ const summarizedFromSeq = plan.summarizedEntries[0]?.seq ?? 1;
3012
+ const summarizedThroughSeq = plan.summarizedEntries.at(-1)?.seq ?? Math.max(0, summarizedFromSeq - 1);
3013
+ const activeCompaction = [...input.entries].reverse().find((entry) => entry.entryType === "compaction");
3014
+ const tokensBefore = this.#estimator.estimateMessages(this.#builder.build(input).messages) + this.#estimator.estimateTools(input.tools);
3015
+ const summary = plan.summarizedEntries.length === 0 ? {
3016
+ summary: "No prior conversation needed compaction.",
3017
+ inputTokens: 0,
3018
+ outputTokens: 0
3019
+ } : await this.#summarize({
3020
+ serialized: serializeConversation(plan.summarizedEntries),
3021
+ maxSummaryTokens: input.policy.maxSummaryTokens
3022
+ });
3023
+ const payload = {
3024
+ summary: summary.summary,
3025
+ summarizedFromSeq,
3026
+ summarizedThroughSeq,
3027
+ retainedFromSeq: plan.retainedFromSeq,
3028
+ ...activeCompaction === void 0 ? {} : { supersedesCompactionId: activeCompaction.id },
3029
+ tokensBefore,
3030
+ retainedTokens: calculated.retainTokens,
3031
+ summaryInputTokens: summary.inputTokens,
3032
+ summaryOutputTokens: summary.outputTokens,
3033
+ provider: input.currentProvider,
3034
+ model: input.currentModel,
3035
+ trigger: input.trigger
3036
+ };
3037
+ return { entry: this.#appendCompaction(payload) };
3038
+ }
3039
+ async completeWithOverflowRecovery(input, send) {
3040
+ let prepared = await this.prepare(input);
3041
+ try {
3042
+ return await send(prepared);
3043
+ } catch (error) {
3044
+ if (!(error instanceof ModelError) || modelErrorKind(error) !== "context_overflow" || error.hadDelta) {
3045
+ throw error;
3046
+ }
3047
+ }
3048
+ const compaction = await this.compact({ ...input, trigger: "provider_overflow" });
3049
+ prepared = await this.prepare({ ...input, entries: [...input.entries, compaction.entry] });
3050
+ return await send(prepared);
3051
+ }
3052
+ measure(input, built) {
3053
+ return this.#estimator.measure({
3054
+ messages: built.messages,
3055
+ tools: input.tools,
3056
+ entries: input.entries,
3057
+ anchor: input.anchor ?? null,
3058
+ provider: input.currentProvider,
3059
+ model: input.currentModel,
3060
+ systemFingerprint: fingerprintContextPart(built.messages.find((message) => message.role === "system") ?? null),
3061
+ projectInstructionsFingerprint: fingerprintContextPart(built.messages.filter((message, index) => index > 0 && message.role === "user")),
3062
+ toolsFingerprint: fingerprintContextPart(input.tools)
3063
+ }).totalTokens;
3064
+ }
3065
+ };
3066
+ function calculateModelBudget(input) {
3067
+ const hardInputLimit = input.budget.contextWindow - input.budget.maxOutputTokens;
3068
+ const safetyTokens = Math.ceil(input.budget.contextWindow * input.policy.safetyRatio);
3069
+ const autoTrigger = Math.min(Math.floor(input.budget.contextWindow * input.policy.thresholdRatio), hardInputLimit - safetyTokens);
3070
+ const retainTokens = input.policy.retainTokens ?? Math.floor(input.budget.contextWindow * input.policy.retainRatio);
3071
+ return { hardInputLimit, safetyTokens, autoTrigger, retainTokens };
3072
+ }
3073
+
3074
+ // ../../packages/session/session-store/dist/schema.js
3075
+ var ENTRY_TYPES = /* @__PURE__ */ new Set([
3076
+ "system_context",
3077
+ "project_instructions",
3078
+ "user_message",
3079
+ "assistant_message",
3080
+ "tool_result",
3081
+ "reminder",
3082
+ "compaction"
3083
+ ]);
3084
+ var RECORD_TYPES = /* @__PURE__ */ new Set([
3085
+ "turn_started",
3086
+ "turn_finished",
3087
+ "model_request",
3088
+ "model_response",
3089
+ "tool_started",
3090
+ "tool_finished",
3091
+ "provider_changed",
3092
+ "model_changed",
3093
+ "reasoning_effort_changed",
3094
+ "cwd_changed",
3095
+ "usage",
3096
+ "error",
3097
+ "cancelled",
3098
+ "queue_enqueued",
3099
+ "queue_started",
3100
+ "queue_finished",
3101
+ "compaction_started",
3102
+ "compaction_finished",
3103
+ "session_closed"
3104
+ ]);
3105
+ var ORIGINS = /* @__PURE__ */ new Set(["new", "fork", "clone", "import"]);
3106
+ var REASONING_EFFORTS2 = /* @__PURE__ */ new Set([
3107
+ "off",
3108
+ "minimal",
3109
+ "low",
3110
+ "medium",
3111
+ "high",
3112
+ "xhigh",
3113
+ "max"
3114
+ ]);
3115
+ var SessionSchemaError = class extends Error {
3116
+ constructor(message) {
3117
+ super(message);
3118
+ this.name = "SessionSchemaError";
3119
+ }
3120
+ };
3121
+ function parseSessionHeader(value) {
3122
+ const input = objectOf(value, "session header");
3123
+ if (input["schemaVersion"] !== 1) {
3124
+ throw new SessionSchemaError("unsupported session schema version");
3125
+ }
3126
+ if (input["type"] !== "session_header") {
3127
+ throw new SessionSchemaError("invalid session header type");
3128
+ }
3129
+ requireString(input, "sessionId");
3130
+ requireString(input, "createdAt");
3131
+ requireString(input, "initialCwd");
3132
+ requireString(input, "projectRoot");
3133
+ requireString(input, "projectKey");
3134
+ requireString(input, "appVersion");
3135
+ requireString(input, "provider");
3136
+ requireString(input, "model");
3137
+ if (!REASONING_EFFORTS2.has(stringValue(input, "reasoningEffort"))) {
3138
+ throw new SessionSchemaError("invalid reasoning effort");
3139
+ }
3140
+ if (!ORIGINS.has(stringValue(input, "origin"))) {
3141
+ throw new SessionSchemaError("invalid session origin");
3142
+ }
3143
+ if (input["parentSessionId"] !== void 0) {
3144
+ requireString(input, "parentSessionId");
3145
+ }
3146
+ if (input["forkedFrom"] !== void 0) {
3147
+ const forkedFrom = objectOf(input["forkedFrom"], "forkedFrom");
3148
+ requireString(forkedFrom, "sessionId");
3149
+ if (forkedFrom["seq"] !== void 0) {
3150
+ requirePositiveInteger(forkedFrom, "seq");
3151
+ }
3152
+ if (forkedFrom["entryId"] !== void 0) {
3153
+ requireString(forkedFrom, "entryId");
3154
+ }
3155
+ const mode = stringValue(forkedFrom, "mode");
3156
+ if (mode !== "before" && mode !== "at" && mode !== "clone") {
3157
+ throw new SessionSchemaError("invalid fork mode");
3158
+ }
3159
+ }
3160
+ return input;
3161
+ }
3162
+ function parseSessionItem(value, expectedSessionId) {
3163
+ const input = objectOf(value, "session item");
3164
+ if (input["schemaVersion"] !== 1) {
3165
+ throw new SessionSchemaError("unsupported session schema version");
3166
+ }
3167
+ const sessionId = requireString(input, "sessionId");
3168
+ if (expectedSessionId !== void 0 && sessionId !== expectedSessionId) {
3169
+ throw new SessionSchemaError("session id does not match header");
3170
+ }
3171
+ requirePositiveInteger(input, "seq");
3172
+ requireString(input, "id");
3173
+ requireString(input, "timestamp");
3174
+ const kind = stringValue(input, "kind");
3175
+ if (kind === "entry") {
3176
+ const entryType = stringValue(input, "entryType");
3177
+ if (!ENTRY_TYPES.has(entryType)) {
3178
+ throw new SessionSchemaError("invalid session entry type");
3179
+ }
3180
+ objectOf(input["payload"], "entry payload");
3181
+ return input;
3182
+ }
3183
+ if (kind === "record") {
3184
+ const recordType = stringValue(input, "recordType");
3185
+ if (!RECORD_TYPES.has(recordType)) {
3186
+ throw new SessionSchemaError("invalid session record type");
3187
+ }
3188
+ objectOf(input["payload"], "record payload");
3189
+ return input;
3190
+ }
3191
+ throw new SessionSchemaError("invalid session item kind");
3192
+ }
3193
+ function objectOf(value, label) {
3194
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3195
+ throw new SessionSchemaError(`${label} must be an object`);
3196
+ }
3197
+ return value;
3198
+ }
3199
+ function requireString(input, key) {
3200
+ const value = input[key];
3201
+ if (typeof value !== "string" || value === "") {
3202
+ throw new SessionSchemaError(`${key} must be a non-empty string`);
3203
+ }
3204
+ return value;
3205
+ }
3206
+ function stringValue(input, key) {
3207
+ const value = requireString(input, key);
3208
+ return value;
3209
+ }
3210
+ function requirePositiveInteger(input, key) {
3211
+ const value = input[key];
3212
+ if (!Number.isInteger(value) || typeof value !== "number" || value < 1) {
3213
+ throw new SessionSchemaError(`${key} must be a positive integer`);
3214
+ }
3215
+ return value;
3216
+ }
3217
+
3218
+ // ../../packages/session/session-store/dist/session-paths.js
3219
+ import { createHash as createHash3 } from "node:crypto";
3220
+ import { mkdirSync, realpathSync as realpathSync2 } from "node:fs";
3221
+ import { basename, join, resolve } from "node:path";
3222
+ function canonicalProjectRoot(projectRoot) {
3223
+ try {
3224
+ return realpathSync2.native(projectRoot);
3225
+ } catch {
3226
+ return resolve(projectRoot);
3227
+ }
3228
+ }
3229
+ function projectKeyForRoot(projectRoot) {
3230
+ const canonical = canonicalProjectRoot(projectRoot);
3231
+ const digest = createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
3232
+ return `${sanitizePathPart(basename(canonical) || "project")}-${digest}`;
3233
+ }
3234
+ function ensureSessionDirectory(sessionsRoot, projectKey) {
3235
+ const directory = join(sessionsRoot, projectKey);
3236
+ mkdirSync(directory, { recursive: true, mode: 448 });
3237
+ return directory;
3238
+ }
3239
+ function sessionPathForHeader(sessionsRoot, projectKey, createdAt, sessionId) {
3240
+ const safeCreatedAt = createdAt.replaceAll(":", "").replaceAll(".", "");
3241
+ return join(ensureSessionDirectory(sessionsRoot, projectKey), `${safeCreatedAt}_${sessionId}.jsonl`);
3242
+ }
3243
+ function sanitizePathPart(value) {
3244
+ const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
3245
+ return sanitized || "project";
3246
+ }
3247
+
3248
+ // ../../packages/session/session-store/dist/session-reader.js
3249
+ import { readFileSync } from "node:fs";
3250
+ var SessionCorruptError = class extends Error {
3251
+ path;
3252
+ line;
3253
+ constructor(path5, line2, message, options = {}) {
3254
+ super(`${path5}:${line2}: ${message}`, options.cause === void 0 ? void 0 : { cause: options.cause });
3255
+ this.name = "SessionCorruptError";
3256
+ this.path = path5;
3257
+ this.line = line2;
3258
+ }
3259
+ };
3260
+ function readSessionFile(path5) {
3261
+ const text = readFileSync(path5, "utf8");
3262
+ const endsWithNewline = text.endsWith("\n");
3263
+ const rawLines = text.split("\n");
3264
+ if (endsWithNewline) {
3265
+ rawLines.pop();
3266
+ }
3267
+ if (rawLines.length === 0 || rawLines[0] === "") {
3268
+ throw new SessionCorruptError(path5, 1, "missing session header");
3269
+ }
3270
+ const header = parseLine(path5, 1, rawLines[0], parseSessionHeader);
3271
+ const items = [];
3272
+ let ignoredTornTail = false;
3273
+ let previousSeq = 0;
3274
+ for (let index = 1; index < rawLines.length; index += 1) {
3275
+ const lineNumber = index + 1;
3276
+ const line2 = rawLines[index];
3277
+ if (line2.trim() === "") {
3278
+ continue;
3279
+ }
3280
+ try {
3281
+ const item = parseSessionItem(JSON.parse(line2), header.sessionId);
3282
+ if (item.seq !== previousSeq + 1) {
3283
+ throw new SessionSchemaError("session item seq must be contiguous");
3284
+ }
3285
+ previousSeq = item.seq;
3286
+ items.push(item);
3287
+ } catch (error) {
3288
+ if (!endsWithNewline && index === rawLines.length - 1) {
3289
+ ignoredTornTail = true;
3290
+ break;
3291
+ }
3292
+ throw corrupt(path5, lineNumber, error);
3293
+ }
3294
+ }
3295
+ return {
3296
+ header,
3297
+ items,
3298
+ lastSeq: previousSeq,
3299
+ ignoredTornTail,
3300
+ openToolCalls: findOpenToolCalls(items)
3301
+ };
3302
+ }
3303
+ function parseLine(path5, line2, raw, parse) {
3304
+ try {
3305
+ return parse(JSON.parse(raw));
3306
+ } catch (error) {
3307
+ throw corrupt(path5, line2, error);
3308
+ }
3309
+ }
3310
+ function corrupt(path5, line2, error) {
3311
+ if (error instanceof SessionCorruptError) {
3312
+ return error;
3313
+ }
3314
+ return new SessionCorruptError(path5, line2, error instanceof Error ? error.message : "invalid session JSONL", { cause: error });
3315
+ }
3316
+ function findOpenToolCalls(items) {
3317
+ const calls = /* @__PURE__ */ new Map();
3318
+ for (const item of items) {
3319
+ if (item.kind === "entry" && item.entryType === "assistant_message") {
3320
+ for (const block of item.payload.message.content) {
3321
+ if (block.type === "tool-call") {
3322
+ calls.set(block.call.id, {
3323
+ entryId: item.id,
3324
+ seq: item.seq,
3325
+ toolCallId: block.call.id,
3326
+ toolName: block.call.name
3327
+ });
3328
+ }
3329
+ }
3330
+ }
3331
+ if (item.kind === "entry" && item.entryType === "tool_result") {
3332
+ calls.delete(item.payload.message.toolCallId);
3333
+ }
3334
+ }
3335
+ return [...calls.values()];
3336
+ }
3337
+
3338
+ // ../../packages/session/session-store/dist/session-journal.js
3339
+ import { closeSync as closeSync2, existsSync as existsSync2, fsyncSync, openSync as openSync2, readFileSync as readFileSync2, unlinkSync, writeSync } from "node:fs";
3340
+ import { randomUUID as randomUUID5 } from "node:crypto";
3341
+ var SessionJournalLockError = class extends Error {
3342
+ constructor(message) {
3343
+ super(message);
3344
+ this.name = "SessionJournalLockError";
3345
+ }
3346
+ };
3347
+ var FileSessionJournal = class {
3348
+ header;
3349
+ path;
3350
+ #fd;
3351
+ #lockPath;
3352
+ #nextSeq;
3353
+ #closed = false;
3354
+ constructor(input) {
3355
+ this.header = input.header;
3356
+ this.path = input.path;
3357
+ this.#nextSeq = input.nextSeq;
3358
+ this.#fd = input.fd;
3359
+ this.#lockPath = `${input.path}.lock`;
3360
+ }
3361
+ get nextSeq() {
3362
+ return this.#nextSeq;
3363
+ }
3364
+ appendEntry(input) {
3365
+ const item = this.makeBase(input);
3366
+ const entry = {
3367
+ ...item,
3368
+ kind: "entry",
3369
+ entryType: input.entryType,
3370
+ payload: input.payload
3371
+ };
3372
+ this.writeItem(entry);
3373
+ if (entry.entryType === "compaction") {
3374
+ this.flush();
3375
+ }
3376
+ return entry;
3377
+ }
3378
+ appendRecord(input) {
3379
+ const record = {
3380
+ ...this.makeBase(input),
3381
+ kind: "record",
3382
+ recordType: input.recordType,
3383
+ payload: input.payload ?? {}
3384
+ };
3385
+ this.writeItem(record);
3386
+ if (record.recordType === "turn_finished" || record.recordType === "compaction_finished" || record.recordType === "session_closed") {
3387
+ this.flush();
3388
+ }
3389
+ return record;
3390
+ }
3391
+ flush() {
3392
+ if (!this.#closed) {
3393
+ fsyncSync(this.#fd);
3394
+ }
3395
+ }
3396
+ close() {
3397
+ if (this.#closed) {
3398
+ return;
3399
+ }
3400
+ this.flush();
3401
+ closeSync2(this.#fd);
3402
+ this.#closed = true;
3403
+ if (existsSync2(this.#lockPath)) {
3404
+ unlinkSync(this.#lockPath);
3405
+ }
3406
+ }
3407
+ makeBase(input) {
3408
+ return {
3409
+ schemaVersion: 1,
3410
+ sessionId: this.header.sessionId,
3411
+ seq: this.#nextSeq,
3412
+ id: randomUUID5().replaceAll("-", ""),
3413
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3414
+ ...input.taskId === void 0 ? {} : { taskId: input.taskId },
3415
+ ...input.turnId === void 0 ? {} : { turnId: input.turnId },
3416
+ ...input.copiedFrom === void 0 ? {} : { copiedFrom: input.copiedFrom }
3417
+ };
3418
+ }
3419
+ writeItem(item) {
3420
+ if (this.#closed) {
3421
+ throw new Error("session journal is closed");
3422
+ }
3423
+ writeLine(this.#fd, item);
3424
+ this.#nextSeq += 1;
3425
+ }
3426
+ };
3427
+ function createSessionJournal(options) {
3428
+ const header = options.header ?? makeHeader(options);
3429
+ const path5 = options.path ?? sessionPathForHeader(options.sessionsRoot, header.projectKey, header.createdAt, header.sessionId);
3430
+ const existing = existsSync2(path5);
3431
+ const lockPath = `${path5}.lock`;
3432
+ acquireLock(lockPath, header.sessionId);
3433
+ try {
3434
+ const fd = openSync2(path5, existing ? "a" : "wx", 384);
3435
+ if (!existing) {
3436
+ writeLine(fd, header);
3437
+ fsyncSync(fd);
3438
+ return new FileSessionJournal({ header, path: path5, nextSeq: 1, fd });
3439
+ }
3440
+ const replay = readSessionFile(path5);
3441
+ return new FileSessionJournal({
3442
+ header: replay.header,
3443
+ path: path5,
3444
+ nextSeq: replay.lastSeq + 1,
3445
+ fd
3446
+ });
3447
+ } catch (error) {
3448
+ releaseLock(lockPath);
3449
+ throw error;
3450
+ }
3451
+ }
3452
+ function makeHeader(options) {
3453
+ const projectRoot = canonicalProjectRoot(required(options.projectRoot, "projectRoot"));
3454
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
3455
+ return {
3456
+ schemaVersion: 1,
3457
+ type: "session_header",
3458
+ sessionId: randomUUID5().replaceAll("-", ""),
3459
+ createdAt,
3460
+ initialCwd: required(options.initialCwd, "initialCwd"),
3461
+ projectRoot,
3462
+ projectKey: projectKeyForRoot(projectRoot),
3463
+ appVersion: required(options.appVersion, "appVersion"),
3464
+ provider: required(options.provider, "provider"),
3465
+ model: required(options.model, "model"),
3466
+ reasoningEffort: options.reasoningEffort ?? "high",
3467
+ origin: options.origin ?? "new",
3468
+ ...options.parentSessionId === void 0 ? {} : { parentSessionId: options.parentSessionId },
3469
+ ...options.forkedFrom === void 0 ? {} : { forkedFrom: options.forkedFrom }
3470
+ };
3471
+ }
3472
+ function acquireLock(path5, sessionId) {
3473
+ try {
3474
+ const fd = openSync2(path5, "wx", 384);
3475
+ writeLine(fd, { pid: process.pid, sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
3476
+ closeSync2(fd);
3477
+ } catch (error) {
3478
+ if (!isLiveLock(path5)) {
3479
+ releaseLock(path5);
3480
+ const fd = openSync2(path5, "wx", 384);
3481
+ writeLine(fd, { pid: process.pid, sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
3482
+ closeSync2(fd);
3483
+ return;
3484
+ }
3485
+ throw new SessionJournalLockError(`session journal is locked: ${path5}`);
3486
+ }
3487
+ }
3488
+ function isLiveLock(path5) {
3489
+ if (!existsSync2(path5)) {
3490
+ return false;
3491
+ }
3492
+ try {
3493
+ const parsed = JSON.parse(readFileSync2(path5, "utf8"));
3494
+ if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid)) {
3495
+ return false;
3496
+ }
3497
+ process.kill(parsed.pid, 0);
3498
+ return true;
3499
+ } catch (error) {
3500
+ const code = error.code;
3501
+ return code === "EPERM";
3502
+ }
3503
+ }
3504
+ function releaseLock(path5) {
3505
+ if (existsSync2(path5)) {
3506
+ unlinkSync(path5);
3507
+ }
3508
+ }
3509
+ function writeLine(fd, value) {
3510
+ writeSync(fd, `${JSON.stringify(value)}
3511
+ `);
3512
+ }
3513
+ function required(value, label) {
3514
+ if (value === void 0 || value === "") {
3515
+ throw new Error(`${label} is required`);
3516
+ }
3517
+ return value;
3518
+ }
3519
+
3520
+ // ../../packages/session/session-store/dist/session-manager.js
3521
+ import { existsSync as existsSync3, readdirSync, statSync as statSync2 } from "node:fs";
3522
+ import { join as join2 } from "node:path";
3523
+
3524
+ // ../../packages/session/session-store/dist/session-tree.js
3525
+ function buildSessionTree(sessions) {
3526
+ const nodes = /* @__PURE__ */ new Map();
3527
+ for (const session of sessions) {
3528
+ nodes.set(session.sessionId, { session, children: [] });
3529
+ }
3530
+ const roots = [];
3531
+ for (const session of sessions) {
3532
+ const node = nodes.get(session.sessionId);
3533
+ const parentId = session.parentSessionId;
3534
+ const parent = parentId === void 0 ? void 0 : nodes.get(parentId);
3535
+ if (parent === void 0) {
3536
+ roots.push(node);
3537
+ } else {
3538
+ parent.children.push(node);
3539
+ }
3540
+ }
3541
+ const sortNodes = (items) => items.sort((left, right) => right.session.createdAt.localeCompare(left.session.createdAt)).map((item) => ({
3542
+ session: item.session,
3543
+ children: sortNodes(item.children)
3544
+ }));
3545
+ return sortNodes(roots);
3546
+ }
3547
+
3548
+ // ../../packages/session/session-store/dist/session-manager.js
3549
+ var SessionManager = class {
3550
+ #sessionsRoot;
3551
+ #appVersion;
3552
+ constructor(options) {
3553
+ this.#sessionsRoot = options.sessionsRoot;
3554
+ this.#appVersion = options.appVersion;
3555
+ }
3556
+ create(options) {
3557
+ const journal = createSessionJournal({
3558
+ sessionsRoot: this.#sessionsRoot,
3559
+ projectRoot: options.projectRoot,
3560
+ initialCwd: options.initialCwd,
3561
+ appVersion: this.#appVersion,
3562
+ provider: options.provider,
3563
+ model: options.model,
3564
+ reasoningEffort: options.reasoningEffort,
3565
+ origin: "new"
3566
+ });
3567
+ return {
3568
+ header: journal.header,
3569
+ journal,
3570
+ replay: {
3571
+ header: journal.header,
3572
+ items: [],
3573
+ lastSeq: 0,
3574
+ ignoredTornTail: false,
3575
+ openToolCalls: []
3576
+ }
3577
+ };
3578
+ }
3579
+ open(sessionId) {
3580
+ const summary = this.list().find((item) => item.sessionId === sessionId);
3581
+ if (summary === void 0 || summary.status === "corrupt") {
3582
+ throw new Error(`unknown session: ${sessionId}`);
3583
+ }
3584
+ const replay = readSessionFile(summary.path);
3585
+ const journal = createSessionJournal({
3586
+ sessionsRoot: this.#sessionsRoot,
3587
+ header: replay.header,
3588
+ path: summary.path
3589
+ });
3590
+ return { header: replay.header, journal, replay };
3591
+ }
3592
+ continueLatest(projectRoot) {
3593
+ const latest = this.list(projectRoot).find((item) => item.status !== "corrupt");
3594
+ return latest === void 0 ? null : this.open(latest.sessionId);
3595
+ }
3596
+ list(projectRoot) {
3597
+ const canonical = projectRoot === void 0 ? null : canonicalProjectRoot(projectRoot);
3598
+ return this.sessionPaths().map((path5) => summaryForPath(path5)).filter((summary) => canonical === null || summary.projectRoot === canonical).sort((left, right) => {
3599
+ const updated = right.updatedAt.localeCompare(left.updatedAt);
3600
+ return updated === 0 ? right.createdAt.localeCompare(left.createdAt) : updated;
3601
+ });
3602
+ }
3603
+ tree(projectRoot) {
3604
+ return buildSessionTree(this.list(projectRoot).filter((item) => item.status !== "corrupt"));
3605
+ }
3606
+ fork(options) {
3607
+ const parent = this.parentReplay(options.parentSessionId);
3608
+ const target = parent.items.find((item) => item.kind === "entry" && item.entryType === "user_message" && item.id === options.entryId);
3609
+ if (target === void 0) {
3610
+ throw new Error("fork target must be a user_message entry");
3611
+ }
3612
+ const child = createSessionJournal({
3613
+ sessionsRoot: this.#sessionsRoot,
3614
+ projectRoot: parent.header.projectRoot,
3615
+ initialCwd: parent.header.initialCwd,
3616
+ appVersion: this.#appVersion,
3617
+ provider: parent.header.provider,
3618
+ model: parent.header.model,
3619
+ reasoningEffort: parent.header.reasoningEffort,
3620
+ origin: "fork",
3621
+ parentSessionId: parent.header.sessionId,
3622
+ forkedFrom: {
3623
+ sessionId: parent.header.sessionId,
3624
+ seq: target.seq,
3625
+ entryId: target.id,
3626
+ mode: options.mode
3627
+ }
3628
+ });
3629
+ const throughSeq = options.mode === "before" ? target.seq - 1 : target.seq;
3630
+ copyEntries(child, parent.items.filter(isEntry).filter((item) => item.seq <= throughSeq));
3631
+ child.close();
3632
+ return {
3633
+ sessionId: child.header.sessionId,
3634
+ path: child.path,
3635
+ editorText: options.mode === "before" ? target.payload.message.content : ""
3636
+ };
3637
+ }
3638
+ clone(options) {
3639
+ const parent = this.parentReplay(options.parentSessionId);
3640
+ const lastEntry = [...parent.items].reverse().find((item) => item.kind === "entry");
3641
+ const child = createSessionJournal({
3642
+ sessionsRoot: this.#sessionsRoot,
3643
+ projectRoot: parent.header.projectRoot,
3644
+ initialCwd: parent.header.initialCwd,
3645
+ appVersion: this.#appVersion,
3646
+ provider: parent.header.provider,
3647
+ model: parent.header.model,
3648
+ reasoningEffort: parent.header.reasoningEffort,
3649
+ origin: "clone",
3650
+ parentSessionId: parent.header.sessionId,
3651
+ forkedFrom: {
3652
+ sessionId: parent.header.sessionId,
3653
+ ...lastEntry === void 0 ? {} : { seq: lastEntry.seq, entryId: lastEntry.id },
3654
+ mode: "clone"
3655
+ }
3656
+ });
3657
+ copyEntries(child, parent.items.filter(isEntry));
3658
+ child.close();
3659
+ return { sessionId: child.header.sessionId, path: child.path };
3660
+ }
3661
+ parentReplay(sessionId) {
3662
+ const summary = this.list().find((item) => item.sessionId === sessionId);
3663
+ if (summary === void 0 || summary.status === "corrupt") {
3664
+ throw new Error(`unknown session: ${sessionId}`);
3665
+ }
3666
+ return readSessionFile(summary.path);
3667
+ }
3668
+ sessionPaths() {
3669
+ if (!existsSync3(this.#sessionsRoot)) {
3670
+ return [];
3671
+ }
3672
+ const paths = [];
3673
+ const visit = (directory) => {
3674
+ for (const entry of readdirSync(directory)) {
3675
+ const absolute = join2(directory, entry);
3676
+ const stat = statSync2(absolute);
3677
+ if (stat.isDirectory()) {
3678
+ visit(absolute);
3679
+ } else if (absolute.endsWith(".jsonl")) {
3680
+ paths.push(absolute);
3681
+ }
3682
+ }
3683
+ };
3684
+ visit(this.#sessionsRoot);
3685
+ return paths.sort();
3686
+ }
3687
+ };
3688
+ function summaryForPath(path5) {
3689
+ try {
3690
+ const replay = readSessionFile(path5);
3691
+ const last = replay.items.at(-1);
3692
+ return {
3693
+ sessionId: replay.header.sessionId,
3694
+ path: path5,
3695
+ createdAt: replay.header.createdAt,
3696
+ updatedAt: last?.timestamp ?? replay.header.createdAt,
3697
+ projectRoot: replay.header.projectRoot,
3698
+ cwd: cwdFrom(replay),
3699
+ provider: replay.header.provider,
3700
+ model: replay.header.model,
3701
+ origin: replay.header.origin,
3702
+ ...replay.header.parentSessionId === void 0 ? {} : { parentSessionId: replay.header.parentSessionId },
3703
+ lastUserText: lastUserText(replay.items),
3704
+ status: last?.kind === "record" && last.recordType === "session_closed" ? "closed" : existsSync3(`${path5}.lock`) ? "open" : "interrupted"
3705
+ };
3706
+ } catch {
3707
+ return {
3708
+ sessionId: path5,
3709
+ path: path5,
3710
+ createdAt: "",
3711
+ updatedAt: "",
3712
+ projectRoot: "",
3713
+ cwd: "",
3714
+ provider: "",
3715
+ model: "",
3716
+ origin: "import",
3717
+ lastUserText: "",
3718
+ status: "corrupt"
3719
+ };
3720
+ }
3721
+ }
3722
+ function cwdFrom(replay) {
3723
+ for (const item of [...replay.items].reverse()) {
3724
+ if (item.kind === "entry" && item.entryType === "system_context") {
3725
+ return item.payload.cwd;
3726
+ }
3727
+ }
3728
+ return replay.header.initialCwd;
3729
+ }
3730
+ function lastUserText(items) {
3731
+ for (const item of [...items].reverse()) {
3732
+ if (item.kind === "entry" && item.entryType === "user_message") {
3733
+ return item.payload.message.content;
3734
+ }
3735
+ }
3736
+ return "";
3737
+ }
3738
+ function copyEntries(journal, entries) {
3739
+ for (const entry of entries) {
3740
+ const input = {
3741
+ entryType: entry.entryType,
3742
+ payload: entry.payload,
3743
+ ...entry.taskId === void 0 ? {} : { taskId: entry.taskId },
3744
+ ...entry.turnId === void 0 ? {} : { turnId: entry.turnId },
3745
+ copiedFrom: {
3746
+ sessionId: entry.sessionId,
3747
+ itemId: entry.id,
3748
+ seq: entry.seq
3749
+ }
3750
+ };
3751
+ journal.appendEntry(input);
3752
+ }
3753
+ }
3754
+ function isEntry(item) {
3755
+ return item.kind === "entry";
3756
+ }
3757
+
3758
+ // ../../packages/session/session-store/dist/transcript-projector.js
3759
+ function projectTranscript(entries) {
3760
+ const items = [];
3761
+ for (const entry of entries) {
3762
+ if (entry.entryType === "user_message") {
3763
+ items.push({ kind: "user", text: entry.payload.message.content });
3764
+ } else if (entry.entryType === "assistant_message") {
3765
+ const text = entry.payload.message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
3766
+ const reasoning = entry.payload.message.content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
3767
+ items.push({
3768
+ kind: "assistant",
3769
+ text,
3770
+ ...reasoning === "" ? {} : { reasoning }
3771
+ });
3772
+ } else if (entry.entryType === "tool_result") {
3773
+ items.push({
3774
+ kind: "tool",
3775
+ callId: entry.payload.message.toolCallId,
3776
+ name: entry.payload.message.toolName,
3777
+ subject: entry.payload.message.toolCallId,
3778
+ result: entry.payload.message.content,
3779
+ isError: entry.payload.message.isError
3780
+ });
3781
+ } else if (entry.entryType === "compaction") {
3782
+ items.push({
3783
+ kind: "notice",
3784
+ text: "Conversation context was compacted.",
3785
+ tone: "info"
3786
+ });
3787
+ }
3788
+ }
3789
+ return items;
3790
+ }
3791
+
2388
3792
  // ../../packages/llm/llm-pi-ai/dist/adapter.js
2389
3793
  import { clampThinkingLevel, ModelsError } from "@earendil-works/pi-ai";
2390
3794
  import { builtinModels } from "@earendil-works/pi-ai/providers/all";
@@ -2811,6 +4215,7 @@ var PiAiAdapter = class {
2811
4215
  const options = {
2812
4216
  ...request.cancelToken === void 0 ? {} : { signal: request.cancelToken.signal },
2813
4217
  ...request.temperature === void 0 ? {} : { temperature: request.temperature },
4218
+ ...request.maxOutputTokens === void 0 ? {} : { maxTokens: request.maxOutputTokens },
2814
4219
  ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
2815
4220
  ...thinkingOption(model, request.reasoningEffort ?? "high"),
2816
4221
  maxRetries: 0
@@ -3273,10 +4678,10 @@ function errorMessage6(error) {
3273
4678
  }
3274
4679
 
3275
4680
  // ../../packages/storage/local-config/dist/config.js
3276
- import { randomUUID as randomUUID4 } from "node:crypto";
3277
- import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
4681
+ import { randomUUID as randomUUID6 } from "node:crypto";
4682
+ import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
3278
4683
  import { homedir } from "node:os";
3279
- import { basename, dirname, join } from "node:path";
4684
+ import { basename as basename2, dirname, join as join3 } from "node:path";
3280
4685
  function isPlainObject(value) {
3281
4686
  return typeof value === "object" && value !== null && !Array.isArray(value);
3282
4687
  }
@@ -3288,7 +4693,7 @@ function expandUser(input) {
3288
4693
  return homedir();
3289
4694
  }
3290
4695
  if (input.startsWith("~/")) {
3291
- return join(homedir(), input.slice(2));
4696
+ return join3(homedir(), input.slice(2));
3292
4697
  }
3293
4698
  return input;
3294
4699
  }
@@ -3298,8 +4703,8 @@ function defaultConfigPath(environ = process.env) {
3298
4703
  return expandUser(explicit);
3299
4704
  }
3300
4705
  const configHome = environ["XDG_CONFIG_HOME"];
3301
- const root = configHome ? expandUser(configHome) : join(homedir(), ".config");
3302
- return join(root, "laohuang", "config.json");
4706
+ const root = configHome ? expandUser(configHome) : join3(homedir(), ".config");
4707
+ return join3(root, "laohuang", "config.json");
3303
4708
  }
3304
4709
  var ConfigManager = class {
3305
4710
  path;
@@ -3373,7 +4778,7 @@ var ConfigManager = class {
3373
4778
  });
3374
4779
  }
3375
4780
  readDocument(options = {}) {
3376
- if (!existsSync2(this.path)) {
4781
+ if (!existsSync4(this.path)) {
3377
4782
  if (options.optional) {
3378
4783
  return {};
3379
4784
  }
@@ -3381,7 +4786,7 @@ var ConfigManager = class {
3381
4786
  }
3382
4787
  let value;
3383
4788
  try {
3384
- value = JSON.parse(readFileSync(this.path, "utf8"));
4789
+ value = JSON.parse(readFileSync3(this.path, "utf8"));
3385
4790
  } catch (error) {
3386
4791
  throw new Error(`Cannot read configuration: ${errorMessage7(error)}`);
3387
4792
  }
@@ -3393,9 +4798,9 @@ var ConfigManager = class {
3393
4798
  }
3394
4799
  writeDocument(document) {
3395
4800
  const parent = dirname(this.path);
3396
- mkdirSync(parent, { recursive: true, mode: 448 });
4801
+ mkdirSync2(parent, { recursive: true, mode: 448 });
3397
4802
  const content = JSON.stringify(document, null, 2) + "\n";
3398
- const temporary = join(parent, `.${basename(this.path)}.${process.pid}.${randomUUID4()}.tmp`);
4803
+ const temporary = join3(parent, `.${basename2(this.path)}.${process.pid}.${randomUUID6()}.tmp`);
3399
4804
  writeFileSync(temporary, content, { encoding: "utf8", mode: 384 });
3400
4805
  chmodSync(temporary, 384);
3401
4806
  renameSync(temporary, this.path);
@@ -3437,9 +4842,9 @@ function validateDocument(document) {
3437
4842
  }
3438
4843
 
3439
4844
  // ../../packages/storage/local-config/dist/credentials.js
3440
- import { randomUUID as randomUUID5 } from "node:crypto";
3441
- import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
3442
- import { basename as basename2, dirname as dirname2, join as join2 } from "node:path";
4845
+ import { randomUUID as randomUUID7 } from "node:crypto";
4846
+ import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
4847
+ import { basename as basename3, dirname as dirname2, join as join4 } from "node:path";
3443
4848
  function isPlainObject2(value) {
3444
4849
  return typeof value === "object" && value !== null && !Array.isArray(value);
3445
4850
  }
@@ -3563,12 +4968,12 @@ var CredentialStore = class {
3563
4968
  await operation;
3564
4969
  }
3565
4970
  readDocument() {
3566
- if (!existsSync3(this.path)) {
4971
+ if (!existsSync5(this.path)) {
3567
4972
  return { version: 2, providers: {} };
3568
4973
  }
3569
4974
  let document;
3570
4975
  try {
3571
- document = JSON.parse(readFileSync2(this.path, "utf8"));
4976
+ document = JSON.parse(readFileSync4(this.path, "utf8"));
3572
4977
  } catch (error) {
3573
4978
  throw new Error(`Cannot read credentials: ${errorMessage8(error)}`);
3574
4979
  }
@@ -3594,8 +4999,8 @@ var CredentialStore = class {
3594
4999
  }
3595
5000
  writeDocument(document) {
3596
5001
  const parent = dirname2(this.path);
3597
- const parentExisted = existsSync3(parent);
3598
- mkdirSync2(parent, { recursive: true, mode: 448 });
5002
+ const parentExisted = existsSync5(parent);
5003
+ mkdirSync3(parent, { recursive: true, mode: 448 });
3599
5004
  if (!parentExisted) {
3600
5005
  chmodSync2(parent, 448);
3601
5006
  }
@@ -3603,7 +5008,7 @@ var CredentialStore = class {
3603
5008
  version: 2,
3604
5009
  providers: document.providers
3605
5010
  }, null, 2) + "\n";
3606
- const temporary = join2(parent, `.${basename2(this.path)}.${process.pid}.${randomUUID5()}.tmp`);
5011
+ const temporary = join4(parent, `.${basename3(this.path)}.${process.pid}.${randomUUID7()}.tmp`);
3607
5012
  writeFileSync2(temporary, content, { encoding: "utf8", mode: 384 });
3608
5013
  chmodSync2(temporary, 384);
3609
5014
  renameSync2(temporary, this.path);
@@ -3618,9 +5023,9 @@ function normalizeStoredCredential(credential) {
3618
5023
  }
3619
5024
 
3620
5025
  // ../../packages/storage/local-config/dist/model-catalog-store.js
3621
- import { randomUUID as randomUUID6 } from "node:crypto";
3622
- import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
3623
- import { basename as basename3, dirname as dirname3, join as join3 } from "node:path";
5026
+ import { randomUUID as randomUUID8 } from "node:crypto";
5027
+ import { chmodSync as chmodSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
5028
+ import { basename as basename4, dirname as dirname3, join as join5 } from "node:path";
3624
5029
  function isPlainObject3(value) {
3625
5030
  return typeof value === "object" && value !== null && !Array.isArray(value);
3626
5031
  }
@@ -3651,12 +5056,12 @@ var ModelCatalogStore = class {
3651
5056
  }
3652
5057
  }
3653
5058
  readDocument() {
3654
- if (!existsSync4(this.path)) {
5059
+ if (!existsSync6(this.path)) {
3655
5060
  return { version: 1, providers: {} };
3656
5061
  }
3657
5062
  let document;
3658
5063
  try {
3659
- document = JSON.parse(readFileSync3(this.path, "utf8"));
5064
+ document = JSON.parse(readFileSync5(this.path, "utf8"));
3660
5065
  } catch (error) {
3661
5066
  throw new Error(`Cannot read model catalog: ${errorMessage9(error)}`);
3662
5067
  }
@@ -3678,13 +5083,13 @@ var ModelCatalogStore = class {
3678
5083
  }
3679
5084
  writeDocument(document) {
3680
5085
  const parent = dirname3(this.path);
3681
- const parentExisted = existsSync4(parent);
3682
- mkdirSync3(parent, { recursive: true, mode: 448 });
5086
+ const parentExisted = existsSync6(parent);
5087
+ mkdirSync4(parent, { recursive: true, mode: 448 });
3683
5088
  if (!parentExisted) {
3684
5089
  chmodSync3(parent, 448);
3685
5090
  }
3686
5091
  const content = JSON.stringify(document, null, 2) + "\n";
3687
- const temporary = join3(parent, `.${basename3(this.path)}.${process.pid}.${randomUUID6()}.tmp`);
5092
+ const temporary = join5(parent, `.${basename4(this.path)}.${process.pid}.${randomUUID8()}.tmp`);
3688
5093
  writeFileSync3(temporary, content, { encoding: "utf8", mode: 384 });
3689
5094
  chmodSync3(temporary, 384);
3690
5095
  renameSync3(temporary, this.path);
@@ -3725,7 +5130,7 @@ function normalizeEntry(providerId, entry) {
3725
5130
  return normalized;
3726
5131
  }
3727
5132
 
3728
- // ../../packages/core/session-runtime/dist/core/task-lifecycle.js
5133
+ // ../../packages/session/session-runtime/dist/core/task-lifecycle.js
3729
5134
  var TaskState = {
3730
5135
  RunningModel: "running_model",
3731
5136
  RunningTools: "running_tools",
@@ -3982,7 +5387,7 @@ var TaskLifecycle = class {
3982
5387
  }
3983
5388
  };
3984
5389
 
3985
- // ../../packages/core/session-runtime/dist/core/agent-turn-loop.js
5390
+ // ../../packages/session/session-runtime/dist/core/agent-turn-loop.js
3986
5391
  var AgentTurnLoop = class {
3987
5392
  lifecycle;
3988
5393
  bridge;
@@ -4047,7 +5452,7 @@ var AgentTurnLoop = class {
4047
5452
  }
4048
5453
  };
4049
5454
 
4050
- // ../../packages/core/session-runtime/dist/core/human-intent-router.js
5455
+ // ../../packages/session/session-runtime/dist/core/human-intent-router.js
4051
5456
  function splitHumanCommand(text) {
4052
5457
  const tokens = [];
4053
5458
  let current = "";
@@ -4137,7 +5542,7 @@ function routeHumanIntent(intent, state) {
4137
5542
  }
4138
5543
  }
4139
5544
 
4140
- // ../../packages/core/session-runtime/dist/routing.js
5545
+ // ../../packages/session/session-runtime/dist/routing.js
4141
5546
  function estimateTokens(text) {
4142
5547
  if (!text) {
4143
5548
  return 0;
@@ -4645,7 +6050,7 @@ ${content}`);
4645
6050
  return "Please handle all of these pending messages in one response:\n\n" + parts.join("\n\n");
4646
6051
  }
4647
6052
 
4648
- // ../../packages/core/session-runtime/dist/core/queue-dispatcher.js
6053
+ // ../../packages/session/session-runtime/dist/core/queue-dispatcher.js
4649
6054
  var QueueDispatchStatus = {
4650
6055
  StartNow: "start_now",
4651
6056
  Pending: "pending",
@@ -4759,8 +6164,8 @@ function makeQueueDispatchResult(status, actionId2, options = {}) {
4759
6164
  };
4760
6165
  }
4761
6166
 
4762
- // ../../packages/core/session-runtime/dist/session.js
4763
- import { randomUUID as randomUUID7 } from "node:crypto";
6167
+ // ../../packages/session/session-runtime/dist/session.js
6168
+ import { randomUUID as randomUUID9 } from "node:crypto";
4764
6169
  var SessionState = {
4765
6170
  Idle: "idle",
4766
6171
  Running: "running",
@@ -4872,7 +6277,7 @@ var TaskContext = class {
4872
6277
  }
4873
6278
  };
4874
6279
  var AgentSession = class {
4875
- sessionId;
6280
+ #sessionId;
4876
6281
  eventBus;
4877
6282
  taskLifecycle;
4878
6283
  taskRegistry;
@@ -4894,7 +6299,7 @@ var AgentSession = class {
4894
6299
  #idleWaiters = /* @__PURE__ */ new Set();
4895
6300
  #finalizePromise = null;
4896
6301
  constructor(runner, options = {}) {
4897
- this.sessionId = options.sessionId ?? randomUUID7().replaceAll("-", "");
6302
+ this.#sessionId = options.sessionId ?? randomUUID9().replaceAll("-", "");
4898
6303
  this.eventBus = options.eventBus ?? new EventBus();
4899
6304
  this.taskRegistry = options.taskRegistry ?? new TaskRegistry();
4900
6305
  this.queueDispatcher = new QueueDispatcher();
@@ -4931,6 +6336,12 @@ var AgentSession = class {
4931
6336
  payload: {}
4932
6337
  });
4933
6338
  }
6339
+ get sessionId() {
6340
+ return this.#sessionId;
6341
+ }
6342
+ setSessionId(sessionId) {
6343
+ this.#sessionId = sessionId;
6344
+ }
4934
6345
  get state() {
4935
6346
  return this.#state;
4936
6347
  }
@@ -5198,20 +6609,20 @@ var AgentSession = class {
5198
6609
  if (timeoutMs !== void 0 && timeoutMs <= 0) {
5199
6610
  return Promise.resolve(false);
5200
6611
  }
5201
- return new Promise((resolve) => {
6612
+ return new Promise((resolve2) => {
5202
6613
  let timer;
5203
6614
  const onIdle = () => {
5204
6615
  if (timer !== void 0) {
5205
6616
  clearTimeout(timer);
5206
6617
  }
5207
6618
  this.#idleWaiters.delete(onIdle);
5208
- resolve(true);
6619
+ resolve2(true);
5209
6620
  };
5210
6621
  this.#idleWaiters.add(onIdle);
5211
6622
  if (timeoutMs !== void 0) {
5212
6623
  timer = setTimeout(() => {
5213
6624
  this.#idleWaiters.delete(onIdle);
5214
- resolve(false);
6625
+ resolve2(false);
5215
6626
  }, timeoutMs);
5216
6627
  timer.unref();
5217
6628
  }
@@ -5326,7 +6737,7 @@ var AgentSession = class {
5326
6737
  });
5327
6738
  }
5328
6739
  startTask(content, inputEventId) {
5329
- const taskId = randomUUID7().replaceAll("-", "");
6740
+ const taskId = randomUUID9().replaceAll("-", "");
5330
6741
  this.taskLifecycle.createTask(taskId, { correlationId: inputEventId });
5331
6742
  this.setIdle(false);
5332
6743
  this.setSessionState(SessionState.Running);
@@ -5525,13 +6936,108 @@ var AgentSession = class {
5525
6936
  if (value) {
5526
6937
  const waiters = [...this.#idleWaiters];
5527
6938
  this.#idleWaiters.clear();
5528
- for (const resolve of waiters) {
5529
- resolve();
6939
+ for (const resolve2 of waiters) {
6940
+ resolve2();
5530
6941
  }
5531
6942
  }
5532
6943
  }
5533
6944
  };
5534
6945
 
6946
+ // ../../packages/session/session-runtime/dist/session-recorder.js
6947
+ var SessionRecorder = class {
6948
+ #journal;
6949
+ #unsubscribe;
6950
+ #projector = new EventProjector();
6951
+ constructor(options) {
6952
+ this.#journal = options.journal;
6953
+ this.#unsubscribe = options.eventBus.subscribe((event) => {
6954
+ this.recordEvent(event);
6955
+ });
6956
+ }
6957
+ recordEvent(event) {
6958
+ const records = [];
6959
+ for (const record of this.recordsFor(event)) {
6960
+ const journal = this.journal();
6961
+ if (journal === null) {
6962
+ continue;
6963
+ }
6964
+ records.push(journal.appendRecord(record));
6965
+ }
6966
+ return records;
6967
+ }
6968
+ async close() {
6969
+ await this.#unsubscribe();
6970
+ }
6971
+ journal() {
6972
+ return typeof this.#journal === "function" ? this.#journal() : this.#journal;
6973
+ }
6974
+ recordsFor(event) {
6975
+ const payload = this.projectPayload(event);
6976
+ const base = {
6977
+ taskId: event.task_id ?? void 0,
6978
+ turnId: event.correlation_id ?? void 0
6979
+ };
6980
+ const record = (recordType, extra = {}) => ({
6981
+ ...base,
6982
+ recordType,
6983
+ payload: {
6984
+ eventId: event.event_id,
6985
+ eventKind: event.kind,
6986
+ eventSequence: event.sequence,
6987
+ ...payload,
6988
+ ...extra
6989
+ }
6990
+ });
6991
+ switch (event.kind) {
6992
+ case EventKind.TaskStarted:
6993
+ return [record("turn_started")];
6994
+ case EventKind.TaskCompleted:
6995
+ return [record("turn_finished", { status: "completed" })];
6996
+ case EventKind.TaskFailed:
6997
+ return [
6998
+ record("error", { status: "failed" }),
6999
+ record("turn_finished", { status: "failed" })
7000
+ ];
7001
+ case EventKind.TaskCancelled:
7002
+ return [
7003
+ record("cancelled"),
7004
+ record("turn_finished", { status: "cancelled" })
7005
+ ];
7006
+ case EventKind.ModelRequestStarted:
7007
+ return [record("model_request")];
7008
+ case EventKind.ModelResponseCommitted:
7009
+ case EventKind.ModelResponseSummary: {
7010
+ const usage = payload["usage"];
7011
+ return usage === void 0 ? [record("model_response")] : [record("model_response"), record("usage", { usage })];
7012
+ }
7013
+ case EventKind.ModelRequestFailed:
7014
+ case EventKind.ModelResponseAborted:
7015
+ return [record("error")];
7016
+ case EventKind.ToolStarted:
7017
+ return [record("tool_started")];
7018
+ case EventKind.ToolFinished:
7019
+ return [record("tool_finished")];
7020
+ case EventKind.InputPending:
7021
+ case EventKind.InputHeld:
7022
+ return [record("queue_enqueued")];
7023
+ case EventKind.ModelSwitched:
7024
+ return [
7025
+ record("provider_changed", { provider: payload["provider"] ?? null }),
7026
+ record("model_changed", { model: payload["model"] ?? null })
7027
+ ];
7028
+ default:
7029
+ return [];
7030
+ }
7031
+ }
7032
+ projectPayload(event) {
7033
+ const projected = this.#projector.project(event, "log").payload;
7034
+ if (projected !== null && typeof projected === "object" && !Array.isArray(projected)) {
7035
+ return projected;
7036
+ }
7037
+ return { value: projected };
7038
+ }
7039
+ };
7040
+
5535
7041
  // ../../packages/terminal/tui/dist/capabilities.js
5536
7042
  var DEFAULT_RUNTIME_CAPABILITIES = {
5537
7043
  streaming: true,
@@ -7387,7 +8893,9 @@ function createUIState() {
7387
8893
  model: "",
7388
8894
  totalTokens: 0,
7389
8895
  inputTokens: 0,
7390
- outputTokens: 0
8896
+ outputTokens: 0,
8897
+ contextTokens: 0,
8898
+ contextWindow: 0
7391
8899
  };
7392
8900
  }
7393
8901
  function createUpdate(kind, fields = {}) {
@@ -7447,6 +8955,7 @@ var UIEventReducer = class _UIEventReducer {
7447
8955
  }
7448
8956
  if (kind === "model.request_started") {
7449
8957
  this.state.sessionState = "RUNNING_MODEL";
8958
+ this.#updateContextUsage(payload);
7450
8959
  this.state.activeResponse = {
7451
8960
  requestId: correlationId,
7452
8961
  text: "",
@@ -7537,6 +9046,7 @@ var UIEventReducer = class _UIEventReducer {
7537
9046
  if (kind === "model.switched") {
7538
9047
  this.state.provider = String("provider" in payload ? payload.provider : this.state.provider);
7539
9048
  this.state.model = String("model" in payload ? payload.model : this.state.model);
9049
+ this.#updateContextUsage(payload);
7540
9050
  return createUpdate(kind, { payload });
7541
9051
  }
7542
9052
  return null;
@@ -7549,6 +9059,14 @@ var UIEventReducer = class _UIEventReducer {
7549
9059
  this.state.heldCount = toInt(payload.held_count);
7550
9060
  }
7551
9061
  }
9062
+ #updateContextUsage(payload) {
9063
+ if ("context_tokens" in payload) {
9064
+ this.state.contextTokens = toInt(payload.context_tokens);
9065
+ }
9066
+ if ("context_window" in payload) {
9067
+ this.state.contextWindow = toInt(payload.context_window);
9068
+ }
9069
+ }
7552
9070
  static #kindOf(event) {
7553
9071
  const raw = "kind" in event ? event.kind : event.type ?? "";
7554
9072
  return String(unwrapValue(raw) ?? "");
@@ -7728,19 +9246,20 @@ var DisplayPolicy = class {
7728
9246
 
7729
9247
  // ../../packages/terminal/tui/dist/tui/transcript-store.js
7730
9248
  function createUserBlock(key, text) {
7731
- return { kind: "user", key, text, mutable: false };
9249
+ return { kind: "user", key, text, mutable: false, revision: 0 };
7732
9250
  }
7733
9251
  function createAssistantBlock(key, text = "", mutable = true) {
7734
- return { kind: "assistant", key, text, mutable };
9252
+ return { kind: "assistant", key, text, mutable, revision: 0 };
7735
9253
  }
7736
9254
  function createThinkingBlock(key, text = "", mutable = true) {
7737
- return { kind: "thinking", key, text, mutable };
9255
+ return { kind: "thinking", key, text, mutable, revision: 0 };
7738
9256
  }
7739
9257
  function createToolBlock(key, fields) {
7740
9258
  return {
7741
9259
  kind: "tool",
7742
9260
  key,
7743
9261
  mutable: true,
9262
+ revision: 0,
7744
9263
  name: redactToolText(fields.name),
7745
9264
  subject: redactToolText(fields.subject),
7746
9265
  status: fields.status,
@@ -7752,10 +9271,10 @@ function createToolBlock(key, fields) {
7752
9271
  };
7753
9272
  }
7754
9273
  function createNoticeBlock(key, text, tone = "info") {
7755
- return { kind: "notice", key, text, tone, mutable: false };
9274
+ return { kind: "notice", key, text, tone, mutable: false, revision: 0 };
7756
9275
  }
7757
9276
  function createWelcomeBlock(title, details) {
7758
- return { kind: "welcome", key: "welcome", title, details, mutable: false };
9277
+ return { kind: "welcome", key: "welcome", title, details, mutable: false, revision: 0 };
7759
9278
  }
7760
9279
  function isRecord4(value) {
7761
9280
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -7772,6 +9291,9 @@ function noticeTone(style) {
7772
9291
  return "dim";
7773
9292
  return "info";
7774
9293
  }
9294
+ function touchBlock(block) {
9295
+ block.revision = (block.revision ?? 0) + 1;
9296
+ }
7775
9297
  var TranscriptStore = class {
7776
9298
  #blocks = [];
7777
9299
  #byCorrelation = /* @__PURE__ */ new Map();
@@ -7796,6 +9318,34 @@ var TranscriptStore = class {
7796
9318
  if (block.key)
7797
9319
  this.#byCorrelation.set(`${block.kind}:${block.key}`, block);
7798
9320
  }
9321
+ replace(items) {
9322
+ this.#blocks.splice(0);
9323
+ this.#byCorrelation.clear();
9324
+ this.#nextBlockId = 0;
9325
+ for (const item of items) {
9326
+ if (item.kind === "user") {
9327
+ this.append(createUserBlock(this.newBlockId(), item.text));
9328
+ } else if (item.kind === "assistant") {
9329
+ if (item.reasoning !== void 0 && item.reasoning !== "") {
9330
+ this.append(createThinkingBlock(this.newBlockId(), item.reasoning, false));
9331
+ }
9332
+ this.append(createAssistantBlock(this.newBlockId(), item.text, false));
9333
+ } else if (item.kind === "tool") {
9334
+ const block = createToolBlock(item.callId, {
9335
+ name: item.name,
9336
+ subject: item.subject,
9337
+ status: item.isError ? "failed" : "completed",
9338
+ expanded: this.#toolOutputExpanded
9339
+ });
9340
+ block.stdout = item.isError ? "" : item.result;
9341
+ block.stderr = item.isError ? item.result : "";
9342
+ block.mutable = false;
9343
+ this.append(block);
9344
+ } else {
9345
+ this.append(createNoticeBlock(this.newBlockId(), item.text, item.tone));
9346
+ }
9347
+ }
9348
+ }
7799
9349
  blockFor(kind, key) {
7800
9350
  const block = this.#byCorrelation.get(`${kind}:${key}`);
7801
9351
  if (block === void 0)
@@ -7815,14 +9365,18 @@ var TranscriptStore = class {
7815
9365
  if (kind === "model.text_delta") {
7816
9366
  this.freezeThinking();
7817
9367
  const item = this.#getOrCreateAssistant(correlationId);
7818
- if (item.mutable)
9368
+ if (item.mutable) {
7819
9369
  item.text += update.text;
9370
+ touchBlock(item);
9371
+ }
7820
9372
  return;
7821
9373
  }
7822
9374
  if (kind === "model.reasoning_delta") {
7823
9375
  const item = this.#getOrCreateThinking(correlationId);
7824
- if (item.mutable)
9376
+ if (item.mutable) {
7825
9377
  item.text += update.text;
9378
+ touchBlock(item);
9379
+ }
7826
9380
  return;
7827
9381
  }
7828
9382
  if (["model.response_committed", "model.response_aborted", "model.request_failed"].includes(kind)) {
@@ -7850,8 +9404,10 @@ var TranscriptStore = class {
7850
9404
  if (item?.kind === "tool") {
7851
9405
  if (update.stream === "stdout") {
7852
9406
  item.stdout = (item.stdout + this.#toolOutputRedactor.redact(correlationId, "stdout", update.text)).slice(-this.#toolBufferLimit);
9407
+ touchBlock(item);
7853
9408
  } else if (update.stream === "stderr") {
7854
9409
  item.stderr = (item.stderr + this.#toolOutputRedactor.redact(correlationId, "stderr", update.text)).slice(-this.#toolBufferLimit);
9410
+ touchBlock(item);
7855
9411
  }
7856
9412
  }
7857
9413
  return;
@@ -7862,6 +9418,7 @@ var TranscriptStore = class {
7862
9418
  item.exitCode = typeof update.payload.exit_code === "number" ? Math.trunc(update.payload.exit_code) : null;
7863
9419
  item.durationMs = typeof update.payload.duration_ms === "number" ? Math.trunc(update.payload.duration_ms) : null;
7864
9420
  item.mutable = false;
9421
+ touchBlock(item);
7865
9422
  this.#toolOutputRedactor.clear(correlationId);
7866
9423
  return;
7867
9424
  }
@@ -7882,8 +9439,10 @@ var TranscriptStore = class {
7882
9439
  setToolOutputExpanded(expanded) {
7883
9440
  this.#toolOutputExpanded = expanded;
7884
9441
  for (const block of this.#blocks) {
7885
- if (block.kind === "tool")
9442
+ if (block.kind === "tool" && block.expanded !== expanded) {
7886
9443
  block.expanded = expanded;
9444
+ touchBlock(block);
9445
+ }
7887
9446
  }
7888
9447
  }
7889
9448
  toolOutputExpanded() {
@@ -8042,13 +9601,11 @@ var Composer = class {
8042
9601
  // ../../packages/terminal/tui/dist/tui/components/status-line.js
8043
9602
  var StatusLine = class {
8044
9603
  #state;
8045
- #cwd;
8046
9604
  #provider;
8047
9605
  #model;
8048
9606
  #effort;
8049
9607
  constructor(options) {
8050
9608
  this.#state = options.state;
8051
- this.#cwd = options.cwd ?? null;
8052
9609
  this.#provider = options.provider ?? null;
8053
9610
  this.#model = options.model ?? null;
8054
9611
  this.#effort = options.effort ?? null;
@@ -8056,15 +9613,11 @@ var StatusLine = class {
8056
9613
  render(context) {
8057
9614
  const width = Math.max(1, context.width);
8058
9615
  const queue = this.#state.pendingCount || this.#state.heldCount ? `queue ${this.#state.pendingCount} pending / ${this.#state.heldCount} held` : null;
8059
- const tokens = this.#state.totalTokens ? `\u2191${this.#state.inputTokens} \u2193${this.#state.outputTokens}` : null;
8060
- const cwd = this.#cwd === null ? null : clippedCwd(this.#cwd);
9616
+ const contextUsage = contextUsageLabel(this.#state.contextTokens, this.#state.contextWindow);
8061
9617
  const right = this.#rightLine();
8062
- let leftItems = [cwd, queue, tokens].filter((value) => value !== null);
8063
- if (!fits(leftItems, right, width) && cwd !== null) {
8064
- leftItems = leftItems.filter((value) => value !== cwd);
8065
- }
8066
- if (!fits(leftItems, right, width) && tokens !== null) {
8067
- leftItems = leftItems.filter((value) => value !== tokens);
9618
+ let leftItems = [contextUsage, queue].filter((value) => value !== null);
9619
+ if (!fits(leftItems, right, width) && queue !== null) {
9620
+ leftItems = leftItems.filter((value) => value !== queue);
8068
9621
  }
8069
9622
  const left = metadataLine(leftItems);
8070
9623
  if (lineText(left).length === 0 && lineText(right).length === 0) {
@@ -8097,8 +9650,29 @@ var StatusLine = class {
8097
9650
  return line(...spans);
8098
9651
  }
8099
9652
  };
8100
- function clippedCwd(path5) {
8101
- return path5.length <= 40 ? path5 : `\u2026${path5.slice(-39)}`;
9653
+ function contextUsageLabel(tokens, contextWindow) {
9654
+ const window = normalizedCount(contextWindow);
9655
+ if (window <= 0) {
9656
+ return null;
9657
+ }
9658
+ const used = normalizedCount(tokens);
9659
+ const percent = Math.max(0, Math.min(100, Math.floor(used / window * 100)));
9660
+ return `context: ${percent}% (${formatCompactCount(used)}/${formatCompactCount(window)})`;
9661
+ }
9662
+ function normalizedCount(value) {
9663
+ return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
9664
+ }
9665
+ function formatCompactCount(value) {
9666
+ if (value >= 1e6) {
9667
+ return `${formatScaled(value / 1e6)}M`;
9668
+ }
9669
+ if (value >= 1e3) {
9670
+ return `${formatScaled(value / 1e3)}K`;
9671
+ }
9672
+ return String(value);
9673
+ }
9674
+ function formatScaled(value) {
9675
+ return value.toFixed(1).replace(/\.0$/u, "");
8102
9676
  }
8103
9677
  function metadataLine(items) {
8104
9678
  return line(...items.flatMap((value, index) => [
@@ -8177,8 +9751,7 @@ function renderBlocks(text, layoutWidth) {
8177
9751
  const fence = trimmed.slice(0, 3);
8178
9752
  index += 1;
8179
9753
  const codeStyle = {
8180
- foreground: "code",
8181
- background: "card"
9754
+ foreground: "code"
8182
9755
  };
8183
9756
  const codeLines = [];
8184
9757
  while (index < lines.length) {
@@ -8424,7 +9997,7 @@ var INLINE_PATTERNS = [
8424
9997
  {
8425
9998
  // Inline code: no further parsing inside backticks.
8426
9999
  pattern: /`([^`]+)`/,
8427
- style: () => ({ foreground: "code", background: "card" })
10000
+ style: () => ({ foreground: "code" })
8428
10001
  },
8429
10002
  {
8430
10003
  pattern: /\*\*([^*]+)\*\*/,
@@ -8790,12 +10363,17 @@ var Box = class {
8790
10363
  const child = this.#child.render({ ...context, width: Math.max(1, width - this.#paddingX * 2) });
8791
10364
  const blank = () => line(span(" ".repeat(width), backgroundStyle2(this.#background)));
8792
10365
  const lines = child.lines.map((value) => this.#renderLine(value, width));
10366
+ const cursor = child.cursor === void 0 ? void 0 : {
10367
+ row: child.cursor.row + this.#paddingY,
10368
+ column: child.cursor.column + this.#paddingX
10369
+ };
8793
10370
  return {
8794
10371
  lines: [
8795
10372
  ...Array.from({ length: this.#paddingY }, blank),
8796
10373
  ...lines,
8797
10374
  ...Array.from({ length: this.#paddingY }, blank)
8798
- ]
10375
+ ],
10376
+ ...cursor === void 0 ? {} : { cursor }
8799
10377
  };
8800
10378
  }
8801
10379
  invalidate() {
@@ -8826,7 +10404,7 @@ var ToolMessage = class {
8826
10404
  const subject = redactToolText(options.subject);
8827
10405
  const stdout = redactToolText(options.stdout);
8828
10406
  const stderr = redactToolText(options.stderr);
8829
- const tone = options.status === "running" ? { background: "tool_pending_bg", title: "accent" } : options.status === "completed" ? { background: "tool_success_bg", title: "success" } : { background: "tool_error_bg", title: "warning" };
10407
+ const tone = options.status === "running" ? { title: "accent" } : options.status === "completed" ? { title: "success" } : { title: "warning" };
8830
10408
  const title = [{ text: `\u25CF ${name || "tool"}`, style: { foreground: tone.title } }];
8831
10409
  if (subject) {
8832
10410
  title.push({
@@ -8844,7 +10422,6 @@ var ToolMessage = class {
8844
10422
  ].filter(Boolean).join(" \xB7 ");
8845
10423
  const output = options.expanded ? [stderr && clip(stderr, 1200), stdout && clip(stdout, 1200)].filter(Boolean) : [];
8846
10424
  this.#content = new Box({
8847
- background: tone.background,
8848
10425
  child: new Text({
8849
10426
  spans: [...title, { text: `
8850
10427
  ${[metadata, ...output].join("\n")}` }]
@@ -9041,6 +10618,7 @@ function counterLine(label, value) {
9041
10618
  // ../../packages/terminal/tui/dist/tui/components/transcript.js
9042
10619
  var Transcript = class {
9043
10620
  #blocks;
10621
+ #cache = /* @__PURE__ */ new Map();
9044
10622
  constructor(options) {
9045
10623
  this.#blocks = options.blocks;
9046
10624
  }
@@ -9051,42 +10629,87 @@ var Transcript = class {
9051
10629
  const usableWidth = Math.max(12, context.width);
9052
10630
  const lines = [];
9053
10631
  let activeStart = null;
10632
+ const seenKeys = /* @__PURE__ */ new Set();
9054
10633
  for (const block of this.#blocks) {
10634
+ if (lines.length > 0) {
10635
+ lines.push(plainLine(""));
10636
+ }
9055
10637
  if (block.mutable && activeStart === null)
9056
10638
  activeStart = lines.length;
9057
- lines.push(...this.#renderBlock(block, { ...context, width: usableWidth }));
10639
+ seenKeys.add(cacheKey(block));
10640
+ lines.push(...this.#renderBlockCached(block, { ...context, width: usableWidth }));
10641
+ }
10642
+ for (const key of this.#cache.keys()) {
10643
+ if (!seenKeys.has(key))
10644
+ this.#cache.delete(key);
9058
10645
  }
9059
10646
  return { lines, activeStart };
9060
10647
  }
9061
10648
  invalidate() {
10649
+ this.#cache.clear();
10650
+ }
10651
+ #renderBlockCached(block, context) {
10652
+ const key = cacheKey(block);
10653
+ const signature = blockSignature(block);
10654
+ const cached = this.#cache.get(key);
10655
+ if (cached !== void 0 && cached.width === context.width && cached.signature === signature) {
10656
+ return cached.lines;
10657
+ }
10658
+ if (cached !== void 0 && cached.signature === signature) {
10659
+ const lines2 = cached.component.render(context).lines;
10660
+ this.#cache.set(key, {
10661
+ ...cached,
10662
+ width: context.width,
10663
+ lines: lines2
10664
+ });
10665
+ return lines2;
10666
+ }
10667
+ const component = this.#createBlockComponent(block);
10668
+ const lines = component.render(context).lines;
10669
+ this.#cache.set(key, {
10670
+ component,
10671
+ width: context.width,
10672
+ signature,
10673
+ lines
10674
+ });
10675
+ return lines;
9062
10676
  }
9063
- #renderBlock(block, context) {
10677
+ #createBlockComponent(block) {
9064
10678
  switch (block.kind) {
9065
10679
  case "assistant":
9066
- return new AssistantMessage(block).render(context).lines;
10680
+ return new AssistantMessage(block);
9067
10681
  case "user":
9068
- return new UserMessage(block).render(context).lines;
10682
+ return new UserMessage(block);
9069
10683
  case "thinking":
9070
- return new ThinkingMessage(block).render(context).lines;
10684
+ return new ThinkingMessage(block);
9071
10685
  case "tool":
9072
- return new ToolMessage(block).render(context).lines;
10686
+ return new ToolMessage(block);
9073
10687
  case "notice":
9074
- return new NoticeMessage(block).render(context).lines;
10688
+ return new NoticeMessage(block);
9075
10689
  case "welcome":
9076
- return new WelcomeMessage(block).render(context).lines;
10690
+ return new WelcomeMessage(block);
9077
10691
  case "help":
9078
- return new HelpView(block).render(context).lines;
10692
+ return new HelpView(block);
9079
10693
  case "provider_list":
9080
- return new ProviderStatusView(block).render(context).lines;
10694
+ return new ProviderStatusView(block);
9081
10695
  case "provider_detail":
9082
- return new ProviderDetailView(block.provider).render(context).lines;
10696
+ return new ProviderDetailView(block.provider);
9083
10697
  case "queue_status":
9084
- return new QueueStatusView(block.queue).render(context).lines;
10698
+ return new QueueStatusView(block.queue);
9085
10699
  default:
9086
10700
  return assertNever(block);
9087
10701
  }
9088
10702
  }
9089
10703
  };
10704
+ function cacheKey(block) {
10705
+ return `${block.kind}:${block.key}`;
10706
+ }
10707
+ function blockSignature(block) {
10708
+ if (block.revision !== void 0) {
10709
+ return `revision:${block.revision}`;
10710
+ }
10711
+ return `content:${JSON.stringify(block)}`;
10712
+ }
9090
10713
  function assertNever(value) {
9091
10714
  throw new Error(`unknown transcript block: ${String(value)}`);
9092
10715
  }
@@ -9301,8 +10924,10 @@ var MainScreen = class {
9301
10924
  row: Math.max(0, dock.lines.length - 1),
9302
10925
  column: 0
9303
10926
  };
10927
+ const dockGap = transcript.lines.length > 0 && dock.lines.length > 0 ? [plainLine("")] : [];
9304
10928
  const contentLines = [
9305
10929
  ...transcript.lines,
10930
+ ...dockGap,
9306
10931
  ...dock.lines,
9307
10932
  ...completion.lines,
9308
10933
  ...status.lines
@@ -9310,7 +10935,7 @@ var MainScreen = class {
9310
10935
  return {
9311
10936
  lines: contentLines,
9312
10937
  cursor: {
9313
- row: transcript.lines.length + dockCursor.row,
10938
+ row: transcript.lines.length + dockGap.length + dockCursor.row,
9314
10939
  column: Math.min(width - 1, dockCursor.column)
9315
10940
  },
9316
10941
  activeStart: transcript.activeStart ?? transcript.lines.length
@@ -9590,13 +11215,28 @@ var VStack = class {
9590
11215
  }
9591
11216
  render(context) {
9592
11217
  const lines = [];
11218
+ let cursor;
11219
+ let rowOffset = 0;
9593
11220
  for (const [index, child] of this.#children.entries()) {
9594
11221
  if (index > 0) {
9595
- lines.push(...Array.from({ length: this.#gap }, () => plainLine("")));
11222
+ const gaps = Array.from({ length: this.#gap }, () => plainLine(""));
11223
+ lines.push(...gaps);
11224
+ rowOffset += gaps.length;
11225
+ }
11226
+ const rendered = child.render(context);
11227
+ if (cursor === void 0 && rendered.cursor !== void 0) {
11228
+ cursor = {
11229
+ row: rowOffset + rendered.cursor.row,
11230
+ column: rendered.cursor.column
11231
+ };
9596
11232
  }
9597
- lines.push(...child.render(context).lines);
11233
+ lines.push(...rendered.lines);
11234
+ rowOffset += rendered.lines.length;
9598
11235
  }
9599
- return { lines };
11236
+ return {
11237
+ lines,
11238
+ ...cursor === void 0 ? {} : { cursor }
11239
+ };
9600
11240
  }
9601
11241
  invalidate() {
9602
11242
  for (const child of this.#children) {
@@ -9648,8 +11288,7 @@ var AuthDialog = class {
9648
11288
  gap: 1
9649
11289
  }),
9650
11290
  paddingX: 2,
9651
- paddingY: 1,
9652
- background: "card"
11291
+ paddingY: 1
9653
11292
  }).render(context);
9654
11293
  }
9655
11294
  handleInput(event) {
@@ -9778,7 +11417,7 @@ var ModelSelectorView = class {
9778
11417
  this.#selectList.setSelectedValue(options.currentValue);
9779
11418
  }
9780
11419
  this.#searchInput = new SearchInput({
9781
- placeholder: "Search models",
11420
+ placeholder: options.searchPlaceholder ?? "Search models",
9782
11421
  onChange: (value) => this.#filterModels(value)
9783
11422
  });
9784
11423
  this.#syncFocus();
@@ -9841,19 +11480,19 @@ var ViewHost = class {
9841
11480
  this.#overlays = overlays;
9842
11481
  }
9843
11482
  openSelection(request) {
9844
- return new Promise((resolve) => {
11483
+ return new Promise((resolve2) => {
9845
11484
  const component = this.#selectionComponent(request);
9846
- this.#open({ id: request.id, component, resolve }, "selector");
11485
+ this.#open({ id: request.id, component, resolve: resolve2 }, "selector");
9847
11486
  });
9848
11487
  }
9849
11488
  openPrompt(request) {
9850
- return new Promise((resolve) => {
11489
+ return new Promise((resolve2) => {
9851
11490
  const component = new AuthDialog({
9852
11491
  request,
9853
11492
  onSubmit: (value) => this.#close(request.id, value),
9854
11493
  onCancel: () => this.#close(request.id, null)
9855
11494
  });
9856
- this.#open({ id: request.id, component, resolve }, "modal");
11495
+ this.#open({ id: request.id, component, resolve: resolve2 }, "modal");
9857
11496
  });
9858
11497
  }
9859
11498
  handleInput(event) {
@@ -10328,8 +11967,8 @@ var InteractiveTerminalLoop = class {
10328
11967
  this.#wakeupEnabled = true;
10329
11968
  try {
10330
11969
  if (!this.#exitRequested) {
10331
- await new Promise((resolve) => {
10332
- this.#exitResolve = resolve;
11970
+ await new Promise((resolve2) => {
11971
+ this.#exitResolve = resolve2;
10333
11972
  });
10334
11973
  }
10335
11974
  } finally {
@@ -10519,7 +12158,7 @@ var InteractiveTerminalLoop = class {
10519
12158
  } else if (action === "submit_follow_up") {
10520
12159
  this.#applyFollowUpSubmit();
10521
12160
  } else if (action === "dismiss") {
10522
- this.#applyEditorAction(inputAction(InputActionKind.Dismiss));
12161
+ this.#applyDismissAction();
10523
12162
  } else if (action === "cancel") {
10524
12163
  this.#applyEditorAction(inputAction(InputActionKind.Cancel));
10525
12164
  } else if (action === "toggle_tool_output") {
@@ -10530,6 +12169,18 @@ var InteractiveTerminalLoop = class {
10530
12169
  this.#needsRender = true;
10531
12170
  }
10532
12171
  }
12172
+ #applyDismissAction() {
12173
+ if (this.#applyCompletionAction(inputAction(InputActionKind.Dismiss))) {
12174
+ this.#needsRender = true;
12175
+ return;
12176
+ }
12177
+ if (this.#ui.isRunning()) {
12178
+ this.#ui.cancelFromKeybinding();
12179
+ this.#needsRender = true;
12180
+ return;
12181
+ }
12182
+ this.#applyEditorAction(inputAction(InputActionKind.Dismiss));
12183
+ }
10533
12184
  #applyFollowUpSubmit() {
10534
12185
  if (this.#applyCompletionAction(inputAction(InputActionKind.Submit))) {
10535
12186
  this.#needsRender = true;
@@ -10643,18 +12294,18 @@ var InteractiveTerminalLoop = class {
10643
12294
  if (this.#closed || !this.#running) {
10644
12295
  return Promise.resolve(null);
10645
12296
  }
10646
- return new Promise((resolve) => {
12297
+ return new Promise((resolve2) => {
10647
12298
  if (type === "open_selection") {
10648
12299
  this.#work.push({
10649
12300
  type,
10650
12301
  request,
10651
- resolve
12302
+ resolve: resolve2
10652
12303
  });
10653
12304
  } else {
10654
12305
  this.#work.push({
10655
12306
  type,
10656
12307
  request,
10657
- resolve
12308
+ resolve: resolve2
10658
12309
  });
10659
12310
  }
10660
12311
  this.#scheduleWakeup();
@@ -10678,6 +12329,9 @@ function resolveFallbackMarkdownWidth() {
10678
12329
  const columns = process.stdout?.columns;
10679
12330
  return typeof columns === "number" && columns > 0 ? columns : FALLBACK_MARKDOWN_WIDTH;
10680
12331
  }
12332
+ function normalizePositiveInteger(value) {
12333
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
12334
+ }
10681
12335
  var TerminalUI = class {
10682
12336
  theme;
10683
12337
  state;
@@ -10697,6 +12351,7 @@ var TerminalUI = class {
10697
12351
  #askFallback;
10698
12352
  #loop = null;
10699
12353
  #transcript;
12354
+ #transcriptView;
10700
12355
  #showReasoning = true;
10701
12356
  #displayPolicy = new DisplayPolicy({
10702
12357
  audience: "terminal",
@@ -10724,10 +12379,14 @@ var TerminalUI = class {
10724
12379
  this.state = createUIState();
10725
12380
  this.state.provider = options.provider ?? "";
10726
12381
  this.state.model = options.model ?? "";
12382
+ this.state.contextWindow = normalizePositiveInteger(options.contextWindow);
10727
12383
  this.reducer = new UIEventReducer(this.state);
10728
12384
  this.#transcript = new TranscriptStore({
10729
12385
  errorStyle: `bold ${this.theme.color("error")}`
10730
12386
  });
12387
+ this.#transcriptView = new Transcript({
12388
+ blocks: this.#transcript.blocks()
12389
+ });
10731
12390
  this.#frameBuilder = new FrameBuilder({
10732
12391
  state: this.state,
10733
12392
  transcript: this.#transcript,
@@ -10824,6 +12483,21 @@ var TerminalUI = class {
10824
12483
  this.#sessionId = sessionId;
10825
12484
  this.#loop?.requestRender();
10826
12485
  }
12486
+ replaceTranscript(items) {
12487
+ this.#transcript.replace(items);
12488
+ this.#transcriptView.invalidate();
12489
+ this.#loop?.requestRender();
12490
+ }
12491
+ setComposerText(text) {
12492
+ const editor = this.#loop?.editor;
12493
+ if (editor === void 0) {
12494
+ return;
12495
+ }
12496
+ editor.text = text;
12497
+ editor.cursor = text.length;
12498
+ editor.setCompletions([]);
12499
+ this.#loop?.requestRender();
12500
+ }
10827
12501
  setKeyActionCallback(callback) {
10828
12502
  this.#keyActionCallback = callback;
10829
12503
  }
@@ -10935,9 +12609,7 @@ var TerminalUI = class {
10935
12609
  return this.#buildHistoryFrameParts(width).lines;
10936
12610
  }
10937
12611
  #buildHistoryFrameParts(width) {
10938
- const rendered = new Transcript({
10939
- blocks: this.#transcript.blocks()
10940
- }).renderWithMetadata({ width, theme: this.theme });
12612
+ const rendered = this.#transcriptView.renderWithMetadata({ width, theme: this.theme });
10941
12613
  return {
10942
12614
  lines: compileStyledLines(rendered.lines, Math.max(12, width), this.theme),
10943
12615
  activeStart: rendered.activeStart
@@ -10951,7 +12623,7 @@ var TerminalUI = class {
10951
12623
  theme: this.theme
10952
12624
  });
10953
12625
  const rendered = new MainScreen({
10954
- transcript: new Transcript({ blocks: this.#transcript.blocks() }),
12626
+ transcript: this.#transcriptView,
10955
12627
  composer: new Composer({
10956
12628
  editor,
10957
12629
  prompt: options.prompt ?? "> ",
@@ -11248,7 +12920,7 @@ var StdTerminalDriver = class {
11248
12920
 
11249
12921
  // ../../packages/fs/tool-fs/dist/file-tools.js
11250
12922
  import { promises as fs } from "node:fs";
11251
- import { realpathSync as realpathSync2 } from "node:fs";
12923
+ import { realpathSync as realpathSync3 } from "node:fs";
11252
12924
  import path3 from "node:path";
11253
12925
  var DEFAULT_IO = {
11254
12926
  readFile: (target) => fs.readFile(target, "utf8"),
@@ -11437,8 +13109,8 @@ async function executeEdit(args, execution, resolvePath2, io) {
11437
13109
  async function withFileMutationLock(key, fn) {
11438
13110
  const previous = mutationLocks.get(key) ?? Promise.resolve();
11439
13111
  let release;
11440
- const current = new Promise((resolve) => {
11441
- release = resolve;
13112
+ const current = new Promise((resolve2) => {
13113
+ release = resolve2;
11442
13114
  });
11443
13115
  const tail = previous.then(() => current);
11444
13116
  mutationLocks.set(key, tail);
@@ -11477,7 +13149,7 @@ function resolveNonStrictSync(pathname) {
11477
13149
  let current = pathname;
11478
13150
  for (; ; ) {
11479
13151
  try {
11480
- const real = realpathSync2(current);
13152
+ const real = realpathSync3(current);
11481
13153
  return missing.length === 0 ? real : path3.join(real, ...missing.reverse());
11482
13154
  } catch (error) {
11483
13155
  if (error.code !== "ENOENT") {
@@ -11523,7 +13195,7 @@ function editsArgument(args) {
11523
13195
 
11524
13196
  // ../../packages/shell/tool-bash/dist/tool-bash.js
11525
13197
  import { promises as fs2 } from "node:fs";
11526
- import { realpathSync as realpathSync3 } from "node:fs";
13198
+ import { realpathSync as realpathSync4 } from "node:fs";
11527
13199
  import path4 from "node:path";
11528
13200
 
11529
13201
  // ../../packages/shell/bash-local/dist/bash-runner.js
@@ -11730,7 +13402,7 @@ var TerminalSanitizer = class {
11730
13402
  }
11731
13403
  };
11732
13404
  function sleep(ms) {
11733
- return new Promise((resolve) => setTimeout(resolve, ms));
13405
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
11734
13406
  }
11735
13407
  function errnoCode(error) {
11736
13408
  return error?.code;
@@ -11807,9 +13479,9 @@ async function runBash(command, options) {
11807
13479
  stdio: ["ignore", "pipe", "pipe"],
11808
13480
  detached: true
11809
13481
  });
11810
- const spawnError = await new Promise((resolve) => {
11811
- child.once("spawn", () => resolve(null));
11812
- child.once("error", (error) => resolve(error));
13482
+ const spawnError = await new Promise((resolve2) => {
13483
+ child.once("spawn", () => resolve2(null));
13484
+ child.once("error", (error) => resolve2(error));
11813
13485
  });
11814
13486
  if (spawnError) {
11815
13487
  const result2 = new BashResult({
@@ -11866,8 +13538,8 @@ async function runBash(command, options) {
11866
13538
  };
11867
13539
  const stdoutDone = readPipe("stdout", child.stdout);
11868
13540
  const stderrDone = readPipe("stderr", child.stderr);
11869
- const exitInfo = new Promise((resolve) => {
11870
- child.once("exit", (code2, signal2) => resolve({ code: code2, signal: signal2 }));
13541
+ const exitInfo = new Promise((resolve2) => {
13542
+ child.once("exit", (code2, signal2) => resolve2({ code: code2, signal: signal2 }));
11871
13543
  });
11872
13544
  let terminalStatus = null;
11873
13545
  const startedAt = performance.now();
@@ -12045,7 +13717,7 @@ function resolveNonStrictSync2(pathname) {
12045
13717
  let current = pathname;
12046
13718
  for (; ; ) {
12047
13719
  try {
12048
- const real = realpathSync3(current);
13720
+ const real = realpathSync4(current);
12049
13721
  return missing.length === 0 ? real : path4.join(real, ...missing.reverse());
12050
13722
  } catch (error) {
12051
13723
  if (error.code !== "ENOENT") {
@@ -12282,6 +13954,63 @@ var tones = {
12282
13954
  function errorMessage10(error) {
12283
13955
  return error instanceof Error ? error.message : String(error);
12284
13956
  }
13957
+ function sessionDisplayTitle(session) {
13958
+ const title = cleanSingleLine(session.title);
13959
+ if (title !== "") {
13960
+ return title;
13961
+ }
13962
+ const lastUserText2 = cleanSingleLine(session.lastUserText);
13963
+ return lastUserText2 === "" ? "Untitled session" : lastUserText2;
13964
+ }
13965
+ function sessionDisplayDescription(session, options) {
13966
+ const displayPath = displayPathFor(session.cwd ?? session.projectRoot ?? "", options.homeDirectory);
13967
+ const updated = relativeTimeLabel(session.updatedAt, options.now);
13968
+ return displayPath === "" ? updated : `${updated} ${displayPath}`;
13969
+ }
13970
+ function cleanSingleLine(value) {
13971
+ return value?.replace(/\s+/g, " ").trim() ?? "";
13972
+ }
13973
+ function displayPathFor(path5, homeDirectory) {
13974
+ if (path5 === "" || homeDirectory === null || homeDirectory === "") {
13975
+ return path5;
13976
+ }
13977
+ const home = homeDirectory.endsWith("/") ? homeDirectory.slice(0, -1) : homeDirectory;
13978
+ if (path5 === home) {
13979
+ return "~";
13980
+ }
13981
+ return path5.startsWith(`${home}/`) ? `~/${path5.slice(home.length + 1)}` : path5;
13982
+ }
13983
+ function relativeTimeLabel(updatedAt, now) {
13984
+ const updated = new Date(updatedAt);
13985
+ const timestamp = updated.getTime();
13986
+ if (!Number.isFinite(timestamp)) {
13987
+ return updatedAt;
13988
+ }
13989
+ const elapsedMs = Math.max(0, now.getTime() - timestamp);
13990
+ const elapsedSeconds = Math.floor(elapsedMs / 1e3);
13991
+ if (elapsedSeconds < 60) {
13992
+ return "just now";
13993
+ }
13994
+ const elapsedMinutes = Math.floor(elapsedSeconds / 60);
13995
+ if (elapsedMinutes < 60) {
13996
+ return `${elapsedMinutes} ${elapsedMinutes === 1 ? "minute" : "minutes"} ago`;
13997
+ }
13998
+ const elapsedHours = Math.floor(elapsedMinutes / 60);
13999
+ if (elapsedHours < 24) {
14000
+ return `${elapsedHours} ${elapsedHours === 1 ? "hour" : "hours"} ago`;
14001
+ }
14002
+ const elapsedDays = Math.floor(elapsedHours / 24);
14003
+ if (elapsedDays === 1) {
14004
+ return `yesterday ${twoDigits(updated.getHours())}:${twoDigits(updated.getMinutes())}`;
14005
+ }
14006
+ if (elapsedDays < 365) {
14007
+ return `${twoDigits(updated.getMonth() + 1)}-${twoDigits(updated.getDate())} ${twoDigits(updated.getHours())}:${twoDigits(updated.getMinutes())}`;
14008
+ }
14009
+ return `${updated.getFullYear()}-${twoDigits(updated.getMonth() + 1)}-${twoDigits(updated.getDate())} ${twoDigits(updated.getHours())}:${twoDigits(updated.getMinutes())}`;
14010
+ }
14011
+ function twoDigits(value) {
14012
+ return value.toString().padStart(2, "0");
14013
+ }
12285
14014
  var SessionCommands = class {
12286
14015
  registry;
12287
14016
  #agent;
@@ -12290,7 +14019,12 @@ var SessionCommands = class {
12290
14019
  #providerAuth;
12291
14020
  #presenter;
12292
14021
  #session;
14022
+ #sessionController;
12293
14023
  #onModelSelected;
14024
+ #onComposerText;
14025
+ #onSessionChanged;
14026
+ #homeDirectory;
14027
+ #now;
12294
14028
  #currentConfig;
12295
14029
  constructor(options) {
12296
14030
  this.#agent = options.agent;
@@ -12300,7 +14034,12 @@ var SessionCommands = class {
12300
14034
  this.#currentConfig = options.currentConfig;
12301
14035
  this.#presenter = options.presenter;
12302
14036
  this.#session = options.session ?? null;
14037
+ this.#sessionController = options.sessionController ?? null;
12303
14038
  this.#onModelSelected = options.onModelSelected ?? null;
14039
+ this.#onComposerText = options.onComposerText ?? null;
14040
+ this.#onSessionChanged = options.onSessionChanged ?? null;
14041
+ this.#homeDirectory = options.homeDirectory ?? null;
14042
+ this.#now = options.now ?? (() => /* @__PURE__ */ new Date());
12304
14043
  this.registry = new CommandRegistry([
12305
14044
  {
12306
14045
  name: "/model",
@@ -12365,10 +14104,53 @@ var SessionCommands = class {
12365
14104
  argumentCompleter: queueCompletions
12366
14105
  },
12367
14106
  {
12368
- name: "/clear",
12369
- description: "\u6E05\u7A7A\u5F53\u524D\u5BF9\u8BDD\u4E0A\u4E0B\u6587",
12370
- usage: "/clear",
12371
- handler: (args) => this.handleClear(args),
14107
+ name: "/new",
14108
+ description: "\u521B\u5EFA\u65B0\u4F1A\u8BDD",
14109
+ usage: "/new",
14110
+ handler: (args) => this.handleNew(args),
14111
+ allowedStates: IDLE_ONLY
14112
+ },
14113
+ {
14114
+ name: "/session",
14115
+ description: "\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD",
14116
+ usage: "/session",
14117
+ handler: (args) => this.handleSession(args),
14118
+ allowedStates: ALL_STATES
14119
+ },
14120
+ {
14121
+ name: "/sessions",
14122
+ description: "\u5217\u51FA\u5F53\u524D\u9879\u76EE\u4F1A\u8BDD",
14123
+ usage: "/sessions",
14124
+ handler: (args) => this.handleSessions(args),
14125
+ allowedStates: ALL_STATES
14126
+ },
14127
+ {
14128
+ name: "/resume",
14129
+ description: "\u9009\u62E9\u6216\u6062\u590D\u6307\u5B9A\u4F1A\u8BDD",
14130
+ usage: "/resume [session-id]",
14131
+ handler: (args) => this.handleResume(args),
14132
+ allowedStates: IDLE_ONLY,
14133
+ argumentCompleter: (args) => this.resumeCompletions(args)
14134
+ },
14135
+ {
14136
+ name: "/fork",
14137
+ description: "\u4ECE\u7528\u6237\u6D88\u606F\u5206\u53C9\u4F1A\u8BDD",
14138
+ usage: "/fork <entry-id> [before|at]",
14139
+ handler: (args) => this.handleFork(args),
14140
+ allowedStates: IDLE_ONLY
14141
+ },
14142
+ {
14143
+ name: "/clone",
14144
+ description: "\u514B\u9686\u5F53\u524D\u4F1A\u8BDD",
14145
+ usage: "/clone",
14146
+ handler: (args) => this.handleClone(args),
14147
+ allowedStates: IDLE_ONLY
14148
+ },
14149
+ {
14150
+ name: "/compact",
14151
+ description: "\u624B\u52A8\u538B\u7F29\u5F53\u524D\u4E0A\u4E0B\u6587",
14152
+ usage: "/compact",
14153
+ handler: (args) => this.handleCompact(args),
12372
14154
  allowedStates: IDLE_ONLY
12373
14155
  },
12374
14156
  {
@@ -12489,18 +14271,149 @@ var SessionCommands = class {
12489
14271
  });
12490
14272
  return true;
12491
14273
  }
12492
- handleClear(args) {
14274
+ handleSession(args) {
14275
+ if (args.length > 0) {
14276
+ this.notice("Usage: /session", tones.invalid);
14277
+ return true;
14278
+ }
14279
+ const controller = this.#sessionController;
14280
+ if (controller === null || controller.currentSessionId === null) {
14281
+ this.notice("No active session.", "warning");
14282
+ return true;
14283
+ }
14284
+ this.notice(
14285
+ `Current session: ${controller.currentSessionId}
14286
+ Path: ${controller.currentPath ?? ""}`,
14287
+ "info"
14288
+ );
14289
+ return true;
14290
+ }
14291
+ async handleNew(args) {
14292
+ if (args.length > 0) {
14293
+ this.notice("Usage: /new", tones.invalid);
14294
+ return true;
14295
+ }
14296
+ const controller = this.#sessionController;
14297
+ if (controller === null) {
14298
+ this.notice("Session creation is unavailable.", "error");
14299
+ return true;
14300
+ }
14301
+ await controller.createNew();
14302
+ await this.#onSessionChanged?.();
14303
+ this.notice("Started a new session.", tones.switched);
14304
+ return true;
14305
+ }
14306
+ handleSessions(args) {
14307
+ if (args.length > 0) {
14308
+ this.notice("Usage: /sessions", tones.invalid);
14309
+ return true;
14310
+ }
14311
+ const sessions = this.#sessionController?.list() ?? [];
14312
+ if (sessions.length === 0) {
14313
+ this.notice("No sessions for this project.", "info");
14314
+ return true;
14315
+ }
14316
+ const now = this.#now();
14317
+ for (const session of sessions) {
14318
+ this.notice(
14319
+ `${sessionDisplayTitle(session)}
14320
+ ${sessionDisplayDescription(session, {
14321
+ homeDirectory: this.#homeDirectory,
14322
+ now
14323
+ })}`,
14324
+ "info"
14325
+ );
14326
+ }
14327
+ return true;
14328
+ }
14329
+ async handleResume(args) {
14330
+ if (args.length > 1) {
14331
+ this.notice("Usage: /resume [session-id]", tones.invalid);
14332
+ return true;
14333
+ }
14334
+ const controller = this.#sessionController;
14335
+ if (controller === null) {
14336
+ this.notice("Session resume is unavailable.", "error");
14337
+ return true;
14338
+ }
14339
+ let sessionId = args[0];
14340
+ if (sessionId === void 0) {
14341
+ const sessions = controller.list();
14342
+ if (sessions.length === 0) {
14343
+ this.notice("No sessions for this project.", "info");
14344
+ return true;
14345
+ }
14346
+ const now = this.#now();
14347
+ sessionId = await this.#presenter.select({
14348
+ id: "session-resume",
14349
+ title: "Resume session",
14350
+ items: sessions.map((session) => ({
14351
+ value: session.sessionId,
14352
+ label: sessionDisplayTitle(session),
14353
+ description: sessionDisplayDescription(session, {
14354
+ homeDirectory: this.#homeDirectory,
14355
+ now
14356
+ })
14357
+ })),
14358
+ currentValue: controller.currentSessionId ?? void 0,
14359
+ searchable: true,
14360
+ searchPlaceholder: "Search sessions",
14361
+ maxVisible: 20
14362
+ }) ?? void 0;
14363
+ if (sessionId === void 0) {
14364
+ return true;
14365
+ }
14366
+ }
14367
+ await controller.resume(sessionId);
14368
+ await this.#onSessionChanged?.();
14369
+ this.notice("Resumed session.", tones.switched);
14370
+ return true;
14371
+ }
14372
+ async handleFork(args) {
14373
+ if (args.length < 1 || args.length > 2 || args[1] !== void 0 && args[1] !== "before" && args[1] !== "at") {
14374
+ this.notice("Usage: /fork <entry-id> [before|at]", tones.invalid);
14375
+ return true;
14376
+ }
14377
+ const controller = this.#sessionController;
14378
+ if (controller === null) {
14379
+ this.notice("Session fork is unavailable.", "error");
14380
+ return true;
14381
+ }
14382
+ const result = await controller.fork(args[0], args[1] ?? "before");
14383
+ await this.#onSessionChanged?.();
14384
+ if (result.editorText !== "") {
14385
+ this.#onComposerText?.(result.editorText);
14386
+ }
14387
+ this.notice(`Forked session ${result.sessionId}.`, tones.switched);
14388
+ return true;
14389
+ }
14390
+ async handleClone(args) {
12493
14391
  if (args.length > 0) {
12494
- this.notice("Usage: /clear", tones.invalid);
14392
+ this.notice("Usage: /clone", tones.invalid);
12495
14393
  return true;
12496
14394
  }
12497
- const clear = this.#agent.clearHistory;
12498
- if (typeof clear === "function") {
12499
- clear.call(this.#agent);
12500
- } else if (this.#agent.messages !== void 0 && this.#agent.messages.length > 0) {
12501
- this.#agent.messages.splice(1);
14395
+ const controller = this.#sessionController;
14396
+ if (controller === null) {
14397
+ this.notice("Session clone is unavailable.", "error");
14398
+ return true;
14399
+ }
14400
+ const result = await controller.clone();
14401
+ await this.#onSessionChanged?.();
14402
+ this.notice(`Cloned session ${result.sessionId}.`, tones.switched);
14403
+ return true;
14404
+ }
14405
+ async handleCompact(args) {
14406
+ if (args.length > 0) {
14407
+ this.notice("Usage: /compact", tones.invalid);
14408
+ return true;
14409
+ }
14410
+ if (this.#sessionController === null) {
14411
+ this.notice("Session compaction is unavailable.", "error");
14412
+ return true;
12502
14413
  }
12503
- this.notice("Conversation cleared.", tones.cleared);
14414
+ await this.#sessionController.compact();
14415
+ await this.#onSessionChanged?.();
14416
+ this.notice("Compacted current session.", tones.switched);
12504
14417
  return true;
12505
14418
  }
12506
14419
  async handleModel(args) {
@@ -12796,6 +14709,10 @@ var SessionCommands = class {
12796
14709
  payload: {
12797
14710
  provider: this.#currentConfig.provider,
12798
14711
  model: this.#currentConfig.model,
14712
+ context_window: this.#catalog.getModel(
14713
+ this.#currentConfig.provider,
14714
+ this.#currentConfig.model
14715
+ )?.contextWindow ?? 0,
12799
14716
  previous_provider: previousProvider,
12800
14717
  previous_model: previousModel
12801
14718
  }
@@ -12881,6 +14798,15 @@ var SessionCommands = class {
12881
14798
  yield [effort, effort === "off" ? "\u5173\u95ED\u601D\u8003" : "\u8BBE\u7F6E\u601D\u8003\u7B49\u7EA7"];
12882
14799
  }
12883
14800
  }
14801
+ *resumeCompletions(args) {
14802
+ if (args.length > 0) {
14803
+ return;
14804
+ }
14805
+ for (const session of this.#sessionController?.list() ?? []) {
14806
+ const description = session.lastUserText === void 0 || session.lastUserText === "" ? session.updatedAt : `${session.updatedAt} ${session.lastUserText}`;
14807
+ yield [session.sessionId, description];
14808
+ }
14809
+ }
12884
14810
  currentReasoningEffort() {
12885
14811
  return this.#agent.getReasoningEffort?.() ?? "high";
12886
14812
  }
@@ -13325,7 +15251,7 @@ var TerminalCommandPresenter = class {
13325
15251
  // src/args.ts
13326
15252
  var CliUsageError = class extends Error {
13327
15253
  };
13328
- var USAGE = "usage: laohuang [--version] [--profile PROFILE] [--model MODEL] [--base-url BASE_URL] [--theme {auto,dark,light}] [config ...] [doctor]";
15254
+ var USAGE = "usage: laohuang [--version] [--profile PROFILE] [--model MODEL] [--base-url BASE_URL] [--theme {auto,dark,light}] [--continue | --resume SESSION_ID] [config ...] [doctor]";
13329
15255
  var HELP = `${USAGE}
13330
15256
 
13331
15257
  A minimal coding agent
@@ -13337,6 +15263,8 @@ options:
13337
15263
  --model MODEL model override for this session
13338
15264
  --base-url URL API base URL override for this session
13339
15265
  --theme THEME interactive terminal theme: auto, dark, light (default: auto)
15266
+ --continue resume the latest session for this project
15267
+ --resume ID resume a specific session id
13340
15268
 
13341
15269
  subcommands:
13342
15270
  config [set|list|use] [target] [--profile P] [--provider P] [--model M] [--base-url U]
@@ -13362,7 +15290,9 @@ function parseArgs(argv) {
13362
15290
  configProfile: "default",
13363
15291
  provider: null,
13364
15292
  configModel: null,
13365
- configBaseUrl: null
15293
+ configBaseUrl: null,
15294
+ continueSession: false,
15295
+ resumeSessionId: null
13366
15296
  };
13367
15297
  let index = 0;
13368
15298
  const takeValue = (option, inline) => {
@@ -13409,10 +15339,19 @@ function parseArgs(argv) {
13409
15339
  args.theme = theme;
13410
15340
  break;
13411
15341
  }
15342
+ case "--continue":
15343
+ args.continueSession = true;
15344
+ break;
15345
+ case "--resume":
15346
+ args.resumeSessionId = takeValue(name, inline);
15347
+ break;
13412
15348
  default:
13413
15349
  throw new CliUsageError(`unrecognized arguments: ${token}`);
13414
15350
  }
13415
15351
  }
15352
+ if (args.continueSession && args.resumeSessionId !== null) {
15353
+ throw new CliUsageError("--continue and --resume cannot be used together");
15354
+ }
13416
15355
  if (args.command === "config") {
13417
15356
  const positionals = [];
13418
15357
  for (; index < argv.length; index += 1) {
@@ -13471,6 +15410,130 @@ function parseArgs(argv) {
13471
15410
  return { kind: "run", args };
13472
15411
  }
13473
15412
 
15413
+ // src/session-controller.ts
15414
+ var SessionController = class {
15415
+ #manager;
15416
+ #projectRoot;
15417
+ #initialCwd;
15418
+ #provider;
15419
+ #model;
15420
+ #reasoningEffort;
15421
+ #sessionsRoot;
15422
+ #compactor = null;
15423
+ #opened = null;
15424
+ #history = null;
15425
+ presentationEventBus = new EventBus();
15426
+ constructor(options) {
15427
+ this.#sessionsRoot = options.sessionsRoot;
15428
+ this.#manager = new SessionManager({
15429
+ sessionsRoot: options.sessionsRoot,
15430
+ appVersion: options.appVersion
15431
+ });
15432
+ this.#projectRoot = canonicalProjectRoot(options.projectRoot);
15433
+ this.#initialCwd = options.initialCwd;
15434
+ this.#provider = options.provider;
15435
+ this.#model = options.model;
15436
+ this.#reasoningEffort = options.reasoningEffort;
15437
+ }
15438
+ get sessionsRoot() {
15439
+ return this.#sessionsRoot;
15440
+ }
15441
+ get currentSessionId() {
15442
+ return this.#opened?.header.sessionId ?? null;
15443
+ }
15444
+ get currentPath() {
15445
+ return this.#opened?.journal.path ?? null;
15446
+ }
15447
+ get history() {
15448
+ return this.#history;
15449
+ }
15450
+ get currentJournal() {
15451
+ return this.#opened?.journal ?? null;
15452
+ }
15453
+ async createNew() {
15454
+ await this.close();
15455
+ this.#opened = this.#manager.create({
15456
+ projectRoot: this.#projectRoot,
15457
+ initialCwd: this.#initialCwd,
15458
+ provider: this.#provider,
15459
+ model: this.#model,
15460
+ reasoningEffort: this.#reasoningEffort
15461
+ });
15462
+ this.#history = ConversationHistory.fromReplay(
15463
+ this.#opened.replay,
15464
+ this.#opened.journal
15465
+ );
15466
+ }
15467
+ async resume(sessionId) {
15468
+ await this.close();
15469
+ const opened = this.#manager.open(sessionId);
15470
+ if (opened.header.projectRoot !== this.#projectRoot) {
15471
+ opened.journal.close();
15472
+ throw new Error("project root mismatch for resumed session");
15473
+ }
15474
+ this.#opened = opened;
15475
+ this.#history = ConversationHistory.fromReplay(opened.replay, opened.journal);
15476
+ }
15477
+ async continueLatest() {
15478
+ await this.close();
15479
+ const opened = this.#manager.continueLatest(this.#projectRoot);
15480
+ if (opened === null) {
15481
+ await this.createNew();
15482
+ return;
15483
+ }
15484
+ this.#opened = opened;
15485
+ this.#history = ConversationHistory.fromReplay(opened.replay, opened.journal);
15486
+ }
15487
+ list() {
15488
+ return this.#manager.list(this.#projectRoot);
15489
+ }
15490
+ async fork(entryId, mode) {
15491
+ if (this.currentSessionId === null) {
15492
+ throw new Error("no active session");
15493
+ }
15494
+ const result = this.#manager.fork({
15495
+ parentSessionId: this.currentSessionId,
15496
+ entryId,
15497
+ mode
15498
+ });
15499
+ await this.resume(result.sessionId);
15500
+ return result;
15501
+ }
15502
+ async clone() {
15503
+ if (this.currentSessionId === null) {
15504
+ throw new Error("no active session");
15505
+ }
15506
+ const result = this.#manager.clone({ parentSessionId: this.currentSessionId });
15507
+ await this.resume(result.sessionId);
15508
+ return result;
15509
+ }
15510
+ setCompactor(compactor) {
15511
+ this.#compactor = compactor;
15512
+ }
15513
+ async compact() {
15514
+ if (this.#history === null) {
15515
+ throw new Error("no active session");
15516
+ }
15517
+ if (this.#compactor === null) {
15518
+ throw new Error("manual compaction requires runtime wiring");
15519
+ }
15520
+ return this.#compactor();
15521
+ }
15522
+ async close() {
15523
+ if (this.#opened === null) {
15524
+ return true;
15525
+ }
15526
+ this.#opened.journal.appendRecord({
15527
+ recordType: "session_closed",
15528
+ payload: { reason: "normal" }
15529
+ });
15530
+ this.#opened.journal.close();
15531
+ this.#opened = null;
15532
+ this.#history = null;
15533
+ return true;
15534
+ }
15535
+ };
15536
+
13474
15537
  // src/repl.ts
13475
15538
  import { readSync as readSync2 } from "node:fs";
13476
15539
  function errorMessage11(error) {
@@ -13727,8 +15790,8 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
13727
15790
  const done = (async () => {
13728
15791
  for (; ; ) {
13729
15792
  if (queue.length === 0) {
13730
- await new Promise((resolve) => {
13731
- wake = resolve;
15793
+ await new Promise((resolve2) => {
15794
+ wake = resolve2;
13732
15795
  });
13733
15796
  wake = null;
13734
15797
  }
@@ -13774,14 +15837,14 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
13774
15837
  };
13775
15838
  }
13776
15839
  function delay(ms) {
13777
- return new Promise((resolve) => {
13778
- setTimeout(resolve, ms);
15840
+ return new Promise((resolve2) => {
15841
+ setTimeout(resolve2, ms);
13779
15842
  });
13780
15843
  }
13781
15844
  async function settlesWithin(promise, ms) {
13782
15845
  let timer;
13783
- const timeout = new Promise((resolve) => {
13784
- timer = setTimeout(() => resolve(false), ms);
15846
+ const timeout = new Promise((resolve2) => {
15847
+ timer = setTimeout(() => resolve2(false), ms);
13785
15848
  });
13786
15849
  const settled = promise.then(
13787
15850
  () => true,
@@ -13953,7 +16016,7 @@ function readPackageVersion() {
13953
16016
  const packageJsonPath = fileURLToPath(
13954
16017
  new URL("../package.json", import.meta.url)
13955
16018
  );
13956
- const parsed = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
16019
+ const parsed = JSON.parse(readFileSync6(packageJsonPath, "utf8"));
13957
16020
  if (typeof parsed === "object" && parsed !== null && typeof parsed.version === "string") {
13958
16021
  return parsed.version;
13959
16022
  }
@@ -13965,6 +16028,10 @@ function writeStderr(message) {
13965
16028
  process.stderr.write(`${message}
13966
16029
  `);
13967
16030
  }
16031
+ function resetEmptySessionTranscript(terminalUi) {
16032
+ terminalUi?.replaceTranscript([]);
16033
+ terminalUi?.showWelcome();
16034
+ }
13968
16035
  async function main(argv, options = {}) {
13969
16036
  let parsed;
13970
16037
  try {
@@ -14003,10 +16070,10 @@ async function main(argv, options = {}) {
14003
16070
  const configPath = options.configPath ?? defaultConfigPath(environ);
14004
16071
  const manager = new ConfigManager(configPath);
14005
16072
  const credentials = new CredentialStore(
14006
- options.credentialsPath ?? join4(dirname4(configPath), "credentials.json")
16073
+ options.credentialsPath ?? join6(dirname4(configPath), "credentials.json")
14007
16074
  );
14008
16075
  const modelCatalogStore = new ModelCatalogStore(
14009
- options.modelsPath ?? join4(dirname4(configPath), "models.json")
16076
+ options.modelsPath ?? join6(dirname4(configPath), "models.json")
14010
16077
  );
14011
16078
  const modelPlatform = await createPiAiPlatform({
14012
16079
  credentials,
@@ -14119,12 +16186,12 @@ async function main(argv, options = {}) {
14119
16186
  outputFn(`Verified: ${provider.verified ? "yes" : "no"}`);
14120
16187
  outputFn(`Configuration: ${configPath}`);
14121
16188
  outputFn(`Node: ${process.version}`);
14122
- outputFn(`Bash: ${existsSync5("/bin/bash") ? "available" : "missing"}`);
16189
+ outputFn(`Bash: ${existsSync7("/bin/bash") ? "available" : "missing"}`);
14123
16190
  return provider !== void 0 && auth.configured && refreshOk && model !== void 0 ? 0 : 1;
14124
16191
  }
14125
16192
  let config;
14126
16193
  try {
14127
- if (existsSync5(configPath)) {
16194
+ if (existsSync7(configPath)) {
14128
16195
  config = manager.resolve({
14129
16196
  environ,
14130
16197
  profile: args.profile,
@@ -14168,7 +16235,28 @@ async function main(argv, options = {}) {
14168
16235
  }
14169
16236
  let terminalUi = null;
14170
16237
  let terminalDriver = null;
14171
- const selectedModel = modelPlatform.catalog.getModel(config.provider, config.model);
16238
+ let selectedModel = modelPlatform.catalog.getModel(config.provider, config.model);
16239
+ const sessionController = new SessionController({
16240
+ sessionsRoot: defaultSessionsRoot(environ),
16241
+ projectRoot,
16242
+ initialCwd: process.cwd(),
16243
+ appVersion: VERSION,
16244
+ provider: config.provider,
16245
+ model: config.model,
16246
+ reasoningEffort: "high"
16247
+ });
16248
+ try {
16249
+ if (args.resumeSessionId !== null) {
16250
+ await sessionController.resume(args.resumeSessionId);
16251
+ } else if (args.continueSession) {
16252
+ await sessionController.continueLatest();
16253
+ } else {
16254
+ await sessionController.createNew();
16255
+ }
16256
+ } catch (error) {
16257
+ writeStderr(`Session error: ${errorMessage11(error)}`);
16258
+ return 2;
16259
+ }
14172
16260
  if (interactive) {
14173
16261
  terminalDriver = new StdTerminalDriver();
14174
16262
  terminalUi = new TerminalUI({
@@ -14178,22 +16266,151 @@ async function main(argv, options = {}) {
14178
16266
  version: VERSION,
14179
16267
  theme: args.theme,
14180
16268
  driver: terminalDriver,
14181
- capabilities: { reasoning: selectedModel?.reasoning ?? false }
16269
+ capabilities: { reasoning: selectedModel?.reasoning ?? false },
16270
+ contextWindow: selectedModel?.contextWindow
14182
16271
  });
14183
16272
  terminalUi.state.provider = config.provider;
14184
16273
  terminalUi.state.model = config.model;
14185
16274
  }
16275
+ const toolRegistry = new ToolRegistry([
16276
+ ...createFileToolDefinitions({ projectRoot }),
16277
+ createBashToolDefinition({ projectRoot })
16278
+ ]);
16279
+ const activeConversationHistory = {
16280
+ appendUser: (input) => {
16281
+ const activeHistory = sessionController.history;
16282
+ if (activeHistory === null) throw new Error("no active session");
16283
+ return activeHistory.appendUser(input);
16284
+ },
16285
+ appendAssistant: (input) => {
16286
+ const activeHistory = sessionController.history;
16287
+ if (activeHistory === null) throw new Error("no active session");
16288
+ return activeHistory.appendAssistant(input);
16289
+ },
16290
+ appendToolResults: (input) => {
16291
+ const activeHistory = sessionController.history;
16292
+ if (activeHistory === null) throw new Error("no active session");
16293
+ return activeHistory.appendToolResults(input);
16294
+ },
16295
+ appendReminder: (input) => {
16296
+ const activeHistory = sessionController.history;
16297
+ if (activeHistory === null) throw new Error("no active session");
16298
+ return activeHistory.appendReminder(input);
16299
+ }
16300
+ };
16301
+ const createContextGovernor = (history2) => {
16302
+ const modelInfo2 = selectedModel;
16303
+ if (modelInfo2 === void 0) {
16304
+ throw new Error("selected model metadata is unavailable");
16305
+ }
16306
+ return new ContextGovernor({
16307
+ appendCompaction: (payload) => history2.appendCompaction(payload),
16308
+ summarize: async ({ serialized, maxSummaryTokens }) => {
16309
+ const summary = await modelRuntime.complete({
16310
+ provider: config.provider,
16311
+ model: config.model,
16312
+ ...config.baseUrl === null ? {} : { baseUrl: config.baseUrl },
16313
+ messages: [
16314
+ { role: "system", content: COMPACTION_SYSTEM_PROMPT },
16315
+ { role: "user", content: serialized }
16316
+ ],
16317
+ tools: [],
16318
+ reasoningEffort: "off",
16319
+ temperature: 0,
16320
+ maxOutputTokens: Math.min(maxSummaryTokens, modelInfo2.maxTokens),
16321
+ maxAttempts: 1
16322
+ });
16323
+ return {
16324
+ summary: summary.message.content.filter((block) => block.type === "text").map((block) => block.text).join(""),
16325
+ inputTokens: summary.usage.inputTokens,
16326
+ outputTokens: summary.usage.outputTokens
16327
+ };
16328
+ }
16329
+ });
16330
+ };
14186
16331
  const agent = new CodingAgent({
14187
16332
  modelAdapter: modelPlatform.adapter,
14188
16333
  model: config.model,
14189
- tools: new ToolRegistry([
14190
- ...createFileToolDefinitions({ projectRoot }),
14191
- createBashToolDefinition({ projectRoot })
14192
- ]),
16334
+ tools: toolRegistry,
16335
+ cliName: "laohuang",
16336
+ cliVersion: VERSION,
14193
16337
  provider: config.provider,
14194
16338
  baseUrl: config.baseUrl,
14195
16339
  projectRoot: instructionRoot,
14196
- startupCwd: process.cwd()
16340
+ startupCwd: process.cwd(),
16341
+ conversationHistory: activeConversationHistory,
16342
+ contextGovernor: selectedModel === void 0 || sessionController.history === null ? null : {
16343
+ prepare: async ({ tools }) => {
16344
+ const history2 = sessionController.history;
16345
+ if (history2 === null || selectedModel === void 0) {
16346
+ return { messages: [], contextTokens: 0, contextWindow: 0 };
16347
+ }
16348
+ const governor = createContextGovernor(history2);
16349
+ const prepared = await governor.prepare({
16350
+ entries: history2.entries(),
16351
+ currentProvider: config.provider,
16352
+ currentModel: config.model,
16353
+ tools,
16354
+ budget: {
16355
+ contextWindow: selectedModel.contextWindow,
16356
+ maxOutputTokens: selectedModel.maxTokens
16357
+ },
16358
+ policy: defaultContextPolicy()
16359
+ });
16360
+ return {
16361
+ messages: prepared.messages,
16362
+ contextTokens: prepared.tokens,
16363
+ contextWindow: selectedModel.contextWindow
16364
+ };
16365
+ }
16366
+ }
16367
+ });
16368
+ const history = sessionController.history;
16369
+ if (history !== null) {
16370
+ if (history.entries().length === 0) {
16371
+ const system = agent.messages.find((message) => message.role === "system");
16372
+ if (system !== void 0) {
16373
+ history.appendSystemContext({
16374
+ message: system,
16375
+ cwd: process.cwd()
16376
+ });
16377
+ }
16378
+ } else {
16379
+ agent.messages = [...new ContextBuilder().build({
16380
+ entries: history.entries(),
16381
+ currentProvider: config.provider,
16382
+ currentModel: config.model
16383
+ }).messages];
16384
+ terminalUi?.replaceTranscript(projectTranscript(history.entries()));
16385
+ }
16386
+ }
16387
+ sessionController.setCompactor(async () => {
16388
+ const activeHistory = sessionController.history;
16389
+ if (activeHistory === null) {
16390
+ throw new Error("no active session");
16391
+ }
16392
+ if (selectedModel === void 0) {
16393
+ throw new Error("selected model metadata is unavailable");
16394
+ }
16395
+ const result = await createContextGovernor(activeHistory).compact({
16396
+ entries: activeHistory.entries(),
16397
+ currentProvider: config.provider,
16398
+ currentModel: config.model,
16399
+ tools: toolRegistry.definitions,
16400
+ budget: {
16401
+ contextWindow: selectedModel.contextWindow,
16402
+ maxOutputTokens: selectedModel.maxTokens
16403
+ },
16404
+ policy: defaultContextPolicy(),
16405
+ trigger: "manual"
16406
+ });
16407
+ agent.messages = [...new ContextBuilder().build({
16408
+ entries: activeHistory.entries(),
16409
+ currentProvider: config.provider,
16410
+ currentModel: config.model
16411
+ }).messages];
16412
+ terminalUi?.replaceTranscript(projectTranscript(activeHistory.entries()));
16413
+ return result;
14197
16414
  });
14198
16415
  const semanticClassifier = new SmallModelSemanticClassifier({
14199
16416
  modelRuntime,
@@ -14205,9 +16422,14 @@ async function main(argv, options = {}) {
14205
16422
  });
14206
16423
  let commandDispatcher;
14207
16424
  const runtime = new AgentSession(agent, {
16425
+ sessionId: sessionController.currentSessionId ?? void 0,
14208
16426
  semanticClassifier,
14209
16427
  commandDispatcher: (command) => commandDispatcher?.(command) ?? { status: "not_found", command }
14210
16428
  });
16429
+ const sessionRecorder = new SessionRecorder({
16430
+ eventBus: runtime.eventBus,
16431
+ journal: () => sessionController.currentJournal
16432
+ });
14211
16433
  terminalUi?.setSessionId(runtime.sessionId);
14212
16434
  const plainSink = terminalUi === null ? new PlainEventSink(outputFn) : null;
14213
16435
  const sessionSink = terminalUi ?? plainSink;
@@ -14236,6 +16458,36 @@ async function main(argv, options = {}) {
14236
16458
  input: presenterInput,
14237
16459
  secretInput: presenterSecretInput
14238
16460
  }) : new TerminalCommandPresenter(terminalUi);
16461
+ const refreshSessionView = () => {
16462
+ const currentSessionId = sessionController.currentSessionId;
16463
+ if (currentSessionId !== null) {
16464
+ runtime.setSessionId(currentSessionId);
16465
+ terminalUi?.setSessionId(currentSessionId);
16466
+ }
16467
+ const activeHistory = sessionController.history;
16468
+ if (activeHistory === null) {
16469
+ return;
16470
+ }
16471
+ const entries = activeHistory.entries();
16472
+ if (entries.length === 0) {
16473
+ const system = agent.messages.find((message) => message.role === "system");
16474
+ if (system !== void 0) {
16475
+ activeHistory.appendSystemContext({
16476
+ message: system,
16477
+ cwd: process.cwd()
16478
+ });
16479
+ agent.messages = [system];
16480
+ }
16481
+ resetEmptySessionTranscript(terminalUi);
16482
+ return;
16483
+ }
16484
+ agent.messages = [...new ContextBuilder().build({
16485
+ entries,
16486
+ currentProvider: config.provider,
16487
+ currentModel: config.model
16488
+ }).messages];
16489
+ terminalUi?.replaceTranscript(projectTranscript(entries));
16490
+ };
14239
16491
  const commands = new SessionCommands({
14240
16492
  agent,
14241
16493
  selector,
@@ -14248,17 +16500,28 @@ async function main(argv, options = {}) {
14248
16500
  providerAuth,
14249
16501
  presenter: commandPresenter,
14250
16502
  session: runtime,
16503
+ sessionController,
16504
+ onComposerText: (text) => terminalUi?.setComposerText(text),
16505
+ onSessionChanged: refreshSessionView,
16506
+ homeDirectory: environ["HOME"],
14251
16507
  onModelSelected: (selection) => {
14252
16508
  semanticClassifier.configure({
14253
16509
  provider: selection.config.provider,
14254
16510
  model: selection.config.model,
14255
16511
  baseUrl: selection.config.baseUrl
14256
16512
  });
16513
+ config = {
16514
+ ...config,
16515
+ provider: selection.config.provider,
16516
+ model: selection.config.model,
16517
+ baseUrl: selection.config.baseUrl
16518
+ };
16519
+ selectedModel = modelPlatform.catalog.getModel(
16520
+ selection.config.provider,
16521
+ selection.config.model
16522
+ );
14257
16523
  if (terminalUi !== null) {
14258
- const model = modelPlatform.catalog.getModel(
14259
- selection.config.provider,
14260
- selection.config.model
14261
- );
16524
+ const model = selectedModel;
14262
16525
  terminalUi.state.provider = selection.config.provider;
14263
16526
  terminalUi.state.model = selection.config.model;
14264
16527
  terminalUi.setRuntimeCapabilities({ reasoning: model?.reasoning ?? false });
@@ -14286,10 +16549,6 @@ async function main(argv, options = {}) {
14286
16549
  void commandDispatcher?.("/model");
14287
16550
  return;
14288
16551
  }
14289
- if (action === "clear_screen") {
14290
- void commandDispatcher?.("/clear");
14291
- return;
14292
- }
14293
16552
  if (action === "toggle_thinking") {
14294
16553
  terminalUi.toggleReasoningFromKeybinding();
14295
16554
  return;
@@ -14331,9 +16590,23 @@ async function main(argv, options = {}) {
14331
16590
  for (const unsubscribe of unsubscribers) {
14332
16591
  unsubscribe();
14333
16592
  }
16593
+ await sessionRecorder.close();
16594
+ await sessionController.close();
14334
16595
  }
14335
16596
  return cleanShutdown ? 0 : 1;
14336
16597
  }
16598
+ function defaultSessionsRoot(environ) {
16599
+ return join6(environ["HOME"] ?? process.cwd(), ".laohuang", "sessions");
16600
+ }
16601
+ function defaultContextPolicy() {
16602
+ return {
16603
+ auto: true,
16604
+ thresholdRatio: 0.8,
16605
+ retainRatio: 0.16,
16606
+ maxSummaryTokens: 8192,
16607
+ safetyRatio: 0.05
16608
+ };
16609
+ }
14337
16610
  async function runInitialModelSelection(options) {
14338
16611
  let providerName = options.providerName;
14339
16612
  if (providerName === void 0) {