@link-assistant/agent 0.27.0 → 0.27.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/README.md +47 -5
- package/package.json +41 -42
- package/src/cli/argv.ts +1 -1
- package/src/cli/cmd/run.ts +7 -1
- package/src/cli/continuous-mode.js +38 -12
- package/src/cli/event-handler.js +3 -1
- package/src/cli/model-config.js +20 -4
- package/src/cli/run-options.js +144 -78
- package/src/config/config.ts +306 -105
- package/src/config/defaults.ts +13 -5
- package/src/config/file-config.ts +16 -0
- package/src/file/ripgrep.ts +38 -5
- package/src/file/time.ts +86 -9
- package/src/index.js +39 -33
- package/src/mcp/index.ts +370 -106
- package/src/permission/index.ts +22 -0
- package/src/project/instance.ts +5 -1
- package/src/project/project.ts +35 -2
- package/src/provider/free-tier-error.ts +1 -1
- package/src/provider/kilo-deprecation.ts +30 -0
- package/src/provider/kilo.ts +25 -7
- package/src/provider/provider.ts +53 -22
- package/src/provider/retry-fetch.ts +5 -0
- package/src/provider/transform.ts +139 -17
- package/src/session/compaction.ts +18 -2
- package/src/session/index.ts +30 -3
- package/src/session/message-v2.ts +318 -32
- package/src/session/processor.ts +274 -140
- package/src/session/prompt.ts +154 -128
- package/src/session/retry.ts +26 -13
- package/src/session/summary.ts +4 -1
- package/src/tool/bash.ts +232 -45
- package/src/tool/bash.txt +1 -1
- package/src/tool/codesearch.ts +15 -78
- package/src/tool/edit.ts +111 -30
- package/src/tool/glob.ts +2 -1
- package/src/tool/grep.ts +175 -57
- package/src/tool/ls.ts +2 -1
- package/src/tool/multiedit.ts +3 -1
- package/src/tool/read.ts +169 -41
- package/src/tool/task.ts +26 -18
- package/src/tool/tool.ts +4 -1
- package/src/tool/webfetch.ts +68 -56
- package/src/tool/webfetch.txt +4 -4
- package/src/tool/websearch.ts +20 -75
- package/src/tool/websearch.txt +2 -0
- package/src/tool/write.ts +23 -6
- package/src/util/bom.ts +32 -0
- package/src/util/exa-mcp.ts +82 -0
- package/src/util/legacy-error-names.ts +39 -0
- package/src/util/read-bounded.ts +32 -0
- package/src/util/sse-usage-extractor.ts +67 -13
- package/src/util/timeout.ts +3 -4
- package/src/util/verbose-fetch.ts +40 -1
package/src/config/config.ts
CHANGED
|
@@ -56,6 +56,20 @@ export interface AgentConfig {
|
|
|
56
56
|
disableTools: string;
|
|
57
57
|
permissionMode: string;
|
|
58
58
|
permission: string;
|
|
59
|
+
// Compatibility switches (issue #335): each restores the behaviour that
|
|
60
|
+
// existed before the upstream reliability fixes, or tunes a new limit.
|
|
61
|
+
bashShell: string;
|
|
62
|
+
legacyToolOutput: boolean;
|
|
63
|
+
legacyEditMatching: boolean;
|
|
64
|
+
globFollowSymlinks: boolean;
|
|
65
|
+
grepHidden: boolean;
|
|
66
|
+
grepTimeoutMs: number;
|
|
67
|
+
readMaxLines: number;
|
|
68
|
+
readMaxBytes: number;
|
|
69
|
+
legacyErrorNames: boolean;
|
|
70
|
+
legacyTokenUsage: boolean;
|
|
71
|
+
legacyLoopExit: boolean;
|
|
72
|
+
mcpConnectTimeout: number;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
// Fallback helpers for when config is not yet initialized (early imports/tests)
|
|
@@ -68,6 +82,39 @@ function getEnvStr(key: string): string | undefined {
|
|
|
68
82
|
return process.env[key];
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
// Boolean env var that defaults to true unless set to "false"/"0".
|
|
86
|
+
function truthyEnvDefaultTrue(key: string): boolean {
|
|
87
|
+
const value = (process.env[key] ?? '').toLowerCase();
|
|
88
|
+
return !(value === 'false' || value === '0');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Non-negative integer env var; falls back when unset or not a number.
|
|
92
|
+
function intEnv(key: string, fallback: number): number {
|
|
93
|
+
const raw = process.env[key];
|
|
94
|
+
if (raw === undefined || raw.trim() === '') return fallback;
|
|
95
|
+
const value = parseInt(raw, 10);
|
|
96
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Defaults of the compatibility switches added in issue #335. Shared by the
|
|
101
|
+
* pre-init config, the yargs options and initConfig so they cannot drift.
|
|
102
|
+
*/
|
|
103
|
+
export const COMPAT_DEFAULTS = {
|
|
104
|
+
bashShell: 'auto',
|
|
105
|
+
legacyToolOutput: false,
|
|
106
|
+
legacyEditMatching: false,
|
|
107
|
+
globFollowSymlinks: false,
|
|
108
|
+
grepHidden: true,
|
|
109
|
+
grepTimeoutMs: 30_000,
|
|
110
|
+
readMaxLines: 2000,
|
|
111
|
+
readMaxBytes: 50 * 1024,
|
|
112
|
+
legacyErrorNames: false,
|
|
113
|
+
legacyTokenUsage: false,
|
|
114
|
+
legacyLoopExit: false,
|
|
115
|
+
mcpConnectTimeout: 30_000,
|
|
116
|
+
} as const;
|
|
117
|
+
|
|
71
118
|
/**
|
|
72
119
|
* Default configuration values, used before initConfig() is called.
|
|
73
120
|
* Falls back to direct env var reads for early-import and test scenarios.
|
|
@@ -140,6 +187,31 @@ function defaultConfig(): AgentConfig {
|
|
|
140
187
|
disableTools: getEnvStr('LINK_ASSISTANT_AGENT_DISABLE_TOOLS') ?? '',
|
|
141
188
|
permissionMode: getEnvStr('LINK_ASSISTANT_AGENT_PERMISSION_MODE') ?? 'auto',
|
|
142
189
|
permission: getEnvStr('LINK_ASSISTANT_AGENT_PERMISSION') ?? '',
|
|
190
|
+
bashShell:
|
|
191
|
+
getEnvStr('LINK_ASSISTANT_AGENT_BASH_SHELL') || COMPAT_DEFAULTS.bashShell,
|
|
192
|
+
legacyToolOutput: truthyEnv('LINK_ASSISTANT_AGENT_LEGACY_TOOL_OUTPUT'),
|
|
193
|
+
legacyEditMatching: truthyEnv('LINK_ASSISTANT_AGENT_LEGACY_EDIT_MATCHING'),
|
|
194
|
+
globFollowSymlinks: truthyEnv('LINK_ASSISTANT_AGENT_GLOB_FOLLOW_SYMLINKS'),
|
|
195
|
+
grepHidden: truthyEnvDefaultTrue('LINK_ASSISTANT_AGENT_GREP_HIDDEN'),
|
|
196
|
+
grepTimeoutMs: intEnv(
|
|
197
|
+
'LINK_ASSISTANT_AGENT_GREP_TIMEOUT_MS',
|
|
198
|
+
COMPAT_DEFAULTS.grepTimeoutMs
|
|
199
|
+
),
|
|
200
|
+
readMaxLines: intEnv(
|
|
201
|
+
'LINK_ASSISTANT_AGENT_READ_MAX_LINES',
|
|
202
|
+
COMPAT_DEFAULTS.readMaxLines
|
|
203
|
+
),
|
|
204
|
+
readMaxBytes: intEnv(
|
|
205
|
+
'LINK_ASSISTANT_AGENT_READ_MAX_BYTES',
|
|
206
|
+
COMPAT_DEFAULTS.readMaxBytes
|
|
207
|
+
),
|
|
208
|
+
legacyErrorNames: truthyEnv('LINK_ASSISTANT_AGENT_LEGACY_ERROR_NAMES'),
|
|
209
|
+
legacyTokenUsage: truthyEnv('LINK_ASSISTANT_AGENT_LEGACY_TOKEN_USAGE'),
|
|
210
|
+
legacyLoopExit: truthyEnv('LINK_ASSISTANT_AGENT_LEGACY_LOOP_EXIT'),
|
|
211
|
+
mcpConnectTimeout: intEnv(
|
|
212
|
+
'LINK_ASSISTANT_AGENT_MCP_CONNECT_TIMEOUT',
|
|
213
|
+
COMPAT_DEFAULTS.mcpConnectTimeout
|
|
214
|
+
),
|
|
143
215
|
};
|
|
144
216
|
}
|
|
145
217
|
|
|
@@ -169,111 +241,216 @@ function buildAgentConfigOptions({
|
|
|
169
241
|
yargs: any;
|
|
170
242
|
getenv: (key: string, defaultValue: any) => any;
|
|
171
243
|
}) {
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
'
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
'
|
|
275
|
-
|
|
276
|
-
|
|
244
|
+
return (
|
|
245
|
+
yargs
|
|
246
|
+
.option('verbose', {
|
|
247
|
+
type: 'boolean',
|
|
248
|
+
description: 'Enable verbose mode to debug API requests',
|
|
249
|
+
default: getenv('LINK_ASSISTANT_AGENT_VERBOSE', false),
|
|
250
|
+
})
|
|
251
|
+
.option('dry-run', {
|
|
252
|
+
type: 'boolean',
|
|
253
|
+
description: 'Simulate operations without making actual API calls',
|
|
254
|
+
default: getenv('LINK_ASSISTANT_AGENT_DRY_RUN', false),
|
|
255
|
+
})
|
|
256
|
+
.option('generate-title', {
|
|
257
|
+
type: 'boolean',
|
|
258
|
+
description: 'Generate session titles using AI',
|
|
259
|
+
default: getenv('LINK_ASSISTANT_AGENT_GENERATE_TITLE', false),
|
|
260
|
+
})
|
|
261
|
+
.option('output-response-model', {
|
|
262
|
+
type: 'boolean',
|
|
263
|
+
description: 'Include model info in step_finish output',
|
|
264
|
+
default: getenv('LINK_ASSISTANT_AGENT_OUTPUT_RESPONSE_MODEL', true),
|
|
265
|
+
})
|
|
266
|
+
.option('summarize-session', {
|
|
267
|
+
type: 'boolean',
|
|
268
|
+
description: 'Generate AI session summaries',
|
|
269
|
+
default: getenv('LINK_ASSISTANT_AGENT_SUMMARIZE_SESSION', true),
|
|
270
|
+
})
|
|
271
|
+
.option('retry-on-rate-limits', {
|
|
272
|
+
type: 'boolean',
|
|
273
|
+
description: 'Retry API requests when rate limited (HTTP 429)',
|
|
274
|
+
default: true,
|
|
275
|
+
})
|
|
276
|
+
.option('compact-json', {
|
|
277
|
+
type: 'boolean',
|
|
278
|
+
description:
|
|
279
|
+
'Output compact JSON (single line) instead of pretty-printed',
|
|
280
|
+
default: getenv('LINK_ASSISTANT_AGENT_COMPACT_JSON', false),
|
|
281
|
+
})
|
|
282
|
+
.option('retry-timeout', {
|
|
283
|
+
type: 'number',
|
|
284
|
+
description:
|
|
285
|
+
'Maximum total retry time in seconds for rate limit errors',
|
|
286
|
+
default: getenv('LINK_ASSISTANT_AGENT_RETRY_TIMEOUT', 604800),
|
|
287
|
+
})
|
|
288
|
+
.option('max-retry-delay', {
|
|
289
|
+
type: 'number',
|
|
290
|
+
description: 'Maximum delay between retries in seconds',
|
|
291
|
+
default: getenv('LINK_ASSISTANT_AGENT_MAX_RETRY_DELAY', 1200),
|
|
292
|
+
})
|
|
293
|
+
.option('min-retry-interval', {
|
|
294
|
+
type: 'number',
|
|
295
|
+
description: 'Minimum interval between retries in seconds',
|
|
296
|
+
default: getenv('LINK_ASSISTANT_AGENT_MIN_RETRY_INTERVAL', 30),
|
|
297
|
+
})
|
|
298
|
+
.option('stream-chunk-timeout-ms', {
|
|
299
|
+
type: 'number',
|
|
300
|
+
description: 'Timeout for individual stream chunks in milliseconds',
|
|
301
|
+
default: getenv('LINK_ASSISTANT_AGENT_STREAM_CHUNK_TIMEOUT_MS', 120000),
|
|
302
|
+
})
|
|
303
|
+
.option('stream-step-timeout-ms', {
|
|
304
|
+
type: 'number',
|
|
305
|
+
description: 'Timeout for stream steps in milliseconds',
|
|
306
|
+
default: getenv('LINK_ASSISTANT_AGENT_STREAM_STEP_TIMEOUT_MS', 600000),
|
|
307
|
+
})
|
|
308
|
+
.option('mcp-default-tool-call-timeout', {
|
|
309
|
+
type: 'number',
|
|
310
|
+
description: 'Default MCP tool call timeout in milliseconds',
|
|
311
|
+
default: getenv(
|
|
312
|
+
'LINK_ASSISTANT_AGENT_MCP_DEFAULT_TOOL_CALL_TIMEOUT',
|
|
313
|
+
120000
|
|
314
|
+
),
|
|
315
|
+
})
|
|
316
|
+
.option('mcp-max-tool-call-timeout', {
|
|
317
|
+
type: 'number',
|
|
318
|
+
description: 'Maximum MCP tool call timeout in milliseconds',
|
|
319
|
+
default: getenv(
|
|
320
|
+
'LINK_ASSISTANT_AGENT_MCP_MAX_TOOL_CALL_TIMEOUT',
|
|
321
|
+
600000
|
|
322
|
+
),
|
|
323
|
+
})
|
|
324
|
+
.option('verify-images-at-read-tool', {
|
|
325
|
+
type: 'boolean',
|
|
326
|
+
description: 'Verify images when using the read tool',
|
|
327
|
+
default: getenv(
|
|
328
|
+
'LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL',
|
|
329
|
+
true
|
|
330
|
+
),
|
|
331
|
+
})
|
|
332
|
+
.option('read-only', {
|
|
333
|
+
type: 'boolean',
|
|
334
|
+
description:
|
|
335
|
+
'Enforceable read-only / planning mode. Disables all filesystem-mutating and shell tools (bash, edit, write, multiedit, patch).',
|
|
336
|
+
default: getenv('LINK_ASSISTANT_AGENT_READ_ONLY', false),
|
|
337
|
+
})
|
|
338
|
+
.option('disable-tools', {
|
|
339
|
+
type: 'string',
|
|
340
|
+
description:
|
|
341
|
+
'Comma-separated list of tool ids to disable (e.g. "bash,write,edit"). Disabled tools are never exposed to the model and are rejected if invoked via batch.',
|
|
342
|
+
default: getenv('LINK_ASSISTANT_AGENT_DISABLE_TOOLS', ''),
|
|
343
|
+
})
|
|
344
|
+
.option('permission-mode', {
|
|
345
|
+
type: 'string',
|
|
346
|
+
choices: ['auto', 'plan', 'readonly', 'ask'],
|
|
347
|
+
description:
|
|
348
|
+
'Permission policy for mutating tools. "auto" (default) allows everything; "plan" allows read-only shell and asks before mutations; "readonly" denies all mutations; "ask" asks before every mutating tool. Approvals are driven over JSON (permission_request / permission_response).',
|
|
349
|
+
default: getenv('LINK_ASSISTANT_AGENT_PERMISSION_MODE', 'auto'),
|
|
350
|
+
})
|
|
351
|
+
.option('permission', {
|
|
352
|
+
type: 'string',
|
|
353
|
+
description:
|
|
354
|
+
'OpenCode-compatible permission override as JSON, merged on top of --permission-mode. Example: \'{"edit":"ask","bash":{"git push*":"ask","*":"allow"},"webfetch":"allow"}\'.',
|
|
355
|
+
default: getenv('LINK_ASSISTANT_AGENT_PERMISSION', ''),
|
|
356
|
+
})
|
|
357
|
+
// ─── Compatibility switches (issue #335) ───────────────────────────────
|
|
358
|
+
.option('bash-shell', {
|
|
359
|
+
type: 'string',
|
|
360
|
+
description:
|
|
361
|
+
'Shell used by the bash tool. "auto" (default) uses $SHELL when it is a POSIX shell (sh, bash, zsh, dash, ksh, mksh, ash), else bash, else /bin/sh; "legacy" uses the platform default shell (/bin/sh) as before; any other value is used as the shell path.',
|
|
362
|
+
default: getenv(
|
|
363
|
+
'LINK_ASSISTANT_AGENT_BASH_SHELL',
|
|
364
|
+
COMPAT_DEFAULTS.bashShell
|
|
365
|
+
),
|
|
366
|
+
})
|
|
367
|
+
.option('legacy-tool-output', {
|
|
368
|
+
type: 'boolean',
|
|
369
|
+
description:
|
|
370
|
+
'Restore the previous tool output formats: empty output from edit/write, head-truncated bash output, and an empty result instead of an error when read starts past the end of a file.',
|
|
371
|
+
default: getenv('LINK_ASSISTANT_AGENT_LEGACY_TOOL_OUTPUT', false),
|
|
372
|
+
})
|
|
373
|
+
.option('legacy-edit-matching', {
|
|
374
|
+
type: 'boolean',
|
|
375
|
+
description:
|
|
376
|
+
'Restore the previous edit matching: an empty oldString overwrites an existing file, and fuzzy block matching uses the old loose thresholds without the size guard.',
|
|
377
|
+
default: getenv('LINK_ASSISTANT_AGENT_LEGACY_EDIT_MATCHING', false),
|
|
378
|
+
})
|
|
379
|
+
.option('glob-follow-symlinks', {
|
|
380
|
+
type: 'boolean',
|
|
381
|
+
description:
|
|
382
|
+
'Follow symlinked directories in glob and ls (previous behaviour).',
|
|
383
|
+
default: getenv('LINK_ASSISTANT_AGENT_GLOB_FOLLOW_SYMLINKS', false),
|
|
384
|
+
})
|
|
385
|
+
.option('grep-hidden', {
|
|
386
|
+
type: 'boolean',
|
|
387
|
+
description:
|
|
388
|
+
'Search hidden files and directories in grep (default true; .git is always skipped). Use --no-grep-hidden for the previous behaviour.',
|
|
389
|
+
default: getenv(
|
|
390
|
+
'LINK_ASSISTANT_AGENT_GREP_HIDDEN',
|
|
391
|
+
COMPAT_DEFAULTS.grepHidden
|
|
392
|
+
),
|
|
393
|
+
})
|
|
394
|
+
.option('grep-timeout-ms', {
|
|
395
|
+
type: 'number',
|
|
396
|
+
description:
|
|
397
|
+
'Timeout for one grep search in milliseconds (0 = no timeout)',
|
|
398
|
+
default: getenv(
|
|
399
|
+
'LINK_ASSISTANT_AGENT_GREP_TIMEOUT_MS',
|
|
400
|
+
COMPAT_DEFAULTS.grepTimeoutMs
|
|
401
|
+
),
|
|
402
|
+
})
|
|
403
|
+
.option('read-max-lines', {
|
|
404
|
+
type: 'number',
|
|
405
|
+
description:
|
|
406
|
+
'Maximum number of lines one read call may return (0 = no cap on the limit parameter)',
|
|
407
|
+
default: getenv(
|
|
408
|
+
'LINK_ASSISTANT_AGENT_READ_MAX_LINES',
|
|
409
|
+
COMPAT_DEFAULTS.readMaxLines
|
|
410
|
+
),
|
|
411
|
+
})
|
|
412
|
+
.option('read-max-bytes', {
|
|
413
|
+
type: 'number',
|
|
414
|
+
description:
|
|
415
|
+
'Maximum bytes of file content one read call may return (0 = no cap)',
|
|
416
|
+
default: getenv(
|
|
417
|
+
'LINK_ASSISTANT_AGENT_READ_MAX_BYTES',
|
|
418
|
+
COMPAT_DEFAULTS.readMaxBytes
|
|
419
|
+
),
|
|
420
|
+
})
|
|
421
|
+
.option('legacy-error-names', {
|
|
422
|
+
type: 'boolean',
|
|
423
|
+
description:
|
|
424
|
+
'Report errors in JSON output under their previous names (ContextOverflowError is reported as APIError).',
|
|
425
|
+
default: getenv('LINK_ASSISTANT_AGENT_LEGACY_ERROR_NAMES', false),
|
|
426
|
+
})
|
|
427
|
+
.option('legacy-token-usage', {
|
|
428
|
+
type: 'boolean',
|
|
429
|
+
description:
|
|
430
|
+
'Report tokens.output including reasoning tokens and compute cost from it, as before.',
|
|
431
|
+
default: getenv('LINK_ASSISTANT_AGENT_LEGACY_TOKEN_USAGE', false),
|
|
432
|
+
})
|
|
433
|
+
.option('legacy-loop-exit', {
|
|
434
|
+
type: 'boolean',
|
|
435
|
+
description:
|
|
436
|
+
'End the turn on any finish reason other than tool-calls/unknown, even if tools ran in that step (previous behaviour).',
|
|
437
|
+
default: getenv('LINK_ASSISTANT_AGENT_LEGACY_LOOP_EXIT', false),
|
|
438
|
+
})
|
|
439
|
+
.option('mcp-connect-timeout', {
|
|
440
|
+
type: 'number',
|
|
441
|
+
description:
|
|
442
|
+
'Default timeout in milliseconds for connecting to and initializing an MCP server (per-server "connect_timeout" overrides it; 0 = no timeout)',
|
|
443
|
+
default: getenv(
|
|
444
|
+
'LINK_ASSISTANT_AGENT_MCP_CONNECT_TIMEOUT',
|
|
445
|
+
COMPAT_DEFAULTS.mcpConnectTimeout
|
|
446
|
+
),
|
|
447
|
+
})
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function nonNegative(value: unknown, fallback: number): number {
|
|
452
|
+
const n = typeof value === 'string' ? parseInt(value, 10) : value;
|
|
453
|
+
return typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
277
454
|
}
|
|
278
455
|
|
|
279
456
|
/**
|
|
@@ -329,6 +506,30 @@ export function initConfig(argv?: string[]): AgentConfig {
|
|
|
329
506
|
disableTools: parsed.disableTools ?? '',
|
|
330
507
|
permissionMode: parsed.permissionMode ?? 'auto',
|
|
331
508
|
permission: parsed.permission ?? '',
|
|
509
|
+
bashShell: parsed.bashShell || COMPAT_DEFAULTS.bashShell,
|
|
510
|
+
legacyToolOutput: parsed.legacyToolOutput ?? false,
|
|
511
|
+
legacyEditMatching: parsed.legacyEditMatching ?? false,
|
|
512
|
+
globFollowSymlinks: parsed.globFollowSymlinks ?? false,
|
|
513
|
+
grepHidden: parsed.grepHidden ?? COMPAT_DEFAULTS.grepHidden,
|
|
514
|
+
grepTimeoutMs: nonNegative(
|
|
515
|
+
parsed.grepTimeoutMs,
|
|
516
|
+
COMPAT_DEFAULTS.grepTimeoutMs
|
|
517
|
+
),
|
|
518
|
+
readMaxLines: nonNegative(
|
|
519
|
+
parsed.readMaxLines,
|
|
520
|
+
COMPAT_DEFAULTS.readMaxLines
|
|
521
|
+
),
|
|
522
|
+
readMaxBytes: nonNegative(
|
|
523
|
+
parsed.readMaxBytes,
|
|
524
|
+
COMPAT_DEFAULTS.readMaxBytes
|
|
525
|
+
),
|
|
526
|
+
legacyErrorNames: parsed.legacyErrorNames ?? false,
|
|
527
|
+
legacyTokenUsage: parsed.legacyTokenUsage ?? false,
|
|
528
|
+
legacyLoopExit: parsed.legacyLoopExit ?? false,
|
|
529
|
+
mcpConnectTimeout: nonNegative(
|
|
530
|
+
parsed.mcpConnectTimeout,
|
|
531
|
+
COMPAT_DEFAULTS.mcpConnectTimeout
|
|
532
|
+
),
|
|
332
533
|
});
|
|
333
534
|
|
|
334
535
|
// Propagate verbose to env var for subprocess resilience (issue #227).
|
package/src/config/defaults.ts
CHANGED
|
@@ -15,29 +15,37 @@ export interface DefaultConfigOptions {
|
|
|
15
15
|
defaultCompactionSafetyMarginPercent?: number | string | null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Default model used when no `--model` CLI argument is provided.
|
|
20
|
+
*
|
|
21
|
+
* Kilo withdrew the free Nemotron 3 Super route; `kilo/nemotron-3-super-free`
|
|
22
|
+
* is now a deprecated alias for this model (#335).
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_MODEL = 'kilo/nemotron-3-ultra-free';
|
|
20
25
|
|
|
21
26
|
/** Env var for overriding the default model in test runs and automation. */
|
|
22
27
|
export const DEFAULT_MODEL_ENV = 'LINK_ASSISTANT_AGENT_DEFAULT_MODEL';
|
|
23
28
|
|
|
24
29
|
/** Default compaction model used when no `--compaction-model` CLI argument is provided. */
|
|
25
|
-
export const DEFAULT_COMPACTION_MODEL = 'kilo/nemotron-3-
|
|
30
|
+
export const DEFAULT_COMPACTION_MODEL = 'kilo/nemotron-3-ultra-free';
|
|
26
31
|
|
|
27
32
|
/** Env var for overriding the default compaction model in test runs and automation. */
|
|
28
33
|
export const DEFAULT_COMPACTION_MODEL_ENV =
|
|
29
34
|
'LINK_ASSISTANT_AGENT_DEFAULT_COMPACTION_MODEL';
|
|
30
35
|
|
|
31
36
|
/**
|
|
32
|
-
*
|
|
37
|
+
* Served free Kilo models, followed by the session model. Both Kilo entries
|
|
38
|
+
* have a 1M-token context, so the order is the preferred model first, then
|
|
39
|
+
* the rate-limit fallback.
|
|
33
40
|
* Compaction skips models whose context is too small or which hit a rate limit.
|
|
34
41
|
* Provider prefixes keep default secondary calls away from Zen's restricted tier.
|
|
35
42
|
* The special value "same" uses the model running the session.
|
|
36
43
|
*
|
|
37
44
|
* @see https://github.com/link-assistant/agent/issues/327
|
|
45
|
+
* @see https://github.com/link-assistant/agent/issues/335
|
|
38
46
|
*/
|
|
39
47
|
export const DEFAULT_COMPACTION_MODELS =
|
|
40
|
-
'(kilo/nemotron-3-
|
|
48
|
+
'(kilo/nemotron-3-ultra-free kilo/nemotron-3.5-lightning-free same)';
|
|
41
49
|
|
|
42
50
|
/** Env var for overriding the default compaction cascade in test runs and automation. */
|
|
43
51
|
export const DEFAULT_COMPACTION_MODELS_ENV =
|
|
@@ -435,6 +435,14 @@ export namespace Config {
|
|
|
435
435
|
.describe(
|
|
436
436
|
'Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.'
|
|
437
437
|
),
|
|
438
|
+
connect_timeout: z
|
|
439
|
+
.number()
|
|
440
|
+
.int()
|
|
441
|
+
.nonnegative()
|
|
442
|
+
.optional()
|
|
443
|
+
.describe(
|
|
444
|
+
'Timeout in ms for starting the MCP server and completing the initialize handshake. Defaults to --mcp-connect-timeout / LINK_ASSISTANT_AGENT_MCP_CONNECT_TIMEOUT (30000). 0 disables the bound.'
|
|
445
|
+
),
|
|
438
446
|
tool_call_timeout: z
|
|
439
447
|
.number()
|
|
440
448
|
.int()
|
|
@@ -475,6 +483,14 @@ export namespace Config {
|
|
|
475
483
|
.describe(
|
|
476
484
|
'Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.'
|
|
477
485
|
),
|
|
486
|
+
connect_timeout: z
|
|
487
|
+
.number()
|
|
488
|
+
.int()
|
|
489
|
+
.nonnegative()
|
|
490
|
+
.optional()
|
|
491
|
+
.describe(
|
|
492
|
+
'Timeout in ms for starting the MCP server and completing the initialize handshake. Defaults to --mcp-connect-timeout / LINK_ASSISTANT_AGENT_MCP_CONNECT_TIMEOUT (30000). 0 disables the bound.'
|
|
493
|
+
),
|
|
478
494
|
tool_call_timeout: z
|
|
479
495
|
.number()
|
|
480
496
|
.int()
|
package/src/file/ripgrep.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { $ } from 'bun';
|
|
|
10
10
|
import { ZipReader, BlobReader, BlobWriter } from '@zip.js/zip.js';
|
|
11
11
|
import { Log } from '../util/log';
|
|
12
12
|
import { createVerboseFetch } from '../util/verbose-fetch';
|
|
13
|
+
import { config as agentConfig } from '../config/config';
|
|
13
14
|
|
|
14
15
|
export namespace Ripgrep {
|
|
15
16
|
const log = Log.create({ service: 'ripgrep' });
|
|
@@ -217,14 +218,26 @@ export namespace Ripgrep {
|
|
|
217
218
|
return filepath;
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
export async function* files(input: {
|
|
221
|
+
export async function* files(input: {
|
|
222
|
+
cwd: string;
|
|
223
|
+
glob?: string[];
|
|
224
|
+
signal?: AbortSignal;
|
|
225
|
+
follow?: boolean;
|
|
226
|
+
}) {
|
|
227
|
+
input.signal?.throwIfAborted();
|
|
228
|
+
|
|
229
|
+
// No `--follow` by default: following symlinked directories can walk huge
|
|
230
|
+
// or cyclic trees (upstream c0e71c4261 #11415). --glob-follow-symlinks /
|
|
231
|
+
// LINK_ASSISTANT_AGENT_GLOB_FOLLOW_SYMLINKS restores it. `--no-config`
|
|
232
|
+
// keeps a user's RIPGREP_CONFIG_PATH from changing the output we parse.
|
|
221
233
|
const args = [
|
|
222
234
|
await filepath(),
|
|
223
235
|
'--files',
|
|
224
|
-
'--follow',
|
|
225
236
|
'--hidden',
|
|
237
|
+
'--no-config',
|
|
226
238
|
'--glob=!.git/*',
|
|
227
239
|
];
|
|
240
|
+
if (input.follow ?? agentConfig.globFollowSymlinks) args.push('--follow');
|
|
228
241
|
if (input.glob) {
|
|
229
242
|
for (const g of input.glob) {
|
|
230
243
|
args.push(`--glob=${g}`);
|
|
@@ -233,7 +246,8 @@ export namespace Ripgrep {
|
|
|
233
246
|
|
|
234
247
|
// Bun.spawn should throw this, but it incorrectly reports that the executable does not exist.
|
|
235
248
|
// See https://github.com/oven-sh/bun/issues/24012
|
|
236
|
-
|
|
249
|
+
const info = await fs.stat(input.cwd).catch(() => undefined);
|
|
250
|
+
if (!info) {
|
|
237
251
|
throw Object.assign(
|
|
238
252
|
new Error(`No such file or directory: '${input.cwd}'`),
|
|
239
253
|
{
|
|
@@ -243,12 +257,24 @@ export namespace Ripgrep {
|
|
|
243
257
|
}
|
|
244
258
|
);
|
|
245
259
|
}
|
|
260
|
+
// upstream f753103e82 (#38890)
|
|
261
|
+
if (!info.isDirectory()) {
|
|
262
|
+
throw Object.assign(
|
|
263
|
+
new Error(`Search path is not a directory: ${input.cwd}`),
|
|
264
|
+
{
|
|
265
|
+
code: 'ENOTDIR',
|
|
266
|
+
errno: -20,
|
|
267
|
+
path: input.cwd,
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
246
271
|
|
|
247
272
|
const proc = Bun.spawn(args, {
|
|
248
273
|
cwd: input.cwd,
|
|
249
274
|
stdout: 'pipe',
|
|
250
275
|
stderr: 'ignore',
|
|
251
276
|
maxBuffer: 1024 * 1024 * 20,
|
|
277
|
+
signal: input.signal,
|
|
252
278
|
});
|
|
253
279
|
|
|
254
280
|
const reader = proc.stdout.getReader();
|
|
@@ -257,11 +283,13 @@ export namespace Ripgrep {
|
|
|
257
283
|
|
|
258
284
|
try {
|
|
259
285
|
while (true) {
|
|
286
|
+
input.signal?.throwIfAborted();
|
|
260
287
|
const { done, value } = await reader.read();
|
|
261
288
|
if (done) break;
|
|
262
289
|
|
|
263
290
|
buffer += decoder.decode(value, { stream: true });
|
|
264
|
-
|
|
291
|
+
// Handle both Unix (\n) and Windows (\r\n) line endings (upstream 5af35117db)
|
|
292
|
+
const lines = buffer.split(/\r?\n/);
|
|
265
293
|
buffer = lines.pop() || '';
|
|
266
294
|
|
|
267
295
|
for (const line of lines) {
|
|
@@ -271,7 +299,12 @@ export namespace Ripgrep {
|
|
|
271
299
|
|
|
272
300
|
if (buffer) yield buffer;
|
|
273
301
|
} finally {
|
|
274
|
-
|
|
302
|
+
// The consumer may stop early (glob/ls stop at 100 results). Waiting
|
|
303
|
+
// for rg to exit without draining its stdout deadlocks once the pipe
|
|
304
|
+
// buffer is full, so cancel the stream and kill rg first (upstream
|
|
305
|
+
// e5b33f8a5e #10833, 76ee87ead8, 3bda0ce123).
|
|
306
|
+
await reader.cancel().catch(() => {});
|
|
307
|
+
if (proc.exitCode === null) proc.kill();
|
|
275
308
|
await proc.exited;
|
|
276
309
|
}
|
|
277
310
|
}
|