@link-assistant/agent 0.0.8 → 0.0.11
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/EXAMPLES.md +80 -1
- package/MODELS.md +72 -24
- package/README.md +95 -2
- package/TOOLS.md +20 -0
- package/package.json +36 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +468 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +210 -53
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +78 -0
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +554 -332
- package/src/json-standard/index.ts +173 -0
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/storage/storage.ts
CHANGED
|
@@ -1,49 +1,51 @@
|
|
|
1
|
-
import { Log } from
|
|
2
|
-
import path from
|
|
3
|
-
import fs from
|
|
4
|
-
import { Global } from
|
|
5
|
-
import { lazy } from
|
|
6
|
-
import { Lock } from
|
|
7
|
-
import { $ } from
|
|
8
|
-
import { NamedError } from
|
|
9
|
-
import z from
|
|
1
|
+
import { Log } from '../util/log';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import { Global } from '../global';
|
|
5
|
+
import { lazy } from '../util/lazy';
|
|
6
|
+
import { Lock } from '../util/lock';
|
|
7
|
+
import { $ } from 'bun';
|
|
8
|
+
import { NamedError } from '../util/error';
|
|
9
|
+
import z from 'zod';
|
|
10
10
|
|
|
11
11
|
export namespace Storage {
|
|
12
|
-
const log = Log.create({ service:
|
|
12
|
+
const log = Log.create({ service: 'storage' });
|
|
13
13
|
|
|
14
|
-
type Migration = (dir: string) => Promise<void
|
|
14
|
+
type Migration = (dir: string) => Promise<void>;
|
|
15
15
|
|
|
16
16
|
export const NotFoundError = NamedError.create(
|
|
17
|
-
|
|
17
|
+
'NotFoundError',
|
|
18
18
|
z.object({
|
|
19
19
|
message: z.string(),
|
|
20
|
-
})
|
|
21
|
-
)
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
22
|
|
|
23
23
|
const MIGRATIONS: Migration[] = [
|
|
24
24
|
async (dir) => {
|
|
25
|
-
const project = path.resolve(dir,
|
|
26
|
-
if (!fs.exists(project)) return
|
|
27
|
-
for await (const projectDir of new Bun.Glob(
|
|
25
|
+
const project = path.resolve(dir, '../project');
|
|
26
|
+
if (!fs.exists(project)) return;
|
|
27
|
+
for await (const projectDir of new Bun.Glob('*').scan({
|
|
28
28
|
cwd: project,
|
|
29
29
|
onlyFiles: false,
|
|
30
30
|
})) {
|
|
31
|
-
log.info(`migrating project ${projectDir}`)
|
|
32
|
-
let projectID = projectDir
|
|
33
|
-
const fullProjectDir = path.join(project, projectDir)
|
|
34
|
-
let worktree =
|
|
31
|
+
log.info(`migrating project ${projectDir}`);
|
|
32
|
+
let projectID = projectDir;
|
|
33
|
+
const fullProjectDir = path.join(project, projectDir);
|
|
34
|
+
let worktree = '/';
|
|
35
35
|
|
|
36
|
-
if (projectID !==
|
|
37
|
-
for await (const msgFile of new Bun.Glob(
|
|
36
|
+
if (projectID !== 'global') {
|
|
37
|
+
for await (const msgFile of new Bun.Glob(
|
|
38
|
+
'storage/session/message/*/*.json'
|
|
39
|
+
).scan({
|
|
38
40
|
cwd: path.join(project, projectDir),
|
|
39
41
|
absolute: true,
|
|
40
42
|
})) {
|
|
41
|
-
const json = await Bun.file(msgFile).json()
|
|
42
|
-
worktree = json.path?.root
|
|
43
|
-
if (worktree) break
|
|
43
|
+
const json = await Bun.file(msgFile).json();
|
|
44
|
+
worktree = json.path?.root;
|
|
45
|
+
if (worktree) break;
|
|
44
46
|
}
|
|
45
|
-
if (!worktree) continue
|
|
46
|
-
if (!(await fs.exists(worktree))) continue
|
|
47
|
+
if (!worktree) continue;
|
|
48
|
+
if (!(await fs.exists(worktree))) continue;
|
|
47
49
|
const [id] = await $`git rev-list --max-parents=0 --all`
|
|
48
50
|
.quiet()
|
|
49
51
|
.nothrow()
|
|
@@ -51,66 +53,85 @@ export namespace Storage {
|
|
|
51
53
|
.text()
|
|
52
54
|
.then((x) =>
|
|
53
55
|
x
|
|
54
|
-
.split(
|
|
56
|
+
.split('\n')
|
|
55
57
|
.filter(Boolean)
|
|
56
58
|
.map((x) => x.trim())
|
|
57
|
-
.toSorted()
|
|
58
|
-
)
|
|
59
|
-
if (!id) continue
|
|
60
|
-
projectID = id
|
|
59
|
+
.toSorted()
|
|
60
|
+
);
|
|
61
|
+
if (!id) continue;
|
|
62
|
+
projectID = id;
|
|
61
63
|
|
|
62
64
|
await Bun.write(
|
|
63
|
-
path.join(dir,
|
|
65
|
+
path.join(dir, 'project', projectID + '.json'),
|
|
64
66
|
JSON.stringify({
|
|
65
67
|
id,
|
|
66
|
-
vcs:
|
|
68
|
+
vcs: 'git',
|
|
67
69
|
worktree,
|
|
68
70
|
time: {
|
|
69
71
|
created: Date.now(),
|
|
70
72
|
initialized: Date.now(),
|
|
71
73
|
},
|
|
72
|
-
})
|
|
73
|
-
)
|
|
74
|
+
})
|
|
75
|
+
);
|
|
74
76
|
|
|
75
|
-
log.info(`migrating sessions for project ${projectID}`)
|
|
76
|
-
for await (const sessionFile of new Bun.Glob(
|
|
77
|
+
log.info(`migrating sessions for project ${projectID}`);
|
|
78
|
+
for await (const sessionFile of new Bun.Glob(
|
|
79
|
+
'storage/session/info/*.json'
|
|
80
|
+
).scan({
|
|
77
81
|
cwd: fullProjectDir,
|
|
78
82
|
absolute: true,
|
|
79
83
|
})) {
|
|
80
|
-
const dest = path.join(
|
|
81
|
-
|
|
84
|
+
const dest = path.join(
|
|
85
|
+
dir,
|
|
86
|
+
'session',
|
|
87
|
+
projectID,
|
|
88
|
+
path.basename(sessionFile)
|
|
89
|
+
);
|
|
90
|
+
log.info('copying', {
|
|
82
91
|
sessionFile,
|
|
83
92
|
dest,
|
|
84
|
-
})
|
|
85
|
-
const session = await Bun.file(sessionFile).json()
|
|
86
|
-
await Bun.write(dest, JSON.stringify(session))
|
|
87
|
-
log.info(`migrating messages for session ${session.id}`)
|
|
88
|
-
for await (const msgFile of new Bun.Glob(
|
|
93
|
+
});
|
|
94
|
+
const session = await Bun.file(sessionFile).json();
|
|
95
|
+
await Bun.write(dest, JSON.stringify(session));
|
|
96
|
+
log.info(`migrating messages for session ${session.id}`);
|
|
97
|
+
for await (const msgFile of new Bun.Glob(
|
|
98
|
+
`storage/session/message/${session.id}/*.json`
|
|
99
|
+
).scan({
|
|
89
100
|
cwd: fullProjectDir,
|
|
90
101
|
absolute: true,
|
|
91
102
|
})) {
|
|
92
|
-
const dest = path.join(
|
|
93
|
-
|
|
103
|
+
const dest = path.join(
|
|
104
|
+
dir,
|
|
105
|
+
'message',
|
|
106
|
+
session.id,
|
|
107
|
+
path.basename(msgFile)
|
|
108
|
+
);
|
|
109
|
+
log.info('copying', {
|
|
94
110
|
msgFile,
|
|
95
111
|
dest,
|
|
96
|
-
})
|
|
97
|
-
const message = await Bun.file(msgFile).json()
|
|
98
|
-
await Bun.write(dest, JSON.stringify(message))
|
|
112
|
+
});
|
|
113
|
+
const message = await Bun.file(msgFile).json();
|
|
114
|
+
await Bun.write(dest, JSON.stringify(message));
|
|
99
115
|
|
|
100
|
-
log.info(`migrating parts for message ${message.id}`)
|
|
101
|
-
for await (const partFile of new Bun.Glob(
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)) {
|
|
107
|
-
const dest = path.join(
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
log.info(`migrating parts for message ${message.id}`);
|
|
117
|
+
for await (const partFile of new Bun.Glob(
|
|
118
|
+
`storage/session/part/${session.id}/${message.id}/*.json`
|
|
119
|
+
).scan({
|
|
120
|
+
cwd: fullProjectDir,
|
|
121
|
+
absolute: true,
|
|
122
|
+
})) {
|
|
123
|
+
const dest = path.join(
|
|
124
|
+
dir,
|
|
125
|
+
'part',
|
|
126
|
+
message.id,
|
|
127
|
+
path.basename(partFile)
|
|
128
|
+
);
|
|
129
|
+
const part = await Bun.file(partFile).json();
|
|
130
|
+
log.info('copying', {
|
|
110
131
|
partFile,
|
|
111
132
|
dest,
|
|
112
|
-
})
|
|
113
|
-
await Bun.write(dest, JSON.stringify(part))
|
|
133
|
+
});
|
|
134
|
+
await Bun.write(dest, JSON.stringify(part));
|
|
114
135
|
}
|
|
115
136
|
}
|
|
116
137
|
}
|
|
@@ -118,109 +139,125 @@ export namespace Storage {
|
|
|
118
139
|
}
|
|
119
140
|
},
|
|
120
141
|
async (dir) => {
|
|
121
|
-
for await (const item of new Bun.Glob(
|
|
142
|
+
for await (const item of new Bun.Glob('session/*/*.json').scan({
|
|
122
143
|
cwd: dir,
|
|
123
144
|
absolute: true,
|
|
124
145
|
})) {
|
|
125
|
-
const session = await Bun.file(item).json()
|
|
126
|
-
if (!session.projectID) continue
|
|
127
|
-
if (!session.summary?.diffs) continue
|
|
128
|
-
const { diffs } = session.summary
|
|
129
|
-
await Bun.file(
|
|
130
|
-
|
|
146
|
+
const session = await Bun.file(item).json();
|
|
147
|
+
if (!session.projectID) continue;
|
|
148
|
+
if (!session.summary?.diffs) continue;
|
|
149
|
+
const { diffs } = session.summary;
|
|
150
|
+
await Bun.file(
|
|
151
|
+
path.join(dir, 'session_diff', session.id + '.json')
|
|
152
|
+
).write(JSON.stringify(diffs));
|
|
153
|
+
await Bun.file(
|
|
154
|
+
path.join(dir, 'session', session.projectID, session.id + '.json')
|
|
155
|
+
).write(
|
|
131
156
|
JSON.stringify({
|
|
132
157
|
...session,
|
|
133
158
|
summary: {
|
|
134
|
-
additions: diffs.reduce(
|
|
135
|
-
|
|
159
|
+
additions: diffs.reduce(
|
|
160
|
+
(sum: any, x: any) => sum + x.additions,
|
|
161
|
+
0
|
|
162
|
+
),
|
|
163
|
+
deletions: diffs.reduce(
|
|
164
|
+
(sum: any, x: any) => sum + x.deletions,
|
|
165
|
+
0
|
|
166
|
+
),
|
|
136
167
|
},
|
|
137
|
-
})
|
|
138
|
-
)
|
|
168
|
+
})
|
|
169
|
+
);
|
|
139
170
|
}
|
|
140
171
|
},
|
|
141
|
-
]
|
|
172
|
+
];
|
|
142
173
|
|
|
143
174
|
const state = lazy(async () => {
|
|
144
|
-
const dir = path.join(Global.Path.data,
|
|
145
|
-
const migration = await Bun.file(path.join(dir,
|
|
175
|
+
const dir = path.join(Global.Path.data, 'storage');
|
|
176
|
+
const migration = await Bun.file(path.join(dir, 'migration'))
|
|
146
177
|
.json()
|
|
147
178
|
.then((x) => parseInt(x))
|
|
148
|
-
.catch(() => 0)
|
|
179
|
+
.catch(() => 0);
|
|
149
180
|
for (let index = migration; index < MIGRATIONS.length; index++) {
|
|
150
|
-
log.info(
|
|
151
|
-
const migration = MIGRATIONS[index]
|
|
152
|
-
await migration(dir).catch(() =>
|
|
153
|
-
|
|
181
|
+
log.info('running migration', { index });
|
|
182
|
+
const migration = MIGRATIONS[index];
|
|
183
|
+
await migration(dir).catch(() =>
|
|
184
|
+
log.error('failed to run migration', { index })
|
|
185
|
+
);
|
|
186
|
+
await Bun.write(path.join(dir, 'migration'), (index + 1).toString());
|
|
154
187
|
}
|
|
155
188
|
return {
|
|
156
189
|
dir,
|
|
157
|
-
}
|
|
158
|
-
})
|
|
190
|
+
};
|
|
191
|
+
});
|
|
159
192
|
|
|
160
193
|
export async function remove(key: string[]) {
|
|
161
|
-
const dir = await state().then((x) => x.dir)
|
|
162
|
-
const target = path.join(dir, ...key) +
|
|
194
|
+
const dir = await state().then((x) => x.dir);
|
|
195
|
+
const target = path.join(dir, ...key) + '.json';
|
|
163
196
|
return withErrorHandling(async () => {
|
|
164
|
-
await fs.unlink(target).catch(() => {})
|
|
165
|
-
})
|
|
197
|
+
await fs.unlink(target).catch(() => {});
|
|
198
|
+
});
|
|
166
199
|
}
|
|
167
200
|
|
|
168
201
|
export async function read<T>(key: string[]) {
|
|
169
|
-
const dir = await state().then((x) => x.dir)
|
|
170
|
-
const target = path.join(dir, ...key) +
|
|
202
|
+
const dir = await state().then((x) => x.dir);
|
|
203
|
+
const target = path.join(dir, ...key) + '.json';
|
|
171
204
|
return withErrorHandling(async () => {
|
|
172
|
-
using _ = await Lock.read(target)
|
|
173
|
-
const result = await Bun.file(target).json()
|
|
174
|
-
return result as T
|
|
175
|
-
})
|
|
205
|
+
using _ = await Lock.read(target);
|
|
206
|
+
const result = await Bun.file(target).json();
|
|
207
|
+
return result as T;
|
|
208
|
+
});
|
|
176
209
|
}
|
|
177
210
|
|
|
178
211
|
export async function update<T>(key: string[], fn: (draft: T) => void) {
|
|
179
|
-
const dir = await state().then((x) => x.dir)
|
|
180
|
-
const target = path.join(dir, ...key) +
|
|
212
|
+
const dir = await state().then((x) => x.dir);
|
|
213
|
+
const target = path.join(dir, ...key) + '.json';
|
|
181
214
|
return withErrorHandling(async () => {
|
|
182
|
-
using _ = await Lock.write(target)
|
|
183
|
-
const content = await Bun.file(target).json()
|
|
184
|
-
fn(content)
|
|
185
|
-
await Bun.write(target, JSON.stringify(content, null, 2))
|
|
186
|
-
return content as T
|
|
187
|
-
})
|
|
215
|
+
using _ = await Lock.write(target);
|
|
216
|
+
const content = await Bun.file(target).json();
|
|
217
|
+
fn(content);
|
|
218
|
+
await Bun.write(target, JSON.stringify(content, null, 2));
|
|
219
|
+
return content as T;
|
|
220
|
+
});
|
|
188
221
|
}
|
|
189
222
|
|
|
190
223
|
export async function write<T>(key: string[], content: T) {
|
|
191
|
-
const dir = await state().then((x) => x.dir)
|
|
192
|
-
const target = path.join(dir, ...key) +
|
|
224
|
+
const dir = await state().then((x) => x.dir);
|
|
225
|
+
const target = path.join(dir, ...key) + '.json';
|
|
193
226
|
return withErrorHandling(async () => {
|
|
194
|
-
using _ = await Lock.write(target)
|
|
195
|
-
await Bun.write(target, JSON.stringify(content, null, 2))
|
|
196
|
-
})
|
|
227
|
+
using _ = await Lock.write(target);
|
|
228
|
+
await Bun.write(target, JSON.stringify(content, null, 2));
|
|
229
|
+
});
|
|
197
230
|
}
|
|
198
231
|
|
|
199
232
|
async function withErrorHandling<T>(body: () => Promise<T>) {
|
|
200
233
|
return body().catch((e) => {
|
|
201
|
-
if (!(e instanceof Error)) throw e
|
|
202
|
-
const errnoException = e as NodeJS.ErrnoException
|
|
203
|
-
if (errnoException.code ===
|
|
204
|
-
throw new NotFoundError({
|
|
234
|
+
if (!(e instanceof Error)) throw e;
|
|
235
|
+
const errnoException = e as NodeJS.ErrnoException;
|
|
236
|
+
if (errnoException.code === 'ENOENT') {
|
|
237
|
+
throw new NotFoundError({
|
|
238
|
+
message: `Resource not found: ${errnoException.path}`,
|
|
239
|
+
});
|
|
205
240
|
}
|
|
206
|
-
throw e
|
|
207
|
-
})
|
|
241
|
+
throw e;
|
|
242
|
+
});
|
|
208
243
|
}
|
|
209
244
|
|
|
210
|
-
const glob = new Bun.Glob(
|
|
245
|
+
const glob = new Bun.Glob('**/*');
|
|
211
246
|
export async function list(prefix: string[]) {
|
|
212
|
-
const dir = await state().then((x) => x.dir)
|
|
247
|
+
const dir = await state().then((x) => x.dir);
|
|
213
248
|
try {
|
|
214
249
|
const result = await Array.fromAsync(
|
|
215
250
|
glob.scan({
|
|
216
251
|
cwd: path.join(dir, ...prefix),
|
|
217
252
|
onlyFiles: true,
|
|
218
|
-
})
|
|
219
|
-
).then((results) =>
|
|
220
|
-
|
|
221
|
-
|
|
253
|
+
})
|
|
254
|
+
).then((results) =>
|
|
255
|
+
results.map((x) => [...prefix, ...x.slice(0, -5).split(path.sep)])
|
|
256
|
+
);
|
|
257
|
+
result.sort();
|
|
258
|
+
return result;
|
|
222
259
|
} catch {
|
|
223
|
-
return []
|
|
260
|
+
return [];
|
|
224
261
|
}
|
|
225
262
|
}
|
|
226
263
|
}
|