@link-assistant/agent 0.3.0 → 0.3.1
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/package.json +1 -1
- package/src/bun/index.ts +105 -18
- package/src/provider/provider.ts +5 -1
- package/src/session/prompt.ts +21 -4
package/package.json
CHANGED
package/src/bun/index.ts
CHANGED
|
@@ -5,10 +5,14 @@ import path from 'path';
|
|
|
5
5
|
import { NamedError } from '../util/error';
|
|
6
6
|
import { readableStreamToText } from 'bun';
|
|
7
7
|
import { Flag } from '../flag/flag';
|
|
8
|
+
import { Lock } from '../util/lock';
|
|
8
9
|
|
|
9
10
|
export namespace BunProc {
|
|
10
11
|
const log = Log.create({ service: 'bun' });
|
|
11
12
|
|
|
13
|
+
// Lock key for serializing package installations to prevent race conditions
|
|
14
|
+
const INSTALL_LOCK_KEY = 'bun-install';
|
|
15
|
+
|
|
12
16
|
export async function run(
|
|
13
17
|
cmd: string[],
|
|
14
18
|
options?: Bun.SpawnOptions.OptionsObject<any, any, any>
|
|
@@ -65,8 +69,38 @@ export namespace BunProc {
|
|
|
65
69
|
})
|
|
66
70
|
);
|
|
67
71
|
|
|
72
|
+
// Maximum number of retry attempts for cache-related errors
|
|
73
|
+
const MAX_RETRIES = 3;
|
|
74
|
+
// Delay between retries in milliseconds
|
|
75
|
+
const RETRY_DELAY_MS = 500;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if an error is related to Bun cache issues
|
|
79
|
+
*/
|
|
80
|
+
function isCacheRelatedError(errorMsg: string): boolean {
|
|
81
|
+
return (
|
|
82
|
+
errorMsg.includes('failed copying files from cache') ||
|
|
83
|
+
errorMsg.includes('FileNotFound') ||
|
|
84
|
+
errorMsg.includes('ENOENT') ||
|
|
85
|
+
errorMsg.includes('EACCES') ||
|
|
86
|
+
errorMsg.includes('EBUSY')
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Wait for a specified duration
|
|
92
|
+
*/
|
|
93
|
+
function delay(ms: number): Promise<void> {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
|
|
68
97
|
export async function install(pkg: string, version = 'latest') {
|
|
69
98
|
const mod = path.join(Global.Path.cache, 'node_modules', pkg);
|
|
99
|
+
|
|
100
|
+
// Use a write lock to serialize all package installations
|
|
101
|
+
// This prevents race conditions when multiple packages are installed concurrently
|
|
102
|
+
using _ = await Lock.write(INSTALL_LOCK_KEY);
|
|
103
|
+
|
|
70
104
|
const pkgjson = Bun.file(path.join(Global.Path.cache, 'package.json'));
|
|
71
105
|
const parsed = await pkgjson.json().catch(async () => {
|
|
72
106
|
const result = { dependencies: {} };
|
|
@@ -108,25 +142,78 @@ export namespace BunProc {
|
|
|
108
142
|
version,
|
|
109
143
|
});
|
|
110
144
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
145
|
+
// Retry logic for cache-related errors
|
|
146
|
+
let lastError: Error | undefined;
|
|
147
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
148
|
+
try {
|
|
149
|
+
await BunProc.run(args, {
|
|
150
|
+
cwd: Global.Path.cache,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
log.info('package installed successfully', { pkg, version, attempt });
|
|
154
|
+
parsed.dependencies[pkg] = version;
|
|
155
|
+
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2));
|
|
156
|
+
return mod;
|
|
157
|
+
} catch (e) {
|
|
158
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
159
|
+
const isCacheError = isCacheRelatedError(errorMsg);
|
|
160
|
+
|
|
161
|
+
log.warn('package installation attempt failed', {
|
|
162
|
+
pkg,
|
|
163
|
+
version,
|
|
164
|
+
attempt,
|
|
165
|
+
maxRetries: MAX_RETRIES,
|
|
166
|
+
error: errorMsg,
|
|
167
|
+
isCacheError,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (isCacheError && attempt < MAX_RETRIES) {
|
|
171
|
+
log.info('retrying installation after cache-related error', {
|
|
172
|
+
pkg,
|
|
173
|
+
version,
|
|
174
|
+
attempt,
|
|
175
|
+
nextAttempt: attempt + 1,
|
|
176
|
+
delayMs: RETRY_DELAY_MS,
|
|
177
|
+
});
|
|
178
|
+
await delay(RETRY_DELAY_MS);
|
|
179
|
+
lastError = e instanceof Error ? e : new Error(errorMsg);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Non-cache error or final attempt - log and throw
|
|
184
|
+
log.error('package installation failed', {
|
|
185
|
+
pkg,
|
|
186
|
+
version,
|
|
187
|
+
error: errorMsg,
|
|
188
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
189
|
+
possibleCacheCorruption: isCacheError,
|
|
190
|
+
attempts: attempt,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Provide helpful recovery instructions for cache-related errors
|
|
194
|
+
if (isCacheError) {
|
|
195
|
+
log.error(
|
|
196
|
+
'Bun package cache may be corrupted. Try clearing the cache with: bun pm cache rm'
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
throw new InstallFailedError(
|
|
201
|
+
{ pkg, version, details: errorMsg },
|
|
202
|
+
{
|
|
203
|
+
cause: e,
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// This should not be reached, but handle it just in case
|
|
210
|
+
throw new InstallFailedError(
|
|
211
|
+
{
|
|
115
212
|
pkg,
|
|
116
213
|
version,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
{ pkg, version, details: e instanceof Error ? e.message : String(e) },
|
|
122
|
-
{
|
|
123
|
-
cause: e,
|
|
124
|
-
}
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
log.info('package installed successfully', { pkg, version });
|
|
128
|
-
parsed.dependencies[pkg] = version;
|
|
129
|
-
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2));
|
|
130
|
-
return mod;
|
|
214
|
+
details: lastError?.message ?? 'Installation failed after all retries',
|
|
215
|
+
},
|
|
216
|
+
{ cause: lastError }
|
|
217
|
+
);
|
|
131
218
|
}
|
|
132
219
|
}
|
package/src/provider/provider.ts
CHANGED
|
@@ -905,6 +905,10 @@ export namespace Provider {
|
|
|
905
905
|
if (opencodeProvider) {
|
|
906
906
|
const [model] = sort(Object.values(opencodeProvider.info.models));
|
|
907
907
|
if (model) {
|
|
908
|
+
log.info('using opencode provider as default', {
|
|
909
|
+
provider: opencodeProvider.info.id,
|
|
910
|
+
model: model.id,
|
|
911
|
+
});
|
|
908
912
|
return {
|
|
909
913
|
providerID: opencodeProvider.info.id,
|
|
910
914
|
modelID: model.id,
|
|
@@ -912,7 +916,7 @@ export namespace Provider {
|
|
|
912
916
|
}
|
|
913
917
|
}
|
|
914
918
|
|
|
915
|
-
// Fall back to any available provider
|
|
919
|
+
// Fall back to any available provider if opencode is not available
|
|
916
920
|
const provider = providers.find(
|
|
917
921
|
(p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id)
|
|
918
922
|
);
|
package/src/session/prompt.ts
CHANGED
|
@@ -290,10 +290,27 @@ export namespace SessionPrompt {
|
|
|
290
290
|
history: msgs,
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
293
|
+
let model;
|
|
294
|
+
try {
|
|
295
|
+
model = await Provider.getModel(
|
|
296
|
+
lastUser.model.providerID,
|
|
297
|
+
lastUser.model.modelID
|
|
298
|
+
);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
log.warn(
|
|
301
|
+
'Failed to initialize specified model, falling back to default model',
|
|
302
|
+
{
|
|
303
|
+
providerID: lastUser.model.providerID,
|
|
304
|
+
modelID: lastUser.model.modelID,
|
|
305
|
+
error: error instanceof Error ? error.message : String(error),
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
const defaultModel = await Provider.defaultModel();
|
|
309
|
+
model = await Provider.getModel(
|
|
310
|
+
defaultModel.providerID,
|
|
311
|
+
defaultModel.modelID
|
|
312
|
+
);
|
|
313
|
+
}
|
|
297
314
|
const task = tasks.pop();
|
|
298
315
|
|
|
299
316
|
// pending subtask
|