@x-code-cli/core 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/api-errors.d.ts.map +1 -1
- package/dist/agent/api-errors.js +18 -0
- package/dist/agent/api-errors.js.map +1 -1
- package/dist/agent/diff.d.ts +35 -0
- package/dist/agent/diff.d.ts.map +1 -0
- package/dist/agent/diff.js +83 -0
- package/dist/agent/diff.js.map +1 -0
- package/dist/agent/loop-state.d.ts +45 -3
- package/dist/agent/loop-state.d.ts.map +1 -1
- package/dist/agent/loop-state.js +24 -3
- package/dist/agent/loop-state.js.map +1 -1
- package/dist/agent/loop.d.ts +10 -6
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +212 -30
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/plan-storage.d.ts +55 -0
- package/dist/agent/plan-storage.d.ts.map +1 -0
- package/dist/agent/plan-storage.js +156 -0
- package/dist/agent/plan-storage.js.map +1 -0
- package/dist/agent/session-store.d.ts +114 -0
- package/dist/agent/session-store.d.ts.map +1 -0
- package/dist/agent/session-store.js +415 -0
- package/dist/agent/session-store.js.map +1 -0
- package/dist/agent/sub-agents/built-in.d.ts +3 -0
- package/dist/agent/sub-agents/built-in.d.ts.map +1 -0
- package/dist/agent/sub-agents/built-in.js +98 -0
- package/dist/agent/sub-agents/built-in.js.map +1 -0
- package/dist/agent/sub-agents/index.d.ts +7 -0
- package/dist/agent/sub-agents/index.d.ts.map +1 -0
- package/dist/agent/sub-agents/index.js +5 -0
- package/dist/agent/sub-agents/index.js.map +1 -0
- package/dist/agent/sub-agents/loader.d.ts +5 -0
- package/dist/agent/sub-agents/loader.d.ts.map +1 -0
- package/dist/agent/sub-agents/loader.js +117 -0
- package/dist/agent/sub-agents/loader.js.map +1 -0
- package/dist/agent/sub-agents/registry.d.ts +14 -0
- package/dist/agent/sub-agents/registry.d.ts.map +1 -0
- package/dist/agent/sub-agents/registry.js +37 -0
- package/dist/agent/sub-agents/registry.js.map +1 -0
- package/dist/agent/sub-agents/runner.d.ts +26 -0
- package/dist/agent/sub-agents/runner.d.ts.map +1 -0
- package/dist/agent/sub-agents/runner.js +287 -0
- package/dist/agent/sub-agents/runner.js.map +1 -0
- package/dist/agent/sub-agents/types.d.ts +63 -0
- package/dist/agent/sub-agents/types.d.ts.map +1 -0
- package/dist/agent/sub-agents/types.js +2 -0
- package/dist/agent/sub-agents/types.js.map +1 -0
- package/dist/agent/system-prompt.d.ts +15 -0
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +161 -0
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/tool-execution.d.ts +4 -3
- package/dist/agent/tool-execution.d.ts.map +1 -1
- package/dist/agent/tool-execution.js +316 -14
- package/dist/agent/tool-execution.js.map +1 -1
- package/dist/agent/tool-result-sanitize.d.ts +12 -0
- package/dist/agent/tool-result-sanitize.d.ts.map +1 -1
- package/dist/agent/tool-result-sanitize.js +70 -0
- package/dist/agent/tool-result-sanitize.js.map +1 -1
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/knowledge/session.d.ts +4 -7
- package/dist/knowledge/session.d.ts.map +1 -1
- package/dist/knowledge/session.js +20 -55
- package/dist/knowledge/session.js.map +1 -1
- package/dist/permissions/index.d.ts +18 -3
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +20 -2
- package/dist/permissions/index.js.map +1 -1
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +8 -6
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/enter-plan-mode.d.ts +25 -0
- package/dist/tools/enter-plan-mode.d.ts.map +1 -0
- package/dist/tools/enter-plan-mode.js +120 -0
- package/dist/tools/enter-plan-mode.js.map +1 -0
- package/dist/tools/exit-plan-mode.d.ts +13 -0
- package/dist/tools/exit-plan-mode.d.ts.map +1 -0
- package/dist/tools/exit-plan-mode.js +22 -0
- package/dist/tools/exit-plan-mode.js.map +1 -0
- package/dist/tools/grep.d.ts +1 -1
- package/dist/tools/index.d.ts +20 -4
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/save-knowledge.d.ts +2 -2
- package/dist/tools/shell-provider.d.ts +4 -0
- package/dist/tools/shell-provider.d.ts.map +1 -1
- package/dist/tools/shell-provider.js +2 -0
- package/dist/tools/shell-provider.js.map +1 -1
- package/dist/tools/task.d.ts +14 -0
- package/dist/tools/task.d.ts.map +1 -0
- package/dist/tools/task.js +95 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/tools/todo-write.d.ts +21 -0
- package/dist/tools/todo-write.d.ts.map +1 -0
- package/dist/tools/todo-write.js +117 -0
- package/dist/tools/todo-write.js.map +1 -0
- package/dist/types/index.d.ts +103 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/knowledge/session-usage.d.ts +0 -24
- package/dist/knowledge/session-usage.d.ts.map +0 -1
- package/dist/knowledge/session-usage.js +0 -86
- package/dist/knowledge/session-usage.js.map +0 -1
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
// @x-code-cli/core — Per-session JSONL transcript store.
|
|
2
|
+
//
|
|
3
|
+
// One file per session: `.x-code/sessions/<slug>-<sessionId>.jsonl` (slug is
|
|
4
|
+
// the same human-readable token used by plan files; falls back to
|
|
5
|
+
// timestamp-only naming when the user's first message has no ASCII content).
|
|
6
|
+
// The file is append-only; everything we record about a session — header,
|
|
7
|
+
// each ModelMessage, periodic token-usage snapshots, compaction boundaries,
|
|
8
|
+
// abort markers — lives as one JSON object per line.
|
|
9
|
+
//
|
|
10
|
+
// Why JSONL and not a single rewritten JSON document:
|
|
11
|
+
// - Crash-safe. A killed process or full-disk error at most loses the line
|
|
12
|
+
// currently being written; everything before it is intact.
|
|
13
|
+
// - Cheap appends. Each turn appends a few hundred bytes; never rewrites.
|
|
14
|
+
// - Mirrors Claude Code's `~/.claude/<project>/<uuid>.jsonl` exactly,
|
|
15
|
+
// including the `compact_boundary` semantics (see `loadSession` below).
|
|
16
|
+
//
|
|
17
|
+
// This module replaces the old per-session `<id>.usage.json` and
|
|
18
|
+
// `<id>.json` (LLM summary) files — both are now meta entries inside the
|
|
19
|
+
// jsonl. /usage history and /resume both source from the same file.
|
|
20
|
+
import { createHash } from 'node:crypto';
|
|
21
|
+
import { readFile } from 'node:fs/promises';
|
|
22
|
+
import fs from 'node:fs/promises';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { XCODE_DIR } from '../utils.js';
|
|
25
|
+
import { createLoopState } from './loop-state.js';
|
|
26
|
+
const SESSIONS_SUBDIR = 'sessions';
|
|
27
|
+
function sessionsDir(cwd = process.cwd()) {
|
|
28
|
+
return path.join(cwd, XCODE_DIR, SESSIONS_SUBDIR);
|
|
29
|
+
}
|
|
30
|
+
/** Build the on-disk filename for a session. Same shape as plan files
|
|
31
|
+
* (`<slug>-<id>.<ext>`) so `ls .x-code/sessions/` and `ls .x-code/plans/`
|
|
32
|
+
* scan the same way. Empty slug (CJK-only first message) collapses to
|
|
33
|
+
* pure-timestamp naming, matching the plan-file fallback. */
|
|
34
|
+
export function getSessionFilePath(state, cwd = process.cwd()) {
|
|
35
|
+
const base = state.taskSlug ? `${state.taskSlug}-${state.sessionId}` : state.sessionId;
|
|
36
|
+
return path.join(sessionsDir(cwd), `${base}.jsonl`);
|
|
37
|
+
}
|
|
38
|
+
// ── Append helpers (fire-and-forget; never throw) ───────────────────────
|
|
39
|
+
async function appendLine(filePath, entry) {
|
|
40
|
+
try {
|
|
41
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
42
|
+
await fs.appendFile(filePath, JSON.stringify(entry) + '\n', 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Persistence is best-effort — never block the agent loop on FS errors.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Try to read the current git branch from `.git/HEAD`. Cheap, fully sync
|
|
49
|
+
* on the calling promise; absent / detached-HEAD / non-git all map to
|
|
50
|
+
* undefined silently. */
|
|
51
|
+
async function readGitBranch(cwd) {
|
|
52
|
+
try {
|
|
53
|
+
const head = await readFile(path.join(cwd, '.git', 'HEAD'), 'utf-8');
|
|
54
|
+
const m = head.match(/^ref: refs\/heads\/(.+)$/m);
|
|
55
|
+
return m ? m[1].trim() : undefined;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Write the session header. Idempotent: if the file already exists (resume
|
|
62
|
+
* path), we skip — the original header is preserved so picker metadata
|
|
63
|
+
* stays stable across resumes. */
|
|
64
|
+
export async function appendHeader(state, modelId, firstPrompt, cwd = process.cwd()) {
|
|
65
|
+
const filePath = getSessionFilePath(state, cwd);
|
|
66
|
+
try {
|
|
67
|
+
await fs.access(filePath);
|
|
68
|
+
return; // file already exists — header preserved from original session
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// File doesn't exist — fall through and write the header.
|
|
72
|
+
}
|
|
73
|
+
const gitBranch = await readGitBranch(cwd);
|
|
74
|
+
const entry = {
|
|
75
|
+
t: 'meta',
|
|
76
|
+
kind: 'header',
|
|
77
|
+
cwd,
|
|
78
|
+
gitBranch,
|
|
79
|
+
modelId,
|
|
80
|
+
startedAt: state.startedAt,
|
|
81
|
+
firstPrompt: firstPrompt.slice(0, 500),
|
|
82
|
+
taskSlug: state.taskSlug,
|
|
83
|
+
sessionId: state.sessionId,
|
|
84
|
+
};
|
|
85
|
+
await appendLine(filePath, entry);
|
|
86
|
+
}
|
|
87
|
+
/** Flush every message in `state.messages` past `state.persistedMessageCount`
|
|
88
|
+
* to the jsonl file. The diff-based design keeps the writer decoupled from
|
|
89
|
+
* the many places in the agent loop that mutate state.messages directly
|
|
90
|
+
* (collectTurnResponse, processToolCalls, length-finish nudge, etc.) — we
|
|
91
|
+
* catch them all by sweeping at turn boundaries.
|
|
92
|
+
*
|
|
93
|
+
* After deep / light compaction the in-memory array shrinks. Callers must
|
|
94
|
+
* call `markBoundaryAndReflush` (below) instead of this — that path writes
|
|
95
|
+
* a compact-boundary marker so the loader can correctly truncate-on-load
|
|
96
|
+
* and then re-appends the trimmed messages so post-boundary jsonl content
|
|
97
|
+
* matches the new in-memory state. */
|
|
98
|
+
export async function flushPendingMessages(state) {
|
|
99
|
+
if (state.persistedMessageCount >= state.messages.length)
|
|
100
|
+
return;
|
|
101
|
+
const filePath = getSessionFilePath(state);
|
|
102
|
+
const ts = new Date().toISOString();
|
|
103
|
+
const lines = [];
|
|
104
|
+
for (let i = state.persistedMessageCount; i < state.messages.length; i++) {
|
|
105
|
+
const message = state.messages[i];
|
|
106
|
+
if (!message)
|
|
107
|
+
continue;
|
|
108
|
+
const entry = { t: 'msg', message, ts };
|
|
109
|
+
lines.push(JSON.stringify(entry));
|
|
110
|
+
}
|
|
111
|
+
if (lines.length === 0)
|
|
112
|
+
return;
|
|
113
|
+
try {
|
|
114
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
115
|
+
await fs.appendFile(filePath, lines.join('\n') + '\n', 'utf-8');
|
|
116
|
+
state.persistedMessageCount = state.messages.length;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// best-effort
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Append a usage snapshot for the current turn. Called from the agent loop
|
|
123
|
+
* after `collectTurnResponse` accepts the provider's `usage` object. The
|
|
124
|
+
* picker reads only the LAST usage line (tail scan) to display per-session
|
|
125
|
+
* totals — no need to keep older snapshots around any more efficiently. */
|
|
126
|
+
export async function appendUsage(state, modelId) {
|
|
127
|
+
const filePath = getSessionFilePath(state);
|
|
128
|
+
const entry = {
|
|
129
|
+
t: 'meta',
|
|
130
|
+
kind: 'usage',
|
|
131
|
+
usage: { ...state.tokenUsage },
|
|
132
|
+
modelId,
|
|
133
|
+
turn: state.turnCount,
|
|
134
|
+
ts: new Date().toISOString(),
|
|
135
|
+
};
|
|
136
|
+
await appendLine(filePath, entry);
|
|
137
|
+
}
|
|
138
|
+
/** Mark a compaction event and re-flush the (just-shrunk) message array.
|
|
139
|
+
* After this returns, the jsonl post-last-boundary content equals
|
|
140
|
+
* `state.messages` exactly — `loadSession` reconstructs the same in-memory
|
|
141
|
+
* state on resume.
|
|
142
|
+
*
|
|
143
|
+
* Why we re-append instead of relying on the pre-boundary messages: our
|
|
144
|
+
* `compressMessages` keeps a `recent N` slice verbatim, but those slices
|
|
145
|
+
* were already persisted before the boundary; the loader's
|
|
146
|
+
* "everything-after-last-boundary wins" rule would otherwise drop them.
|
|
147
|
+
* Duplicating ~6 messages on disk is cheap and keeps the load logic
|
|
148
|
+
* trivial.
|
|
149
|
+
*
|
|
150
|
+
* Light compaction (loop-guard pruning) calls this with `summary=undefined`
|
|
151
|
+
* — the trimmed messages still need a boundary so the loader doesn't
|
|
152
|
+
* resurrect the dropped loop-guard pairs. */
|
|
153
|
+
export async function markBoundaryAndReflush(state, summary) {
|
|
154
|
+
const filePath = getSessionFilePath(state);
|
|
155
|
+
const ts = new Date().toISOString();
|
|
156
|
+
const boundary = { t: 'meta', kind: 'compact-boundary', ts };
|
|
157
|
+
if (summary !== undefined)
|
|
158
|
+
boundary.summary = summary;
|
|
159
|
+
const lines = [JSON.stringify(boundary)];
|
|
160
|
+
for (const message of state.messages) {
|
|
161
|
+
const entry = { t: 'msg', message, ts };
|
|
162
|
+
lines.push(JSON.stringify(entry));
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
166
|
+
await fs.appendFile(filePath, lines.join('\n') + '\n', 'utf-8');
|
|
167
|
+
state.persistedMessageCount = state.messages.length;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// best-effort
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Append an `interrupted` marker. Purely informational — the loader
|
|
174
|
+
* ignores it for state reconstruction; the picker can show "interrupted"
|
|
175
|
+
* next to sessions that ended mid-turn so users know what they're
|
|
176
|
+
* resuming into. */
|
|
177
|
+
export async function appendInterrupted(state) {
|
|
178
|
+
if (!state.sessionId)
|
|
179
|
+
return;
|
|
180
|
+
const filePath = getSessionFilePath(state);
|
|
181
|
+
const entry = { t: 'meta', kind: 'interrupted', ts: new Date().toISOString() };
|
|
182
|
+
await appendLine(filePath, entry);
|
|
183
|
+
}
|
|
184
|
+
const EMPTY_USAGE = {
|
|
185
|
+
inputTokens: 0,
|
|
186
|
+
outputTokens: 0,
|
|
187
|
+
totalTokens: 0,
|
|
188
|
+
cacheReadTokens: 0,
|
|
189
|
+
cacheCreationTokens: 0,
|
|
190
|
+
currentContextTokens: 0,
|
|
191
|
+
};
|
|
192
|
+
/** Walk a session jsonl and reconstruct a LoadedSession.
|
|
193
|
+
*
|
|
194
|
+
* Compact-boundary semantics (matches Claude Code): every time we see a
|
|
195
|
+
* `compact-boundary` line, the message accumulator is cleared. So the
|
|
196
|
+
* returned `messages` reflects only what's after the LAST boundary —
|
|
197
|
+
* which by construction equals the in-memory state at the point of
|
|
198
|
+
* compaction (see `markBoundaryAndReflush`).
|
|
199
|
+
*
|
|
200
|
+
* Trailing tool_call / tool_result orphans are trimmed (the next API
|
|
201
|
+
* request would otherwise reject the message array) — see
|
|
202
|
+
* `sanitizeMessageTail` for the exact rule. */
|
|
203
|
+
export async function loadSession(filePath) {
|
|
204
|
+
let raw;
|
|
205
|
+
try {
|
|
206
|
+
raw = await readFile(filePath, 'utf-8');
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
let header = null;
|
|
212
|
+
let lastUsage = null;
|
|
213
|
+
let messages = [];
|
|
214
|
+
for (const line of raw.split('\n')) {
|
|
215
|
+
if (!line.trim())
|
|
216
|
+
continue;
|
|
217
|
+
let entry;
|
|
218
|
+
try {
|
|
219
|
+
entry = JSON.parse(line);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
continue; // skip malformed lines silently
|
|
223
|
+
}
|
|
224
|
+
if (entry.t === 'meta') {
|
|
225
|
+
if (entry.kind === 'header') {
|
|
226
|
+
header = entry;
|
|
227
|
+
}
|
|
228
|
+
else if (entry.kind === 'usage') {
|
|
229
|
+
lastUsage = entry;
|
|
230
|
+
}
|
|
231
|
+
else if (entry.kind === 'compact-boundary') {
|
|
232
|
+
messages = [];
|
|
233
|
+
}
|
|
234
|
+
// 'interrupted' is informational only — doesn't affect state
|
|
235
|
+
}
|
|
236
|
+
else if (entry.t === 'msg') {
|
|
237
|
+
messages.push(entry.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!header)
|
|
241
|
+
return null;
|
|
242
|
+
return {
|
|
243
|
+
sessionId: header.sessionId,
|
|
244
|
+
taskSlug: header.taskSlug,
|
|
245
|
+
startedAt: header.startedAt,
|
|
246
|
+
modelId: header.modelId,
|
|
247
|
+
cwd: header.cwd,
|
|
248
|
+
gitBranch: header.gitBranch,
|
|
249
|
+
firstPrompt: header.firstPrompt,
|
|
250
|
+
messages: sanitizeMessageTail(messages),
|
|
251
|
+
tokenUsage: lastUsage?.usage ?? EMPTY_USAGE,
|
|
252
|
+
turnCount: lastUsage?.turn ?? 0,
|
|
253
|
+
filePath,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/** Drop trailing assistant tool_calls that have no matching tool_result
|
|
257
|
+
* later in the array. Providers reject any orphan with "tool_use without
|
|
258
|
+
* tool_result", so resuming a session that ended mid-tool-execution must
|
|
259
|
+
* trim back to the last fully-resolved boundary.
|
|
260
|
+
*
|
|
261
|
+
* Algorithm: collect every toolCallId that has a tool_result somewhere,
|
|
262
|
+
* then walk back from the end and drop any assistant message whose
|
|
263
|
+
* tool_call parts include an unresolved id. Stops at the first clean
|
|
264
|
+
* message (text-only assistant, or assistant whose every tool_call IS
|
|
265
|
+
* resolved). */
|
|
266
|
+
function sanitizeMessageTail(messages) {
|
|
267
|
+
const resolvedIds = new Set();
|
|
268
|
+
for (const msg of messages) {
|
|
269
|
+
if (msg.role !== 'tool' || !Array.isArray(msg.content))
|
|
270
|
+
continue;
|
|
271
|
+
for (const part of msg.content) {
|
|
272
|
+
if (part?.type === 'tool-result' && typeof part.toolCallId === 'string') {
|
|
273
|
+
resolvedIds.add(part.toolCallId);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
let cutAt = messages.length;
|
|
278
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
279
|
+
const msg = messages[i];
|
|
280
|
+
if (!msg) {
|
|
281
|
+
cutAt = i;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (msg.role !== 'assistant') {
|
|
285
|
+
// Bare 'tool' or 'user' at the tail without an upstream tool_call is
|
|
286
|
+
// legal — keep walking; the cut is driven only by orphan tool_calls.
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
const content = msg.content;
|
|
290
|
+
if (typeof content === 'string')
|
|
291
|
+
break; // text-only assistant — clean tail
|
|
292
|
+
if (!Array.isArray(content))
|
|
293
|
+
break;
|
|
294
|
+
const hasOrphan = content.some((p) => p?.type === 'tool-call' && typeof p.toolCallId === 'string' && !resolvedIds.has(p.toolCallId));
|
|
295
|
+
if (hasOrphan) {
|
|
296
|
+
cutAt = i;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
return cutAt < messages.length ? messages.slice(0, cutAt) : messages;
|
|
302
|
+
}
|
|
303
|
+
/** Enumerate every session jsonl in the current project, newest first.
|
|
304
|
+
* Reads only the head (~8KB, for the header line) and tail (~4KB, for
|
|
305
|
+
* the last usage line) of each file — no full-file load — so the picker
|
|
306
|
+
* is responsive even with hundreds of historical sessions. Files
|
|
307
|
+
* without a parseable header are dropped silently. */
|
|
308
|
+
export async function listSessions(cwd = process.cwd()) {
|
|
309
|
+
const dir = sessionsDir(cwd);
|
|
310
|
+
let entries;
|
|
311
|
+
try {
|
|
312
|
+
entries = await fs.readdir(dir);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
const jsonlFiles = entries.filter((f) => f.endsWith('.jsonl'));
|
|
318
|
+
const results = await Promise.all(jsonlFiles.map(async (f) => {
|
|
319
|
+
const filePath = path.join(dir, f);
|
|
320
|
+
try {
|
|
321
|
+
const stat = await fs.stat(filePath);
|
|
322
|
+
const head = await readRange(filePath, 0, Math.min(8 * 1024, stat.size));
|
|
323
|
+
const headerLine = head.split('\n').find((l) => l.includes('"kind":"header"'));
|
|
324
|
+
if (!headerLine)
|
|
325
|
+
return null;
|
|
326
|
+
let header;
|
|
327
|
+
try {
|
|
328
|
+
header = JSON.parse(headerLine);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const tailStart = Math.max(0, stat.size - 4 * 1024);
|
|
334
|
+
const tail = await readRange(filePath, tailStart, stat.size - tailStart);
|
|
335
|
+
let tokenUsage = null;
|
|
336
|
+
const tailLines = tail.split('\n').reverse();
|
|
337
|
+
for (const l of tailLines) {
|
|
338
|
+
if (!l.trim())
|
|
339
|
+
continue;
|
|
340
|
+
if (l.includes('"kind":"usage"')) {
|
|
341
|
+
try {
|
|
342
|
+
const e = JSON.parse(l);
|
|
343
|
+
tokenUsage = e.usage;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Malformed line — keep scanning earlier lines.
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
filePath,
|
|
353
|
+
sessionId: header.sessionId,
|
|
354
|
+
taskSlug: header.taskSlug,
|
|
355
|
+
firstPrompt: header.firstPrompt,
|
|
356
|
+
startedAt: header.startedAt,
|
|
357
|
+
modelId: header.modelId,
|
|
358
|
+
mtime: stat.mtimeMs,
|
|
359
|
+
tokenUsage,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}));
|
|
366
|
+
return results.filter((r) => r !== null).sort((a, b) => b.mtime - a.mtime);
|
|
367
|
+
}
|
|
368
|
+
/** Read [offset, offset+length) bytes of a file as utf-8. Used by
|
|
369
|
+
* `listSessions` to grab head/tail without slurping the full file. */
|
|
370
|
+
async function readRange(filePath, offset, length) {
|
|
371
|
+
if (length <= 0)
|
|
372
|
+
return '';
|
|
373
|
+
const fh = await fs.open(filePath, 'r');
|
|
374
|
+
try {
|
|
375
|
+
const buf = Buffer.alloc(length);
|
|
376
|
+
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
377
|
+
return buf.subarray(0, bytesRead).toString('utf-8');
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
await fh.close();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/** Pick the most recently modified session file in the current project, or
|
|
384
|
+
* null if none exist. Used by `xc --continue` / `-c` to skip the picker
|
|
385
|
+
* and resume the latest session unconditionally. */
|
|
386
|
+
export async function pickLatestSession(cwd = process.cwd()) {
|
|
387
|
+
const all = await listSessions(cwd);
|
|
388
|
+
return all[0] ?? null;
|
|
389
|
+
}
|
|
390
|
+
/** Stable identifier for a session in picker UI. We can't use the filename
|
|
391
|
+
* directly (it can collide visually when multiple sessions share a slug)
|
|
392
|
+
* and the sessionId alone isn't unique across renames — but the file path
|
|
393
|
+
* is, by definition. Hashed to keep the choice label compact. */
|
|
394
|
+
export function shortIdFor(filePath) {
|
|
395
|
+
return createHash('sha1').update(filePath).digest('hex').slice(0, 8);
|
|
396
|
+
}
|
|
397
|
+
/** Build a LoopState seeded from a previously-saved session. The agent
|
|
398
|
+
* loop accepts `existingState` and will continue appending to the same
|
|
399
|
+
* jsonl file (filename derives from `sessionId` + `taskSlug`, both
|
|
400
|
+
* preserved here). `persistedMessageCount` is set to the loaded length
|
|
401
|
+
* so the very first flush after the next user submit only appends NEW
|
|
402
|
+
* messages — the loaded tail is already on disk. */
|
|
403
|
+
export function hydrateLoopState(loaded, initialMode = 'default') {
|
|
404
|
+
const state = createLoopState(initialMode);
|
|
405
|
+
state.sessionId = loaded.sessionId;
|
|
406
|
+
state.taskSlug = loaded.taskSlug;
|
|
407
|
+
state.startedAt = loaded.startedAt;
|
|
408
|
+
state.messages = loaded.messages.slice();
|
|
409
|
+
state.tokenUsage = { ...loaded.tokenUsage };
|
|
410
|
+
state.lastInputTokens = loaded.tokenUsage.inputTokens;
|
|
411
|
+
state.turnCount = loaded.turnCount;
|
|
412
|
+
state.persistedMessageCount = loaded.messages.length;
|
|
413
|
+
return state;
|
|
414
|
+
}
|
|
415
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/agent/session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"built-in.d.ts","sourceRoot":"","sources":["../../../src/agent/sub-agents/built-in.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAapD,eAAO,MAAM,aAAa,EAAE,kBAAkB,EA0F7C,CAAA"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const SHELL_DENY_KEYWORDS = [
|
|
2
|
+
'rm ', 'rm\t', 'rmdir', 'del ', 'rd ',
|
|
3
|
+
'mv ', 'move ', 'ren ',
|
|
4
|
+
'git commit', 'git push', 'git merge', 'git rebase', 'git reset',
|
|
5
|
+
'git checkout -b', 'git branch -d', 'git branch -D',
|
|
6
|
+
'>', '>>', 'tee ', 'tee\t',
|
|
7
|
+
'chmod', 'chown',
|
|
8
|
+
'npm publish', 'pnpm publish', 'yarn publish',
|
|
9
|
+
'docker rm', 'docker rmi',
|
|
10
|
+
];
|
|
11
|
+
export const builtInAgents = [
|
|
12
|
+
{
|
|
13
|
+
name: 'explore',
|
|
14
|
+
description: 'Read-only codebase exploration. Use for "where is X defined", "find all callers of Y", or any pure-read research question.',
|
|
15
|
+
prompt: `You are a read-only codebase explorer. Your job is to find information, trace code paths, and report findings clearly.
|
|
16
|
+
|
|
17
|
+
Guidelines:
|
|
18
|
+
- Search broadly first (glob, grep), then read specific files
|
|
19
|
+
- Report file paths and line numbers so the parent agent can reference them
|
|
20
|
+
- If the codebase is large, prioritize the most relevant files
|
|
21
|
+
- Do NOT suggest code changes — just report what you find
|
|
22
|
+
|
|
23
|
+
CRITICAL — your final message is ALL the parent agent sees. It will NOT re-read files you've already read. Your output must be comprehensive enough that the parent can act on it directly:
|
|
24
|
+
- Include key code snippets (function signatures, type definitions, important logic) — not just file paths
|
|
25
|
+
- For architecture questions, describe the data flow and module relationships
|
|
26
|
+
- For "find all X" questions, list every match with file:line and a brief context line
|
|
27
|
+
- When exploring project structure, include dependency lists, entry points, and config details
|
|
28
|
+
- Never say "see file X for details" — the parent CANNOT see file X. Inline the relevant details.`,
|
|
29
|
+
tools: ['readFile', 'glob', 'grep', 'listDir', 'shell'],
|
|
30
|
+
shellRestrictions: SHELL_DENY_KEYWORDS,
|
|
31
|
+
maxTurns: 25,
|
|
32
|
+
source: 'built-in',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'general-purpose',
|
|
36
|
+
description: 'Multi-step research and investigation. Use for open-ended questions that may need 3+ tool calls and you don\'t want noise in the main context.',
|
|
37
|
+
prompt: `You are a general-purpose research assistant. You can read files, search code, and run shell commands to investigate questions.
|
|
38
|
+
|
|
39
|
+
Guidelines:
|
|
40
|
+
- Be thorough but efficient — minimize unnecessary tool calls
|
|
41
|
+
- Synthesize findings into a clear, actionable summary
|
|
42
|
+
- Include file paths and line numbers for key references
|
|
43
|
+
- If you find issues or patterns, describe them concisely
|
|
44
|
+
|
|
45
|
+
CRITICAL — your final message is ALL the parent agent sees. It will NOT re-read files you've already read. Your output must be self-contained:
|
|
46
|
+
- Include key code snippets, not just references — the parent cannot read the files
|
|
47
|
+
- For multi-file investigations, summarize each file's role and relevant content
|
|
48
|
+
- When the parent needs to modify code, include the exact current code that needs changing`,
|
|
49
|
+
tools: ['readFile', 'glob', 'grep', 'listDir', 'shell'],
|
|
50
|
+
maxTurns: 40,
|
|
51
|
+
source: 'built-in',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'plan',
|
|
55
|
+
description: 'Design an implementation plan. Returns step-by-step plans, identifies critical files, considers tradeoffs.',
|
|
56
|
+
prompt: `You are a planning assistant. Given a task description, explore the codebase and produce a detailed implementation plan.
|
|
57
|
+
|
|
58
|
+
Your plan should include:
|
|
59
|
+
1. **Context** — what problem is being solved and why
|
|
60
|
+
2. **Critical files** — which files need to change, with paths
|
|
61
|
+
3. **Step-by-step approach** — ordered implementation steps
|
|
62
|
+
4. **Existing code to reuse** — functions, patterns, utilities already in the repo
|
|
63
|
+
5. **Risks and tradeoffs** — edge cases, breaking changes, alternatives considered
|
|
64
|
+
6. **Verification** — how to test the changes
|
|
65
|
+
|
|
66
|
+
Guidelines:
|
|
67
|
+
- Read the relevant code before planning — don't guess at file structure
|
|
68
|
+
- Reference existing patterns in the codebase (don't reinvent)
|
|
69
|
+
- Keep the plan concise enough to execute, detailed enough to be unambiguous`,
|
|
70
|
+
tools: ['readFile', 'glob', 'grep', 'listDir'],
|
|
71
|
+
maxTurns: 30,
|
|
72
|
+
source: 'built-in',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'code-reviewer',
|
|
76
|
+
description: 'Review pending changes (or specific files) for bugs, security issues, and style violations. Returns a punch list.',
|
|
77
|
+
prompt: `You are a code reviewer. Examine the specified files or pending changes and produce a structured review.
|
|
78
|
+
|
|
79
|
+
Your review should cover:
|
|
80
|
+
- **Bugs** — logic errors, off-by-one, null/undefined hazards, race conditions
|
|
81
|
+
- **Security** — injection, XSS, secrets in code, unsafe deserialization
|
|
82
|
+
- **Style** — naming, consistency with surrounding code, dead code
|
|
83
|
+
- **Performance** — unnecessary allocations, O(n^2) where O(n) suffices
|
|
84
|
+
- **Missing edge cases** — error handling, empty inputs, concurrent access
|
|
85
|
+
|
|
86
|
+
Output format: a numbered punch list, each item with severity (critical/warning/nit), file:line, and a one-line description. Group by file.
|
|
87
|
+
|
|
88
|
+
Guidelines:
|
|
89
|
+
- Use git diff (shell) to see pending changes when reviewing uncommitted work
|
|
90
|
+
- Read surrounding code for context — don't flag patterns that are idiomatic in this codebase
|
|
91
|
+
- Be specific: "line 42: array index not bounds-checked" not "consider adding validation"`,
|
|
92
|
+
tools: ['readFile', 'glob', 'grep', 'listDir', 'shell'],
|
|
93
|
+
shellRestrictions: SHELL_DENY_KEYWORDS,
|
|
94
|
+
maxTurns: 25,
|
|
95
|
+
source: 'built-in',
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
//# sourceMappingURL=built-in.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"built-in.js","sourceRoot":"","sources":["../../../src/agent/sub-agents/built-in.ts"],"names":[],"mappings":"AAGA,MAAM,mBAAmB,GAAG;IAC1B,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK;IACrC,KAAK,EAAE,OAAO,EAAE,MAAM;IACtB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW;IAChE,iBAAiB,EAAE,eAAe,EAAE,eAAe;IACnD,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO;IAC1B,OAAO,EAAE,OAAO;IAChB,aAAa,EAAE,cAAc,EAAE,cAAc;IAC7C,WAAW,EAAE,YAAY;CAC1B,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAyB;IACjD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EACT,4HAA4H;QAC9H,MAAM,EAAE;;;;;;;;;;;;;kGAasF;QAC9F,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;QACvD,iBAAiB,EAAE,mBAAmB;QACtC,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,UAAU;KACnB;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,WAAW,EACT,gJAAgJ;QAClJ,MAAM,EAAE;;;;;;;;;;;2FAW+E;QACvF,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;QACvD,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,UAAU;KACnB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,WAAW,EACT,4GAA4G;QAC9G,MAAM,EAAE;;;;;;;;;;;;;6EAaiE;QACzE,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC;QAC9C,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,UAAU;KACnB;IACD;QACE,IAAI,EAAE,eAAe;QACrB,WAAW,EACT,mHAAmH;QACrH,MAAM,EAAE;;;;;;;;;;;;;;0FAc8E;QACtF,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;QACvD,iBAAiB,EAAE,mBAAmB;QACtC,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,UAAU;KACnB;CACF,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { SubAgentDefinition, SubAgentTrace, SubAgentEvent } from './types.js';
|
|
2
|
+
export { builtInAgents } from './built-in.js';
|
|
3
|
+
export { loadCustomAgents } from './loader.js';
|
|
4
|
+
export { SubAgentRegistry, createSubAgentRegistry, createBuiltInRegistry } from './registry.js';
|
|
5
|
+
export { runSubAgent } from './runner.js';
|
|
6
|
+
export type { RunSubAgentArgs, RunSubAgentResult } from './runner.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/sub-agents/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC/F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { builtInAgents } from './built-in.js';
|
|
2
|
+
export { loadCustomAgents } from './loader.js';
|
|
3
|
+
export { SubAgentRegistry, createSubAgentRegistry, createBuiltInRegistry } from './registry.js';
|
|
4
|
+
export { runSubAgent } from './runner.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/agent/sub-agents/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC/F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SubAgentDefinition } from './types.js';
|
|
2
|
+
/** Load custom sub-agents from global + project directories.
|
|
3
|
+
* Environment variable `XC_AGENTS_DIR` overrides paths (testing only). */
|
|
4
|
+
export declare function loadCustomAgents(): Promise<SubAgentDefinition[]>;
|
|
5
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/agent/sub-agents/loader.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAiHpD;2EAC2E;AAC3E,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAatE"}
|