@jun133/kitty 0.0.8 → 0.0.9
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/README.md +59 -20
- package/dist/App-6FETP3LH.mjs +521 -0
- package/dist/chunk-3KMC6H5K.mjs +2701 -0
- package/dist/chunk-4BN45TQG.mjs +654 -0
- package/dist/chunk-6NJJLOY3.mjs +2129 -0
- package/dist/chunk-DFDOKON5.mjs +530 -0
- package/dist/chunk-ELBEXOR7.mjs +10020 -0
- package/dist/chunk-YSWK3BGL.mjs +84 -0
- package/dist/cli.js +1321 -655
- package/dist/cli.js.map +1 -1
- package/dist/interactive-KLW4JL7R.mjs +340 -0
- package/dist/oneShot-YHDMPFQM.mjs +54 -0
- package/dist/session-XKWJHRVY.mjs +66 -0
- package/dist/tui.mjs +728 -0
- package/package.json +8 -2
|
@@ -0,0 +1,2129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SessionStore,
|
|
3
|
+
parseRuntimeMemoryAssetMetadata
|
|
4
|
+
} from "./chunk-4BN45TQG.mjs";
|
|
5
|
+
import {
|
|
6
|
+
BackgroundExecutionStore,
|
|
7
|
+
ControlPlaneLedger,
|
|
8
|
+
EXTENSION_ENV_KEYS,
|
|
9
|
+
ExecutionStore,
|
|
10
|
+
SessionEventStore,
|
|
11
|
+
buildProjectMap,
|
|
12
|
+
createRuntimeUiEvent,
|
|
13
|
+
formatRuntimeUiEventLine,
|
|
14
|
+
getErrorMessage,
|
|
15
|
+
isProcessAlive,
|
|
16
|
+
loadProjectContext,
|
|
17
|
+
reconcileBackgroundExecutions,
|
|
18
|
+
resolveProjectRoots,
|
|
19
|
+
runHostTurn,
|
|
20
|
+
summarizeExecution,
|
|
21
|
+
summarizeExecutionSet,
|
|
22
|
+
terminateBackgroundExecution,
|
|
23
|
+
terminatePid,
|
|
24
|
+
writeStderrLine,
|
|
25
|
+
writeStdoutLine
|
|
26
|
+
} from "./chunk-ELBEXOR7.mjs";
|
|
27
|
+
import {
|
|
28
|
+
PRESERVED_PROJECT_STATE_ENTRY_NAMES,
|
|
29
|
+
PROJECT_STATE_DIR_NAME,
|
|
30
|
+
PROJECT_STATE_ENV_EXAMPLE_FILE_NAME,
|
|
31
|
+
PROJECT_STATE_ENV_FILE_NAME,
|
|
32
|
+
PROJECT_STATE_IGNORE_FILE_NAME,
|
|
33
|
+
getProjectStatePaths
|
|
34
|
+
} from "./chunk-3KMC6H5K.mjs";
|
|
35
|
+
|
|
36
|
+
// src/observability/terminalLog.ts
|
|
37
|
+
import fs from "fs";
|
|
38
|
+
import path from "path";
|
|
39
|
+
var outputMirrorSuppressDepth = 0;
|
|
40
|
+
var outputMirrorWriteCount = 0;
|
|
41
|
+
function createTerminalLogWriter(rootDir, sessionId, now = /* @__PURE__ */ new Date()) {
|
|
42
|
+
const timestamp = now.toISOString();
|
|
43
|
+
const date = timestamp.slice(0, 10).replaceAll("-", "");
|
|
44
|
+
const terminalDir = path.join(getProjectStatePaths(rootDir).observabilityDir, "terminal", date);
|
|
45
|
+
fs.mkdirSync(terminalDir, { recursive: true });
|
|
46
|
+
const logPath = path.join(terminalDir, `${safePathPart(sessionId)}.log`);
|
|
47
|
+
return {
|
|
48
|
+
write(text) {
|
|
49
|
+
fs.appendFileSync(logPath, text, "utf8");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function mirrorProcessOutputToTerminalLog(writer) {
|
|
54
|
+
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
55
|
+
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
56
|
+
const writeSync = fs.writeSync;
|
|
57
|
+
let active = true;
|
|
58
|
+
process.stdout.write = ((chunk, ...args) => {
|
|
59
|
+
if (active && outputMirrorSuppressDepth === 0) {
|
|
60
|
+
writeMirroredProcessChunk(writer, chunk);
|
|
61
|
+
}
|
|
62
|
+
return stdoutWrite(chunk, ...args);
|
|
63
|
+
});
|
|
64
|
+
process.stderr.write = ((chunk, ...args) => {
|
|
65
|
+
if (active && outputMirrorSuppressDepth === 0) {
|
|
66
|
+
writeMirroredProcessChunk(writer, chunk);
|
|
67
|
+
}
|
|
68
|
+
return stderrWrite(chunk, ...args);
|
|
69
|
+
});
|
|
70
|
+
fs.writeSync = ((fd, buffer, ...args) => {
|
|
71
|
+
if (active && outputMirrorSuppressDepth === 0 && (fd === 1 || fd === 2)) {
|
|
72
|
+
writeMirroredProcessChunk(writer, buffer);
|
|
73
|
+
}
|
|
74
|
+
return writeSync(fd, buffer, ...args);
|
|
75
|
+
});
|
|
76
|
+
return () => {
|
|
77
|
+
active = false;
|
|
78
|
+
process.stdout.write = stdoutWrite;
|
|
79
|
+
process.stderr.write = stderrWrite;
|
|
80
|
+
fs.writeSync = writeSync;
|
|
81
|
+
writer.dispose?.();
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function safePathPart(value) {
|
|
85
|
+
const normalized = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
86
|
+
return normalized || `session-${process.pid}`;
|
|
87
|
+
}
|
|
88
|
+
function mirrorInteractionShellToTerminalLog(shell, writer) {
|
|
89
|
+
return {
|
|
90
|
+
input: mirrorInput(shell.input, writer),
|
|
91
|
+
output: mirrorOutput(shell.output, writer),
|
|
92
|
+
createTurnDisplay(options) {
|
|
93
|
+
return mirrorTurnDisplay(shell.createTurnDisplay(options), writer);
|
|
94
|
+
},
|
|
95
|
+
dispose() {
|
|
96
|
+
shell.dispose?.();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function mirrorOutput(output, writer) {
|
|
101
|
+
return {
|
|
102
|
+
plain(text) {
|
|
103
|
+
writeOutputAndForward(writer, `${text}
|
|
104
|
+
`, () => output.plain(text));
|
|
105
|
+
},
|
|
106
|
+
info(text) {
|
|
107
|
+
writeOutputAndForward(writer, `${text}
|
|
108
|
+
`, () => output.info(text));
|
|
109
|
+
},
|
|
110
|
+
warn(text) {
|
|
111
|
+
writeOutputAndForward(writer, `${text}
|
|
112
|
+
`, () => output.warn(text));
|
|
113
|
+
},
|
|
114
|
+
error(text) {
|
|
115
|
+
writeOutputAndForward(writer, `${text}
|
|
116
|
+
`, () => output.error(text));
|
|
117
|
+
},
|
|
118
|
+
dim(text) {
|
|
119
|
+
writeOutputAndForward(writer, `${text}
|
|
120
|
+
`, () => output.dim(text));
|
|
121
|
+
},
|
|
122
|
+
heading(text) {
|
|
123
|
+
writeOutputAndForward(writer, `${text}
|
|
124
|
+
`, () => output.heading(text));
|
|
125
|
+
},
|
|
126
|
+
interrupt(text) {
|
|
127
|
+
writeOutputAndForward(writer, `${text}
|
|
128
|
+
`, () => output.interrupt(text));
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function writeOutputAndForward(writer, text, forward) {
|
|
133
|
+
outputMirrorWriteCount += 1;
|
|
134
|
+
writer.write(text);
|
|
135
|
+
outputMirrorSuppressDepth += 1;
|
|
136
|
+
try {
|
|
137
|
+
forward();
|
|
138
|
+
} finally {
|
|
139
|
+
outputMirrorSuppressDepth -= 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function mirrorTurnDisplay(display, writer) {
|
|
143
|
+
return {
|
|
144
|
+
callbacks: {
|
|
145
|
+
...display.callbacks,
|
|
146
|
+
onAssistantDelta(delta) {
|
|
147
|
+
forwardWithFallback(writer, () => display.callbacks.onAssistantDelta?.(delta), delta);
|
|
148
|
+
},
|
|
149
|
+
onAssistantText(text) {
|
|
150
|
+
forwardWithFallback(writer, () => display.callbacks.onAssistantText?.(text), text);
|
|
151
|
+
},
|
|
152
|
+
onAssistantDone(text) {
|
|
153
|
+
display.callbacks.onAssistantDone?.(text);
|
|
154
|
+
},
|
|
155
|
+
onReasoningDelta(delta) {
|
|
156
|
+
forwardWithFallback(writer, () => display.callbacks.onReasoningDelta?.(delta), delta);
|
|
157
|
+
},
|
|
158
|
+
onReasoning(text) {
|
|
159
|
+
forwardWithFallback(writer, () => display.callbacks.onReasoning?.(text), text);
|
|
160
|
+
},
|
|
161
|
+
onStatus(message) {
|
|
162
|
+
forwardWithFallback(writer, () => display.callbacks.onStatus?.(message), `${message}
|
|
163
|
+
`);
|
|
164
|
+
},
|
|
165
|
+
onToolCall(name, args) {
|
|
166
|
+
forwardWithFallback(writer, () => display.callbacks.onToolCall?.(name, args), formatRuntimeUiEventLine(createRuntimeUiEvent({
|
|
167
|
+
channel: "lead",
|
|
168
|
+
kind: "tool_call",
|
|
169
|
+
toolName: name
|
|
170
|
+
})));
|
|
171
|
+
},
|
|
172
|
+
onToolResult(name, output) {
|
|
173
|
+
forwardWithFallback(writer, () => display.callbacks.onToolResult?.(name, output), formatRuntimeUiEventLine(createRuntimeUiEvent({
|
|
174
|
+
channel: "lead",
|
|
175
|
+
kind: "tool_result",
|
|
176
|
+
toolName: name
|
|
177
|
+
})));
|
|
178
|
+
},
|
|
179
|
+
onToolError(name, error) {
|
|
180
|
+
forwardWithFallback(writer, () => display.callbacks.onToolError?.(name, error), formatRuntimeUiEventLine(createRuntimeUiEvent({
|
|
181
|
+
channel: "lead",
|
|
182
|
+
kind: "tool_error",
|
|
183
|
+
toolName: name
|
|
184
|
+
})));
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
flush() {
|
|
188
|
+
display.flush();
|
|
189
|
+
},
|
|
190
|
+
dispose() {
|
|
191
|
+
display.dispose();
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function forwardWithFallback(writer, forward, fallback) {
|
|
196
|
+
const before = outputMirrorWriteCount;
|
|
197
|
+
forward();
|
|
198
|
+
if (outputMirrorWriteCount === before && fallback.length > 0) {
|
|
199
|
+
outputMirrorWriteCount += 1;
|
|
200
|
+
writer.write(`${fallback}
|
|
201
|
+
`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function mirrorInput(input, writer) {
|
|
205
|
+
return {
|
|
206
|
+
async readInput(promptLabel) {
|
|
207
|
+
const result = await input.readInput(promptLabel);
|
|
208
|
+
if (result.kind === "submit") {
|
|
209
|
+
writer.write(`${promptLabel ?? "> "}${result.value}
|
|
210
|
+
`);
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
},
|
|
214
|
+
bindInterrupt(handler) {
|
|
215
|
+
return input.bindInterrupt(handler);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function bufferToText(chunk) {
|
|
220
|
+
if (typeof chunk === "string") {
|
|
221
|
+
return chunk;
|
|
222
|
+
}
|
|
223
|
+
if (Buffer.isBuffer(chunk)) {
|
|
224
|
+
return chunk.toString("utf8");
|
|
225
|
+
}
|
|
226
|
+
return String(chunk ?? "");
|
|
227
|
+
}
|
|
228
|
+
function writeMirroredProcessChunk(writer, chunk) {
|
|
229
|
+
const text = bufferToText(chunk);
|
|
230
|
+
if (isTransientTerminalFrame(text)) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
outputMirrorWriteCount += 1;
|
|
234
|
+
writer.write(text);
|
|
235
|
+
}
|
|
236
|
+
function isTransientTerminalFrame(text) {
|
|
237
|
+
const normalized = stripAnsi(text).replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
238
|
+
return /^\r?\[[ ■]{4}\] thinking\s*$/.test(normalized);
|
|
239
|
+
}
|
|
240
|
+
function stripAnsi(value) {
|
|
241
|
+
return value.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/interaction/sessionDriver.ts
|
|
245
|
+
import process3 from "process";
|
|
246
|
+
|
|
247
|
+
// src/execution/kinds.ts
|
|
248
|
+
var EXECUTION_KINDS = ["background", "subagent"];
|
|
249
|
+
|
|
250
|
+
// src/execution/lifecycle.ts
|
|
251
|
+
function collectRunningExecutionProcesses(rootDir, cwd) {
|
|
252
|
+
reconcileRunningExecutions(rootDir);
|
|
253
|
+
return new ExecutionStore(rootDir).list({ kinds: EXECUTION_KINDS, statuses: ["running"], cwd }).filter((execution) => typeof execution.pid === "number" && execution.pid > 0).map((execution) => ({
|
|
254
|
+
kind: execution.kind,
|
|
255
|
+
id: execution.id,
|
|
256
|
+
pid: execution.pid,
|
|
257
|
+
summary: formatRunningExecutionSummary(execution)
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
function terminateRunningExecutionProcesses(rootDir, processes) {
|
|
261
|
+
const terminatedPids = [];
|
|
262
|
+
const failedPids = [];
|
|
263
|
+
for (const processInfo of processes) {
|
|
264
|
+
try {
|
|
265
|
+
terminateRunningExecution(rootDir, processInfo);
|
|
266
|
+
terminatedPids.push(processInfo.pid);
|
|
267
|
+
} catch {
|
|
268
|
+
failedPids.push(processInfo.pid);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { terminatedPids, failedPids };
|
|
272
|
+
}
|
|
273
|
+
function reconcileRunningExecutions(rootDir) {
|
|
274
|
+
reconcileBackgroundExecutions(rootDir);
|
|
275
|
+
const store = new ExecutionStore(rootDir);
|
|
276
|
+
for (const execution of store.list({ kinds: EXECUTION_KINDS, statuses: ["running"] })) {
|
|
277
|
+
if (typeof execution.pid !== "number" || isProcessAlive(execution.pid)) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
store.close(execution.id, {
|
|
281
|
+
status: "stale",
|
|
282
|
+
summary: `${execution.kind} process disappeared before reporting completion: pid=${execution.pid}`
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function terminateRunningExecution(rootDir, processInfo) {
|
|
287
|
+
if (processInfo.kind === "background") {
|
|
288
|
+
terminateBackgroundExecution(rootDir, processInfo.id);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
terminatePid(processInfo.pid);
|
|
292
|
+
new ExecutionStore(rootDir).close(processInfo.id, {
|
|
293
|
+
status: "aborted",
|
|
294
|
+
summary: `${processInfo.kind} execution terminated by host lifecycle.`
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function formatRunningExecutionSummary(execution) {
|
|
298
|
+
const subject = execution.kind === "background" ? execution.command : execution.actorName;
|
|
299
|
+
return `${execution.kind} ${execution.id} pid=${execution.pid ?? ""} ${subject ?? ""}`.trim();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/interaction/exitGuard.ts
|
|
303
|
+
var defaultInteractiveExitGuard = {
|
|
304
|
+
collectRunningProcesses,
|
|
305
|
+
terminateProcesses
|
|
306
|
+
};
|
|
307
|
+
async function collectRunningProcesses(cwd) {
|
|
308
|
+
const roots = await resolveProjectRoots(cwd);
|
|
309
|
+
return collectRunningExecutionProcesses(roots.stateRootDir, cwd);
|
|
310
|
+
}
|
|
311
|
+
async function terminateProcesses(processes, cwd) {
|
|
312
|
+
const roots = await resolveProjectRoots(cwd);
|
|
313
|
+
return terminateRunningExecutionProcesses(roots.stateRootDir, processes);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/runtime/scene.ts
|
|
317
|
+
function buildRuntimeScene(status) {
|
|
318
|
+
const executions = status.executions.active.map(buildExecutionScene);
|
|
319
|
+
const blockedExecutions = executions.filter((execution) => execution.risk === "blocked");
|
|
320
|
+
const watchExecutions = executions.filter((execution) => execution.risk === "watch");
|
|
321
|
+
const activeBackground = executions.filter((execution) => execution.kind === "background");
|
|
322
|
+
const blockedBackground = activeBackground.filter((execution) => execution.risk !== "none");
|
|
323
|
+
return {
|
|
324
|
+
headline: buildHeadline(status, blockedExecutions, watchExecutions),
|
|
325
|
+
focus: readFocus(status),
|
|
326
|
+
nextAction: readNextAction(status, blockedExecutions, watchExecutions),
|
|
327
|
+
blocked: readBlocked(blockedExecutions),
|
|
328
|
+
cost: readCost(status),
|
|
329
|
+
recovery: readRecovery(status, executions),
|
|
330
|
+
skills: {
|
|
331
|
+
ready: status.skills.ready,
|
|
332
|
+
total: status.skills.total,
|
|
333
|
+
nextAction: readSkillsNextAction(status)
|
|
334
|
+
},
|
|
335
|
+
memory: {
|
|
336
|
+
assets: status.memory.assets.length,
|
|
337
|
+
latestSessionMemory: Boolean(status.sessions.latest?.hasMemory),
|
|
338
|
+
nextAction: readMemoryNextAction(status)
|
|
339
|
+
},
|
|
340
|
+
background: {
|
|
341
|
+
active: activeBackground.length,
|
|
342
|
+
blocked: blockedBackground.length,
|
|
343
|
+
nextAction: readBackgroundNextAction(activeBackground)
|
|
344
|
+
},
|
|
345
|
+
executions
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function buildExecutionScene(execution) {
|
|
349
|
+
const risk = readExecutionRisk(execution);
|
|
350
|
+
return {
|
|
351
|
+
id: execution.id,
|
|
352
|
+
kind: execution.kind,
|
|
353
|
+
status: execution.status,
|
|
354
|
+
health: execution.health?.message ?? `Execution is ${execution.status}.`,
|
|
355
|
+
risk,
|
|
356
|
+
summary: readExecutionSummary(execution),
|
|
357
|
+
nextAction: readExecutionNextAction(execution, risk),
|
|
358
|
+
lastOutput: execution.outputPreview ? truncateText(execution.outputPreview, 160) : void 0
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function buildHeadline(status, blockedExecutions, watchExecutions) {
|
|
362
|
+
if (!status.sessions.latest) {
|
|
363
|
+
return "No active session yet.";
|
|
364
|
+
}
|
|
365
|
+
if (blockedExecutions.length > 0) {
|
|
366
|
+
return `${blockedExecutions.length} execution(s) need attention.`;
|
|
367
|
+
}
|
|
368
|
+
if (watchExecutions.length > 0) {
|
|
369
|
+
return `${watchExecutions.length} execution(s) should be watched.`;
|
|
370
|
+
}
|
|
371
|
+
if (status.executions.active.length > 0) {
|
|
372
|
+
return `${status.executions.active.length} execution(s) are running.`;
|
|
373
|
+
}
|
|
374
|
+
return "Ready to continue the latest session.";
|
|
375
|
+
}
|
|
376
|
+
function readFocus(status) {
|
|
377
|
+
const focus = status.sessions.latest?.focus;
|
|
378
|
+
if (focus) {
|
|
379
|
+
return truncateText(focus, 120);
|
|
380
|
+
}
|
|
381
|
+
const title = status.sessions.latest?.title;
|
|
382
|
+
if (title) {
|
|
383
|
+
return truncateText(title, 120);
|
|
384
|
+
}
|
|
385
|
+
return "none";
|
|
386
|
+
}
|
|
387
|
+
function readNextAction(status, blockedExecutions, watchExecutions) {
|
|
388
|
+
const urgent = blockedExecutions[0] ?? watchExecutions[0];
|
|
389
|
+
if (urgent) {
|
|
390
|
+
return urgent.nextAction;
|
|
391
|
+
}
|
|
392
|
+
if (status.executions.active.length > 0) {
|
|
393
|
+
return "Let active work finish, or inspect it with `kitty status` / `kitty background`.";
|
|
394
|
+
}
|
|
395
|
+
if (!status.sessions.latest) {
|
|
396
|
+
return "Start a session with `kitty`.";
|
|
397
|
+
}
|
|
398
|
+
return "Continue from the current session focus.";
|
|
399
|
+
}
|
|
400
|
+
function readBlocked(blockedExecutions) {
|
|
401
|
+
if (blockedExecutions.length === 0) {
|
|
402
|
+
return "no";
|
|
403
|
+
}
|
|
404
|
+
return blockedExecutions.slice(0, 3).map((execution) => `${execution.kind} ${execution.id}: ${execution.health}`).join(" | ");
|
|
405
|
+
}
|
|
406
|
+
function readCost(status) {
|
|
407
|
+
const budget = status.sessions.latest?.contextBudget;
|
|
408
|
+
const latest = status.modelRequests.recent[0];
|
|
409
|
+
const budgetText = budget ? `${Math.round(budget.usageRatio * 100)}% context${budget.compressed ? ", compressed" : ""}` : "context unknown";
|
|
410
|
+
const layout = budget?.cacheLayout;
|
|
411
|
+
const layoutText = layout ? `stable ${readStableRatio(layout.stablePrefixChars, layout.volatileTailChars)}` : "cache layout unknown";
|
|
412
|
+
const usageText = latest?.usage ? readUsageCost(latest.usage) : latest ? "provider usage unavailable" : "no model request yet";
|
|
413
|
+
return `${budgetText}; ${layoutText}; ${usageText}`;
|
|
414
|
+
}
|
|
415
|
+
function readStableRatio(stableChars, volatileChars) {
|
|
416
|
+
const total = stableChars + volatileChars;
|
|
417
|
+
return total > 0 ? `${Math.round(stableChars / total * 100)}%` : "unknown";
|
|
418
|
+
}
|
|
419
|
+
function readUsageCost(usage) {
|
|
420
|
+
const cached = usage.cacheHitTokens ?? usage.cacheReadTokens;
|
|
421
|
+
const hitRate = usage.cacheHitRate === void 0 ? void 0 : `${Math.round(usage.cacheHitRate * 100)}% hit`;
|
|
422
|
+
return [
|
|
423
|
+
usage.totalTokens === void 0 ? void 0 : `${usage.totalTokens} tokens`,
|
|
424
|
+
cached === void 0 ? "cache unknown" : `${cached} cached`,
|
|
425
|
+
hitRate
|
|
426
|
+
].filter(Boolean).join(", ");
|
|
427
|
+
}
|
|
428
|
+
function readRecovery(status, executions) {
|
|
429
|
+
const risky = executions.filter((execution) => execution.risk !== "none").length;
|
|
430
|
+
if (risky > 0) {
|
|
431
|
+
return `${risky} execution(s) need recovery attention.`;
|
|
432
|
+
}
|
|
433
|
+
if (status.wakeSignals.recent.length > 0) {
|
|
434
|
+
return `${status.wakeSignals.recent.length} wake signal(s) recorded.`;
|
|
435
|
+
}
|
|
436
|
+
return "no recovery action needed";
|
|
437
|
+
}
|
|
438
|
+
function readSkillsNextAction(status) {
|
|
439
|
+
if (status.skills.total === 0) {
|
|
440
|
+
return "No runtime skills discovered.";
|
|
441
|
+
}
|
|
442
|
+
if (status.skills.needsAttention.length > 0) {
|
|
443
|
+
return "Inspect skill issues before relying on those skills.";
|
|
444
|
+
}
|
|
445
|
+
return "Skills are ready; load full skill content only when needed.";
|
|
446
|
+
}
|
|
447
|
+
function readMemoryNextAction(status) {
|
|
448
|
+
if (!status.sessions.latest) {
|
|
449
|
+
return "No session memory yet.";
|
|
450
|
+
}
|
|
451
|
+
if (!status.sessions.latest.hasMemory && status.memory.assets.length === 0) {
|
|
452
|
+
return "Continue the session until useful memory is saved.";
|
|
453
|
+
}
|
|
454
|
+
if (!status.sessions.latest.hasMemory && status.memory.assets.length > 0) {
|
|
455
|
+
return "Review memory assets when prior evidence is needed.";
|
|
456
|
+
}
|
|
457
|
+
return "Session memory is available; use assets only when needed.";
|
|
458
|
+
}
|
|
459
|
+
function readBackgroundNextAction(backgrounds) {
|
|
460
|
+
if (backgrounds.length === 0) {
|
|
461
|
+
return "No active background work.";
|
|
462
|
+
}
|
|
463
|
+
const blocked = backgrounds.find((execution) => execution.risk === "blocked");
|
|
464
|
+
if (blocked) {
|
|
465
|
+
return blocked.nextAction;
|
|
466
|
+
}
|
|
467
|
+
const watch = backgrounds.find((execution) => execution.risk === "watch");
|
|
468
|
+
if (watch) {
|
|
469
|
+
return watch.nextAction;
|
|
470
|
+
}
|
|
471
|
+
return "Background work is running; wait or inspect latest output.";
|
|
472
|
+
}
|
|
473
|
+
function readExecutionRisk(execution) {
|
|
474
|
+
switch (execution.health?.state) {
|
|
475
|
+
case "deadline_passed":
|
|
476
|
+
case "stale":
|
|
477
|
+
return "blocked";
|
|
478
|
+
case "no_output":
|
|
479
|
+
return "watch";
|
|
480
|
+
case "running":
|
|
481
|
+
case "settled":
|
|
482
|
+
case void 0:
|
|
483
|
+
return "none";
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function readExecutionSummary(execution) {
|
|
487
|
+
if (execution.assignment?.objective) {
|
|
488
|
+
return truncateText(execution.assignment.objective, 120);
|
|
489
|
+
}
|
|
490
|
+
if (execution.summary) {
|
|
491
|
+
return truncateText(execution.summary, 120);
|
|
492
|
+
}
|
|
493
|
+
if (execution.command) {
|
|
494
|
+
return truncateText(execution.command, 120);
|
|
495
|
+
}
|
|
496
|
+
return `${execution.kind} execution`;
|
|
497
|
+
}
|
|
498
|
+
function readExecutionNextAction(execution, risk) {
|
|
499
|
+
if (execution.kind === "background") {
|
|
500
|
+
if (risk === "blocked") {
|
|
501
|
+
return `Inspect or stop with \`kitty background stop ${execution.id}\`.`;
|
|
502
|
+
}
|
|
503
|
+
if (risk === "watch") {
|
|
504
|
+
return `Wait for first output or inspect with \`kitty background wait ${execution.id}\`.`;
|
|
505
|
+
}
|
|
506
|
+
return `Inspect with \`kitty background wait ${execution.id}\` if you need the result now.`;
|
|
507
|
+
}
|
|
508
|
+
if (risk === "blocked") {
|
|
509
|
+
return `Inspect execution ${execution.id} in status before continuing.`;
|
|
510
|
+
}
|
|
511
|
+
if (risk === "watch") {
|
|
512
|
+
return `Watch execution ${execution.id} for output or deadline.`;
|
|
513
|
+
}
|
|
514
|
+
if (execution.waitPolicy === "block_lead_until_complete") {
|
|
515
|
+
return "Lead should wait for this execution to finish.";
|
|
516
|
+
}
|
|
517
|
+
return "Execution is active.";
|
|
518
|
+
}
|
|
519
|
+
function truncateText(value, maxChars) {
|
|
520
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
521
|
+
if (normalized.length <= maxChars) {
|
|
522
|
+
return normalized;
|
|
523
|
+
}
|
|
524
|
+
return `${normalized.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/utils/console.ts
|
|
528
|
+
import chalk from "chalk";
|
|
529
|
+
var ANSI_RESET = "\x1B[0m";
|
|
530
|
+
var ui = {
|
|
531
|
+
info(message) {
|
|
532
|
+
writeStdoutLine(`${chalk.cyan("[i]")} ${message}`);
|
|
533
|
+
},
|
|
534
|
+
success(message) {
|
|
535
|
+
writeStdoutLine(`${chalk.green("[ok]")} ${message}`);
|
|
536
|
+
},
|
|
537
|
+
warn(message) {
|
|
538
|
+
writeStdoutLine(`${chalk.yellow("!")} ${message}`);
|
|
539
|
+
},
|
|
540
|
+
error(message) {
|
|
541
|
+
writeStderrLine(`${chalk.red("[x]")} ${message}`);
|
|
542
|
+
},
|
|
543
|
+
dim(message) {
|
|
544
|
+
writeStdoutLine(`${chalk.gray(message)}${ANSI_RESET}`);
|
|
545
|
+
},
|
|
546
|
+
heading(message) {
|
|
547
|
+
writeStdoutLine(chalk.bold(message));
|
|
548
|
+
},
|
|
549
|
+
plain(message) {
|
|
550
|
+
writeStdoutLine(message);
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// src/cli/cliValues.ts
|
|
555
|
+
function truncateCliValue(value, maxChars) {
|
|
556
|
+
if (value.length <= maxChars) {
|
|
557
|
+
return value;
|
|
558
|
+
}
|
|
559
|
+
return `${value.slice(0, maxChars)}...`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/cli/commands/background.ts
|
|
563
|
+
function formatBackgroundExecution(execution) {
|
|
564
|
+
const scene = buildExecutionScene(execution);
|
|
565
|
+
return [
|
|
566
|
+
execution.id,
|
|
567
|
+
execution.status,
|
|
568
|
+
`risk=${scene.risk}`,
|
|
569
|
+
execution.pid === void 0 ? void 0 : `pid=${execution.pid}`,
|
|
570
|
+
`health=${truncateCliValue(scene.health, 90)}`,
|
|
571
|
+
execution.deadlineAt ? `deadline=${execution.deadlineAt}` : void 0,
|
|
572
|
+
`summary=${truncateCliValue(scene.summary, 90)}`,
|
|
573
|
+
`next=${scene.nextAction}`,
|
|
574
|
+
scene.lastOutput ? `lastOutput=${truncateCliValue(scene.lastOutput, 120)}` : void 0
|
|
575
|
+
].filter(Boolean).join(" ");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/config/preflight.ts
|
|
579
|
+
import fs2 from "fs/promises";
|
|
580
|
+
import path2 from "path";
|
|
581
|
+
import dotenv from "dotenv";
|
|
582
|
+
|
|
583
|
+
// src/config/envKeys.ts
|
|
584
|
+
var KITTY_BASE_ENV = {
|
|
585
|
+
apiKey: "KITTY_API_KEY",
|
|
586
|
+
provider: "KITTY_PROVIDER",
|
|
587
|
+
baseUrl: "KITTY_BASE_URL",
|
|
588
|
+
model: "KITTY_MODEL",
|
|
589
|
+
profile: "KITTY_PROFILE",
|
|
590
|
+
thinking: "KITTY_THINKING",
|
|
591
|
+
reasoningEffort: "KITTY_REASONING_EFFORT",
|
|
592
|
+
maxOutputTokens: "KITTY_MAX_OUTPUT_TOKENS",
|
|
593
|
+
contextWindowMessages: "KITTY_CONTEXT_WINDOW_MESSAGES",
|
|
594
|
+
maxContextChars: "KITTY_MAX_CONTEXT_CHARS",
|
|
595
|
+
contextSummaryChars: "KITTY_CONTEXT_SUMMARY_CHARS",
|
|
596
|
+
maxReadBytes: "KITTY_MAX_READ_BYTES",
|
|
597
|
+
projectDocMaxBytes: "KITTY_PROJECT_DOC_MAX_BYTES",
|
|
598
|
+
commandStallTimeoutMs: "KITTY_COMMAND_STALL_TIMEOUT_MS",
|
|
599
|
+
showReasoning: "KITTY_SHOW_REASONING",
|
|
600
|
+
telegramToken: "KITTY_TELEGRAM_TOKEN",
|
|
601
|
+
telegramAllowedUserIds: "KITTY_TELEGRAM_ALLOWED_USER_IDS",
|
|
602
|
+
telegramApiBaseUrl: "KITTY_TELEGRAM_API_BASE_URL",
|
|
603
|
+
telegramProxyUrl: "KITTY_TELEGRAM_PROXY_URL",
|
|
604
|
+
telegramPollingTimeoutSeconds: "KITTY_TELEGRAM_POLLING_TIMEOUT_SECONDS",
|
|
605
|
+
telegramPollingLimit: "KITTY_TELEGRAM_POLLING_LIMIT",
|
|
606
|
+
telegramPollingRetryBackoffMs: "KITTY_TELEGRAM_POLLING_RETRY_BACKOFF_MS",
|
|
607
|
+
telegramMessageChunkChars: "KITTY_TELEGRAM_MESSAGE_CHUNK_CHARS",
|
|
608
|
+
telegramTypingIntervalMs: "KITTY_TELEGRAM_TYPING_INTERVAL_MS",
|
|
609
|
+
telegramDeliveryMaxRetries: "KITTY_TELEGRAM_DELIVERY_MAX_RETRIES",
|
|
610
|
+
telegramDeliveryBaseDelayMs: "KITTY_TELEGRAM_DELIVERY_BASE_DELAY_MS",
|
|
611
|
+
telegramDeliveryMaxDelayMs: "KITTY_TELEGRAM_DELIVERY_MAX_DELAY_MS"
|
|
612
|
+
};
|
|
613
|
+
var KITTY_ENV = {
|
|
614
|
+
...KITTY_BASE_ENV,
|
|
615
|
+
extensions: EXTENSION_ENV_KEYS
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/config/providerPresets.ts
|
|
619
|
+
var PROVIDER_PRESETS = [
|
|
620
|
+
{
|
|
621
|
+
label: "YLS Codex + GPT-5.4",
|
|
622
|
+
provider: "openai",
|
|
623
|
+
baseUrl: "https://code.ylsagi.com/codex",
|
|
624
|
+
model: "gpt-5.4",
|
|
625
|
+
thinking: "disabled",
|
|
626
|
+
reasoningEffort: "xhigh",
|
|
627
|
+
activeByDefault: false
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
label: "TTAPI + GPT-5.4",
|
|
631
|
+
provider: "openai",
|
|
632
|
+
baseUrl: "https://w.ciykj.cn",
|
|
633
|
+
model: "gpt-5.4",
|
|
634
|
+
thinking: "disabled",
|
|
635
|
+
reasoningEffort: "xhigh",
|
|
636
|
+
activeByDefault: false
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
label: "DeepSeek official V4",
|
|
640
|
+
provider: "deepseek",
|
|
641
|
+
baseUrl: "https://api.deepseek.com",
|
|
642
|
+
model: "deepseek-v4-flash",
|
|
643
|
+
thinking: "enabled",
|
|
644
|
+
reasoningEffort: "max",
|
|
645
|
+
activeByDefault: true
|
|
646
|
+
}
|
|
647
|
+
];
|
|
648
|
+
|
|
649
|
+
// src/config/preflight.ts
|
|
650
|
+
async function inspectConfigPreflight(rootDir) {
|
|
651
|
+
const normalizedRoot = path2.resolve(rootDir);
|
|
652
|
+
const kittyDir = path2.join(normalizedRoot, PROJECT_STATE_DIR_NAME);
|
|
653
|
+
const envPath = path2.join(kittyDir, PROJECT_STATE_ENV_FILE_NAME);
|
|
654
|
+
const files = await Promise.all([
|
|
655
|
+
inspectFile(kittyDir),
|
|
656
|
+
inspectFile(envPath),
|
|
657
|
+
inspectFile(path2.join(kittyDir, PROJECT_STATE_ENV_EXAMPLE_FILE_NAME)),
|
|
658
|
+
inspectFile(path2.join(kittyDir, PROJECT_STATE_IGNORE_FILE_NAME))
|
|
659
|
+
]);
|
|
660
|
+
const parsedEnv = await readEnvFile(envPath);
|
|
661
|
+
const expectedKeys = readExpectedEnvKeys();
|
|
662
|
+
const activeKeys = Object.keys(parsedEnv).sort();
|
|
663
|
+
const missingKeys = expectedKeys.filter((key) => !(key in parsedEnv));
|
|
664
|
+
const provider = parsedEnv[KITTY_ENV.provider] ?? "";
|
|
665
|
+
const model = parsedEnv[KITTY_ENV.model] ?? "";
|
|
666
|
+
const baseUrl = parsedEnv[KITTY_ENV.baseUrl] ?? "";
|
|
667
|
+
const providerPreset = readProviderPresetLabel({ provider, model, baseUrl });
|
|
668
|
+
const ready = files.every((file) => file.exists) && missingKeys.length === 0;
|
|
669
|
+
return {
|
|
670
|
+
rootDir: normalizedRoot,
|
|
671
|
+
kittyDir,
|
|
672
|
+
files,
|
|
673
|
+
env: {
|
|
674
|
+
activeKeys,
|
|
675
|
+
missingKeys,
|
|
676
|
+
providerPreset,
|
|
677
|
+
provider,
|
|
678
|
+
model,
|
|
679
|
+
baseUrl,
|
|
680
|
+
apiKeyPresent: Boolean(parsedEnv[KITTY_ENV.apiKey]?.trim())
|
|
681
|
+
},
|
|
682
|
+
ready,
|
|
683
|
+
nextSteps: buildPreflightNextSteps({
|
|
684
|
+
filesReady: files.every((file) => file.exists),
|
|
685
|
+
missingKeys,
|
|
686
|
+
apiKeyPresent: Boolean(parsedEnv[KITTY_ENV.apiKey]?.trim()),
|
|
687
|
+
ready
|
|
688
|
+
})
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function formatConfigPreflightReport(report) {
|
|
692
|
+
return [
|
|
693
|
+
`project: ${report.rootDir}`,
|
|
694
|
+
`state: ${report.kittyDir}`,
|
|
695
|
+
...report.files.map((file) => `file: ${file.exists ? "ok" : "missing"} ${file.path}`),
|
|
696
|
+
`env keys: ${report.env.activeKeys.length} active / ${readExpectedEnvKeys().length} expected`,
|
|
697
|
+
report.env.missingKeys.length > 0 ? `missing keys: ${report.env.missingKeys.join(", ")}` : "missing keys: none",
|
|
698
|
+
`provider: ${report.env.provider || "(missing)"}`,
|
|
699
|
+
`model: ${report.env.model || "(missing)"}`,
|
|
700
|
+
`baseUrl: ${report.env.baseUrl || "(missing)"}`,
|
|
701
|
+
`provider preset: ${formatProviderPresetFact(report)}`,
|
|
702
|
+
`api key: ${report.env.apiKeyPresent ? "present" : "missing"}`,
|
|
703
|
+
`preflight: ${report.ready ? "ready" : "not_ready"}`,
|
|
704
|
+
"next:",
|
|
705
|
+
...report.nextSteps.map((step) => `- ${step}`)
|
|
706
|
+
];
|
|
707
|
+
}
|
|
708
|
+
function buildPreflightNextSteps(input) {
|
|
709
|
+
if (!input.filesReady) {
|
|
710
|
+
return ["run `kitty init` to create the local .kitty files"];
|
|
711
|
+
}
|
|
712
|
+
if (input.missingKeys.length > 0) {
|
|
713
|
+
return ["open `.kitty/.env` and fill the missing keys", "rerun `kitty doctor`"];
|
|
714
|
+
}
|
|
715
|
+
if (!input.apiKeyPresent) {
|
|
716
|
+
return ["set `KITTY_API_KEY` in `.kitty/.env`", "rerun `kitty doctor`"];
|
|
717
|
+
}
|
|
718
|
+
if (input.ready) {
|
|
719
|
+
return ["run `kitty doctor` to verify provider connectivity", "start Kitty with `kitty`"];
|
|
720
|
+
}
|
|
721
|
+
return ["review `.kitty/.env`", "rerun `kitty doctor`"];
|
|
722
|
+
}
|
|
723
|
+
function readExpectedEnvKeys() {
|
|
724
|
+
return [
|
|
725
|
+
...Object.values(KITTY_BASE_ENV),
|
|
726
|
+
...Object.values(KITTY_ENV.extensions)
|
|
727
|
+
].sort();
|
|
728
|
+
}
|
|
729
|
+
async function inspectFile(targetPath) {
|
|
730
|
+
return {
|
|
731
|
+
path: targetPath,
|
|
732
|
+
exists: await exists(targetPath)
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
async function readEnvFile(envPath) {
|
|
736
|
+
try {
|
|
737
|
+
return dotenv.parse(await fs2.readFile(envPath, "utf8"));
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (error.code === "ENOENT") {
|
|
740
|
+
return {};
|
|
741
|
+
}
|
|
742
|
+
throw error;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function readProviderPresetLabel(input) {
|
|
746
|
+
return PROVIDER_PRESETS.find(
|
|
747
|
+
(preset) => preset.provider === input.provider && preset.model === input.model && preset.baseUrl === input.baseUrl
|
|
748
|
+
)?.label;
|
|
749
|
+
}
|
|
750
|
+
function formatProviderPresetFact(report) {
|
|
751
|
+
if (report.env.providerPreset) {
|
|
752
|
+
return report.env.providerPreset;
|
|
753
|
+
}
|
|
754
|
+
if (report.env.provider || report.env.model || report.env.baseUrl) {
|
|
755
|
+
return "custom";
|
|
756
|
+
}
|
|
757
|
+
return "missing";
|
|
758
|
+
}
|
|
759
|
+
async function exists(targetPath) {
|
|
760
|
+
try {
|
|
761
|
+
await fs2.access(targetPath);
|
|
762
|
+
return true;
|
|
763
|
+
} catch {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/runtime/status.ts
|
|
769
|
+
import fs7 from "fs/promises";
|
|
770
|
+
import path6 from "path";
|
|
771
|
+
|
|
772
|
+
// src/runtime/memory/store.ts
|
|
773
|
+
import fs3 from "fs/promises";
|
|
774
|
+
import path3 from "path";
|
|
775
|
+
async function listRuntimeMemoryAssets(rootDir) {
|
|
776
|
+
const paths = getProjectStatePaths(rootDir);
|
|
777
|
+
const assets = (await Promise.all([
|
|
778
|
+
listMemoryAssetsInDirectory(paths.rootDir, paths.evidenceMemoryDir, "evidence"),
|
|
779
|
+
listMemoryAssetsInDirectory(paths.rootDir, paths.projectMemoryDir, "project"),
|
|
780
|
+
listMemoryAssetsInDirectory(paths.rootDir, paths.sessionMemoryDir, "session"),
|
|
781
|
+
listMemoryAssetsInDirectory(paths.rootDir, paths.userMemoryDir, "user")
|
|
782
|
+
])).flat();
|
|
783
|
+
return assets.sort((left, right) => (right.updatedAt ?? "").localeCompare(left.updatedAt ?? ""));
|
|
784
|
+
}
|
|
785
|
+
async function listMemoryAssetsInDirectory(rootDir, memoryDir, kind) {
|
|
786
|
+
const entries = await fs3.readdir(memoryDir, { withFileTypes: true }).catch(() => []);
|
|
787
|
+
const assets = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map(async (entry) => {
|
|
788
|
+
const absolutePath = path3.join(memoryDir, entry.name);
|
|
789
|
+
const stat = await fs3.stat(absolutePath);
|
|
790
|
+
const body = await fs3.readFile(absolutePath, "utf8").catch(() => "");
|
|
791
|
+
const basename = entry.name.slice(0, -".md".length);
|
|
792
|
+
const id = kind === "session" ? basename : `${kind}/${basename}`;
|
|
793
|
+
const metadata = parseRuntimeMemoryAssetMetadata(body, { kind, basename });
|
|
794
|
+
return {
|
|
795
|
+
id,
|
|
796
|
+
kind: metadata.kind ?? kind,
|
|
797
|
+
title: metadata.title,
|
|
798
|
+
path: path3.relative(rootDir, absolutePath),
|
|
799
|
+
absolutePath,
|
|
800
|
+
updatedAt: metadata.updatedAt ?? stat.mtime.toISOString(),
|
|
801
|
+
size: stat.size,
|
|
802
|
+
evidenceRefs: metadata.evidenceRefs,
|
|
803
|
+
scope: metadata.scope,
|
|
804
|
+
tags: metadata.tags
|
|
805
|
+
};
|
|
806
|
+
}));
|
|
807
|
+
return assets;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/runtime/memory/search.ts
|
|
811
|
+
import fs4 from "fs/promises";
|
|
812
|
+
|
|
813
|
+
// src/runtime/memory/writer.ts
|
|
814
|
+
import fs5 from "fs/promises";
|
|
815
|
+
import path4 from "path";
|
|
816
|
+
|
|
817
|
+
// src/runtime/memory/sinks.ts
|
|
818
|
+
import fs6 from "fs/promises";
|
|
819
|
+
import path5 from "path";
|
|
820
|
+
|
|
821
|
+
// src/runtime/status.ts
|
|
822
|
+
var DEFAULT_RECENT_LIMIT = 10;
|
|
823
|
+
async function buildRuntimeStatus(rootDir) {
|
|
824
|
+
const paths = getProjectStatePaths(rootDir);
|
|
825
|
+
const sessionStore = new SessionStore(paths.sessionsDir, {
|
|
826
|
+
memorySessionsDir: paths.sessionMemoryDir
|
|
827
|
+
});
|
|
828
|
+
const [sessionRead, memoryAssets, control, projectMap, projectContext, modelRequests] = await Promise.all([
|
|
829
|
+
sessionStore.listReadable?.(DEFAULT_RECENT_LIMIT) ?? sessionStore.list(DEFAULT_RECENT_LIMIT).then((sessions2) => ({ sessions: sessions2, skipped: [] })),
|
|
830
|
+
listRuntimeMemoryAssets(paths.rootDir),
|
|
831
|
+
readControlPlaneStatus(paths.rootDir),
|
|
832
|
+
buildProjectMap(paths.rootDir),
|
|
833
|
+
loadProjectContext(paths.rootDir, { projectDocMaxBytes: 24576 }),
|
|
834
|
+
readRecentModelRequests(paths.observabilityEventsDir)
|
|
835
|
+
]);
|
|
836
|
+
const sessions = sessionRead.sessions.map(summarizeSession);
|
|
837
|
+
const taskLifecycle = sessions[0] ? readTaskLifecycleStatus(paths.rootDir, sessions[0].id) : void 0;
|
|
838
|
+
const statusWithoutScene = {
|
|
839
|
+
rootDir: paths.rootDir,
|
|
840
|
+
stateDir: paths.kittyDir,
|
|
841
|
+
sessions: {
|
|
842
|
+
total: sessions.length,
|
|
843
|
+
latest: sessions[0],
|
|
844
|
+
recent: sessions,
|
|
845
|
+
skipped: sessionRead.skipped.length
|
|
846
|
+
},
|
|
847
|
+
memory: {
|
|
848
|
+
assets: memoryAssets
|
|
849
|
+
},
|
|
850
|
+
skills: summarizeSkills(projectContext.skills),
|
|
851
|
+
projectMap: summarizeProjectMap(projectMap),
|
|
852
|
+
modelRequests: {
|
|
853
|
+
recent: modelRequests
|
|
854
|
+
},
|
|
855
|
+
taskLifecycle,
|
|
856
|
+
executions: control.executions,
|
|
857
|
+
wakeSignals: control.wakeSignals
|
|
858
|
+
};
|
|
859
|
+
return {
|
|
860
|
+
...statusWithoutScene,
|
|
861
|
+
scene: buildRuntimeScene(statusWithoutScene)
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function summarizeProjectMap(projectMap) {
|
|
865
|
+
return {
|
|
866
|
+
rootDir: projectMap.rootDir,
|
|
867
|
+
topLevelDirectories: projectMap.topLevelDirectories,
|
|
868
|
+
entryFiles: projectMap.entryFiles,
|
|
869
|
+
testDirectories: projectMap.testDirectories,
|
|
870
|
+
packageScripts: projectMap.packageScripts,
|
|
871
|
+
specDocuments: projectMap.specDocuments,
|
|
872
|
+
git: projectMap.git,
|
|
873
|
+
updatedAt: projectMap.updatedAt
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function summarizeSession(session) {
|
|
877
|
+
return {
|
|
878
|
+
id: session.id,
|
|
879
|
+
title: session.title,
|
|
880
|
+
cwd: session.cwd,
|
|
881
|
+
updatedAt: session.updatedAt,
|
|
882
|
+
messageCount: session.messageCount,
|
|
883
|
+
focus: session.taskState?.focus ?? session.checkpoint?.focus,
|
|
884
|
+
hasMemory: Boolean(session.sessionMemory?.summary.trim()),
|
|
885
|
+
contextBudget: session.contextBudget ? {
|
|
886
|
+
limitChars: session.contextBudget.limitChars,
|
|
887
|
+
estimatedChars: session.contextBudget.estimatedChars,
|
|
888
|
+
remainingChars: session.contextBudget.remainingChars,
|
|
889
|
+
usageRatio: session.contextBudget.usageRatio,
|
|
890
|
+
compressed: session.contextBudget.compressed,
|
|
891
|
+
compressionMode: session.contextBudget.compressionMode,
|
|
892
|
+
compressionReason: session.contextBudget.compressionReason,
|
|
893
|
+
sources: session.contextBudget.sources,
|
|
894
|
+
promptHotspots: session.contextBudget.promptHotspots,
|
|
895
|
+
cacheLayout: session.contextBudget.cacheLayout
|
|
896
|
+
} : void 0,
|
|
897
|
+
workset: session.workset ? {
|
|
898
|
+
total: session.workset.files.length,
|
|
899
|
+
files: session.workset.files.slice(-10).map((file) => ({
|
|
900
|
+
path: file.path,
|
|
901
|
+
readCount: file.readCount,
|
|
902
|
+
changedCount: file.changedCount,
|
|
903
|
+
lastTool: file.lastTool,
|
|
904
|
+
lastChangeId: file.lastChangeId,
|
|
905
|
+
reason: file.reason
|
|
906
|
+
}))
|
|
907
|
+
} : void 0
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
async function readRecentModelRequests(eventsDir) {
|
|
911
|
+
const files = await fs7.readdir(eventsDir).catch(() => []);
|
|
912
|
+
const jsonlFiles = files.filter((file) => file.endsWith(".jsonl")).sort().slice(-3);
|
|
913
|
+
const records = [];
|
|
914
|
+
for (const file of jsonlFiles) {
|
|
915
|
+
const content = await fs7.readFile(path6.join(eventsDir, file), "utf8").catch(() => "");
|
|
916
|
+
for (const line of content.split(/\r?\n/)) {
|
|
917
|
+
if (!line.trim()) {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const record = parseObservabilityRecord(line);
|
|
921
|
+
if (!record || record.event !== "model.request" || record.status !== "completed") {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
records.push(summarizeModelRequest(record));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return records.slice(-DEFAULT_RECENT_LIMIT).reverse();
|
|
928
|
+
}
|
|
929
|
+
function parseObservabilityRecord(line) {
|
|
930
|
+
try {
|
|
931
|
+
const parsed = JSON.parse(line);
|
|
932
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
933
|
+
} catch {
|
|
934
|
+
return void 0;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function summarizeModelRequest(record) {
|
|
938
|
+
const details = record.details ?? {};
|
|
939
|
+
const usage = readUsageSummary(details.usage);
|
|
940
|
+
return {
|
|
941
|
+
timestamp: record.timestamp,
|
|
942
|
+
provider: typeof details.provider === "string" ? details.provider : void 0,
|
|
943
|
+
model: record.model,
|
|
944
|
+
durationMs: record.durationMs,
|
|
945
|
+
usageAvailable: typeof details.usageAvailable === "boolean" ? details.usageAvailable : Boolean(usage),
|
|
946
|
+
usage
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function readUsageSummary(value) {
|
|
950
|
+
if (!value || typeof value !== "object") {
|
|
951
|
+
return void 0;
|
|
952
|
+
}
|
|
953
|
+
const record = value;
|
|
954
|
+
const usage = {
|
|
955
|
+
inputTokens: readNumber(record.inputTokens),
|
|
956
|
+
outputTokens: readNumber(record.outputTokens),
|
|
957
|
+
totalTokens: readNumber(record.totalTokens),
|
|
958
|
+
reasoningTokens: readNumber(record.reasoningTokens),
|
|
959
|
+
cacheReadTokens: readNumber(record.cacheReadTokens),
|
|
960
|
+
cacheCreationTokens: readNumber(record.cacheCreationTokens),
|
|
961
|
+
cacheHitTokens: readNumber(record.cacheHitTokens),
|
|
962
|
+
cacheMissTokens: readNumber(record.cacheMissTokens),
|
|
963
|
+
cacheHitRate: readNumber(record.cacheHitRate)
|
|
964
|
+
};
|
|
965
|
+
return Object.values(usage).some((item) => typeof item === "number") ? usage : void 0;
|
|
966
|
+
}
|
|
967
|
+
function readNumber(value) {
|
|
968
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
969
|
+
}
|
|
970
|
+
function summarizeSkills(skills) {
|
|
971
|
+
const summaries = skills.map((skill) => ({
|
|
972
|
+
name: skill.name,
|
|
973
|
+
path: skill.path,
|
|
974
|
+
status: skill.health.status,
|
|
975
|
+
resources: skill.health.resourceCount,
|
|
976
|
+
dependencies: skill.health.dependencyCount,
|
|
977
|
+
issues: skill.health.issues
|
|
978
|
+
}));
|
|
979
|
+
return {
|
|
980
|
+
total: summaries.length,
|
|
981
|
+
ready: summaries.filter((skill) => skill.status === "ready").length,
|
|
982
|
+
needsAttention: summaries.filter((skill) => skill.status !== "ready")
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function readControlPlaneStatus(rootDir) {
|
|
986
|
+
const ledger = new ControlPlaneLedger(rootDir);
|
|
987
|
+
try {
|
|
988
|
+
const executions = ledger.executions.list();
|
|
989
|
+
return {
|
|
990
|
+
executions: summarizeExecutionSet(executions, { recentLimit: DEFAULT_RECENT_LIMIT }),
|
|
991
|
+
wakeSignals: {
|
|
992
|
+
recent: ledger.wakeSignals.list().map(summarizeWakeSignal).slice(0, DEFAULT_RECENT_LIMIT)
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
} finally {
|
|
996
|
+
ledger.close();
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function readTaskLifecycleStatus(rootDir, sessionId) {
|
|
1000
|
+
const ledger = new ControlPlaneLedger(rootDir);
|
|
1001
|
+
try {
|
|
1002
|
+
const lifecycle = ledger.taskLifecycle.loadCurrent(sessionId);
|
|
1003
|
+
return lifecycle ? summarizeTaskLifecycle(lifecycle) : void 0;
|
|
1004
|
+
} finally {
|
|
1005
|
+
ledger.close();
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function summarizeTaskLifecycle(lifecycle) {
|
|
1009
|
+
return {
|
|
1010
|
+
id: lifecycle.id,
|
|
1011
|
+
sessionId: lifecycle.sessionId,
|
|
1012
|
+
stage: lifecycle.stage,
|
|
1013
|
+
reason: lifecycle.reason,
|
|
1014
|
+
activeExecutionIds: lifecycle.activeExecutionIds,
|
|
1015
|
+
activeTodoIds: lifecycle.activeTodoIds,
|
|
1016
|
+
verificationFacts: lifecycle.verificationFacts,
|
|
1017
|
+
completionFacts: lifecycle.completionFacts,
|
|
1018
|
+
updatedAt: lifecycle.updatedAt,
|
|
1019
|
+
completedAt: lifecycle.completedAt
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function summarizeWakeSignal(signal) {
|
|
1023
|
+
return {
|
|
1024
|
+
id: signal.id,
|
|
1025
|
+
executionId: signal.executionId,
|
|
1026
|
+
reason: signal.reason,
|
|
1027
|
+
createdAt: signal.createdAt
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// src/cli/commands/memory.ts
|
|
1032
|
+
async function readMemoryListForCli(cwd) {
|
|
1033
|
+
return (await buildRuntimeStatus(cwd)).memory;
|
|
1034
|
+
}
|
|
1035
|
+
function formatMemoryListForCli(memory) {
|
|
1036
|
+
if (memory.assets.length === 0) {
|
|
1037
|
+
return "No runtime memory assets yet.";
|
|
1038
|
+
}
|
|
1039
|
+
return memory.assets.map((asset) => [
|
|
1040
|
+
asset.id,
|
|
1041
|
+
asset.kind,
|
|
1042
|
+
asset.updatedAt ?? "",
|
|
1043
|
+
`bytes=${asset.size}`,
|
|
1044
|
+
asset.evidenceRefs.length > 0 ? `evidence=${asset.evidenceRefs.join(",")}` : void 0,
|
|
1045
|
+
asset.path
|
|
1046
|
+
].filter(Boolean).join(" ")).join("\n");
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/cli/commands/runtimeStatusPresenter.ts
|
|
1050
|
+
function formatRuntimeStatusText(status) {
|
|
1051
|
+
const lines = [];
|
|
1052
|
+
lines.push(`Project: ${status.rootDir}`);
|
|
1053
|
+
lines.push(`State: ${status.stateDir}`);
|
|
1054
|
+
lines.push("");
|
|
1055
|
+
lines.push("Scene:");
|
|
1056
|
+
lines.push(`- Now: ${status.scene.headline}`);
|
|
1057
|
+
lines.push(`- Focus: ${status.scene.focus}`);
|
|
1058
|
+
lines.push(`- Next: ${status.scene.nextAction}`);
|
|
1059
|
+
lines.push(`- Blocked: ${status.scene.blocked}`);
|
|
1060
|
+
lines.push(`- Background: ${status.scene.background.active} active / ${status.scene.background.blocked} need attention`);
|
|
1061
|
+
lines.push(`- Background next: ${status.scene.background.nextAction}`);
|
|
1062
|
+
lines.push(`- Skills: ${status.scene.skills.ready}/${status.scene.skills.total} ready; ${status.scene.skills.nextAction}`);
|
|
1063
|
+
lines.push(`- Memory: ${status.scene.memory.assets} asset(s), session=${status.scene.memory.latestSessionMemory ? "yes" : "no"}; ${status.scene.memory.nextAction}`);
|
|
1064
|
+
lines.push(`- Cost: ${status.scene.cost}`);
|
|
1065
|
+
lines.push(`- Recovery: ${status.scene.recovery}`);
|
|
1066
|
+
lines.push("");
|
|
1067
|
+
lines.push("Current workspace:");
|
|
1068
|
+
lines.push(`- Focus: ${status.scene.focus}`);
|
|
1069
|
+
lines.push(`- Session: ${readSessionLine(status)}`);
|
|
1070
|
+
lines.push(`- Next: ${status.scene.nextAction}`);
|
|
1071
|
+
lines.push(`- Blocked: ${status.scene.blocked}`);
|
|
1072
|
+
lines.push(`- Context budget: ${readContextBudgetLine(status)}`);
|
|
1073
|
+
lines.push(`- Workset: ${status.sessions.latest?.workset ? `${status.sessions.latest.workset.total} file(s)` : "none"}`);
|
|
1074
|
+
lines.push(`- Memory: ${status.memory.assets.length > 0 ? `${status.memory.assets.length} asset(s)` : "none"}`);
|
|
1075
|
+
lines.push(`- Skills: ${status.skills.ready}/${status.skills.total} ready`);
|
|
1076
|
+
lines.push(`- Model cache: ${readModelCacheLine(status)}`);
|
|
1077
|
+
lines.push(`- Project map: ${status.projectMap ? "ready" : "missing"}`);
|
|
1078
|
+
lines.push(`- Executions: ${status.executions.active.length} active / ${status.executions.total} total`);
|
|
1079
|
+
lines.push(`- Wake signals: ${status.wakeSignals.recent.length}`);
|
|
1080
|
+
if (status.taskLifecycle) {
|
|
1081
|
+
lines.push("");
|
|
1082
|
+
lines.push("Task lifecycle:");
|
|
1083
|
+
lines.push([
|
|
1084
|
+
status.taskLifecycle.stage,
|
|
1085
|
+
status.taskLifecycle.reason ? `reason=${status.taskLifecycle.reason}` : void 0,
|
|
1086
|
+
status.taskLifecycle.updatedAt
|
|
1087
|
+
].filter(Boolean).join(" "));
|
|
1088
|
+
}
|
|
1089
|
+
if (status.sessions.latest) {
|
|
1090
|
+
lines.push("");
|
|
1091
|
+
lines.push("Latest session:");
|
|
1092
|
+
lines.push([
|
|
1093
|
+
status.sessions.latest.id,
|
|
1094
|
+
status.sessions.latest.title ?? "(untitled)",
|
|
1095
|
+
`messages=${status.sessions.latest.messageCount}`,
|
|
1096
|
+
status.sessions.latest.hasMemory ? "memory=yes" : "memory=no"
|
|
1097
|
+
].join(" "));
|
|
1098
|
+
}
|
|
1099
|
+
if (status.projectMap) {
|
|
1100
|
+
lines.push("");
|
|
1101
|
+
lines.push("Project map:");
|
|
1102
|
+
lines.push([
|
|
1103
|
+
`dirs=${status.projectMap.topLevelDirectories.slice(0, 6).join(", ") || "none"}`,
|
|
1104
|
+
`scripts=${status.projectMap.packageScripts.slice(0, 6).join(", ") || "none"}`,
|
|
1105
|
+
`tests=${status.projectMap.testDirectories.slice(0, 4).join(", ") || "none"}`,
|
|
1106
|
+
status.projectMap.git.available ? `git=${status.projectMap.git.hasChanges ? "changed" : "clean"}` : "git=unavailable"
|
|
1107
|
+
].join(" "));
|
|
1108
|
+
}
|
|
1109
|
+
if (status.sessions.latest?.workset?.files.length) {
|
|
1110
|
+
lines.push("");
|
|
1111
|
+
lines.push("Workset:");
|
|
1112
|
+
for (const file of status.sessions.latest.workset.files) {
|
|
1113
|
+
lines.push([
|
|
1114
|
+
file.path,
|
|
1115
|
+
`read=${file.readCount}`,
|
|
1116
|
+
`changed=${file.changedCount}`,
|
|
1117
|
+
`last=${file.lastTool}`,
|
|
1118
|
+
file.lastChangeId ? `change=${file.lastChangeId}` : void 0,
|
|
1119
|
+
file.reason ? `reason=${file.reason}` : void 0
|
|
1120
|
+
].filter(Boolean).join(" "));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (status.sessions.latest?.contextBudget?.promptHotspots.length) {
|
|
1124
|
+
lines.push("");
|
|
1125
|
+
lines.push("Context budget hotspots:");
|
|
1126
|
+
for (const hotspot of status.sessions.latest.contextBudget.promptHotspots.slice(0, 3)) {
|
|
1127
|
+
lines.push([
|
|
1128
|
+
hotspot.layer,
|
|
1129
|
+
hotspot.title,
|
|
1130
|
+
`chars=${hotspot.chars}`,
|
|
1131
|
+
`lines=${hotspot.lines}`
|
|
1132
|
+
].join(" "));
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (status.sessions.latest?.contextBudget?.sources.length) {
|
|
1136
|
+
lines.push("");
|
|
1137
|
+
lines.push("Context budget sources:");
|
|
1138
|
+
for (const source of status.sessions.latest.contextBudget.sources) {
|
|
1139
|
+
lines.push([
|
|
1140
|
+
source.name,
|
|
1141
|
+
`chars=${source.chars}`,
|
|
1142
|
+
source.messages === void 0 ? void 0 : `messages=${source.messages}`
|
|
1143
|
+
].filter(Boolean).join(" "));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (status.sessions.latest?.contextBudget?.cacheLayout) {
|
|
1147
|
+
const layout = status.sessions.latest.contextBudget.cacheLayout;
|
|
1148
|
+
const totalChars = layout.stablePrefixChars + layout.volatileTailChars;
|
|
1149
|
+
const stableRatio = totalChars > 0 ? `${Math.round(layout.stablePrefixChars / totalChars * 100)}%` : "unknown";
|
|
1150
|
+
lines.push("");
|
|
1151
|
+
lines.push("Cache layout:");
|
|
1152
|
+
lines.push([
|
|
1153
|
+
`stable=${layout.stablePrefixFingerprint}`,
|
|
1154
|
+
`stableChars=${layout.stablePrefixChars}`,
|
|
1155
|
+
`tail=${layout.volatileTailFingerprint}`,
|
|
1156
|
+
`tailChars=${layout.volatileTailChars}`,
|
|
1157
|
+
`stableRatio=${stableRatio}`
|
|
1158
|
+
].join(" "));
|
|
1159
|
+
lines.push([
|
|
1160
|
+
`stableSources=${layout.stableSources.join(",") || "none"}`,
|
|
1161
|
+
`volatileSources=${layout.volatileSources.join(",") || "none"}`
|
|
1162
|
+
].join(" "));
|
|
1163
|
+
}
|
|
1164
|
+
if (status.modelRequests.recent.length > 0) {
|
|
1165
|
+
lines.push("");
|
|
1166
|
+
lines.push("Recent model requests:");
|
|
1167
|
+
for (const request of status.modelRequests.recent.slice(0, 5)) {
|
|
1168
|
+
lines.push([
|
|
1169
|
+
request.model ?? "unknown-model",
|
|
1170
|
+
request.provider ? `provider=${request.provider}` : void 0,
|
|
1171
|
+
request.durationMs === void 0 ? void 0 : `duration=${request.durationMs}ms`,
|
|
1172
|
+
request.usage ? formatUsage(request.usage) : "usage=unavailable"
|
|
1173
|
+
].filter(Boolean).join(" "));
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (status.executions.active.length > 0) {
|
|
1177
|
+
lines.push("");
|
|
1178
|
+
lines.push("Active executions:");
|
|
1179
|
+
for (const execution of status.scene.executions) {
|
|
1180
|
+
lines.push([
|
|
1181
|
+
execution.id,
|
|
1182
|
+
execution.kind,
|
|
1183
|
+
execution.status,
|
|
1184
|
+
`risk=${execution.risk}`,
|
|
1185
|
+
`summary=${truncateCliValue(execution.summary, 80)}`,
|
|
1186
|
+
`next=${execution.nextAction}`
|
|
1187
|
+
].filter(Boolean).join(" "));
|
|
1188
|
+
if (execution.lastOutput) {
|
|
1189
|
+
lines.push(` lastOutput=${execution.lastOutput}`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (status.executions.recent.length > 0) {
|
|
1194
|
+
lines.push("");
|
|
1195
|
+
lines.push("Recent executions:");
|
|
1196
|
+
for (const execution of status.executions.recent.slice(0, 5)) {
|
|
1197
|
+
lines.push([
|
|
1198
|
+
execution.id,
|
|
1199
|
+
execution.kind,
|
|
1200
|
+
execution.status,
|
|
1201
|
+
execution.actorName ? `actor=${execution.actorName}` : void 0,
|
|
1202
|
+
execution.summary ? truncateCliValue(execution.summary, 80) : void 0,
|
|
1203
|
+
execution.assignment?.expectedOutput ? `expected=${truncateCliValue(execution.assignment.expectedOutput, 60)}` : void 0
|
|
1204
|
+
].filter(Boolean).join(" "));
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (status.memory.assets.length > 0) {
|
|
1208
|
+
lines.push("");
|
|
1209
|
+
lines.push("Memory:");
|
|
1210
|
+
for (const memory of status.memory.assets.slice(0, 5)) {
|
|
1211
|
+
lines.push([
|
|
1212
|
+
memory.id,
|
|
1213
|
+
memory.kind,
|
|
1214
|
+
`bytes=${memory.size}`,
|
|
1215
|
+
memory.evidenceRefs.length > 0 ? `evidence=${memory.evidenceRefs.join(",")}` : void 0,
|
|
1216
|
+
memory.path
|
|
1217
|
+
].filter(Boolean).join(" "));
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (status.skills.needsAttention.length > 0) {
|
|
1221
|
+
lines.push("");
|
|
1222
|
+
lines.push("Skills needing attention:");
|
|
1223
|
+
for (const skill of status.skills.needsAttention) {
|
|
1224
|
+
lines.push([
|
|
1225
|
+
skill.name,
|
|
1226
|
+
skill.path,
|
|
1227
|
+
`resources=${skill.resources}`,
|
|
1228
|
+
`dependencies=${skill.dependencies}`,
|
|
1229
|
+
skill.issues.length > 0 ? `issues=${skill.issues.join("; ")}` : void 0
|
|
1230
|
+
].filter(Boolean).join(" "));
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return `${lines.join("\n")}
|
|
1234
|
+
`;
|
|
1235
|
+
}
|
|
1236
|
+
function readSessionLine(status) {
|
|
1237
|
+
if (!status.sessions.latest) {
|
|
1238
|
+
return "none";
|
|
1239
|
+
}
|
|
1240
|
+
return `${status.sessions.latest.id} (${status.sessions.latest.messageCount} message(s))`;
|
|
1241
|
+
}
|
|
1242
|
+
function readContextBudgetLine(status) {
|
|
1243
|
+
const budget = status.sessions.latest?.contextBudget;
|
|
1244
|
+
if (!budget) {
|
|
1245
|
+
return "none";
|
|
1246
|
+
}
|
|
1247
|
+
const percent = Math.round(budget.usageRatio * 100);
|
|
1248
|
+
return [
|
|
1249
|
+
`${budget.estimatedChars}/${budget.limitChars} chars`,
|
|
1250
|
+
`${percent}%`,
|
|
1251
|
+
budget.compressed ? `compressed=${budget.compressionMode}` : "compressed=no",
|
|
1252
|
+
`reason=${budget.compressionReason}`
|
|
1253
|
+
].join(" ");
|
|
1254
|
+
}
|
|
1255
|
+
function readModelCacheLine(status) {
|
|
1256
|
+
const latest = status.modelRequests.recent[0];
|
|
1257
|
+
if (!latest) {
|
|
1258
|
+
return "none";
|
|
1259
|
+
}
|
|
1260
|
+
if (!latest.usage) {
|
|
1261
|
+
return latest.usageAvailable ? "usage unavailable" : "usage unavailable from provider";
|
|
1262
|
+
}
|
|
1263
|
+
const cacheTokens = latest.usage.cacheHitTokens ?? latest.usage.cacheReadTokens;
|
|
1264
|
+
const missTokens = latest.usage.cacheMissTokens;
|
|
1265
|
+
const rate = latest.usage.cacheHitRate === void 0 ? void 0 : `${Math.round(latest.usage.cacheHitRate * 100)}%`;
|
|
1266
|
+
return [
|
|
1267
|
+
cacheTokens === void 0 ? "cached=unknown" : `cached=${cacheTokens}`,
|
|
1268
|
+
missTokens === void 0 ? void 0 : `miss=${missTokens}`,
|
|
1269
|
+
rate ? `hit=${rate}` : void 0
|
|
1270
|
+
].filter(Boolean).join(" ");
|
|
1271
|
+
}
|
|
1272
|
+
function formatUsage(usage) {
|
|
1273
|
+
return [
|
|
1274
|
+
usage.inputTokens === void 0 ? void 0 : `input=${usage.inputTokens}`,
|
|
1275
|
+
usage.outputTokens === void 0 ? void 0 : `output=${usage.outputTokens}`,
|
|
1276
|
+
usage.reasoningTokens === void 0 ? void 0 : `reasoning=${usage.reasoningTokens}`,
|
|
1277
|
+
usage.cacheHitTokens === void 0 ? void 0 : `cacheHit=${usage.cacheHitTokens}`,
|
|
1278
|
+
usage.cacheReadTokens === void 0 ? void 0 : `cacheRead=${usage.cacheReadTokens}`,
|
|
1279
|
+
usage.cacheCreationTokens === void 0 ? void 0 : `cacheWrite=${usage.cacheCreationTokens}`,
|
|
1280
|
+
usage.cacheMissTokens === void 0 ? void 0 : `cacheMiss=${usage.cacheMissTokens}`,
|
|
1281
|
+
usage.cacheHitRate === void 0 ? void 0 : `hit=${Math.round(usage.cacheHitRate * 100)}%`
|
|
1282
|
+
].filter(Boolean).join(" ");
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/host/session.ts
|
|
1286
|
+
async function createHostSession(sessionStore, cwd) {
|
|
1287
|
+
return sessionStore.create(cwd);
|
|
1288
|
+
}
|
|
1289
|
+
async function loadLatestSession(sessionStore) {
|
|
1290
|
+
return sessionStore.loadLatest();
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/cli/commands/sessionPicker.ts
|
|
1294
|
+
import readline from "readline/promises";
|
|
1295
|
+
import process2 from "process";
|
|
1296
|
+
|
|
1297
|
+
// src/session/picker.ts
|
|
1298
|
+
function parseSessionPickerChoice(input, sessionCount) {
|
|
1299
|
+
const trimmed = input.trim();
|
|
1300
|
+
if (trimmed === "") {
|
|
1301
|
+
return sessionCount > 0 ? { kind: "existing", index: 0 } : { kind: "new" };
|
|
1302
|
+
}
|
|
1303
|
+
const value = Number.parseInt(trimmed, 10);
|
|
1304
|
+
if (!Number.isInteger(value) || String(value) !== trimmed) {
|
|
1305
|
+
return { kind: "invalid" };
|
|
1306
|
+
}
|
|
1307
|
+
if (value === 0) {
|
|
1308
|
+
return { kind: "new" };
|
|
1309
|
+
}
|
|
1310
|
+
if (value >= 1 && value <= sessionCount) {
|
|
1311
|
+
return { kind: "existing", index: value - 1 };
|
|
1312
|
+
}
|
|
1313
|
+
return { kind: "invalid" };
|
|
1314
|
+
}
|
|
1315
|
+
function formatRelativeSessionTime(updatedAt, now) {
|
|
1316
|
+
const updatedTime = new Date(updatedAt).getTime();
|
|
1317
|
+
if (!Number.isFinite(updatedTime)) {
|
|
1318
|
+
return updatedAt;
|
|
1319
|
+
}
|
|
1320
|
+
const seconds = Math.max(0, Math.floor((now.getTime() - updatedTime) / 1e3));
|
|
1321
|
+
if (seconds < 60) {
|
|
1322
|
+
return "\u521A\u521A";
|
|
1323
|
+
}
|
|
1324
|
+
const minutes = Math.floor(seconds / 60);
|
|
1325
|
+
if (minutes < 60) {
|
|
1326
|
+
return `${minutes} \u5206\u949F\u524D`;
|
|
1327
|
+
}
|
|
1328
|
+
const hours = Math.floor(minutes / 60);
|
|
1329
|
+
if (hours < 24) {
|
|
1330
|
+
return `${hours} \u5C0F\u65F6\u524D`;
|
|
1331
|
+
}
|
|
1332
|
+
const days = Math.floor(hours / 24);
|
|
1333
|
+
if (days < 7) {
|
|
1334
|
+
return `${days} \u5929\u524D`;
|
|
1335
|
+
}
|
|
1336
|
+
const weeks = Math.floor(days / 7);
|
|
1337
|
+
if (days < 30) {
|
|
1338
|
+
return `${weeks} \u5468\u524D`;
|
|
1339
|
+
}
|
|
1340
|
+
const months = Math.floor(days / 30);
|
|
1341
|
+
if (days < 365) {
|
|
1342
|
+
return `${months} \u4E2A\u6708\u524D`;
|
|
1343
|
+
}
|
|
1344
|
+
return `${Math.floor(days / 365)} \u5E74\u524D`;
|
|
1345
|
+
}
|
|
1346
|
+
function formatSessionPickerTitle(session) {
|
|
1347
|
+
const title = session.title?.trim();
|
|
1348
|
+
return truncateDisplayTitle(title || `\u672A\u547D\u540D\u4F1A\u8BDD ${session.id}`);
|
|
1349
|
+
}
|
|
1350
|
+
function truncateDisplayTitle(title) {
|
|
1351
|
+
const chars = Array.from(title);
|
|
1352
|
+
const maxChars = 36;
|
|
1353
|
+
return chars.length > maxChars ? `${chars.slice(0, maxChars).join("")}...` : title;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// src/cli/commands/sessionHelpers.ts
|
|
1357
|
+
async function createSessionStore(sessionsDir) {
|
|
1358
|
+
const { SessionStore: SessionStore2 } = await import("./session-XKWJHRVY.mjs");
|
|
1359
|
+
return new SessionStore2(sessionsDir);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/cli/commands/events.ts
|
|
1363
|
+
async function readSessionEventsForCli(input) {
|
|
1364
|
+
const sessionStore = await createSessionStore(input.paths.sessionsDir);
|
|
1365
|
+
const session = input.sessionId ? await sessionStore.load(input.sessionId) : await loadLatestSession(sessionStore);
|
|
1366
|
+
if (!session) {
|
|
1367
|
+
return {
|
|
1368
|
+
sessionId: null,
|
|
1369
|
+
events: []
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
return {
|
|
1373
|
+
sessionId: session.id,
|
|
1374
|
+
events: await new SessionEventStore(input.paths.eventsDir).list(session.id, input.limit)
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
function formatSessionEventsForCli(result) {
|
|
1378
|
+
if (!result.sessionId) {
|
|
1379
|
+
return "No saved sessions yet.";
|
|
1380
|
+
}
|
|
1381
|
+
if (result.events.length === 0) {
|
|
1382
|
+
return `No events recorded for session ${result.sessionId}.`;
|
|
1383
|
+
}
|
|
1384
|
+
return result.events.map(formatSessionEventForCli).join("\n");
|
|
1385
|
+
}
|
|
1386
|
+
function formatSessionEventForCli(event) {
|
|
1387
|
+
const parts = [
|
|
1388
|
+
event.createdAt,
|
|
1389
|
+
event.type,
|
|
1390
|
+
event.host ? `host=${event.host}` : void 0,
|
|
1391
|
+
event.message ? `message=${formatInline(event.message)}` : void 0,
|
|
1392
|
+
event.details ? `details=${formatInline(JSON.stringify(event.details))}` : void 0
|
|
1393
|
+
];
|
|
1394
|
+
return parts.filter(Boolean).join(" ");
|
|
1395
|
+
}
|
|
1396
|
+
function formatInline(value) {
|
|
1397
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
1398
|
+
return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// src/config/paths.ts
|
|
1402
|
+
function getAppPaths(rootDir = process.cwd()) {
|
|
1403
|
+
const statePaths = getProjectStatePaths(rootDir);
|
|
1404
|
+
return {
|
|
1405
|
+
configDir: statePaths.kittyDir,
|
|
1406
|
+
dataDir: statePaths.kittyDir,
|
|
1407
|
+
cacheDir: statePaths.cacheDir,
|
|
1408
|
+
sessionsDir: statePaths.sessionsDir,
|
|
1409
|
+
memoryDir: statePaths.memoryDir,
|
|
1410
|
+
sessionMemoryDir: statePaths.sessionMemoryDir,
|
|
1411
|
+
changesDir: statePaths.changesDir,
|
|
1412
|
+
eventsDir: statePaths.eventsDir
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// src/project/reset.ts
|
|
1417
|
+
import fs9 from "fs/promises";
|
|
1418
|
+
import path8 from "path";
|
|
1419
|
+
|
|
1420
|
+
// src/project/resetSupport.ts
|
|
1421
|
+
import fs8 from "fs/promises";
|
|
1422
|
+
import path7 from "path";
|
|
1423
|
+
async function waitForRemovedPaths(paths, attempts = 20, delayMs = 50) {
|
|
1424
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1425
|
+
const remaining = await Promise.all(
|
|
1426
|
+
paths.map(async (entry) => ({
|
|
1427
|
+
entry,
|
|
1428
|
+
exists: await pathExists(entry)
|
|
1429
|
+
}))
|
|
1430
|
+
);
|
|
1431
|
+
if (remaining.every((item) => item.exists === false)) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
async function isSameOrDescendant(targetPath, possibleAncestor) {
|
|
1438
|
+
if (!targetPath.trim() || !possibleAncestor.trim()) {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
const resolvedTarget = await canonicalizePathForComparison(targetPath);
|
|
1442
|
+
const resolvedAncestor = await canonicalizePathForComparison(possibleAncestor);
|
|
1443
|
+
const relative = path7.relative(resolvedAncestor, resolvedTarget);
|
|
1444
|
+
return relative === "" || !relative.startsWith("..") && !path7.isAbsolute(relative);
|
|
1445
|
+
}
|
|
1446
|
+
async function pathExists(targetPath) {
|
|
1447
|
+
try {
|
|
1448
|
+
await fs8.access(targetPath);
|
|
1449
|
+
return true;
|
|
1450
|
+
} catch {
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
async function canonicalizePathForComparison(targetPath) {
|
|
1455
|
+
let candidate = path7.resolve(targetPath);
|
|
1456
|
+
const tail = [];
|
|
1457
|
+
while (true) {
|
|
1458
|
+
try {
|
|
1459
|
+
const real = await fs8.realpath(candidate);
|
|
1460
|
+
return tail.length > 0 ? path7.join(real, ...tail.reverse()) : real;
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
if (error.code !== "ENOENT") {
|
|
1463
|
+
return tail.length > 0 ? path7.join(candidate, ...tail.reverse()) : candidate;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const parent = path7.dirname(candidate);
|
|
1467
|
+
if (parent === candidate) {
|
|
1468
|
+
return tail.length > 0 ? path7.join(candidate, ...tail.reverse()) : candidate;
|
|
1469
|
+
}
|
|
1470
|
+
tail.push(path7.basename(candidate));
|
|
1471
|
+
candidate = parent;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// src/project/reset.ts
|
|
1476
|
+
var PRESERVED_PROJECT_STATE_ENTRIES = new Set(PRESERVED_PROJECT_STATE_ENTRY_NAMES);
|
|
1477
|
+
async function resetProjectRuntime(input) {
|
|
1478
|
+
const roots = await resolveProjectRoots(input.cwd);
|
|
1479
|
+
const statePaths = getProjectStatePaths(roots.stateRootDir);
|
|
1480
|
+
const kittyDir = statePaths.kittyDir;
|
|
1481
|
+
const removedSessionIds = await removeProjectSessions({
|
|
1482
|
+
sessionsDir: input.config.paths.sessionsDir,
|
|
1483
|
+
stateRootDir: roots.stateRootDir,
|
|
1484
|
+
currentSessionId: input.currentSessionId
|
|
1485
|
+
});
|
|
1486
|
+
await waitForRemovedPaths(removedSessionIds.map((sessionId) => path8.join(input.config.paths.sessionsDir, `${sessionId}.json`)));
|
|
1487
|
+
const removedChangeIds = await removeProjectChanges({
|
|
1488
|
+
changesDir: input.config.paths.changesDir,
|
|
1489
|
+
stateRootDir: roots.stateRootDir,
|
|
1490
|
+
removedSessionIds
|
|
1491
|
+
});
|
|
1492
|
+
const { removedEntries, preservedEntries } = await clearProjectKittyDirectory(kittyDir);
|
|
1493
|
+
await waitForRemovedPaths(removedEntries.map((entry) => path8.join(kittyDir, entry)));
|
|
1494
|
+
return {
|
|
1495
|
+
rootDir: roots.rootDir,
|
|
1496
|
+
stateRootDir: roots.stateRootDir,
|
|
1497
|
+
removedSessionIds,
|
|
1498
|
+
removedChangeIds,
|
|
1499
|
+
removedStateEntries: removedEntries,
|
|
1500
|
+
preservedStateEntries: preservedEntries
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
async function removeProjectSessions(input) {
|
|
1504
|
+
const removedIds = [];
|
|
1505
|
+
try {
|
|
1506
|
+
const entries = await fs9.readdir(input.sessionsDir, { withFileTypes: true });
|
|
1507
|
+
for (const entry of entries) {
|
|
1508
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
const sessionId = path8.basename(entry.name, ".json");
|
|
1512
|
+
const absolutePath = path8.join(input.sessionsDir, entry.name);
|
|
1513
|
+
const removeById = input.currentSessionId === sessionId;
|
|
1514
|
+
let removeByPath = false;
|
|
1515
|
+
if (!removeById) {
|
|
1516
|
+
const raw = await fs9.readFile(absolutePath, "utf8");
|
|
1517
|
+
const parsed = JSON.parse(raw);
|
|
1518
|
+
removeByPath = await isSameOrDescendant(String(parsed.cwd ?? ""), input.stateRootDir);
|
|
1519
|
+
}
|
|
1520
|
+
if (!removeById && !removeByPath) {
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
await fs9.rm(absolutePath, { force: true });
|
|
1524
|
+
removedIds.push(sessionId);
|
|
1525
|
+
}
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
if (error.code !== "ENOENT") {
|
|
1528
|
+
throw error;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return removedIds;
|
|
1532
|
+
}
|
|
1533
|
+
async function removeProjectChanges(input) {
|
|
1534
|
+
const removedIds = [];
|
|
1535
|
+
const removedSessionIds = new Set(input.removedSessionIds);
|
|
1536
|
+
try {
|
|
1537
|
+
const entries = await fs9.readdir(input.changesDir, { withFileTypes: true });
|
|
1538
|
+
for (const entry of entries) {
|
|
1539
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
const changeId = path8.basename(entry.name, ".json");
|
|
1543
|
+
const metadataPath = path8.join(input.changesDir, entry.name);
|
|
1544
|
+
const raw = await fs9.readFile(metadataPath, "utf8");
|
|
1545
|
+
const parsed = JSON.parse(raw);
|
|
1546
|
+
const remove = typeof parsed.sessionId === "string" && removedSessionIds.has(parsed.sessionId) || await isSameOrDescendant(String(parsed.cwd ?? ""), input.stateRootDir);
|
|
1547
|
+
if (!remove) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
await fs9.rm(metadataPath, { force: true });
|
|
1551
|
+
await fs9.rm(path8.join(input.changesDir, changeId), { recursive: true, force: true }).catch(() => null);
|
|
1552
|
+
removedIds.push(changeId);
|
|
1553
|
+
}
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
if (error.code !== "ENOENT") {
|
|
1556
|
+
throw error;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return removedIds;
|
|
1560
|
+
}
|
|
1561
|
+
async function clearProjectKittyDirectory(kittyDir) {
|
|
1562
|
+
const removedEntries = [];
|
|
1563
|
+
const preservedEntries = [];
|
|
1564
|
+
try {
|
|
1565
|
+
const entries = await fs9.readdir(kittyDir, { withFileTypes: true });
|
|
1566
|
+
for (const entry of entries) {
|
|
1567
|
+
const absolutePath = path8.join(kittyDir, entry.name);
|
|
1568
|
+
if (PRESERVED_PROJECT_STATE_ENTRIES.has(entry.name)) {
|
|
1569
|
+
preservedEntries.push(entry.name);
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
await fs9.rm(absolutePath, { recursive: true, force: true });
|
|
1573
|
+
removedEntries.push(entry.name);
|
|
1574
|
+
}
|
|
1575
|
+
} catch (error) {
|
|
1576
|
+
if (error.code !== "ENOENT") {
|
|
1577
|
+
throw error;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
return {
|
|
1581
|
+
removedEntries,
|
|
1582
|
+
preservedEntries
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// src/interaction/localCommandDefinitions.ts
|
|
1587
|
+
var LOCAL_COMMAND_DEFINITIONS = [
|
|
1588
|
+
{
|
|
1589
|
+
id: "exit",
|
|
1590
|
+
category: "system",
|
|
1591
|
+
aliases: ["q", "quit", "exit", "/q", "/quit", "/exit"],
|
|
1592
|
+
slashName: "exit",
|
|
1593
|
+
description: "Exit the session",
|
|
1594
|
+
helpLabel: "quit",
|
|
1595
|
+
helpText: "Exit the session",
|
|
1596
|
+
showInIntro: true
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
id: "reset",
|
|
1600
|
+
category: "project",
|
|
1601
|
+
aliases: ["reset", "/reset"],
|
|
1602
|
+
slashName: "reset",
|
|
1603
|
+
description: "Clear current project runtime state and exit",
|
|
1604
|
+
helpLabel: "/reset",
|
|
1605
|
+
helpText: "Clear current project runtime state and exit",
|
|
1606
|
+
showInIntro: true
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
id: "help",
|
|
1610
|
+
category: "system",
|
|
1611
|
+
aliases: ["/help"],
|
|
1612
|
+
slashName: "help",
|
|
1613
|
+
description: "Show slash commands",
|
|
1614
|
+
helpLabel: "/help",
|
|
1615
|
+
helpText: "Show slash commands",
|
|
1616
|
+
showInIntro: true
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
id: "session",
|
|
1620
|
+
category: "session",
|
|
1621
|
+
aliases: ["/session"],
|
|
1622
|
+
slashName: "session",
|
|
1623
|
+
description: "Show current session ID",
|
|
1624
|
+
helpLabel: "/session",
|
|
1625
|
+
helpText: "Show current session ID"
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
id: "sessions",
|
|
1629
|
+
category: "session",
|
|
1630
|
+
aliases: ["/sessions", "/resume", "/continue"],
|
|
1631
|
+
slashName: "sessions",
|
|
1632
|
+
description: "List recent sessions",
|
|
1633
|
+
helpLabel: "/sessions",
|
|
1634
|
+
helpText: "List recent sessions"
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
id: "config",
|
|
1638
|
+
category: "project",
|
|
1639
|
+
aliases: ["/config"],
|
|
1640
|
+
slashName: "config",
|
|
1641
|
+
description: "Show current runtime config",
|
|
1642
|
+
helpLabel: "/config",
|
|
1643
|
+
helpText: "Show current runtime config"
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
id: "status",
|
|
1647
|
+
category: "runtime",
|
|
1648
|
+
aliases: ["/status"],
|
|
1649
|
+
slashName: "status",
|
|
1650
|
+
description: "Show current project scene",
|
|
1651
|
+
helpLabel: "/status",
|
|
1652
|
+
helpText: "Show current project scene"
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
id: "background",
|
|
1656
|
+
category: "runtime",
|
|
1657
|
+
aliases: ["/background", "/bg"],
|
|
1658
|
+
slashName: "background",
|
|
1659
|
+
description: "Show background task scene",
|
|
1660
|
+
helpLabel: "/background",
|
|
1661
|
+
helpText: "Show background task scene"
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
id: "events",
|
|
1665
|
+
category: "runtime",
|
|
1666
|
+
aliases: ["/events"],
|
|
1667
|
+
slashName: "events",
|
|
1668
|
+
description: "Show recent session events",
|
|
1669
|
+
helpLabel: "/events",
|
|
1670
|
+
helpText: "Show recent session events"
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
id: "memory",
|
|
1674
|
+
category: "runtime",
|
|
1675
|
+
aliases: ["/memory"],
|
|
1676
|
+
slashName: "memory",
|
|
1677
|
+
description: "List runtime memory assets",
|
|
1678
|
+
helpLabel: "/memory",
|
|
1679
|
+
helpText: "List runtime memory assets"
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
id: "skills",
|
|
1683
|
+
category: "runtime",
|
|
1684
|
+
aliases: ["/skills"],
|
|
1685
|
+
slashName: "skills",
|
|
1686
|
+
description: "List runtime skills",
|
|
1687
|
+
helpLabel: "/skills",
|
|
1688
|
+
helpText: "List runtime skills"
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
id: "doctor",
|
|
1692
|
+
category: "project",
|
|
1693
|
+
aliases: ["/doctor"],
|
|
1694
|
+
slashName: "doctor",
|
|
1695
|
+
description: "Run local setup preflight",
|
|
1696
|
+
helpLabel: "/doctor",
|
|
1697
|
+
helpText: "Run local setup preflight"
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
id: "copy",
|
|
1701
|
+
category: "session",
|
|
1702
|
+
aliases: ["/copy"],
|
|
1703
|
+
slashName: "copy",
|
|
1704
|
+
description: "Print current session transcript",
|
|
1705
|
+
helpLabel: "/copy",
|
|
1706
|
+
helpText: "Print current session transcript"
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
id: "export",
|
|
1710
|
+
category: "session",
|
|
1711
|
+
aliases: ["/export"],
|
|
1712
|
+
slashName: "export",
|
|
1713
|
+
description: "Print current session snapshot JSON",
|
|
1714
|
+
helpLabel: "/export",
|
|
1715
|
+
helpText: "Print current session snapshot JSON"
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
id: "clear",
|
|
1719
|
+
category: "session",
|
|
1720
|
+
aliases: ["/clear"],
|
|
1721
|
+
slashName: "clear",
|
|
1722
|
+
description: "Clear the current prompt in UI shells",
|
|
1723
|
+
helpLabel: "/clear",
|
|
1724
|
+
helpText: "Clear the current prompt in UI shells"
|
|
1725
|
+
}
|
|
1726
|
+
];
|
|
1727
|
+
var ALL_LOCAL_COMMAND_DEFINITIONS = LOCAL_COMMAND_DEFINITIONS;
|
|
1728
|
+
var LOCAL_COMMAND_BY_ALIAS = new Map(
|
|
1729
|
+
ALL_LOCAL_COMMAND_DEFINITIONS.flatMap(
|
|
1730
|
+
(definition) => definition.aliases.map((alias) => [alias, definition.id])
|
|
1731
|
+
)
|
|
1732
|
+
);
|
|
1733
|
+
function normalizeLocalCommand(input) {
|
|
1734
|
+
return LOCAL_COMMAND_BY_ALIAS.get(input.trim().toLowerCase());
|
|
1735
|
+
}
|
|
1736
|
+
function getLocalCommandDefinition(id) {
|
|
1737
|
+
const definition = ALL_LOCAL_COMMAND_DEFINITIONS.find((item) => item.id === id);
|
|
1738
|
+
if (!definition) {
|
|
1739
|
+
throw new Error(`Unknown local command: ${id}`);
|
|
1740
|
+
}
|
|
1741
|
+
return definition;
|
|
1742
|
+
}
|
|
1743
|
+
function formatLocalCommandHelpLine(id) {
|
|
1744
|
+
const definition = getLocalCommandDefinition(id);
|
|
1745
|
+
return `${definition.helpLabel.padEnd(12)} ${definition.helpText}`;
|
|
1746
|
+
}
|
|
1747
|
+
function formatLocalCommandHelp() {
|
|
1748
|
+
return [
|
|
1749
|
+
"Slash commands:",
|
|
1750
|
+
...ALL_LOCAL_COMMAND_DEFINITIONS.map((definition) => formatLocalCommandHelpLine(definition.id)),
|
|
1751
|
+
"",
|
|
1752
|
+
"Any other input is sent directly to kitty."
|
|
1753
|
+
].join("\n");
|
|
1754
|
+
}
|
|
1755
|
+
function listIntroLocalCommands() {
|
|
1756
|
+
return ALL_LOCAL_COMMAND_DEFINITIONS.filter((definition) => definition.showInIntro);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// src/interaction/localCommands.ts
|
|
1760
|
+
async function handleLocalCommand(input, context, output) {
|
|
1761
|
+
if (!input.trim()) {
|
|
1762
|
+
return "handled";
|
|
1763
|
+
}
|
|
1764
|
+
const command = normalizeLocalCommand(input);
|
|
1765
|
+
if (command === "exit") {
|
|
1766
|
+
return "quit";
|
|
1767
|
+
}
|
|
1768
|
+
if (command === "reset") {
|
|
1769
|
+
await resetProjectRuntime({
|
|
1770
|
+
cwd: context.cwd,
|
|
1771
|
+
config: context.config,
|
|
1772
|
+
currentSessionId: context.session.id
|
|
1773
|
+
});
|
|
1774
|
+
output.warn("Project runtime reset. Session closed.");
|
|
1775
|
+
return "quit";
|
|
1776
|
+
}
|
|
1777
|
+
if (command === "help") {
|
|
1778
|
+
output.plain(formatLocalCommandHelp());
|
|
1779
|
+
return "handled";
|
|
1780
|
+
}
|
|
1781
|
+
if (command === "session") {
|
|
1782
|
+
output.info(`Current session: ${context.session.id}`);
|
|
1783
|
+
return "handled";
|
|
1784
|
+
}
|
|
1785
|
+
if (command === "config") {
|
|
1786
|
+
output.info(`model=${context.config.model} baseUrl=${context.config.baseUrl}`);
|
|
1787
|
+
return "handled";
|
|
1788
|
+
}
|
|
1789
|
+
if (command === "status") {
|
|
1790
|
+
output.plain(formatRuntimeStatusText(await buildRuntimeStatus(await resolveLocalStateRootDir(context))).trimEnd());
|
|
1791
|
+
return "handled";
|
|
1792
|
+
}
|
|
1793
|
+
if (command === "background") {
|
|
1794
|
+
output.plain(await formatBackgroundExecutionsForLocalCommand(context));
|
|
1795
|
+
return "handled";
|
|
1796
|
+
}
|
|
1797
|
+
if (command === "memory") {
|
|
1798
|
+
output.plain(formatMemoryListForCli(await readMemoryListForCli(await resolveLocalStateRootDir(context))));
|
|
1799
|
+
return "handled";
|
|
1800
|
+
}
|
|
1801
|
+
if (command === "skills") {
|
|
1802
|
+
output.plain(formatSkillsForLocalCommand(await buildRuntimeStatus(await resolveLocalStateRootDir(context))));
|
|
1803
|
+
return "handled";
|
|
1804
|
+
}
|
|
1805
|
+
if (command === "events") {
|
|
1806
|
+
const stateRootDir = await resolveLocalStateRootDir(context);
|
|
1807
|
+
output.plain(formatSessionEventsForCli(await readSessionEventsForCli({
|
|
1808
|
+
cwd: stateRootDir,
|
|
1809
|
+
paths: getAppPaths(stateRootDir),
|
|
1810
|
+
sessionId: context.session.id,
|
|
1811
|
+
limit: 20
|
|
1812
|
+
})));
|
|
1813
|
+
return "handled";
|
|
1814
|
+
}
|
|
1815
|
+
if (command === "doctor") {
|
|
1816
|
+
output.plain(formatConfigPreflightReport(await inspectConfigPreflight(context.cwd)).join("\n"));
|
|
1817
|
+
return "handled";
|
|
1818
|
+
}
|
|
1819
|
+
if (command === "sessions") {
|
|
1820
|
+
output.plain(await formatSessionsForLocalCommand(context));
|
|
1821
|
+
return "handled";
|
|
1822
|
+
}
|
|
1823
|
+
if (command === "copy") {
|
|
1824
|
+
output.plain(formatSessionTranscript(context.session));
|
|
1825
|
+
return "handled";
|
|
1826
|
+
}
|
|
1827
|
+
if (command === "export") {
|
|
1828
|
+
output.plain(JSON.stringify(context.session, null, 2));
|
|
1829
|
+
return "handled";
|
|
1830
|
+
}
|
|
1831
|
+
if (command === "clear") {
|
|
1832
|
+
output.info("Current prompt cleared.");
|
|
1833
|
+
return "handled";
|
|
1834
|
+
}
|
|
1835
|
+
return "continue";
|
|
1836
|
+
}
|
|
1837
|
+
async function formatBackgroundExecutionsForLocalCommand(context) {
|
|
1838
|
+
const stateRootDir = await resolveLocalStateRootDir(context);
|
|
1839
|
+
const store = new BackgroundExecutionStore(stateRootDir);
|
|
1840
|
+
const executions = store.listAll().map(summarizeExecution).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
1841
|
+
if (executions.length === 0) {
|
|
1842
|
+
return "No background executions recorded.";
|
|
1843
|
+
}
|
|
1844
|
+
return executions.map(formatBackgroundExecution).join("\n");
|
|
1845
|
+
}
|
|
1846
|
+
async function resolveLocalStateRootDir(context) {
|
|
1847
|
+
return context.stateRootDir ?? (await resolveProjectRoots(context.cwd)).stateRootDir;
|
|
1848
|
+
}
|
|
1849
|
+
function formatSkillsForLocalCommand(status) {
|
|
1850
|
+
if (status.skills.total === 0) {
|
|
1851
|
+
return "No runtime skills discovered.";
|
|
1852
|
+
}
|
|
1853
|
+
return [
|
|
1854
|
+
`skills: ${status.skills.ready}/${status.skills.total} ready`,
|
|
1855
|
+
...status.skills.needsAttention.map((skill) => `${skill.name} status=${skill.status} issues=${skill.issues.join("; ") || "none"}`)
|
|
1856
|
+
].join("\n");
|
|
1857
|
+
}
|
|
1858
|
+
async function formatSessionsForLocalCommand(context) {
|
|
1859
|
+
const sessions = await (context.sessionStore?.list(10) ?? []);
|
|
1860
|
+
if (sessions.length === 0) {
|
|
1861
|
+
return "No saved sessions yet.";
|
|
1862
|
+
}
|
|
1863
|
+
return sessions.map((session) => [
|
|
1864
|
+
session.id === context.session.id ? "*" : " ",
|
|
1865
|
+
session.id,
|
|
1866
|
+
session.updatedAt,
|
|
1867
|
+
session.title ?? "(untitled)",
|
|
1868
|
+
`messages=${session.messageCount}`
|
|
1869
|
+
].join(" ")).join("\n");
|
|
1870
|
+
}
|
|
1871
|
+
function formatSessionTranscript(session) {
|
|
1872
|
+
if (session.messages.length === 0) {
|
|
1873
|
+
return "Current session has no messages yet.";
|
|
1874
|
+
}
|
|
1875
|
+
return session.messages.map((message) => `${message.role}: ${message.content}`).join("\n\n");
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// src/interaction/sessionDriver.ts
|
|
1879
|
+
var InteractiveSessionDriver = class {
|
|
1880
|
+
constructor(options) {
|
|
1881
|
+
this.options = options;
|
|
1882
|
+
this.session = options.session;
|
|
1883
|
+
}
|
|
1884
|
+
session;
|
|
1885
|
+
turnInFlight = false;
|
|
1886
|
+
turnAbortController = null;
|
|
1887
|
+
lastInterruptNoticeAt = 0;
|
|
1888
|
+
exitRequested = false;
|
|
1889
|
+
terminationInProgress = false;
|
|
1890
|
+
async run() {
|
|
1891
|
+
const releaseInterrupt = this.options.shell.input.bindInterrupt(() => {
|
|
1892
|
+
this.handleInterrupt();
|
|
1893
|
+
});
|
|
1894
|
+
const releaseProcessTermination = this.bindProcessTerminationCleanup();
|
|
1895
|
+
try {
|
|
1896
|
+
while (true) {
|
|
1897
|
+
const prompt = await this.options.shell.input.readInput("> ");
|
|
1898
|
+
if (prompt.kind === "closed") {
|
|
1899
|
+
await this.terminateRunningProcessesForForcedExit("Input closed. Stopping running processes before exit.");
|
|
1900
|
+
return this.session;
|
|
1901
|
+
}
|
|
1902
|
+
const input = prompt.value.trim();
|
|
1903
|
+
if (!input) {
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
const decision = await this.handleInput(input);
|
|
1907
|
+
if (decision === "quit") {
|
|
1908
|
+
return this.session;
|
|
1909
|
+
}
|
|
1910
|
+
if (this.exitRequested) {
|
|
1911
|
+
return this.session;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
} finally {
|
|
1915
|
+
releaseProcessTermination();
|
|
1916
|
+
releaseInterrupt();
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
async handleInput(input) {
|
|
1920
|
+
let localCommandResult;
|
|
1921
|
+
try {
|
|
1922
|
+
localCommandResult = await (this.options.localCommandHandler ?? handleLocalCommand)(
|
|
1923
|
+
input,
|
|
1924
|
+
{
|
|
1925
|
+
cwd: this.options.cwd,
|
|
1926
|
+
session: this.session,
|
|
1927
|
+
config: this.options.config,
|
|
1928
|
+
sessionStore: this.options.sessionStore
|
|
1929
|
+
},
|
|
1930
|
+
this.options.shell.output
|
|
1931
|
+
);
|
|
1932
|
+
} catch (error) {
|
|
1933
|
+
this.options.shell.output.error(getErrorMessage(error));
|
|
1934
|
+
return "handled";
|
|
1935
|
+
}
|
|
1936
|
+
if (localCommandResult === "continue") {
|
|
1937
|
+
await this.runTurn(input);
|
|
1938
|
+
} else if (localCommandResult === "quit") {
|
|
1939
|
+
return this.handleQuitRequest();
|
|
1940
|
+
}
|
|
1941
|
+
return localCommandResult;
|
|
1942
|
+
}
|
|
1943
|
+
async handleQuitRequest() {
|
|
1944
|
+
const exitGuard = this.options.exitGuard ?? defaultInteractiveExitGuard;
|
|
1945
|
+
let runningProcesses;
|
|
1946
|
+
try {
|
|
1947
|
+
runningProcesses = await exitGuard.collectRunningProcesses(this.options.cwd);
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
this.options.shell.output.error(`Failed to inspect running background processes: ${getErrorMessage(error)}`);
|
|
1950
|
+
return "handled";
|
|
1951
|
+
}
|
|
1952
|
+
if (runningProcesses.length === 0) {
|
|
1953
|
+
this.options.shell.output.info("Session saved.");
|
|
1954
|
+
return "quit";
|
|
1955
|
+
}
|
|
1956
|
+
this.options.shell.output.warn("Running processes detected. Exiting now will kill them all.");
|
|
1957
|
+
this.options.shell.output.plain(runningProcesses.map((process4) => process4.summary).join("\n"));
|
|
1958
|
+
const confirmation = await this.options.shell.input.readInput(
|
|
1959
|
+
"Kill all running processes and exit? [y/N] "
|
|
1960
|
+
);
|
|
1961
|
+
if (confirmation.kind !== "submit" || !isYes(confirmation.value)) {
|
|
1962
|
+
this.options.shell.output.info("Exit cancelled. Background processes will keep running.");
|
|
1963
|
+
return "handled";
|
|
1964
|
+
}
|
|
1965
|
+
try {
|
|
1966
|
+
const result = await exitGuard.terminateProcesses(runningProcesses, this.options.cwd);
|
|
1967
|
+
if (result.failedPids.length > 0) {
|
|
1968
|
+
this.options.shell.output.error(
|
|
1969
|
+
`Could not stop all background processes. Still running: ${result.failedPids.join(", ")}. Exit cancelled.`
|
|
1970
|
+
);
|
|
1971
|
+
return "handled";
|
|
1972
|
+
}
|
|
1973
|
+
this.options.shell.output.warn(`Stopped ${result.terminatedPids.length} background process(es).`);
|
|
1974
|
+
this.options.shell.output.info("Session saved.");
|
|
1975
|
+
return "quit";
|
|
1976
|
+
} catch (error) {
|
|
1977
|
+
this.options.shell.output.error(`Failed to stop background processes: ${getErrorMessage(error)}`);
|
|
1978
|
+
return "handled";
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
handleInterrupt() {
|
|
1982
|
+
if (this.turnInFlight && this.turnAbortController && !this.turnAbortController.signal.aborted) {
|
|
1983
|
+
this.turnAbortController.abort();
|
|
1984
|
+
this.showInterruptNotice("Interrupted the current turn. You can continue typing.");
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
this.showInterruptNotice("This session will not exit automatically. Type quit or q to exit.");
|
|
1988
|
+
}
|
|
1989
|
+
showInterruptNotice(message) {
|
|
1990
|
+
const now = Date.now();
|
|
1991
|
+
if (now - this.lastInterruptNoticeAt < 150) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
this.lastInterruptNoticeAt = now;
|
|
1995
|
+
this.options.shell.output.interrupt(message);
|
|
1996
|
+
}
|
|
1997
|
+
bindProcessTerminationCleanup() {
|
|
1998
|
+
const signals = ["SIGHUP", "SIGTERM", "SIGBREAK"];
|
|
1999
|
+
const handler = (signal) => {
|
|
2000
|
+
void this.terminateRunningProcessesForForcedExit(
|
|
2001
|
+
`Received ${signal}. Stopping running processes before exit.`
|
|
2002
|
+
).finally(() => {
|
|
2003
|
+
process3.exit(0);
|
|
2004
|
+
});
|
|
2005
|
+
};
|
|
2006
|
+
for (const signal of signals) {
|
|
2007
|
+
process3.once(signal, handler);
|
|
2008
|
+
}
|
|
2009
|
+
return () => {
|
|
2010
|
+
for (const signal of signals) {
|
|
2011
|
+
process3.off(signal, handler);
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
async terminateRunningProcessesForForcedExit(reason) {
|
|
2016
|
+
if (this.terminationInProgress) {
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
this.terminationInProgress = true;
|
|
2020
|
+
this.exitRequested = true;
|
|
2021
|
+
if (this.turnAbortController && !this.turnAbortController.signal.aborted) {
|
|
2022
|
+
this.turnAbortController.abort();
|
|
2023
|
+
}
|
|
2024
|
+
const exitGuard = this.options.exitGuard ?? defaultInteractiveExitGuard;
|
|
2025
|
+
try {
|
|
2026
|
+
const runningProcesses = await exitGuard.collectRunningProcesses(this.options.cwd);
|
|
2027
|
+
if (runningProcesses.length === 0) {
|
|
2028
|
+
this.options.shell.output.info("Session saved.");
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
this.options.shell.output.warn(reason);
|
|
2032
|
+
this.options.shell.output.plain(runningProcesses.map((processInfo) => processInfo.summary).join("\n"));
|
|
2033
|
+
const result = await exitGuard.terminateProcesses(runningProcesses, this.options.cwd);
|
|
2034
|
+
if (result.failedPids.length > 0) {
|
|
2035
|
+
this.options.shell.output.error(`Could not stop all running processes. Still running: ${result.failedPids.join(", ")}.`);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
this.options.shell.output.warn(`Stopped ${result.terminatedPids.length} running process(es).`);
|
|
2039
|
+
this.options.shell.output.info("Session saved.");
|
|
2040
|
+
} catch (error) {
|
|
2041
|
+
this.options.shell.output.error(`Failed to stop running processes: ${getErrorMessage(error)}`);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
async runTurn(input) {
|
|
2045
|
+
this.options.shell.output.plain(formatSubmittedInput(input));
|
|
2046
|
+
this.turnInFlight = true;
|
|
2047
|
+
const controller = new AbortController();
|
|
2048
|
+
this.turnAbortController = controller;
|
|
2049
|
+
const turnDisplay = this.options.shell.createTurnDisplay({
|
|
2050
|
+
cwd: this.options.cwd,
|
|
2051
|
+
config: this.options.config,
|
|
2052
|
+
abortSignal: controller.signal
|
|
2053
|
+
});
|
|
2054
|
+
try {
|
|
2055
|
+
const turnContext = await this.options.turnContextProvider?.(this.session, input);
|
|
2056
|
+
const outcome = await runHostTurn({
|
|
2057
|
+
host: "interactive",
|
|
2058
|
+
input,
|
|
2059
|
+
cwd: turnContext?.cwd ?? this.options.cwd,
|
|
2060
|
+
stateRootDir: turnContext?.stateRootDir,
|
|
2061
|
+
config: this.options.config,
|
|
2062
|
+
session: this.session,
|
|
2063
|
+
sessionStore: this.options.sessionStore,
|
|
2064
|
+
builtinToolFilter: turnContext?.builtinToolFilter,
|
|
2065
|
+
extraTools: turnContext?.extraTools,
|
|
2066
|
+
runtimePromptState: turnContext?.runtimePromptState,
|
|
2067
|
+
abortSignal: controller.signal,
|
|
2068
|
+
callbacks: turnDisplay.callbacks
|
|
2069
|
+
}, {
|
|
2070
|
+
runTurn: this.options.runTurn
|
|
2071
|
+
});
|
|
2072
|
+
this.session = outcome.session;
|
|
2073
|
+
this.options.onSessionUpdated?.(this.session);
|
|
2074
|
+
if (outcome.status === "aborted") {
|
|
2075
|
+
turnDisplay.flush();
|
|
2076
|
+
this.options.shell.output.warn(outcome.errorMessage ?? "Turn interrupted. You can keep chatting.");
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
if (outcome.status === "failed") {
|
|
2080
|
+
turnDisplay.flush();
|
|
2081
|
+
this.options.shell.output.error(outcome.errorMessage ?? "The request failed.");
|
|
2082
|
+
this.options.shell.output.info("The request failed, but the session is still alive. You can keep chatting.");
|
|
2083
|
+
}
|
|
2084
|
+
} catch (error) {
|
|
2085
|
+
turnDisplay.flush();
|
|
2086
|
+
this.options.shell.output.error(getErrorMessage(error));
|
|
2087
|
+
this.options.shell.output.info("The request failed, but the session is still alive. You can keep chatting.");
|
|
2088
|
+
} finally {
|
|
2089
|
+
turnDisplay.dispose();
|
|
2090
|
+
this.turnInFlight = false;
|
|
2091
|
+
this.turnAbortController = null;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2095
|
+
function isYes(value) {
|
|
2096
|
+
const normalized = value.trim().toLowerCase();
|
|
2097
|
+
return normalized === "y" || normalized === "yes";
|
|
2098
|
+
}
|
|
2099
|
+
function formatSubmittedInput(input) {
|
|
2100
|
+
return input.split("\n").map((line, index) => `${index === 0 ? "> " : "\u2026 "}${line}`).join("\n");
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// src/shell/banner.ts
|
|
2104
|
+
import figlet from "figlet";
|
|
2105
|
+
var KITTY_WORDMARK_FONT = "ANSI Shadow";
|
|
2106
|
+
function renderKittyBanner() {
|
|
2107
|
+
return figlet.textSync("kitty agent", {
|
|
2108
|
+
font: KITTY_WORDMARK_FONT,
|
|
2109
|
+
horizontalLayout: "default",
|
|
2110
|
+
verticalLayout: "default",
|
|
2111
|
+
width: 120,
|
|
2112
|
+
whitespaceBreak: false
|
|
2113
|
+
}).trimEnd();
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
export {
|
|
2117
|
+
ui,
|
|
2118
|
+
createHostSession,
|
|
2119
|
+
parseSessionPickerChoice,
|
|
2120
|
+
formatRelativeSessionTime,
|
|
2121
|
+
formatSessionPickerTitle,
|
|
2122
|
+
createTerminalLogWriter,
|
|
2123
|
+
mirrorProcessOutputToTerminalLog,
|
|
2124
|
+
mirrorInteractionShellToTerminalLog,
|
|
2125
|
+
formatLocalCommandHelpLine,
|
|
2126
|
+
listIntroLocalCommands,
|
|
2127
|
+
renderKittyBanner,
|
|
2128
|
+
InteractiveSessionDriver
|
|
2129
|
+
};
|