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.
- package/dist/bin.js +2463 -190
- package/dist/bin.js.map +4 -4
- 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
|
|
5
|
-
import { dirname as dirname4, join as
|
|
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
|
|
66
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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((
|
|
650
|
-
this.flushWaiters.push(
|
|
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((
|
|
659
|
-
this.flushWaiters.push(
|
|
662
|
+
const waiter = new Promise((resolve2) => {
|
|
663
|
+
this.flushWaiters.push(resolve2);
|
|
660
664
|
});
|
|
661
665
|
let timer;
|
|
662
|
-
const timeout = new Promise((
|
|
663
|
-
timer = setTimeout(
|
|
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
|
|
693
|
-
|
|
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
|
|
700
|
-
|
|
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((
|
|
707
|
-
this.wakeWaiters.push(
|
|
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((
|
|
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((
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 = [
|
|
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
|
|
3277
|
-
import { chmodSync, existsSync as
|
|
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
|
|
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) :
|
|
3302
|
-
return
|
|
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 (!
|
|
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(
|
|
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
|
-
|
|
4801
|
+
mkdirSync2(parent, { recursive: true, mode: 448 });
|
|
3397
4802
|
const content = JSON.stringify(document, null, 2) + "\n";
|
|
3398
|
-
const temporary =
|
|
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
|
|
3441
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
3442
|
-
import { basename as
|
|
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 (!
|
|
4971
|
+
if (!existsSync5(this.path)) {
|
|
3567
4972
|
return { version: 2, providers: {} };
|
|
3568
4973
|
}
|
|
3569
4974
|
let document;
|
|
3570
4975
|
try {
|
|
3571
|
-
document = JSON.parse(
|
|
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 =
|
|
3598
|
-
|
|
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 =
|
|
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
|
|
3622
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
3623
|
-
import { basename as
|
|
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 (!
|
|
5059
|
+
if (!existsSync6(this.path)) {
|
|
3655
5060
|
return { version: 1, providers: {} };
|
|
3656
5061
|
}
|
|
3657
5062
|
let document;
|
|
3658
5063
|
try {
|
|
3659
|
-
document = JSON.parse(
|
|
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 =
|
|
3682
|
-
|
|
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 =
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
4763
|
-
import { randomUUID as
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
5529
|
-
|
|
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
|
|
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 = [
|
|
8063
|
-
if (!fits(leftItems, right, width) &&
|
|
8064
|
-
leftItems = leftItems.filter((value) => value !==
|
|
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
|
|
8101
|
-
|
|
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"
|
|
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" ? {
|
|
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
|
-
|
|
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
|
-
#
|
|
10677
|
+
#createBlockComponent(block) {
|
|
9064
10678
|
switch (block.kind) {
|
|
9065
10679
|
case "assistant":
|
|
9066
|
-
return new AssistantMessage(block)
|
|
10680
|
+
return new AssistantMessage(block);
|
|
9067
10681
|
case "user":
|
|
9068
|
-
return new UserMessage(block)
|
|
10682
|
+
return new UserMessage(block);
|
|
9069
10683
|
case "thinking":
|
|
9070
|
-
return new ThinkingMessage(block)
|
|
10684
|
+
return new ThinkingMessage(block);
|
|
9071
10685
|
case "tool":
|
|
9072
|
-
return new ToolMessage(block)
|
|
10686
|
+
return new ToolMessage(block);
|
|
9073
10687
|
case "notice":
|
|
9074
|
-
return new NoticeMessage(block)
|
|
10688
|
+
return new NoticeMessage(block);
|
|
9075
10689
|
case "welcome":
|
|
9076
|
-
return new WelcomeMessage(block)
|
|
10690
|
+
return new WelcomeMessage(block);
|
|
9077
10691
|
case "help":
|
|
9078
|
-
return new HelpView(block)
|
|
10692
|
+
return new HelpView(block);
|
|
9079
10693
|
case "provider_list":
|
|
9080
|
-
return new ProviderStatusView(block)
|
|
10694
|
+
return new ProviderStatusView(block);
|
|
9081
10695
|
case "provider_detail":
|
|
9082
|
-
return new ProviderDetailView(block.provider)
|
|
10696
|
+
return new ProviderDetailView(block.provider);
|
|
9083
10697
|
case "queue_status":
|
|
9084
|
-
return new QueueStatusView(block.queue)
|
|
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
|
-
|
|
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(...
|
|
11233
|
+
lines.push(...rendered.lines);
|
|
11234
|
+
rowOffset += rendered.lines.length;
|
|
9598
11235
|
}
|
|
9599
|
-
return {
|
|
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((
|
|
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((
|
|
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((
|
|
10332
|
-
this.#exitResolve =
|
|
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.#
|
|
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((
|
|
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 =
|
|
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:
|
|
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
|
|
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((
|
|
11441
|
-
release =
|
|
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 =
|
|
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
|
|
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((
|
|
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((
|
|
11811
|
-
child.once("spawn", () =>
|
|
11812
|
-
child.once("error", (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((
|
|
11870
|
-
child.once("exit", (code2, 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 =
|
|
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: "/
|
|
12369
|
-
description: "\
|
|
12370
|
-
usage: "/
|
|
12371
|
-
handler: (args) => this.
|
|
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
|
-
|
|
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: /
|
|
14392
|
+
this.notice("Usage: /clone", tones.invalid);
|
|
12495
14393
|
return true;
|
|
12496
14394
|
}
|
|
12497
|
-
const
|
|
12498
|
-
if (
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
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.
|
|
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((
|
|
13731
|
-
wake =
|
|
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((
|
|
13778
|
-
setTimeout(
|
|
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((
|
|
13784
|
-
timer = setTimeout(() =>
|
|
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(
|
|
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 ??
|
|
16073
|
+
options.credentialsPath ?? join6(dirname4(configPath), "credentials.json")
|
|
14007
16074
|
);
|
|
14008
16075
|
const modelCatalogStore = new ModelCatalogStore(
|
|
14009
|
-
options.modelsPath ??
|
|
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: ${
|
|
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 (
|
|
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
|
-
|
|
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:
|
|
14190
|
-
|
|
14191
|
-
|
|
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 =
|
|
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) {
|