@myagentroam/node 0.9.106 → 0.9.108
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/claude-agent-sdk.js +2 -1
- package/dist/operational.js +23 -3
- package/dist/runner/mar-agent/conversation-normalizer.js +38 -2
- package/dist/runner/mar-agent/managed-run-controller.js +2 -1
- package/dist/service/code-navigation/ctags-index-storage.js +34 -4
- package/dist/service/conversation-history-service.js +24 -0
- package/dist/service/native-session-watch-service.js +19 -5
- package/dist/service/workspace-change-service.js +2 -1
- package/dist/service/workspace-content-search-service.js +46 -8
- package/dist/workspace.js +32 -6
- package/package.json +4 -4
package/dist/claude-agent-sdk.js
CHANGED
|
@@ -301,7 +301,8 @@ export async function probeClaudeCodeAsync(command = 'claude') {
|
|
|
301
301
|
const authInvocation = platformCliInvocation(command, ['auth', 'status', '--json']);
|
|
302
302
|
const authResult = await runRunnerProbe(authInvocation.command, authInvocation.args, {
|
|
303
303
|
env: minimalRunnerEnvironment(),
|
|
304
|
-
timeoutMs: 3_000
|
|
304
|
+
timeoutMs: 3_000,
|
|
305
|
+
logStderr: false
|
|
305
306
|
});
|
|
306
307
|
let authenticated = false;
|
|
307
308
|
if (authResult.status === 0) {
|
package/dist/operational.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { basename } from 'node:path';
|
|
2
3
|
import { writeNodeLog } from './rotating-log.js';
|
|
3
4
|
const secretKey = /(?:password|credential|token|cookie|authorization|secret|api[_-]?key)/i;
|
|
4
5
|
export function runRunnerProbe(command, args, options) {
|
|
5
6
|
return new Promise((resolve) => {
|
|
7
|
+
const startedAt = performance.now();
|
|
6
8
|
const child = spawn(command, args, {
|
|
7
9
|
windowsHide: true,
|
|
8
10
|
env: options.env,
|
|
@@ -11,22 +13,29 @@ export function runRunnerProbe(command, args, options) {
|
|
|
11
13
|
let stdout = '';
|
|
12
14
|
let stderr = '';
|
|
13
15
|
let settled = false;
|
|
14
|
-
const finish = (status) => {
|
|
16
|
+
const finish = (status, reason) => {
|
|
15
17
|
if (settled)
|
|
16
18
|
return;
|
|
17
19
|
settled = true;
|
|
18
20
|
clearTimeout(timer);
|
|
21
|
+
if (status !== 0)
|
|
22
|
+
logNodeCommandFailure(basename(command), {
|
|
23
|
+
...(status === null ? {} : { exitCode: status }),
|
|
24
|
+
...(options.logStderr === false ? {} : { stderr }),
|
|
25
|
+
...(reason === undefined ? {} : { reason }),
|
|
26
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
27
|
+
});
|
|
19
28
|
resolve({ status, stdout, stderr });
|
|
20
29
|
};
|
|
21
30
|
const timer = setTimeout(() => {
|
|
22
|
-
finish(null);
|
|
31
|
+
finish(null, 'TIMEOUT');
|
|
23
32
|
child.kill();
|
|
24
33
|
}, options.timeoutMs);
|
|
25
34
|
child.stdout.setEncoding('utf8');
|
|
26
35
|
child.stderr.setEncoding('utf8');
|
|
27
36
|
child.stdout.on('data', (value) => (stdout += value));
|
|
28
37
|
child.stderr.on('data', (value) => (stderr += value));
|
|
29
|
-
child.once('error', () => finish(null));
|
|
38
|
+
child.once('error', (error) => finish(null, error.code ?? 'START_FAILED'));
|
|
30
39
|
child.once('close', (status) => finish(status));
|
|
31
40
|
});
|
|
32
41
|
}
|
|
@@ -120,3 +129,14 @@ export function nodeLog(event, fields = {}) {
|
|
|
120
129
|
process.stdout.write(line);
|
|
121
130
|
writeNodeLog(line);
|
|
122
131
|
}
|
|
132
|
+
/** Logs Node-owned subprocess failures without copying argv, stdout or environment values. */
|
|
133
|
+
export function logNodeCommandFailure(command, result) {
|
|
134
|
+
const details = result.stderr?.trim().slice(0, 1_000);
|
|
135
|
+
nodeLog('node.command.failed', {
|
|
136
|
+
command,
|
|
137
|
+
...(result.exitCode === undefined ? {} : { exitCode: result.exitCode }),
|
|
138
|
+
...(result.reason === undefined ? {} : { reason: result.reason }),
|
|
139
|
+
...(result.durationMs === undefined ? {} : { durationMs: result.durationMs }),
|
|
140
|
+
...(details ? { errorDetails: details } : {})
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -134,8 +134,10 @@ export class MarAgentConversationNormalizer {
|
|
|
134
134
|
if (action === 'start' && typeof input.command === 'string') {
|
|
135
135
|
const command = {
|
|
136
136
|
itemId: ownItemId,
|
|
137
|
+
turnKey: event.turnId ?? event.executionId,
|
|
137
138
|
command: compactRunnerText(input.command, 10_000),
|
|
138
139
|
cwd: typeof input.cwd === 'string' ? compactRunnerText(input.cwd, 4_096) : null,
|
|
140
|
+
status: 'IN_PROGRESS',
|
|
139
141
|
outputPreview: '',
|
|
140
142
|
exitCode: null,
|
|
141
143
|
truncated: false
|
|
@@ -154,6 +156,29 @@ export class MarAgentConversationNormalizer {
|
|
|
154
156
|
typeof input.processId === 'string') {
|
|
155
157
|
const origin = this.#processOrigins.get(input.processId);
|
|
156
158
|
if (origin !== undefined) {
|
|
159
|
+
const previous = this.#commands.get(origin);
|
|
160
|
+
const sameTurn = previous?.turnKey === (event.turnId ?? event.executionId);
|
|
161
|
+
if (previous !== undefined && !sameTurn) {
|
|
162
|
+
const command = {
|
|
163
|
+
itemId: ownItemId,
|
|
164
|
+
turnKey: event.turnId ?? event.executionId,
|
|
165
|
+
command: previous.command,
|
|
166
|
+
cwd: previous.cwd,
|
|
167
|
+
status: 'IN_PROGRESS',
|
|
168
|
+
outputPreview: '',
|
|
169
|
+
exitCode: null,
|
|
170
|
+
truncated: false
|
|
171
|
+
};
|
|
172
|
+
this.#commands.set(event.callId, command);
|
|
173
|
+
this.#tools.set(event.callId, {
|
|
174
|
+
toolName: event.toolName,
|
|
175
|
+
input: event.input,
|
|
176
|
+
itemId: ownItemId,
|
|
177
|
+
structured: true,
|
|
178
|
+
commandOrigin: event.callId
|
|
179
|
+
});
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
157
182
|
this.#tools.set(event.callId, {
|
|
158
183
|
toolName: event.toolName,
|
|
159
184
|
input: event.input,
|
|
@@ -201,6 +226,7 @@ export class MarAgentConversationNormalizer {
|
|
|
201
226
|
: result.status === 'completed' && result.exitCode === 0
|
|
202
227
|
? 'COMPLETED'
|
|
203
228
|
: 'FAILED';
|
|
229
|
+
command.status = status;
|
|
204
230
|
return [commandItem(command, status)];
|
|
205
231
|
}
|
|
206
232
|
}
|
|
@@ -256,8 +282,15 @@ export class MarAgentConversationNormalizer {
|
|
|
256
282
|
const state = this.#tools.get(event.callId);
|
|
257
283
|
if (event.toolName === 'exec' && state?.commandOrigin !== undefined) {
|
|
258
284
|
const command = this.#commands.get(state.commandOrigin);
|
|
259
|
-
|
|
285
|
+
const input = record(state.input);
|
|
286
|
+
if (command !== undefined &&
|
|
287
|
+
command.status === 'IN_PROGRESS' &&
|
|
288
|
+
(state.itemId !== command.itemId ||
|
|
289
|
+
input?.action === 'start' ||
|
|
290
|
+
(input?.action == null && typeof input?.command === 'string'))) {
|
|
291
|
+
command.status = 'FAILED';
|
|
260
292
|
return [commandItem(command, 'FAILED')];
|
|
293
|
+
}
|
|
261
294
|
}
|
|
262
295
|
if (event.toolName === 'apply_patch' && state?.structured === true)
|
|
263
296
|
return [fileChangeItem(state?.itemId ?? itemId('tool', event.callId), 'FAILED', [])];
|
|
@@ -361,7 +394,9 @@ function commandItem(command, status) {
|
|
|
361
394
|
function applyCommandResult(command, result) {
|
|
362
395
|
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
363
396
|
if (output.length > 0) {
|
|
364
|
-
const combined =
|
|
397
|
+
const combined = result.cached
|
|
398
|
+
? output
|
|
399
|
+
: [command.outputPreview, output].filter(Boolean).join('\n');
|
|
365
400
|
command.truncated ||= combined.length > 10_000;
|
|
366
401
|
command.outputPreview = compactRunnerText(combined, 10_000);
|
|
367
402
|
}
|
|
@@ -443,6 +478,7 @@ function execResult(value, summary, eventTruncated = false) {
|
|
|
443
478
|
stdout,
|
|
444
479
|
stderr,
|
|
445
480
|
...(exitCode === undefined ? {} : { exitCode }),
|
|
481
|
+
...(data?.cached === true || summaryData?.cached === true ? { cached: true } : {}),
|
|
446
482
|
truncated: eventTruncated ||
|
|
447
483
|
data?.truncated === true ||
|
|
448
484
|
summaryData?.truncated === true ||
|
|
@@ -511,6 +511,7 @@ export class MarAgentManagedRunController {
|
|
|
511
511
|
agent,
|
|
512
512
|
session,
|
|
513
513
|
host: localHost,
|
|
514
|
+
normalizer: new MarAgentConversationNormalizer(),
|
|
514
515
|
mcpLease,
|
|
515
516
|
dynamicCredentials,
|
|
516
517
|
models,
|
|
@@ -639,7 +640,7 @@ export class MarAgentManagedRunController {
|
|
|
639
640
|
itemId: boundedConversationItemId('mar-agent-message', event.itemId)
|
|
640
641
|
});
|
|
641
642
|
else {
|
|
642
|
-
let normalizer = this.#normalizers.get(runId);
|
|
643
|
+
let normalizer = this.#normalizers.get(runId) ?? this.#sessionRuntimes.get(event.sessionId)?.normalizer;
|
|
643
644
|
if (normalizer === undefined) {
|
|
644
645
|
normalizer = new MarAgentConversationNormalizer();
|
|
645
646
|
this.#normalizers.set(runId, normalizer);
|
|
@@ -4,6 +4,7 @@ import { createReadStream, createWriteStream } from 'node:fs';
|
|
|
4
4
|
import { lstat, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { relative, resolve, sep } from 'node:path';
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
|
+
import { logNodeCommandFailure } from '../../operational.js';
|
|
7
8
|
import { compareTagNames } from './ctags-query.js';
|
|
8
9
|
import { sourceFileFingerprint } from './navigation-target-validator.js';
|
|
9
10
|
const BUILD_TIMEOUT_MS = 120_000;
|
|
@@ -175,6 +176,15 @@ export async function runCtags(executable, cwd, fileList, destination) {
|
|
|
175
176
|
windowsHide: true,
|
|
176
177
|
stdio: ['ignore', 'ignore', 'pipe']
|
|
177
178
|
});
|
|
179
|
+
const errors = [];
|
|
180
|
+
let errorBytes = 0;
|
|
181
|
+
const stderr = () => Buffer.concat(errors).toString('utf8');
|
|
182
|
+
const reportFailure = (reason, exitCode) => logNodeCommandFailure('ctags', {
|
|
183
|
+
reason,
|
|
184
|
+
...(exitCode === undefined ? {} : { exitCode }),
|
|
185
|
+
stderr: stderr(),
|
|
186
|
+
durationMs: Date.now() - startedAt
|
|
187
|
+
});
|
|
178
188
|
let settled = false;
|
|
179
189
|
const settle = (callback) => {
|
|
180
190
|
if (settled)
|
|
@@ -197,12 +207,32 @@ export async function runCtags(executable, cwd, fileList, destination) {
|
|
|
197
207
|
};
|
|
198
208
|
const timer = setTimeout(() => {
|
|
199
209
|
terminate();
|
|
200
|
-
settle(() =>
|
|
210
|
+
settle(() => {
|
|
211
|
+
reportFailure('TIMEOUT');
|
|
212
|
+
reject(new Error('SYMBOL_INDEX_BUILD_FAILED'));
|
|
213
|
+
});
|
|
201
214
|
}, BUILD_TIMEOUT_MS);
|
|
202
215
|
timer.unref();
|
|
203
|
-
child.stderr.on('data', () =>
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
child.stderr.on('data', (chunk) => {
|
|
217
|
+
const remaining = Math.max(0, 1_000 - errorBytes);
|
|
218
|
+
if (remaining === 0)
|
|
219
|
+
return;
|
|
220
|
+
const retained = chunk.subarray(0, remaining);
|
|
221
|
+
errors.push(retained);
|
|
222
|
+
errorBytes += retained.length;
|
|
223
|
+
});
|
|
224
|
+
child.once('error', (error) => settle(() => {
|
|
225
|
+
reportFailure(error.code ?? 'START_FAILED');
|
|
226
|
+
reject(new Error('SYMBOL_INDEX_BUILD_FAILED'));
|
|
227
|
+
}));
|
|
228
|
+
child.once('close', (code) => settle(() => {
|
|
229
|
+
if (code === 0)
|
|
230
|
+
resolvePromise();
|
|
231
|
+
else {
|
|
232
|
+
reportFailure('EXIT', code ?? undefined);
|
|
233
|
+
reject(new Error('SYMBOL_INDEX_BUILD_FAILED'));
|
|
234
|
+
}
|
|
235
|
+
}));
|
|
206
236
|
});
|
|
207
237
|
return Date.now() - startedAt;
|
|
208
238
|
}
|
|
@@ -18,6 +18,30 @@ export function convergeConversationWatchPage(previous, previousObserved, next,
|
|
|
18
18
|
page: composeConversationRuntimePage(previous, next),
|
|
19
19
|
authority: authority ?? next.source
|
|
20
20
|
};
|
|
21
|
+
// A Runner history read can lag a Run that the Watch has already delivered. Keep the
|
|
22
|
+
// unconfirmed Runtime tail until the history includes that Turn; source priority alone
|
|
23
|
+
// does not establish that the returned window contains the latest conversation.
|
|
24
|
+
if (previous?.source === 'runtime' &&
|
|
25
|
+
previous.turns.length > 0 &&
|
|
26
|
+
next.source !== 'runtime' &&
|
|
27
|
+
(authority === undefined || sourceRank(next.source) >= sourceRank(authority))) {
|
|
28
|
+
const boundary = previous.turns.findLastIndex((turn) => next.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
29
|
+
if (boundary < 0)
|
|
30
|
+
return { page: previous, authority: next.source };
|
|
31
|
+
const tail = previous.turns
|
|
32
|
+
.slice(boundary + 1)
|
|
33
|
+
.filter((turn) => !next.turns.some((candidate) => sameConversationTurn(candidate, turn)));
|
|
34
|
+
if (tail.length > 0)
|
|
35
|
+
return {
|
|
36
|
+
page: {
|
|
37
|
+
...next,
|
|
38
|
+
source: 'runtime',
|
|
39
|
+
snapshotSequence: previous.snapshotSequence,
|
|
40
|
+
turns: [...next.turns, ...tail]
|
|
41
|
+
},
|
|
42
|
+
authority: next.source
|
|
43
|
+
};
|
|
44
|
+
}
|
|
21
45
|
if (previous === undefined ||
|
|
22
46
|
authority === undefined ||
|
|
23
47
|
sourceRank(next.source) >= sourceRank(authority))
|
|
@@ -249,8 +249,20 @@ export class NativeSessionWatchService {
|
|
|
249
249
|
const page = await (readHistoricalPage
|
|
250
250
|
? this.options.readHistoryPage(session, limit)
|
|
251
251
|
: this.options.readPage(session, limit));
|
|
252
|
-
if (
|
|
253
|
-
|
|
252
|
+
if (revisionBeforeRead !== watch.eventRevision) {
|
|
253
|
+
// Initial still needs a full page for its new lease, whose pending events will
|
|
254
|
+
// replay above that page's sequence. Never publish this invalidated read to
|
|
255
|
+
// existing leases or replace their shared observation state.
|
|
256
|
+
if (mode !== 'initial')
|
|
257
|
+
return watch.page;
|
|
258
|
+
const initial = this.options.convergePage(watch.page, watch.observedPage, page, watch.authority).page;
|
|
259
|
+
if (watch.page === undefined && watch.signature === undefined)
|
|
260
|
+
watch.signature = JSON.stringify({
|
|
261
|
+
source: initial.source,
|
|
262
|
+
turns: initial.turns.slice(-(EMITTED_TURN_LIMIT + 1))
|
|
263
|
+
});
|
|
264
|
+
return initial;
|
|
265
|
+
}
|
|
254
266
|
stage = 'present_session';
|
|
255
267
|
const presented = this.options.present(session);
|
|
256
268
|
const sessionSignature = JSON.stringify(presented);
|
|
@@ -265,7 +277,7 @@ export class NativeSessionWatchService {
|
|
|
265
277
|
: page.turns.slice(-EMITTED_TURN_LIMIT);
|
|
266
278
|
const observed = { ...page, turns };
|
|
267
279
|
stage = 'converge_page';
|
|
268
|
-
const convergence = this.options.convergePage(watch.page, watch.observedPage,
|
|
280
|
+
const convergence = this.options.convergePage(watch.page, watch.observedPage, page, watch.authority);
|
|
269
281
|
const converged = convergence.page;
|
|
270
282
|
const accepted = converged.turns.length <= EMITTED_TURN_LIMIT + 1
|
|
271
283
|
? converged
|
|
@@ -285,8 +297,9 @@ export class NativeSessionWatchService {
|
|
|
285
297
|
const changed = signature !== watch.signature;
|
|
286
298
|
if (changed) {
|
|
287
299
|
watch.signature = signature;
|
|
288
|
-
watch.page = accepted;
|
|
289
300
|
}
|
|
301
|
+
if (changed || watch.page === undefined)
|
|
302
|
+
watch.page = accepted;
|
|
290
303
|
stage = 'emit_page';
|
|
291
304
|
if (completingConvergence && accepted.source === 'runtime')
|
|
292
305
|
this.releasePendingEvents(session.id);
|
|
@@ -294,7 +307,8 @@ export class NativeSessionWatchService {
|
|
|
294
307
|
this.emitSnapshotToLeases(session, accepted, true);
|
|
295
308
|
else if (changed)
|
|
296
309
|
this.emitObservedPage(session, accepted);
|
|
297
|
-
|
|
310
|
+
// Initial uses the full result of the same authority decision as observation.
|
|
311
|
+
return mode === 'initial' ? converged : accepted;
|
|
298
312
|
}
|
|
299
313
|
catch (error) {
|
|
300
314
|
watch.failures += 1;
|
|
@@ -42,7 +42,7 @@ export class WorkspaceChangeService {
|
|
|
42
42
|
...(error instanceof GitCommandError
|
|
43
43
|
? {
|
|
44
44
|
parameters: { gitExitCode: error.code },
|
|
45
|
-
details:
|
|
45
|
+
details: error.publicDetails
|
|
46
46
|
}
|
|
47
47
|
: {})
|
|
48
48
|
});
|
|
@@ -53,6 +53,7 @@ export class WorkspaceChangeService {
|
|
|
53
53
|
groupPath: path,
|
|
54
54
|
errorCode: error.code,
|
|
55
55
|
...error.parameters,
|
|
56
|
+
...(error.details === undefined ? {} : { errorDetails: error.details }),
|
|
56
57
|
durationMs: Math.round(performance.now() - startedAt)
|
|
57
58
|
});
|
|
58
59
|
};
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
3
|
import { lstat, realpath } from 'node:fs/promises';
|
|
4
4
|
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
5
|
+
import { logNodeCommandFailure } from '../operational.js';
|
|
5
6
|
import { bundledRipgrepPath } from '../ripgrep-binary.js';
|
|
6
7
|
const RESULT_LIMIT = 200;
|
|
7
8
|
const PER_FILE_LIMIT = 20;
|
|
@@ -113,10 +114,14 @@ export class WorkspaceContentSearchService {
|
|
|
113
114
|
async validateNativeRegex(root, search, deadline) {
|
|
114
115
|
const command = commandFor('RIPGREP', [], search, this.ripgrep ?? undefined);
|
|
115
116
|
const result = await runCommand(command.file, [...command.args, '-'], root, deadline);
|
|
116
|
-
if (result.code === 2)
|
|
117
|
+
if (result.code === 2) {
|
|
118
|
+
logNodeCommandFailure('rg', { exitCode: result.code, reason: 'INVALID_QUERY' });
|
|
117
119
|
throw new Error('FILE_CONTENT_SEARCH_INVALID');
|
|
118
|
-
|
|
120
|
+
}
|
|
121
|
+
if (!command.validCodes.includes(result.code)) {
|
|
122
|
+
logNodeCommandFailure('rg', { exitCode: result.code, stderr: result.stderr });
|
|
119
123
|
throw new Error('CONTENT_SEARCH_COMMAND_FAILED');
|
|
124
|
+
}
|
|
120
125
|
}
|
|
121
126
|
async candidates(workspace, deadline) {
|
|
122
127
|
const root = await realpath(workspace.path);
|
|
@@ -167,8 +172,11 @@ export class WorkspaceContentSearchService {
|
|
|
167
172
|
const command = commandFor(engine, batch, search, this.ripgrep ?? undefined);
|
|
168
173
|
const result = await runCommand(command.file, command.args, root, deadline, engine === 'RIPGREP');
|
|
169
174
|
if (!command.validCodes.includes(result.code)) {
|
|
170
|
-
if (search.regex && result.code === 2)
|
|
175
|
+
if (search.regex && result.code === 2) {
|
|
176
|
+
logNodeCommandFailure('rg', { exitCode: result.code, reason: 'INVALID_QUERY' });
|
|
171
177
|
throw new Error('FILE_CONTENT_SEARCH_INVALID');
|
|
178
|
+
}
|
|
179
|
+
logNodeCommandFailure('rg', { exitCode: result.code, stderr: result.stderr });
|
|
172
180
|
throw new Error('CONTENT_SEARCH_COMMAND_FAILED');
|
|
173
181
|
}
|
|
174
182
|
const paths = new Map(batch.flatMap((file) => [
|
|
@@ -268,21 +276,42 @@ function resolveRipgrep() {
|
|
|
268
276
|
}
|
|
269
277
|
async function runCommand(file, args, cwd, deadline, stopAfterResultLimit = false) {
|
|
270
278
|
return new Promise((resolve, reject) => {
|
|
279
|
+
const startedAt = performance.now();
|
|
271
280
|
const child = spawn(file, args, {
|
|
272
281
|
cwd,
|
|
273
282
|
windowsHide: true,
|
|
274
|
-
stdio: ['ignore', 'pipe', '
|
|
283
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
275
284
|
});
|
|
276
285
|
const chunks = [];
|
|
286
|
+
const errors = [];
|
|
277
287
|
let pending = Buffer.alloc(0);
|
|
278
288
|
let bytes = 0;
|
|
289
|
+
let errorBytes = 0;
|
|
279
290
|
let matches = 0;
|
|
280
291
|
let stopped = false;
|
|
281
|
-
|
|
292
|
+
let failed = false;
|
|
293
|
+
let timedOut = false;
|
|
294
|
+
const timer = setTimeout(() => {
|
|
295
|
+
timedOut = true;
|
|
296
|
+
child.kill();
|
|
297
|
+
}, Math.max(1, deadline - Date.now()));
|
|
282
298
|
child.once('error', (error) => {
|
|
283
299
|
clearTimeout(timer);
|
|
300
|
+
failed = true;
|
|
301
|
+
logNodeCommandFailure('rg', {
|
|
302
|
+
reason: error.code ?? 'START_FAILED',
|
|
303
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
304
|
+
});
|
|
284
305
|
reject(error);
|
|
285
306
|
});
|
|
307
|
+
child.stderr.on('data', (chunk) => {
|
|
308
|
+
const remaining = Math.max(0, 1_000 - errorBytes);
|
|
309
|
+
if (remaining === 0)
|
|
310
|
+
return;
|
|
311
|
+
const retained = chunk.subarray(0, remaining);
|
|
312
|
+
errors.push(retained);
|
|
313
|
+
errorBytes += retained.length;
|
|
314
|
+
});
|
|
286
315
|
child.stdout.on('data', (chunk) => {
|
|
287
316
|
bytes += chunk.length;
|
|
288
317
|
if (bytes > OUTPUT_BYTE_LIMIT) {
|
|
@@ -311,14 +340,23 @@ async function runCommand(file, args, cwd, deadline, stopAfterResultLimit = fals
|
|
|
311
340
|
});
|
|
312
341
|
child.once('close', (code, signal) => {
|
|
313
342
|
clearTimeout(timer);
|
|
343
|
+
if (failed)
|
|
344
|
+
return;
|
|
345
|
+
const stderr = Buffer.concat(errors).toString('utf8');
|
|
314
346
|
if (stopped)
|
|
315
|
-
resolve({ code: 0, stdout: Buffer.concat(chunks) });
|
|
316
|
-
else if (signal !== null || bytes > OUTPUT_BYTE_LIMIT)
|
|
347
|
+
resolve({ code: 0, stdout: Buffer.concat(chunks), stderr });
|
|
348
|
+
else if (timedOut || signal !== null || bytes > OUTPUT_BYTE_LIMIT) {
|
|
349
|
+
logNodeCommandFailure('rg', {
|
|
350
|
+
reason: timedOut ? 'TIMEOUT' : bytes > OUTPUT_BYTE_LIMIT ? 'OUTPUT_LIMIT' : 'SIGNAL',
|
|
351
|
+
stderr,
|
|
352
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
353
|
+
});
|
|
317
354
|
reject(new Error('CONTENT_SEARCH_COMMAND_LIMIT'));
|
|
355
|
+
}
|
|
318
356
|
else {
|
|
319
357
|
if (stopAfterResultLimit && pending.length > 0)
|
|
320
358
|
chunks.push(pending);
|
|
321
|
-
resolve({ code: code ?? -1, stdout: Buffer.concat(chunks) });
|
|
359
|
+
resolve({ code: code ?? -1, stdout: Buffer.concat(chunks), stderr });
|
|
322
360
|
}
|
|
323
361
|
});
|
|
324
362
|
});
|
package/dist/workspace.js
CHANGED
|
@@ -4,6 +4,7 @@ import { homedir, platform as hostPlatform } from 'node:os';
|
|
|
4
4
|
import { basename, dirname, isAbsolute, posix, relative, resolve, sep, win32 } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { minimatch } from 'minimatch';
|
|
7
|
+
import { logNodeCommandFailure } from './operational.js';
|
|
7
8
|
import { terminateProcessTree } from './process-tree.js';
|
|
8
9
|
import { gitOperationEnvironment } from './service/git-operation-service.js';
|
|
9
10
|
const GIT_TIMEOUT_MS = 5_000;
|
|
@@ -1116,7 +1117,7 @@ export async function listGitWorktrees(workspacePath) {
|
|
|
1116
1117
|
if (nulDelimited.code === 0)
|
|
1117
1118
|
return parseGitWorktreeList(workspacePath, nulDelimited, true);
|
|
1118
1119
|
if (!canRetryGitWorktreeListWithoutNullOutput(nulDelimited))
|
|
1119
|
-
throw
|
|
1120
|
+
throw gitCommandFailure(nulDelimited.code, nulDelimited.stderr, 'worktree');
|
|
1120
1121
|
const lineDelimited = await runReadOnlyGit(workspacePath, ['worktree', 'list', '--porcelain'], options);
|
|
1121
1122
|
return parseGitWorktreeList(workspacePath, lineDelimited, false);
|
|
1122
1123
|
}
|
|
@@ -1570,7 +1571,7 @@ export async function readGitHistoryFileContentRange(repository, commit, request
|
|
|
1570
1571
|
timeoutMs: GIT_HISTORY_TIMEOUT_MS
|
|
1571
1572
|
});
|
|
1572
1573
|
if (result.code !== 0)
|
|
1573
|
-
throw
|
|
1574
|
+
throw gitCommandFailure(result.code, result.stderr, 'cat-file');
|
|
1574
1575
|
const content = result.output.subarray(offset, Math.min(size, offset + limit));
|
|
1575
1576
|
const nextOffset = offset + content.length;
|
|
1576
1577
|
return {
|
|
@@ -1633,7 +1634,7 @@ export async function openGitHistoryFileContent(repository, commit, requestedPat
|
|
|
1633
1634
|
yield new Uint8Array(chunk);
|
|
1634
1635
|
const result = await exit;
|
|
1635
1636
|
if (result.code !== 0)
|
|
1636
|
-
throw
|
|
1637
|
+
throw gitCommandFailure(result.code, result.stderr, 'cat-file');
|
|
1637
1638
|
}
|
|
1638
1639
|
finally {
|
|
1639
1640
|
if (!closed)
|
|
@@ -1686,7 +1687,7 @@ async function readCurrentChangeGitDiff(workspacePath, path) {
|
|
|
1686
1687
|
path
|
|
1687
1688
|
]);
|
|
1688
1689
|
if (result.code !== 0 && result.code !== 1)
|
|
1689
|
-
throw
|
|
1690
|
+
throw gitCommandFailure(result.code, result.stderr, 'diff');
|
|
1690
1691
|
return result;
|
|
1691
1692
|
}
|
|
1692
1693
|
return runReadOnlyGit(workspacePath, [
|
|
@@ -1816,12 +1817,25 @@ function gitChange(status) {
|
|
|
1816
1817
|
export class GitCommandError extends Error {
|
|
1817
1818
|
code;
|
|
1818
1819
|
stderr;
|
|
1820
|
+
publicDetails;
|
|
1821
|
+
publicParameters;
|
|
1819
1822
|
constructor(code, stderr) {
|
|
1820
1823
|
super('GIT_COMMAND_FAILED');
|
|
1821
1824
|
this.code = code;
|
|
1822
1825
|
this.stderr = stderr;
|
|
1826
|
+
this.publicParameters = { gitExitCode: code };
|
|
1827
|
+
this.publicDetails = stderr.trim().slice(0, 1_000) || `Git exited with status ${code}.`;
|
|
1823
1828
|
}
|
|
1824
1829
|
}
|
|
1830
|
+
function gitCommandFailure(code, stderr, subcommand, durationMs, reason) {
|
|
1831
|
+
logNodeCommandFailure(`git ${subcommand}`, {
|
|
1832
|
+
exitCode: code,
|
|
1833
|
+
stderr,
|
|
1834
|
+
...(durationMs === undefined ? {} : { durationMs }),
|
|
1835
|
+
...(reason === undefined ? {} : { reason })
|
|
1836
|
+
});
|
|
1837
|
+
return new GitCommandError(code, stderr);
|
|
1838
|
+
}
|
|
1825
1839
|
async function runReadOnlyGit(workspacePath, args, options = {}) {
|
|
1826
1840
|
return runGit(workspacePath, args, { ...options, readOnly: true });
|
|
1827
1841
|
}
|
|
@@ -1829,14 +1843,16 @@ async function runReadOnlyGitAllowFailure(workspacePath, args, options = {}) {
|
|
|
1829
1843
|
return runGitAllowFailure(workspacePath, args, { ...options, readOnly: true });
|
|
1830
1844
|
}
|
|
1831
1845
|
async function runGit(workspacePath, args, options = {}) {
|
|
1846
|
+
const startedAt = performance.now();
|
|
1832
1847
|
const result = await runGitAllowFailure(workspacePath, args, options);
|
|
1833
1848
|
if (result.code !== 0) {
|
|
1834
|
-
throw
|
|
1849
|
+
throw gitCommandFailure(result.code, result.stderr, args[0] ?? 'unknown', Math.round(performance.now() - startedAt));
|
|
1835
1850
|
}
|
|
1836
1851
|
return result;
|
|
1837
1852
|
}
|
|
1838
1853
|
async function runGitAllowFailure(workspacePath, args, options = {}) {
|
|
1839
1854
|
return new Promise((resolve, reject) => {
|
|
1855
|
+
const startedAt = performance.now();
|
|
1840
1856
|
const outputLimit = options.outputLimit ?? GIT_OUTPUT_LIMIT;
|
|
1841
1857
|
const child = spawn('git', [...args], {
|
|
1842
1858
|
cwd: workspacePath,
|
|
@@ -1854,7 +1870,9 @@ async function runGitAllowFailure(workspacePath, args, options = {}) {
|
|
|
1854
1870
|
let size = 0;
|
|
1855
1871
|
let errorSize = 0;
|
|
1856
1872
|
let truncated = false;
|
|
1873
|
+
let timedOut = false;
|
|
1857
1874
|
const timer = setTimeout(() => {
|
|
1875
|
+
timedOut = true;
|
|
1858
1876
|
if (process.platform === 'win32') {
|
|
1859
1877
|
const pid = child.pid;
|
|
1860
1878
|
if (pid === undefined) {
|
|
@@ -1895,16 +1913,24 @@ async function runGitAllowFailure(workspacePath, args, options = {}) {
|
|
|
1895
1913
|
});
|
|
1896
1914
|
child.once('error', (error) => {
|
|
1897
1915
|
clearTimeout(timer);
|
|
1916
|
+
logNodeCommandFailure(`git ${args[0] ?? 'unknown'}`, {
|
|
1917
|
+
reason: error.code ?? 'START_FAILED'
|
|
1918
|
+
});
|
|
1898
1919
|
reject(error);
|
|
1899
1920
|
});
|
|
1900
1921
|
child.once('close', (code) => {
|
|
1901
1922
|
clearTimeout(timer);
|
|
1923
|
+
const stderr = Buffer.concat(errors).toString('utf8');
|
|
1924
|
+
if (timedOut) {
|
|
1925
|
+
reject(gitCommandFailure(code === null || code === 0 ? 1 : code, `Git command timed out after ${options.timeoutMs ?? GIT_TIMEOUT_MS} ms.${stderr ? `\n${stderr}` : ''}`, args[0] ?? 'unknown', Math.round(performance.now() - startedAt), 'TIMEOUT'));
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1902
1928
|
const output = Buffer.concat(chunks);
|
|
1903
1929
|
resolve({
|
|
1904
1930
|
code: code ?? 1,
|
|
1905
1931
|
output,
|
|
1906
1932
|
text: output.toString('utf8'),
|
|
1907
|
-
stderr
|
|
1933
|
+
stderr,
|
|
1908
1934
|
truncated
|
|
1909
1935
|
});
|
|
1910
1936
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.108",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"npm": "11.12.1",
|
|
31
31
|
"ws": "^8.21.3",
|
|
32
32
|
"zod": "4.4.3",
|
|
33
|
-
"@myagentroam/agent": "0.9.
|
|
34
|
-
"@myagentroam/
|
|
35
|
-
"@myagentroam/
|
|
33
|
+
"@myagentroam/agent": "0.9.108",
|
|
34
|
+
"@myagentroam/protocol": "0.9.108",
|
|
35
|
+
"@myagentroam/code-navigation": "0.9.108"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/ws": "^8.18.1"
|