@zerone-agent/open-agent-sdk 0.5.8 → 0.6.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.d.ts.map +1 -1
- package/dist/agent.js +6 -0
- package/dist/agent.js.map +1 -1
- package/dist/engine.d.ts +19 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +89 -2
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/providers/types.d.ts +7 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/revert/index.d.ts +63 -0
- package/dist/revert/index.d.ts.map +1 -0
- package/dist/revert/index.js +125 -0
- package/dist/revert/index.js.map +1 -0
- package/dist/session.d.ts +20 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +40 -6
- package/dist/session.js.map +1 -1
- package/dist/snapshot/git-detector.d.ts +9 -0
- package/dist/snapshot/git-detector.d.ts.map +1 -0
- package/dist/snapshot/git-detector.js +26 -0
- package/dist/snapshot/git-detector.js.map +1 -0
- package/dist/snapshot/index.d.ts +76 -0
- package/dist/snapshot/index.d.ts.map +1 -0
- package/dist/snapshot/index.js +288 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/snapshot/semaphore.d.ts +11 -0
- package/dist/snapshot/semaphore.d.ts.map +1 -0
- package/dist/snapshot/semaphore.js +27 -0
- package/dist/snapshot/semaphore.js.map +1 -0
- package/dist/tools/todo-tool.d.ts +8 -18
- package/dist/tools/todo-tool.d.ts.map +1 -1
- package/dist/tools/todo-tool.js +138 -76
- package/dist/tools/todo-tool.js.map +1 -1
- package/dist/tools/todowrite.txt +25 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/compact.d.ts +5 -0
- package/dist/utils/compact.d.ts.map +1 -1
- package/dist/utils/compact.js +30 -0
- package/dist/utils/compact.js.map +1 -1
- package/dist/utils/messages.js +2 -2
- package/dist/utils/messages.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { mkdir, rm, readFile, writeFile, stat } from 'fs/promises';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { Semaphore } from './semaphore.js';
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const LARGE_FILE_LIMIT = 2 * 1024 * 1024; // 2MB
|
|
10
|
+
/**
|
|
11
|
+
* Compute the default snapshot directory for a worktree.
|
|
12
|
+
*
|
|
13
|
+
* Layout: ~/.openagent/snapshot/<project-hash>/<worktree-hash>/
|
|
14
|
+
*
|
|
15
|
+
* - project-hash: groups all worktrees of the same project together.
|
|
16
|
+
* Derived from git remote URL, or directory path as fallback.
|
|
17
|
+
* - worktree-hash: identifies the specific worktree path.
|
|
18
|
+
*/
|
|
19
|
+
async function defaultSnapshotDir(worktree) {
|
|
20
|
+
const projectHash = await computeProjectHash(worktree);
|
|
21
|
+
const worktreeHash = createHash('sha256').update(worktree).digest('hex').slice(0, 16);
|
|
22
|
+
return join(homedir(), '.openagent', 'snapshot', projectHash, worktreeHash);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Derive a project-level hash from a worktree path.
|
|
26
|
+
*
|
|
27
|
+
* Strategy:
|
|
28
|
+
* 1. If git repo with remote → hash the remote URL (same project across clones/worktrees)
|
|
29
|
+
* 2. Otherwise → hash the absolute directory path
|
|
30
|
+
*/
|
|
31
|
+
async function computeProjectHash(worktree) {
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], {
|
|
34
|
+
cwd: worktree,
|
|
35
|
+
env: { ...process.env },
|
|
36
|
+
});
|
|
37
|
+
const remote = stdout.trim();
|
|
38
|
+
if (remote) {
|
|
39
|
+
return createHash('sha256').update(remote).digest('hex').slice(0, 16);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// not a git repo or no remote — fall through
|
|
44
|
+
}
|
|
45
|
+
// Fallback: hash the directory path (without trailing slash)
|
|
46
|
+
return createHash('sha256').update(worktree.replace(/\/$/, '')).digest('hex').slice(0, 16);
|
|
47
|
+
}
|
|
48
|
+
export class SnapshotEngine {
|
|
49
|
+
worktree;
|
|
50
|
+
_gitDir;
|
|
51
|
+
lock = new Semaphore();
|
|
52
|
+
gcTimer;
|
|
53
|
+
gcInterval;
|
|
54
|
+
constructor(opts) {
|
|
55
|
+
this.worktree = opts.worktree;
|
|
56
|
+
this._gitDir = opts.snapshotDir;
|
|
57
|
+
}
|
|
58
|
+
/** Snapshot repo path. Available after init(). */
|
|
59
|
+
get gitDir() {
|
|
60
|
+
if (!this._gitDir)
|
|
61
|
+
throw new Error('SnapshotEngine not initialized — call init() first');
|
|
62
|
+
return this._gitDir;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Initialize the snapshot git repo.
|
|
66
|
+
* Creates a bare repo and syncs ignore rules from the source repo.
|
|
67
|
+
*/
|
|
68
|
+
async init() {
|
|
69
|
+
// Resolve default path if not explicitly provided
|
|
70
|
+
if (!this._gitDir) {
|
|
71
|
+
this._gitDir = await defaultSnapshotDir(this.worktree);
|
|
72
|
+
}
|
|
73
|
+
await this.lock.acquire();
|
|
74
|
+
try {
|
|
75
|
+
await mkdir(this.gitDir, { recursive: true });
|
|
76
|
+
await this.execRaw(['init', '--bare', this.gitDir]);
|
|
77
|
+
await this.syncIgnoreRules();
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
this.lock.release();
|
|
81
|
+
}
|
|
82
|
+
this.startGc();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Run a git command with GIT_DIR and GIT_WORK_TREE set.
|
|
86
|
+
*/
|
|
87
|
+
async exec(args) {
|
|
88
|
+
return execFileAsync('git', args, {
|
|
89
|
+
env: {
|
|
90
|
+
...process.env,
|
|
91
|
+
GIT_DIR: this.gitDir,
|
|
92
|
+
GIT_WORK_TREE: this.worktree,
|
|
93
|
+
},
|
|
94
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Run a git command without GIT_DIR/GIT_WORK_TREE (for init).
|
|
99
|
+
*/
|
|
100
|
+
async execRaw(args, cwd) {
|
|
101
|
+
return execFileAsync('git', args, {
|
|
102
|
+
env: { ...process.env },
|
|
103
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
104
|
+
cwd,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Sync ignore rules from the source repo's .git/info/exclude.
|
|
109
|
+
*/
|
|
110
|
+
async syncIgnoreRules() {
|
|
111
|
+
try {
|
|
112
|
+
const sourceExclude = join(this.worktree, '.git', 'info', 'exclude');
|
|
113
|
+
const content = await readFile(sourceExclude, 'utf-8');
|
|
114
|
+
const targetExclude = join(this.gitDir, 'info', 'exclude');
|
|
115
|
+
await mkdir(join(this.gitDir, 'info'), { recursive: true });
|
|
116
|
+
await writeFile(targetExclude, content, 'utf-8');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// no source exclude — skip
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Snapshot the current workspace state.
|
|
124
|
+
* Returns a tree hash that can be used to restore or revert later.
|
|
125
|
+
*/
|
|
126
|
+
async track() {
|
|
127
|
+
await this.lock.acquire();
|
|
128
|
+
try {
|
|
129
|
+
await this.filterLargeUntracked();
|
|
130
|
+
await this.exec(['add', '--all']);
|
|
131
|
+
const { stdout } = await this.exec(['write-tree']);
|
|
132
|
+
return stdout.trim();
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
this.lock.release();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Remove large untracked files from the index and add them to exclude.
|
|
140
|
+
*/
|
|
141
|
+
async filterLargeUntracked() {
|
|
142
|
+
try {
|
|
143
|
+
const { stdout } = await this.exec(['status', '--porcelain', '--untracked-files=all']);
|
|
144
|
+
const excludeAdditions = [];
|
|
145
|
+
for (const line of stdout.split('\n')) {
|
|
146
|
+
if (!line.startsWith('??'))
|
|
147
|
+
continue;
|
|
148
|
+
const filePath = line.slice(3).trim().replace(/"/g, '');
|
|
149
|
+
if (!filePath)
|
|
150
|
+
continue;
|
|
151
|
+
try {
|
|
152
|
+
const full = join(this.worktree, filePath);
|
|
153
|
+
const s = await stat(full);
|
|
154
|
+
if (s.size > LARGE_FILE_LIMIT) {
|
|
155
|
+
excludeAdditions.push(filePath);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// stat failed — skip
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (excludeAdditions.length > 0) {
|
|
163
|
+
const targetExclude = join(this.gitDir, 'info', 'exclude');
|
|
164
|
+
const existing = await readFile(targetExclude, 'utf-8').catch(() => '');
|
|
165
|
+
const additions = excludeAdditions
|
|
166
|
+
.filter((p) => !existing.includes(p))
|
|
167
|
+
.join('\n');
|
|
168
|
+
if (additions) {
|
|
169
|
+
await writeFile(targetExclude, existing + '\n' + additions + '\n', 'utf-8');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// non-fatal
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Restore the entire workspace to a snapshot state.
|
|
179
|
+
*/
|
|
180
|
+
async restore(hash) {
|
|
181
|
+
await this.lock.acquire();
|
|
182
|
+
try {
|
|
183
|
+
await this.exec(['read-tree', hash]);
|
|
184
|
+
await this.exec(['checkout-index', '-a', '-f']);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
this.lock.release();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* List files changed since the given snapshot hash.
|
|
192
|
+
*/
|
|
193
|
+
async patchList(fromHash) {
|
|
194
|
+
await this.lock.acquire();
|
|
195
|
+
try {
|
|
196
|
+
await this.filterLargeUntracked();
|
|
197
|
+
await this.exec(['add', '--all']);
|
|
198
|
+
const { stdout } = await this.exec(['diff', '--cached', '--name-only', fromHash]);
|
|
199
|
+
return stdout.trim().split('\n').filter(Boolean);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
this.lock.release();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Generate a unified diff between a snapshot and the current workspace.
|
|
207
|
+
*/
|
|
208
|
+
async diff(fromHash) {
|
|
209
|
+
await this.lock.acquire();
|
|
210
|
+
try {
|
|
211
|
+
await this.filterLargeUntracked();
|
|
212
|
+
await this.exec(['add', '--all']);
|
|
213
|
+
const { stdout } = await this.exec(['diff', '--cached', fromHash]);
|
|
214
|
+
return stdout;
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
this.lock.release();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Revert specific files to their state at a given snapshot.
|
|
222
|
+
* Files that did not exist in the snapshot are deleted.
|
|
223
|
+
*/
|
|
224
|
+
async revert(entries) {
|
|
225
|
+
await this.lock.acquire();
|
|
226
|
+
try {
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
const fullPath = join(this.worktree, entry.path);
|
|
229
|
+
try {
|
|
230
|
+
await this.exec(['ls-tree', entry.hash, '--', entry.path]);
|
|
231
|
+
await this.exec(['checkout', entry.hash, '--', entry.path]);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
try {
|
|
235
|
+
await rm(fullPath, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// already gone — fine
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
this.lock.release();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Run git gc to prune unreachable objects older than 7 days.
|
|
249
|
+
*/
|
|
250
|
+
async gc() {
|
|
251
|
+
await this.lock.acquire();
|
|
252
|
+
try {
|
|
253
|
+
await this.exec(['gc', '--prune=7.days', '--quiet']);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// non-fatal — gc is best-effort
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
this.lock.release();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Start periodic background gc.
|
|
264
|
+
* First run after 1 minute, then every hour.
|
|
265
|
+
*/
|
|
266
|
+
startGc() {
|
|
267
|
+
this.gcTimer = setTimeout(() => {
|
|
268
|
+
this.gc();
|
|
269
|
+
this.gcInterval = setInterval(() => {
|
|
270
|
+
this.gc();
|
|
271
|
+
}, 60 * 60 * 1000);
|
|
272
|
+
}, 60 * 1000);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Stop background gc. Call on engine shutdown or in test cleanup.
|
|
276
|
+
*/
|
|
277
|
+
stopGc() {
|
|
278
|
+
if (this.gcTimer) {
|
|
279
|
+
clearTimeout(this.gcTimer);
|
|
280
|
+
this.gcTimer = undefined;
|
|
281
|
+
}
|
|
282
|
+
if (this.gcInterval) {
|
|
283
|
+
clearInterval(this.gcInterval);
|
|
284
|
+
this.gcInterval = undefined;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/snapshot/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple promise-based serial lock.
|
|
3
|
+
* Ensures git operations on the same repo are serialized.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Semaphore {
|
|
6
|
+
private queue;
|
|
7
|
+
private locked;
|
|
8
|
+
acquire(): Promise<void>;
|
|
9
|
+
release(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=semaphore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semaphore.d.ts","sourceRoot":"","sources":["../../src/snapshot/semaphore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,MAAM,CAAQ;IAEhB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAU9B,OAAO,IAAI,IAAI;CAQhB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple promise-based serial lock.
|
|
3
|
+
* Ensures git operations on the same repo are serialized.
|
|
4
|
+
*/
|
|
5
|
+
export class Semaphore {
|
|
6
|
+
queue = [];
|
|
7
|
+
locked = false;
|
|
8
|
+
async acquire() {
|
|
9
|
+
if (!this.locked) {
|
|
10
|
+
this.locked = true;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
this.queue.push(resolve);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
release() {
|
|
18
|
+
const next = this.queue.shift();
|
|
19
|
+
if (next) {
|
|
20
|
+
next();
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.locked = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=semaphore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semaphore.js","sourceRoot":"","sources":["../../src/snapshot/semaphore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,SAAS;IACZ,KAAK,GAAmB,EAAE,CAAA;IAC1B,MAAM,GAAG,KAAK,CAAA;IAEtB,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;YAClB,OAAM;QACR,CAAC;QACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC1B,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO;QACL,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,EAAE,CAAA;QACR,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACrB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TodoWriteTool - Session todo/checklist management
|
|
3
|
-
*
|
|
4
|
-
* Manages a session-scoped todo list for tracking work items.
|
|
5
|
-
*/
|
|
6
1
|
import type { ToolDefinition } from '../types.js';
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
export type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled';
|
|
3
|
+
export type TodoPriority = 'high' | 'medium' | 'low';
|
|
4
|
+
export interface TodoInfo {
|
|
5
|
+
content: string;
|
|
6
|
+
status: TodoStatus;
|
|
7
|
+
priority: TodoPriority;
|
|
12
8
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
16
|
-
export declare function getTodos(): TodoItem[];
|
|
17
|
-
/**
|
|
18
|
-
* Clear all todos.
|
|
19
|
-
*/
|
|
20
|
-
export declare function clearTodos(): void;
|
|
9
|
+
export declare function getTodos(sessionId: string): Promise<TodoInfo[]>;
|
|
10
|
+
export declare function clearTodos(sessionId: string): Promise<void>;
|
|
21
11
|
export declare const TodoWriteTool: ToolDefinition;
|
|
22
12
|
//# sourceMappingURL=todo-tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"todo-tool.d.ts","sourceRoot":"","sources":["../../src/tools/todo-tool.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"todo-tool.d.ts","sourceRoot":"","sources":["../../src/tools/todo-tool.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,aAAa,CAAA;AAW1E,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,WAAW,CAAA;AAC9E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEpD,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,UAAU,CAAA;IAClB,QAAQ,EAAE,YAAY,CAAA;CACvB;AA6FD,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAErE;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjE;AAED,eAAO,MAAM,aAAa,EAAE,cAqE3B,CAAA"}
|
package/dist/tools/todo-tool.js
CHANGED
|
@@ -1,93 +1,155 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Get all todos.
|
|
10
|
-
*/
|
|
11
|
-
export function getTodos() {
|
|
12
|
-
return [...todoList];
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
let _description;
|
|
7
|
+
try {
|
|
8
|
+
_description = readFileSync(join(__dirname, 'todowrite.txt'), 'utf-8');
|
|
13
9
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
catch {
|
|
11
|
+
_description = 'Manage a structured task list for your current coding session.';
|
|
12
|
+
}
|
|
13
|
+
const VALID_STATUSES = ['pending', 'in_progress', 'completed', 'cancelled'];
|
|
14
|
+
const VALID_PRIORITIES = ['high', 'medium', 'low'];
|
|
15
|
+
function getTodosDir() {
|
|
16
|
+
const home = process.env.HOME || process.env.USERPROFILE || '/tmp';
|
|
17
|
+
return join(home, '.openagent', 'sessions');
|
|
18
|
+
}
|
|
19
|
+
function getTodosPath(sessionId) {
|
|
20
|
+
return join(getTodosDir(), sessionId, 'todos.json');
|
|
21
|
+
}
|
|
22
|
+
function validateSessionId(sessionId) {
|
|
23
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(sessionId)) {
|
|
24
|
+
throw new Error(`Invalid sessionId: ${sessionId}. Must match /^[a-zA-Z0-9_-]+$/`);
|
|
25
|
+
}
|
|
26
|
+
return sessionId;
|
|
27
|
+
}
|
|
28
|
+
async function saveTodos(sessionId, todos) {
|
|
29
|
+
validateSessionId(sessionId);
|
|
30
|
+
const dir = join(getTodosDir(), sessionId);
|
|
31
|
+
await mkdir(dir, { recursive: true });
|
|
32
|
+
const data = {
|
|
33
|
+
updatedAt: new Date().toISOString(),
|
|
34
|
+
todos,
|
|
35
|
+
};
|
|
36
|
+
await writeFile(getTodosPath(sessionId), JSON.stringify(data, null, 2), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
async function loadTodos(sessionId) {
|
|
39
|
+
validateSessionId(sessionId);
|
|
40
|
+
try {
|
|
41
|
+
const raw = await readFile(getTodosPath(sessionId), 'utf-8');
|
|
42
|
+
const data = JSON.parse(raw);
|
|
43
|
+
return data.todos || [];
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function validateTodos(todos) {
|
|
50
|
+
if (!Array.isArray(todos))
|
|
51
|
+
return 'todos must be an array';
|
|
52
|
+
for (let i = 0; i < todos.length; i++) {
|
|
53
|
+
const item = todos[i];
|
|
54
|
+
if (!item.content || typeof item.content !== 'string' || item.content.trim() === '') {
|
|
55
|
+
return `todos[${i}].content must be a non-empty string`;
|
|
56
|
+
}
|
|
57
|
+
if (!VALID_STATUSES.includes(item.status)) {
|
|
58
|
+
return `todos[${i}].status must be one of: ${VALID_STATUSES.join(', ')}`;
|
|
59
|
+
}
|
|
60
|
+
if (!VALID_PRIORITIES.includes(item.priority)) {
|
|
61
|
+
return `todos[${i}].priority must be one of: ${VALID_PRIORITIES.join(', ')}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const inProgressCount = todos.filter((t) => t.status === 'in_progress').length;
|
|
65
|
+
if (inProgressCount > 1) {
|
|
66
|
+
return `Warning: ${inProgressCount} tasks are in_progress. Only one should be in_progress at a time.`;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const STATUS_MARKS = {
|
|
71
|
+
pending: ' ',
|
|
72
|
+
in_progress: '•',
|
|
73
|
+
completed: '✓',
|
|
74
|
+
cancelled: '✗',
|
|
75
|
+
};
|
|
76
|
+
function formatTodos(todos) {
|
|
77
|
+
if (todos.length === 0)
|
|
78
|
+
return 'No todos.';
|
|
79
|
+
const lines = [];
|
|
80
|
+
for (const t of todos) {
|
|
81
|
+
lines.push(`[${STATUS_MARKS[t.status]}] ${t.content}`);
|
|
82
|
+
}
|
|
83
|
+
return lines.join('\n');
|
|
84
|
+
}
|
|
85
|
+
export async function getTodos(sessionId) {
|
|
86
|
+
return loadTodos(sessionId);
|
|
87
|
+
}
|
|
88
|
+
export async function clearTodos(sessionId) {
|
|
89
|
+
await saveTodos(sessionId, []);
|
|
20
90
|
}
|
|
21
91
|
export const TodoWriteTool = {
|
|
22
92
|
name: 'TodoWrite',
|
|
23
|
-
description:
|
|
93
|
+
description: _description,
|
|
24
94
|
inputSchema: {
|
|
25
95
|
type: 'object',
|
|
26
96
|
properties: {
|
|
27
|
-
|
|
28
|
-
type: '
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
97
|
+
todos: {
|
|
98
|
+
type: 'array',
|
|
99
|
+
description: 'The updated todo list',
|
|
100
|
+
items: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
content: { type: 'string', description: 'Brief description of the task' },
|
|
104
|
+
status: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
enum: ['pending', 'in_progress', 'completed', 'cancelled'],
|
|
107
|
+
description: 'Current status of the task',
|
|
108
|
+
},
|
|
109
|
+
priority: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
enum: ['high', 'medium', 'low'],
|
|
112
|
+
description: 'Priority level of the task',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
required: ['content', 'status', 'priority'],
|
|
116
|
+
},
|
|
38
117
|
},
|
|
39
118
|
},
|
|
40
|
-
required: ['
|
|
119
|
+
required: ['todos'],
|
|
41
120
|
},
|
|
42
121
|
isReadOnly: () => false,
|
|
43
122
|
isConcurrencySafe: () => true,
|
|
44
123
|
isEnabled: () => true,
|
|
45
|
-
async prompt() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!item) {
|
|
64
|
-
return { type: 'tool_result', tool_use_id: '', content: `Todo #${input.id} not found`, is_error: true };
|
|
65
|
-
}
|
|
66
|
-
item.done = !item.done;
|
|
67
|
-
return { type: 'tool_result', tool_use_id: '', content: `Todo #${item.id} ${item.done ? 'completed' : 'reopened'}` };
|
|
68
|
-
}
|
|
69
|
-
case 'remove': {
|
|
70
|
-
const idx = todoList.findIndex(t => t.id === input.id);
|
|
71
|
-
if (idx === -1) {
|
|
72
|
-
return { type: 'tool_result', tool_use_id: '', content: `Todo #${input.id} not found`, is_error: true };
|
|
73
|
-
}
|
|
74
|
-
todoList.splice(idx, 1);
|
|
75
|
-
return { type: 'tool_result', tool_use_id: '', content: `Todo #${input.id} removed` };
|
|
76
|
-
}
|
|
77
|
-
case 'list': {
|
|
78
|
-
if (todoList.length === 0) {
|
|
79
|
-
return { type: 'tool_result', tool_use_id: '', content: 'No todos.' };
|
|
80
|
-
}
|
|
81
|
-
const lines = todoList.map(t => `${t.done ? '[x]' : '[ ]'} #${t.id} ${t.text}${t.priority ? ` (${t.priority})` : ''}`);
|
|
82
|
-
return { type: 'tool_result', tool_use_id: '', content: lines.join('\n') };
|
|
83
|
-
}
|
|
84
|
-
case 'clear': {
|
|
85
|
-
todoList.length = 0;
|
|
86
|
-
return { type: 'tool_result', tool_use_id: '', content: 'All todos cleared.' };
|
|
87
|
-
}
|
|
88
|
-
default:
|
|
89
|
-
return { type: 'tool_result', tool_use_id: '', content: `Unknown action: ${input.action}`, is_error: true };
|
|
124
|
+
async prompt() {
|
|
125
|
+
return _description;
|
|
126
|
+
},
|
|
127
|
+
async call(input, context) {
|
|
128
|
+
const todos = input.todos;
|
|
129
|
+
if (!Array.isArray(todos)) {
|
|
130
|
+
return { type: 'tool_result', tool_use_id: '', content: 'todos must be an array', is_error: true };
|
|
131
|
+
}
|
|
132
|
+
const validationError = validateTodos(todos);
|
|
133
|
+
if (validationError && validationError.startsWith('todos[')) {
|
|
134
|
+
return { type: 'tool_result', tool_use_id: '', content: validationError, is_error: true };
|
|
135
|
+
}
|
|
136
|
+
const sessionId = context.sessionId || 'default';
|
|
137
|
+
try {
|
|
138
|
+
validateSessionId(sessionId);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
return { type: 'tool_result', tool_use_id: '', content: e.message, is_error: true };
|
|
90
142
|
}
|
|
143
|
+
await saveTodos(sessionId, todos);
|
|
144
|
+
const formatted = formatTodos(todos);
|
|
145
|
+
const json = JSON.stringify(todos, null, 2);
|
|
146
|
+
const output = `${formatted}\n\n${json}`;
|
|
147
|
+
const warning = validationError ? `\n\nNote: ${validationError}` : '';
|
|
148
|
+
return {
|
|
149
|
+
type: 'tool_result',
|
|
150
|
+
tool_use_id: '',
|
|
151
|
+
content: output + warning,
|
|
152
|
+
};
|
|
91
153
|
},
|
|
92
154
|
};
|
|
93
155
|
//# sourceMappingURL=todo-tool.js.map
|