@xandout/libra-harness 0.1.12 → 0.1.14
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.d.ts.map +1 -1
- package/dist/agent.js +48 -11
- package/dist/agent.js.map +1 -1
- package/dist/extras/extensions/code-tools/extension.d.ts +83 -0
- package/dist/extras/extensions/code-tools/extension.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/extension.js +93 -0
- package/dist/extras/extensions/code-tools/extension.js.map +1 -0
- package/dist/extras/extensions/code-tools/extension.json +7 -0
- package/dist/extras/extensions/code-tools/extension.test.d.ts +2 -0
- package/dist/extras/extensions/code-tools/extension.test.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/extension.test.js +656 -0
- package/dist/extras/extensions/code-tools/extension.test.js.map +1 -0
- package/dist/extras/extensions/code-tools/index.d.ts +3 -0
- package/dist/extras/extensions/code-tools/index.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/index.js +2 -0
- package/dist/extras/extensions/code-tools/index.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/code-search.d.ts +12 -0
- package/dist/extras/extensions/code-tools/tools/code-search.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/code-search.js +86 -0
- package/dist/extras/extensions/code-tools/tools/code-search.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/find.d.ts +3 -0
- package/dist/extras/extensions/code-tools/tools/find.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/find.js +160 -0
- package/dist/extras/extensions/code-tools/tools/find.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/grep.d.ts +3 -0
- package/dist/extras/extensions/code-tools/tools/grep.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/grep.js +306 -0
- package/dist/extras/extensions/code-tools/tools/grep.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/read.d.ts +3 -0
- package/dist/extras/extensions/code-tools/tools/read.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/read.js +97 -0
- package/dist/extras/extensions/code-tools/tools/read.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/shared.d.ts +14 -0
- package/dist/extras/extensions/code-tools/tools/shared.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/shared.js +53 -0
- package/dist/extras/extensions/code-tools/tools/shared.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/shell.d.ts +49 -0
- package/dist/extras/extensions/code-tools/tools/shell.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/shell.js +623 -0
- package/dist/extras/extensions/code-tools/tools/shell.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/todo.d.ts +24 -0
- package/dist/extras/extensions/code-tools/tools/todo.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/todo.js +131 -0
- package/dist/extras/extensions/code-tools/tools/todo.js.map +1 -0
- package/dist/extras/extensions/code-tools/tools/write.d.ts +4 -0
- package/dist/extras/extensions/code-tools/tools/write.d.ts.map +1 -0
- package/dist/extras/extensions/code-tools/tools/write.js +179 -0
- package/dist/extras/extensions/code-tools/tools/write.js.map +1 -0
- package/dist/extras/extensions/disk-session/extension.d.ts +5 -0
- package/dist/extras/extensions/disk-session/extension.d.ts.map +1 -1
- package/dist/extras/extensions/disk-session/extension.js +148 -20
- package/dist/extras/extensions/disk-session/extension.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type TodoStatus = 'pending' | 'in_progress' | 'completed';
|
|
2
|
+
export interface Todo {
|
|
3
|
+
content: string;
|
|
4
|
+
status: TodoStatus;
|
|
5
|
+
}
|
|
6
|
+
export declare class TodoStore {
|
|
7
|
+
private todos;
|
|
8
|
+
private filePath?;
|
|
9
|
+
constructor(filePath?: string);
|
|
10
|
+
/** Load todos from disk. */
|
|
11
|
+
private load;
|
|
12
|
+
/** Save todos to disk. */
|
|
13
|
+
private save;
|
|
14
|
+
/** Replace the entire todo list. */
|
|
15
|
+
setAll(todos: Todo[]): void;
|
|
16
|
+
/** Get a snapshot of the current todos. */
|
|
17
|
+
getAll(): Todo[];
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const todoWriteTool: (cfg: {
|
|
21
|
+
toolPrefix: string;
|
|
22
|
+
store: TodoStore;
|
|
23
|
+
}) => any;
|
|
24
|
+
//# sourceMappingURL=todo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo.d.ts","sourceRoot":"","sources":["../../../../../src/extras/extensions/code-tools/tools/todo.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,IAAI;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAS;IAE1B,YAAY,QAAQ,CAAC,EAAE,MAAM,EAK5B;IAED,4BAA4B;IAC5B,OAAO,CAAC,IAAI;IAmBZ,0BAA0B;IAC1B,OAAO,CAAC,IAAI;IAUZ,oCAAoC;IACpC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAG1B;IAED,2CAA2C;IAC3C,MAAM,IAAI,IAAI,EAAE,CAEf;IAED,KAAK,IAAI,IAAI,CAGZ;CACF;AAGD,eAAO,MAAM,aAAa,EAAE,CAAC,GAAG,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,KAAK,GA4E7E,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { makeToolName } from './shared.js';
|
|
4
|
+
export class TodoStore {
|
|
5
|
+
todos = [];
|
|
6
|
+
filePath;
|
|
7
|
+
constructor(filePath) {
|
|
8
|
+
this.filePath = filePath;
|
|
9
|
+
if (filePath) {
|
|
10
|
+
this.load();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Load todos from disk. */
|
|
14
|
+
load() {
|
|
15
|
+
if (!this.filePath)
|
|
16
|
+
return;
|
|
17
|
+
try {
|
|
18
|
+
if (existsSync(this.filePath)) {
|
|
19
|
+
const data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
|
20
|
+
if (Array.isArray(data)) {
|
|
21
|
+
this.todos = data.map((t) => ({
|
|
22
|
+
content: String(t.content ?? ''),
|
|
23
|
+
status: (t.status === 'in_progress' || t.status === 'completed')
|
|
24
|
+
? t.status
|
|
25
|
+
: 'pending',
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// ignore corrupt file
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Save todos to disk. */
|
|
35
|
+
save() {
|
|
36
|
+
if (!this.filePath)
|
|
37
|
+
return;
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
40
|
+
writeFileSync(this.filePath, JSON.stringify(this.todos, null, 2) + '\n', 'utf-8');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore write errors
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Replace the entire todo list. */
|
|
47
|
+
setAll(todos) {
|
|
48
|
+
this.todos = todos;
|
|
49
|
+
this.save();
|
|
50
|
+
}
|
|
51
|
+
/** Get a snapshot of the current todos. */
|
|
52
|
+
getAll() {
|
|
53
|
+
return [...this.todos];
|
|
54
|
+
}
|
|
55
|
+
clear() {
|
|
56
|
+
this.todos = [];
|
|
57
|
+
this.save();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ── todo_write tool ──────────────────────────────────────────────────
|
|
61
|
+
export const todoWriteTool = (cfg) => ({
|
|
62
|
+
name: makeToolName(cfg.toolPrefix, 'todo_write'),
|
|
63
|
+
description: 'Create and manage a structured task list for tracking multi-step work. ' +
|
|
64
|
+
'Use this for tasks with 3 or more distinct steps. Skip it for trivial single-step work. ' +
|
|
65
|
+
'Each todo has a status: "pending", "in_progress", or "completed". ' +
|
|
66
|
+
'Keep exactly ONE todo as "in_progress" at a time — mark it in_progress when starting, ' +
|
|
67
|
+
'and mark it "completed" immediately when done. ' +
|
|
68
|
+
'The entire list is replaced on each call (not appended). ' +
|
|
69
|
+
'Add follow-up tasks discovered during work. Remove tasks that are no longer relevant. ' +
|
|
70
|
+
'Todos persist across sessions, so you can pick up where you left off.',
|
|
71
|
+
parameters: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
todos: {
|
|
75
|
+
type: 'array',
|
|
76
|
+
description: 'The complete todo list. Replaces the previous list on each call.',
|
|
77
|
+
items: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
content: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
description: 'The task description.',
|
|
83
|
+
},
|
|
84
|
+
status: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
enum: ['pending', 'in_progress', 'completed'],
|
|
87
|
+
description: 'Current status of the todo.',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ['content', 'status'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ['todos'],
|
|
95
|
+
},
|
|
96
|
+
async execute(args) {
|
|
97
|
+
const input = args.todos;
|
|
98
|
+
if (!Array.isArray(input)) {
|
|
99
|
+
return { toolCallId: '', content: 'Error: todos must be an array' };
|
|
100
|
+
}
|
|
101
|
+
const todos = input.map((t) => ({
|
|
102
|
+
content: String(t.content ?? ''),
|
|
103
|
+
status: (t.status === 'in_progress' || t.status === 'completed')
|
|
104
|
+
? t.status
|
|
105
|
+
: 'pending',
|
|
106
|
+
}));
|
|
107
|
+
// Validate: at most one in_progress.
|
|
108
|
+
const inProgressCount = todos.filter((t) => t.status === 'in_progress').length;
|
|
109
|
+
if (inProgressCount > 1) {
|
|
110
|
+
return {
|
|
111
|
+
toolCallId: '',
|
|
112
|
+
content: 'Error: only one todo can be "in_progress" at a time. Mark the others "pending" or "completed".',
|
|
113
|
+
isError: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
cfg.store.setAll(todos);
|
|
117
|
+
// Format the response so the model sees the current state.
|
|
118
|
+
if (todos.length === 0) {
|
|
119
|
+
return { toolCallId: '', content: 'Todo list cleared.' };
|
|
120
|
+
}
|
|
121
|
+
const lines = todos.map((t, i) => {
|
|
122
|
+
const marker = t.status === 'completed' ? '[x]' : t.status === 'in_progress' ? '[~]' : '[ ]';
|
|
123
|
+
return `${i + 1}. ${marker} ${t.content}`;
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
toolCallId: '',
|
|
127
|
+
content: `Updated todo list:\n${lines.join('\n')}`,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
//# sourceMappingURL=todo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo.js","sourceRoot":"","sources":["../../../../../src/extras/extensions/code-tools/tools/todo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../../../src/extras/extensions/code-tools/tools/write.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4B,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAGzE,eAAO,MAAM,SAAS,EAAE,WA2DtB,CAAC;AAGH,eAAO,MAAM,QAAQ,EAAE,WA2HrB,CAAC"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { makeToolName, getReadSet } from './shared.js';
|
|
4
|
+
// ── write ────────────────────────────────────────────────────────────
|
|
5
|
+
export const writeTool = (cfg) => ({
|
|
6
|
+
name: makeToolName(cfg.toolPrefix, 'write'),
|
|
7
|
+
description: 'Write content to a file, creating it if it does not exist or overwriting it if it does. ' +
|
|
8
|
+
'You MUST read an existing file before overwriting it — this is enforced. ' +
|
|
9
|
+
'Use absolute paths. Creates parent directories as needed. ' +
|
|
10
|
+
'The content parameter is the complete file contents (not a patch).',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
file_path: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'The absolute path to the file to write.',
|
|
17
|
+
},
|
|
18
|
+
content: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'The complete content to write to the file.',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ['file_path', 'content'],
|
|
24
|
+
},
|
|
25
|
+
async execute(args, ctx) {
|
|
26
|
+
const filePath = String(args.file_path ?? '');
|
|
27
|
+
if (!filePath) {
|
|
28
|
+
return { toolCallId: '', content: 'Error: file_path is required' };
|
|
29
|
+
}
|
|
30
|
+
const content = String(args.content ?? '');
|
|
31
|
+
// Enforce read-before-overwrite for existing files.
|
|
32
|
+
if (existsSync(filePath)) {
|
|
33
|
+
const readSet = getReadSet(ctx.metadata);
|
|
34
|
+
if (!readSet.has(filePath)) {
|
|
35
|
+
return {
|
|
36
|
+
toolCallId: '',
|
|
37
|
+
content: `Error: You must read "${filePath}" before overwriting it. Use the read tool first.`,
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const dir = dirname(filePath);
|
|
44
|
+
if (!existsSync(dir)) {
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
48
|
+
return {
|
|
49
|
+
toolCallId: '',
|
|
50
|
+
content: `Wrote ${content.length} chars to ${filePath}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
return {
|
|
55
|
+
toolCallId: '',
|
|
56
|
+
content: `Error writing file: ${err instanceof Error ? err.message : String(err)}`,
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
// ── edit ─────────────────────────────────────────────────────────────
|
|
63
|
+
export const editTool = (cfg) => ({
|
|
64
|
+
name: makeToolName(cfg.toolPrefix, 'edit'),
|
|
65
|
+
description: 'Replace a string in a file with a new string. The old_string must be unique in the file ' +
|
|
66
|
+
'unless replace_all is true. You MUST read the file before editing it. ' +
|
|
67
|
+
'Use absolute paths. Preserve indentation exactly — match the file\'s actual content. ' +
|
|
68
|
+
'The new_string must differ from old_string.',
|
|
69
|
+
parameters: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
file_path: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'The absolute path to the file to edit.',
|
|
75
|
+
},
|
|
76
|
+
old_string: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'The exact string to find in the file. Must be unique unless replace_all is true.',
|
|
79
|
+
},
|
|
80
|
+
new_string: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
description: 'The string to replace old_string with. Must differ from old_string.',
|
|
83
|
+
},
|
|
84
|
+
replace_all: {
|
|
85
|
+
type: 'boolean',
|
|
86
|
+
description: 'If true, replace all occurrences of old_string. Default: false.',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
required: ['file_path', 'old_string', 'new_string'],
|
|
90
|
+
},
|
|
91
|
+
async execute(args, ctx) {
|
|
92
|
+
const filePath = String(args.file_path ?? '');
|
|
93
|
+
if (!filePath) {
|
|
94
|
+
return { toolCallId: '', content: 'Error: file_path is required' };
|
|
95
|
+
}
|
|
96
|
+
const oldString = String(args.old_string ?? '');
|
|
97
|
+
const newString = String(args.new_string ?? '');
|
|
98
|
+
const replaceAll = args.replace_all === true;
|
|
99
|
+
if (oldString === newString) {
|
|
100
|
+
return {
|
|
101
|
+
toolCallId: '',
|
|
102
|
+
content: 'Error: new_string must differ from old_string.',
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!existsSync(filePath)) {
|
|
107
|
+
return {
|
|
108
|
+
toolCallId: '',
|
|
109
|
+
content: `File not found: ${filePath}`,
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Enforce read-before-edit.
|
|
114
|
+
const readSet = getReadSet(ctx.metadata);
|
|
115
|
+
if (!readSet.has(filePath)) {
|
|
116
|
+
return {
|
|
117
|
+
toolCallId: '',
|
|
118
|
+
content: `Error: You must read "${filePath}" before editing it. Use the read tool first.`,
|
|
119
|
+
isError: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
let content;
|
|
123
|
+
try {
|
|
124
|
+
content = readFileSync(filePath, 'utf-8');
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return {
|
|
128
|
+
toolCallId: '',
|
|
129
|
+
content: `Error reading file: ${err instanceof Error ? err.message : String(err)}`,
|
|
130
|
+
isError: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Count occurrences.
|
|
134
|
+
let count = 0;
|
|
135
|
+
let idx = 0;
|
|
136
|
+
while ((idx = content.indexOf(oldString, idx)) !== -1) {
|
|
137
|
+
count++;
|
|
138
|
+
idx += oldString.length;
|
|
139
|
+
}
|
|
140
|
+
if (count === 0) {
|
|
141
|
+
return {
|
|
142
|
+
toolCallId: '',
|
|
143
|
+
content: `Error: old_string not found in ${filePath}. Check for exact whitespace, indentation, and line endings.`,
|
|
144
|
+
isError: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (count > 1 && !replaceAll) {
|
|
148
|
+
return {
|
|
149
|
+
toolCallId: '',
|
|
150
|
+
content: `Error: old_string appears ${count} times in ${filePath}. Provide more surrounding context to make it unique, or set replace_all to true.`,
|
|
151
|
+
isError: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Perform the replacement.
|
|
155
|
+
let newContent;
|
|
156
|
+
if (replaceAll) {
|
|
157
|
+
newContent = content.split(oldString).join(newString);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
newContent = content.replace(oldString, newString);
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
writeFileSync(filePath, newContent, 'utf-8');
|
|
164
|
+
const replaced = replaceAll ? count : 1;
|
|
165
|
+
return {
|
|
166
|
+
toolCallId: '',
|
|
167
|
+
content: `Replaced ${replaced} occurrence${replaced === 1 ? '' : 's'} in ${filePath}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
return {
|
|
172
|
+
toolCallId: '',
|
|
173
|
+
content: `Error writing file: ${err instanceof Error ? err.message : String(err)}`,
|
|
174
|
+
isError: true,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../../../../src/extras/extensions/code-tools/tools/write.ts"],"names":[],"mappings":"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"}
|
|
@@ -107,6 +107,11 @@ export interface DiskSessionConfig {
|
|
|
107
107
|
recentChannelMessages?: number;
|
|
108
108
|
/** Load existing JSONL files into memory on startup. Default: true. */
|
|
109
109
|
loadOnStartup?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Print a summary line when sessions are loaded from disk.
|
|
112
|
+
* Default: true.
|
|
113
|
+
*/
|
|
114
|
+
verbose?: boolean;
|
|
110
115
|
/**
|
|
111
116
|
* Resolve a {@link SessionIdentity} from turn metadata. Default:
|
|
112
117
|
* reads `metadata.session` as a `SessionIdentity`, falls back to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../../../src/extras/extensions/disk-session/extension.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../../../src/extras/extensions/disk-session/extension.ts"],"names":[],"mappings":"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"}
|
|
@@ -83,7 +83,7 @@ export default function createDiskSessionExtension(config) {
|
|
|
83
83
|
// Skip corrupt files.
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
if (store.size > 0) {
|
|
86
|
+
if (store.size > 0 && config?.verbose !== false) {
|
|
87
87
|
console.log(`[disk-session] loaded ${store.size} session(s), ${totalRecords} record(s) from ${dir}`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -150,6 +150,17 @@ export default function createDiskSessionExtension(config) {
|
|
|
150
150
|
return false;
|
|
151
151
|
return true; // user, system, assistant (without tool calls)
|
|
152
152
|
};
|
|
153
|
+
// Drop trailing user messages that don't have a following assistant
|
|
154
|
+
// response. This happens when a turn was interrupted (crash, halt,
|
|
155
|
+
// kill -9). Including an unanswered user request from a prior session
|
|
156
|
+
// confuses the agent — it sees a request it never responded to.
|
|
157
|
+
function dropIncompleteTrailingTurn(records) {
|
|
158
|
+
const result = [...records];
|
|
159
|
+
while (result.length > 0 && result[result.length - 1].role === 'user') {
|
|
160
|
+
result.pop();
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
153
164
|
// ── Build context (the "fork") from a snapshot ────────────────
|
|
154
165
|
// The snapshot is a read-only copy of the session records at the
|
|
155
166
|
// moment the turn started. This function builds the messages array
|
|
@@ -163,8 +174,13 @@ export default function createDiskSessionExtension(config) {
|
|
|
163
174
|
// DM or top-level message: last N messages, but filter out
|
|
164
175
|
// tool-call intermediates that might get split by the slice.
|
|
165
176
|
if (isDirect || !threadTs) {
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
const filtered = snapshot.filter(isConversationMessage);
|
|
178
|
+
// Drop incomplete turns: a user message at the end without a
|
|
179
|
+
// following assistant response means the turn was interrupted
|
|
180
|
+
// (crash, halt, etc). Including it would confuse the agent with
|
|
181
|
+
// an unanswered request from a previous session.
|
|
182
|
+
const complete = dropIncompleteTrailingTurn(filtered);
|
|
183
|
+
return complete
|
|
168
184
|
.slice(-maxContextMessages)
|
|
169
185
|
.map(toMessage);
|
|
170
186
|
}
|
|
@@ -268,26 +284,91 @@ export default function createDiskSessionExtension(config) {
|
|
|
268
284
|
ctx.turn.messages = [...history, ...ctx.turn.messages];
|
|
269
285
|
}
|
|
270
286
|
});
|
|
271
|
-
// ── afterLLM: accumulate token usage
|
|
287
|
+
// ── afterLLM: accumulate token usage + append assistant msg ──
|
|
272
288
|
// Each LLM call returns usage (prompt/completion tokens). We
|
|
273
289
|
// accumulate them across all iterations in the turn so the final
|
|
274
290
|
// assistant record has the total cost.
|
|
291
|
+
//
|
|
292
|
+
// We also append the assistant message (including tool calls) to
|
|
293
|
+
// the session log immediately, so the JSONL file is updated
|
|
294
|
+
// iteration by iteration rather than only at afterTurn. Tool
|
|
295
|
+
// results are appended in the afterTool hook below.
|
|
275
296
|
agent.hook('afterLLM', 'disk-session', async (ctx) => {
|
|
297
|
+
const identity = identityFromCtx(ctx);
|
|
298
|
+
if (!identity)
|
|
299
|
+
return;
|
|
300
|
+
const { key, messageTs, threadTs } = identity;
|
|
276
301
|
const usage = ctx.modelResponse?.usage;
|
|
277
|
-
if (
|
|
302
|
+
if (usage) {
|
|
303
|
+
const prev = ctx.turn.metadata['_diskSessionUsage'] ?? { promptTokens: 0, completionTokens: 0, iterations: 0 };
|
|
304
|
+
ctx.turn.metadata['_diskSessionUsage'] = {
|
|
305
|
+
promptTokens: prev.promptTokens + usage.promptTokens,
|
|
306
|
+
completionTokens: prev.completionTokens + usage.completionTokens,
|
|
307
|
+
iterations: prev.iterations + 1,
|
|
308
|
+
...(usage.cachedPromptTokens && {
|
|
309
|
+
cachedPromptTokens: (prev.cachedPromptTokens ?? 0) + usage.cachedPromptTokens,
|
|
310
|
+
}),
|
|
311
|
+
...(usage.reasoningTokens && {
|
|
312
|
+
reasoningTokens: (prev.reasoningTokens ?? 0) + usage.reasoningTokens,
|
|
313
|
+
}),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// ── Incremental append: write the assistant message now ──
|
|
317
|
+
// The agent core pushes the assistant message to turn.messages
|
|
318
|
+
// AFTER afterLLM fires, so we can't read it from there yet.
|
|
319
|
+
// Instead, we build the record from modelResponse.message.
|
|
320
|
+
const modelResponse = ctx.modelResponse;
|
|
321
|
+
if (modelResponse?.message) {
|
|
322
|
+
const sessionMeta = ctx.turn.metadata.sessionMeta;
|
|
323
|
+
const record = {
|
|
324
|
+
...toRecord(modelResponse.message, messageTs, threadTs),
|
|
325
|
+
...(sessionMeta ? { meta: sessionMeta } : {}),
|
|
326
|
+
};
|
|
327
|
+
const records = store.get(key) ?? [];
|
|
328
|
+
records.push(record);
|
|
329
|
+
if (records.length > maxRecords) {
|
|
330
|
+
records.splice(0, records.length - maxRecords);
|
|
331
|
+
}
|
|
332
|
+
store.set(key, records);
|
|
333
|
+
appendToFile(key, [record]);
|
|
334
|
+
// Track how many records we've already written so afterTurn
|
|
335
|
+
// doesn't duplicate them.
|
|
336
|
+
const written = ctx.turn.metadata['_diskSessionWritten'] ?? 0;
|
|
337
|
+
ctx.turn.metadata['_diskSessionWritten'] = written + 1;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
// ── afterTool: append tool results incrementally ──────────────
|
|
341
|
+
// Each tool result is written to disk as soon as it completes,
|
|
342
|
+
// so the JSONL file reflects progress in real time.
|
|
343
|
+
agent.hook('afterTool', 'disk-session', async (ctx) => {
|
|
344
|
+
const identity = identityFromCtx(ctx);
|
|
345
|
+
if (!identity)
|
|
346
|
+
return;
|
|
347
|
+
const { key, messageTs, threadTs } = identity;
|
|
348
|
+
const toolResult = ctx.toolResult;
|
|
349
|
+
const toolCall = ctx.toolCall;
|
|
350
|
+
if (!toolResult || !toolCall)
|
|
278
351
|
return;
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}),
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}),
|
|
352
|
+
const sessionMeta = ctx.turn.metadata.sessionMeta;
|
|
353
|
+
const record = {
|
|
354
|
+
role: 'tool',
|
|
355
|
+
content: toolResult.content,
|
|
356
|
+
toolCallId: toolResult.toolCallId,
|
|
357
|
+
name: toolCall.name,
|
|
358
|
+
ts: messageTs,
|
|
359
|
+
...(threadTs ? { threadTs } : {}),
|
|
360
|
+
recordedAt: new Date().toISOString(),
|
|
361
|
+
...(sessionMeta ? { meta: sessionMeta } : {}),
|
|
290
362
|
};
|
|
363
|
+
const records = store.get(key) ?? [];
|
|
364
|
+
records.push(record);
|
|
365
|
+
if (records.length > maxRecords) {
|
|
366
|
+
records.splice(0, records.length - maxRecords);
|
|
367
|
+
}
|
|
368
|
+
store.set(key, records);
|
|
369
|
+
appendToFile(key, [record]);
|
|
370
|
+
const written = ctx.turn.metadata['_diskSessionWritten'] ?? 0;
|
|
371
|
+
ctx.turn.metadata['_diskSessionWritten'] = written + 1;
|
|
291
372
|
});
|
|
292
373
|
// ── afterTurn: append assistant response + tool calls ─────
|
|
293
374
|
// The user message was already persisted in beforeTurn.
|
|
@@ -339,11 +420,58 @@ export default function createDiskSessionExtension(config) {
|
|
|
339
420
|
const systemMsgCount = ctx.turn.messages[historyLen]?.role === 'system' ? 1 : 0;
|
|
340
421
|
const newStart = historyLen + systemMsgCount;
|
|
341
422
|
const newMessages = ctx.turn.messages.slice(newStart);
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
|
|
423
|
+
// Persist non-user, non-system messages here. The initial user
|
|
424
|
+
// message was already written in beforeTurn. EXCEPTION: steering
|
|
425
|
+
// messages are injected mid-turn as user messages with a
|
|
426
|
+
// [steering] prefix — those are new and must be persisted.
|
|
427
|
+
let filteredMessages = newMessages.filter((m) => {
|
|
428
|
+
if (m.role === 'system')
|
|
429
|
+
return false;
|
|
430
|
+
if (m.role === 'user') {
|
|
431
|
+
const text = typeof m.content === 'string'
|
|
432
|
+
? m.content
|
|
433
|
+
: Array.isArray(m.content)
|
|
434
|
+
? m.content.filter((p) => p.type === 'text').map((p) => p.text).join('')
|
|
435
|
+
: '';
|
|
436
|
+
return text.startsWith('[steering]');
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
});
|
|
440
|
+
// ── Skip records already written incrementally ───────────
|
|
441
|
+
// afterLLM and afterTool hooks append assistant messages and
|
|
442
|
+
// tool results as they happen. Skip those here to avoid
|
|
443
|
+
// duplicates. Only the final assistant response (with no tool
|
|
444
|
+
// calls) may not have been written yet — it's the one that
|
|
445
|
+
// ends the turn without triggering afterTool.
|
|
446
|
+
const alreadyWritten = ctx.turn.metadata['_diskSessionWritten'] ?? 0;
|
|
447
|
+
delete ctx.turn.metadata['_diskSessionWritten'];
|
|
448
|
+
if (alreadyWritten > 0) {
|
|
449
|
+
filteredMessages = filteredMessages.slice(alreadyWritten);
|
|
450
|
+
}
|
|
451
|
+
if (filteredMessages.length === 0) {
|
|
452
|
+
// All records were already written incrementally by afterLLM
|
|
453
|
+
// and afterTool. Still need to backfill the system prompt on
|
|
454
|
+
// the user record and clean up usage metadata.
|
|
455
|
+
delete ctx.turn.metadata['_diskSessionUsage'];
|
|
456
|
+
// ── Backfill system prompt on the last user record ───────
|
|
457
|
+
const finalSystemPrompt = ctx.turn.systemPrompt;
|
|
458
|
+
if (finalSystemPrompt) {
|
|
459
|
+
const records = store.get(key) ?? [];
|
|
460
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
461
|
+
if (records[i].role === 'user') {
|
|
462
|
+
records[i].systemPrompt = finalSystemPrompt;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
appendToFile(key, [{
|
|
467
|
+
role: 'control',
|
|
468
|
+
content: '',
|
|
469
|
+
recordedAt: new Date().toISOString(),
|
|
470
|
+
systemPrompt: finalSystemPrompt,
|
|
471
|
+
}]);
|
|
472
|
+
}
|
|
346
473
|
return;
|
|
474
|
+
}
|
|
347
475
|
// Pull accumulated usage for this turn (set by afterLLM hook).
|
|
348
476
|
const usage = ctx.turn.metadata['_diskSessionUsage'];
|
|
349
477
|
delete ctx.turn.metadata['_diskSessionUsage'];
|