@myagentroam/node 0.9.94 → 0.9.96
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/computer-use-runtime-version.js +1 -0
- package/dist/computer-use-runtime.js +330 -0
- package/dist/config.js +35 -1
- package/dist/connector.js +49 -7
- package/dist/ctags-binary.js +18 -0
- package/dist/runner/mar-agent/computer-use.js +750 -0
- package/dist/runner/mar-agent/managed-run-controller.js +55 -7
- package/dist/service/computer-use-enablement-service.js +172 -0
- package/dist/service/continuous-run-coordinator.js +1 -0
- package/dist/service/node-connection-lifecycle-service.js +1 -0
- package/dist/service/session-lifecycle-service.js +1 -0
- package/dist/service/session-message-service.js +1 -0
- package/dist/service/workspace-code-navigation-service.js +1219 -0
- package/dist/service/workspace-content-search-service.js +55 -25
- package/dist/service/workspace-file-service.js +1 -1
- package/dist/service/workspace-workbench-service.js +28 -1
- package/dist/workspace.js +70 -6
- package/package.json +7 -4
- package/vendor/ctags/NOTICE +10 -0
- package/vendor/ctags/licenses/COPYING +340 -0
- package/vendor/ctags/licenses/COPYING.MinGW-w64-runtime.txt +240 -0
- package/vendor/ctags/licenses/LICENSE.janssen +19 -0
- package/vendor/ctags/licenses/LICENSE.libyaml +19 -0
- package/vendor/ctags/linux-x64/ctags +0 -0
- package/vendor/ctags/manifest.json +25 -0
- package/vendor/ctags/source/universal-ctags-6.2.1.tar.gz +0 -0
- package/vendor/ctags/win32-x64/ctags.exe +0 -0
|
@@ -6,7 +6,7 @@ import { bundledRipgrepPath } from '../ripgrep-binary.js';
|
|
|
6
6
|
const RESULT_LIMIT = 200;
|
|
7
7
|
const PER_FILE_LIMIT = 20;
|
|
8
8
|
const FILE_BYTE_LIMIT = 2 * 1024 * 1024;
|
|
9
|
-
const SCAN_BYTE_LIMIT =
|
|
9
|
+
const SCAN_BYTE_LIMIT = 64 * 1024 * 1024;
|
|
10
10
|
const OUTPUT_BYTE_LIMIT = 2 * 1024 * 1024;
|
|
11
11
|
const TIMEOUT_MS = 5_000;
|
|
12
12
|
const WINDOWS_ARGUMENT_BATCH_BYTES = 24 * 1024;
|
|
@@ -53,29 +53,38 @@ export class WorkspaceContentSearchService {
|
|
|
53
53
|
search(workspace, input) {
|
|
54
54
|
if (typeof input.query !== 'string' || input.query.length < 1 || input.query.length > 256)
|
|
55
55
|
throw new Error('FILE_CONTENT_SEARCH_INVALID');
|
|
56
|
-
|
|
56
|
+
if (input.regex !== undefined && typeof input.regex !== 'boolean')
|
|
57
|
+
throw new Error('FILE_CONTENT_SEARCH_INVALID');
|
|
58
|
+
const search = { query: input.query, regex: input.regex === true };
|
|
59
|
+
const queryKey = search.regex ? search.query : search.query.toLocaleLowerCase();
|
|
60
|
+
const key = `${workspace.id}\0${search.regex}\0${queryKey}`;
|
|
57
61
|
const existing = this.pending.get(key);
|
|
58
62
|
if (existing !== undefined)
|
|
59
63
|
return existing;
|
|
60
|
-
const task = this.perform(workspace,
|
|
64
|
+
const task = this.perform(workspace, search).finally(() => this.pending.delete(key));
|
|
61
65
|
this.pending.set(key, task);
|
|
62
66
|
return task;
|
|
63
67
|
}
|
|
64
|
-
async perform(workspace,
|
|
68
|
+
async perform(workspace, search) {
|
|
65
69
|
const deadline = Date.now() + TIMEOUT_MS;
|
|
66
70
|
const release = await acquireSearch(deadline);
|
|
67
71
|
try {
|
|
72
|
+
if (search.regex && this.ripgrep === null)
|
|
73
|
+
throw new Error('RIPGREP_RESOURCE_UNAVAILABLE');
|
|
68
74
|
const prepared = await this.candidates(workspace, deadline);
|
|
69
|
-
if (prepared.files.length === 0)
|
|
75
|
+
if (prepared.files.length === 0) {
|
|
76
|
+
if (search.regex)
|
|
77
|
+
await this.validateNativeRegex(workspace.path, search, deadline);
|
|
70
78
|
return {
|
|
71
79
|
matches: [],
|
|
72
80
|
engine: 'NODE',
|
|
73
81
|
truncated: prepared.truncated || Date.now() >= deadline,
|
|
74
82
|
skippedFiles: prepared.skippedFiles
|
|
75
83
|
};
|
|
84
|
+
}
|
|
76
85
|
if (this.ripgrep !== null) {
|
|
77
86
|
try {
|
|
78
|
-
const matches = await this.nativeSearch('RIPGREP', workspace.path, prepared.files,
|
|
87
|
+
const matches = await this.nativeSearch('RIPGREP', workspace.path, prepared.files, search, deadline);
|
|
79
88
|
return {
|
|
80
89
|
matches: matches.slice(0, RESULT_LIMIT),
|
|
81
90
|
engine: 'RIPGREP',
|
|
@@ -83,11 +92,13 @@ export class WorkspaceContentSearchService {
|
|
|
83
92
|
skippedFiles: prepared.skippedFiles
|
|
84
93
|
};
|
|
85
94
|
}
|
|
86
|
-
catch {
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (search.regex)
|
|
97
|
+
throw error;
|
|
87
98
|
// Missing, incompatible or failed bundled resources safely fall through.
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
|
-
const fallback = await nodeSearch(prepared.files, query, deadline);
|
|
101
|
+
const fallback = await nodeSearch(prepared.files, search.query, deadline);
|
|
91
102
|
return {
|
|
92
103
|
matches: fallback.matches.slice(0, RESULT_LIMIT),
|
|
93
104
|
engine: 'NODE',
|
|
@@ -99,6 +110,14 @@ export class WorkspaceContentSearchService {
|
|
|
99
110
|
release();
|
|
100
111
|
}
|
|
101
112
|
}
|
|
113
|
+
async validateNativeRegex(root, search, deadline) {
|
|
114
|
+
const command = commandFor('RIPGREP', [], search, this.ripgrep ?? undefined);
|
|
115
|
+
const result = await runCommand(command.file, [...command.args, '-'], root, deadline);
|
|
116
|
+
if (result.code === 2)
|
|
117
|
+
throw new Error('FILE_CONTENT_SEARCH_INVALID');
|
|
118
|
+
if (!command.validCodes.includes(result.code))
|
|
119
|
+
throw new Error('CONTENT_SEARCH_COMMAND_FAILED');
|
|
120
|
+
}
|
|
102
121
|
async candidates(workspace, deadline) {
|
|
103
122
|
const root = await realpath(workspace.path);
|
|
104
123
|
let candidates;
|
|
@@ -139,21 +158,24 @@ export class WorkspaceContentSearchService {
|
|
|
139
158
|
}
|
|
140
159
|
return { files, skippedFiles, truncated };
|
|
141
160
|
}
|
|
142
|
-
async nativeSearch(engine, root, files,
|
|
161
|
+
async nativeSearch(engine, root, files, search, deadline) {
|
|
143
162
|
const matches = [];
|
|
144
163
|
const argumentBatchBytes = this.platform === 'win32' ? WINDOWS_ARGUMENT_BATCH_BYTES : POSIX_ARGUMENT_BATCH_BYTES;
|
|
145
164
|
for (const batch of batches(files, argumentBatchBytes)) {
|
|
146
165
|
if (Date.now() >= deadline || matches.length > RESULT_LIMIT)
|
|
147
166
|
break;
|
|
148
|
-
const command = commandFor(engine, batch,
|
|
167
|
+
const command = commandFor(engine, batch, search, this.ripgrep ?? undefined);
|
|
149
168
|
const result = await runCommand(command.file, command.args, root, deadline, engine === 'RIPGREP');
|
|
150
|
-
if (!command.validCodes.includes(result.code))
|
|
169
|
+
if (!command.validCodes.includes(result.code)) {
|
|
170
|
+
if (search.regex && result.code === 2)
|
|
171
|
+
throw new Error('FILE_CONTENT_SEARCH_INVALID');
|
|
151
172
|
throw new Error('CONTENT_SEARCH_COMMAND_FAILED');
|
|
173
|
+
}
|
|
152
174
|
const paths = new Map(batch.flatMap((file) => [
|
|
153
175
|
[normalizeResultPath(file.path, this.platform), file.path],
|
|
154
176
|
[normalizeResultPath(file.absolutePath, this.platform), file.path]
|
|
155
177
|
]));
|
|
156
|
-
matches.push(...parseOutput(engine, result.stdout,
|
|
178
|
+
matches.push(...parseOutput(engine, result.stdout, search).map((match) => ({
|
|
157
179
|
...match,
|
|
158
180
|
path: paths.get(normalizeResultPath(match.path, this.platform)) ??
|
|
159
181
|
(() => {
|
|
@@ -213,7 +235,7 @@ function batches(files, maximumBytes) {
|
|
|
213
235
|
result.push(current);
|
|
214
236
|
return result;
|
|
215
237
|
}
|
|
216
|
-
function commandFor(engine, files,
|
|
238
|
+
function commandFor(engine, files, search, ripgrep) {
|
|
217
239
|
const paths = files.map((file) => file.path);
|
|
218
240
|
if (engine === 'RIPGREP')
|
|
219
241
|
return {
|
|
@@ -224,12 +246,12 @@ function commandFor(engine, files, query, ripgrep) {
|
|
|
224
246
|
args: [
|
|
225
247
|
'--json',
|
|
226
248
|
'--no-config',
|
|
227
|
-
'--fixed-strings',
|
|
228
249
|
'--ignore-case',
|
|
229
250
|
'--max-count',
|
|
230
251
|
String(PER_FILE_LIMIT),
|
|
252
|
+
...(search.regex ? [] : ['--fixed-strings']),
|
|
231
253
|
'--',
|
|
232
|
-
query,
|
|
254
|
+
search.query,
|
|
233
255
|
...paths
|
|
234
256
|
],
|
|
235
257
|
validCodes: [0, 1]
|
|
@@ -301,14 +323,14 @@ async function runCommand(file, args, cwd, deadline, stopAfterResultLimit = fals
|
|
|
301
323
|
});
|
|
302
324
|
});
|
|
303
325
|
}
|
|
304
|
-
function parseOutput(engine, output,
|
|
326
|
+
function parseOutput(engine, output, search) {
|
|
305
327
|
if (engine === 'RIPGREP')
|
|
306
|
-
return parseRipgrep(output,
|
|
328
|
+
return parseRipgrep(output, search);
|
|
307
329
|
if (engine === 'GREP')
|
|
308
|
-
return parseGrep(output,
|
|
309
|
-
return parsePowerShell(output,
|
|
330
|
+
return parseGrep(output, search);
|
|
331
|
+
return parsePowerShell(output, search);
|
|
310
332
|
}
|
|
311
|
-
function parseRipgrep(output,
|
|
333
|
+
function parseRipgrep(output, search) {
|
|
312
334
|
return output
|
|
313
335
|
.toString('utf8')
|
|
314
336
|
.split('\n')
|
|
@@ -320,7 +342,9 @@ function parseRipgrep(output, query) {
|
|
|
320
342
|
return [];
|
|
321
343
|
const data = value.data;
|
|
322
344
|
const preview = String(data?.lines?.text).replace(/[\r\n]+$/u, '');
|
|
323
|
-
const column =
|
|
345
|
+
const column = search.regex
|
|
346
|
+
? regexRipgrepIndex(preview, data?.submatches)
|
|
347
|
+
: lowerIndex(preview, search.query);
|
|
324
348
|
if (typeof data?.path?.text !== 'string' || !Number.isInteger(data.line_number) || column < 0)
|
|
325
349
|
throw new Error('CONTENT_SEARCH_OUTPUT_INVALID');
|
|
326
350
|
return [
|
|
@@ -333,7 +357,7 @@ function parseRipgrep(output, query) {
|
|
|
333
357
|
];
|
|
334
358
|
});
|
|
335
359
|
}
|
|
336
|
-
function parseGrep(output,
|
|
360
|
+
function parseGrep(output, search) {
|
|
337
361
|
const matches = [];
|
|
338
362
|
let offset = 0;
|
|
339
363
|
while (offset < output.length) {
|
|
@@ -345,7 +369,7 @@ function parseGrep(output, query) {
|
|
|
345
369
|
const record = output.subarray(separator + 1, end).toString('utf8');
|
|
346
370
|
const colon = record.indexOf(':');
|
|
347
371
|
const preview = record.slice(colon + 1).replace(/\r$/u, '');
|
|
348
|
-
const column = lowerIndex(preview, query);
|
|
372
|
+
const column = lowerIndex(preview, search.query);
|
|
349
373
|
if (colon < 1 || column < 0)
|
|
350
374
|
throw new Error('CONTENT_SEARCH_OUTPUT_INVALID');
|
|
351
375
|
matches.push({
|
|
@@ -358,7 +382,7 @@ function parseGrep(output, query) {
|
|
|
358
382
|
}
|
|
359
383
|
return matches;
|
|
360
384
|
}
|
|
361
|
-
function parsePowerShell(output,
|
|
385
|
+
function parsePowerShell(output, search) {
|
|
362
386
|
return output
|
|
363
387
|
.toString('utf8')
|
|
364
388
|
.split(/\r?\n/u)
|
|
@@ -370,7 +394,7 @@ function parsePowerShell(output, query) {
|
|
|
370
394
|
!Number.isInteger(match.line) ||
|
|
371
395
|
typeof match.preview !== 'string')
|
|
372
396
|
throw new Error('CONTENT_SEARCH_OUTPUT_INVALID');
|
|
373
|
-
const column = lowerIndex(match.preview, query);
|
|
397
|
+
const column = lowerIndex(match.preview, search.query);
|
|
374
398
|
if (column < 0)
|
|
375
399
|
throw new Error('CONTENT_SEARCH_OUTPUT_INVALID');
|
|
376
400
|
return [
|
|
@@ -445,6 +469,12 @@ async function nodeSearch(files, query, deadline) {
|
|
|
445
469
|
function lowerIndex(source, query) {
|
|
446
470
|
return source.toLocaleLowerCase().indexOf(query.toLocaleLowerCase());
|
|
447
471
|
}
|
|
472
|
+
function regexRipgrepIndex(preview, submatches) {
|
|
473
|
+
const start = submatches?.[0]?.start;
|
|
474
|
+
if (typeof start !== 'number' || !Number.isInteger(start) || start < 0)
|
|
475
|
+
return -1;
|
|
476
|
+
return Buffer.from(preview, 'utf8').subarray(0, start).toString('utf8').length;
|
|
477
|
+
}
|
|
448
478
|
function limitPerFile(matches) {
|
|
449
479
|
const counts = new Map();
|
|
450
480
|
return matches.filter((match) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tmpdir } from 'node:os';
|
|
2
2
|
import { isAbsolute, win32 } from 'node:path';
|
|
3
3
|
import { listWorkspaceFiles, mutateWorkspaceFile, normalizeFilePreviewPath, readAllowedFileContentRange, readWorkspaceFileContentRange, readAllowedTextFile, readWorkspaceTextFile, writeWorkspaceTextFile, WorkspaceFileIndex } from '../workspace.js';
|
|
4
|
-
const INDEX_CACHE_LIMIT =
|
|
4
|
+
const INDEX_CACHE_LIMIT = 32;
|
|
5
5
|
export class WorkspaceFileService {
|
|
6
6
|
state;
|
|
7
7
|
allowedRoots;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { listGitHistoryRefs, openGitHistoryFileContent, readGitHistory, readGitHistoryFileDiff, readGitHistoryFileContentRange, readGitHistoryFiles, readCurrentChangeDiff, readRestrictedDiff, resolveGitRepository, resolveGitRepositoryTarget, restoreCurrentChange } from '../workspace.js';
|
|
3
|
+
import { WorkspaceCodeNavigationService } from './workspace-code-navigation-service.js';
|
|
3
4
|
import { WorkspaceContentSearchService } from './workspace-content-search-service.js';
|
|
4
5
|
import { WorkspaceWatchController } from './workspace-watch-controller.js';
|
|
5
6
|
import { WorkspaceWatchSnapshotService } from './workspace-watch-snapshot-service.js';
|
|
6
7
|
export class WorkspaceWorkbenchService {
|
|
7
8
|
options;
|
|
8
9
|
contentSearch;
|
|
10
|
+
codeNavigation;
|
|
9
11
|
snapshots;
|
|
10
12
|
watchController;
|
|
11
13
|
constructor(options) {
|
|
12
14
|
this.options = options;
|
|
13
15
|
this.contentSearch = new WorkspaceContentSearchService(options.files);
|
|
16
|
+
this.codeNavigation = new WorkspaceCodeNavigationService({
|
|
17
|
+
files: options.files,
|
|
18
|
+
nodeId: options.nodeId,
|
|
19
|
+
searchContent: (workspace, query) => this.contentSearch.search(workspace, { query })
|
|
20
|
+
});
|
|
14
21
|
this.snapshots = new WorkspaceWatchSnapshotService(options);
|
|
15
22
|
this.watchController = new WorkspaceWatchController({
|
|
16
23
|
requireWorkspace: options.requireWorkspace,
|
|
@@ -30,6 +37,8 @@ export class WorkspaceWorkbenchService {
|
|
|
30
37
|
'workspace.files.search': (data) => this.searchFiles(record(data)),
|
|
31
38
|
'workspace.files.mutate': (data) => this.mutateFile(record(data)),
|
|
32
39
|
'workspace.content.search': (data) => this.searchContent(record(data)),
|
|
40
|
+
'workspace.symbol.prepare': (data) => this.prepareSymbolIndex(record(data)),
|
|
41
|
+
'workspace.symbol.definition': (data) => this.symbolDefinition(record(data)),
|
|
33
42
|
'workspace.files.upload.preflight': (data) => this.preflight(record(data)),
|
|
34
43
|
'workspace.files.upload.create': (data) => this.createUpload(record(data)),
|
|
35
44
|
'workspace.files.upload.chunk': (data) => this.writeUpload(record(data)),
|
|
@@ -56,9 +65,19 @@ export class WorkspaceWorkbenchService {
|
|
|
56
65
|
ensureTopicSnapshot(workspaceId, topic) {
|
|
57
66
|
return this.snapshots.ensureSnapshot(workspaceId, topic);
|
|
58
67
|
}
|
|
59
|
-
|
|
68
|
+
/** Node-owned file mutations must not reuse a generation's old candidate list. */
|
|
69
|
+
invalidateWorkspaceFiles(workspaceId) {
|
|
70
|
+
this.codeNavigation.invalidate(workspaceId);
|
|
71
|
+
}
|
|
72
|
+
/** Releases page-watch state without discarding the durable code index. */
|
|
73
|
+
releaseWorkspace(workspaceId) {
|
|
60
74
|
this.snapshots.clear(workspaceId);
|
|
61
75
|
}
|
|
76
|
+
/** Removes all Workspace state after the Workspace itself is unregistered. */
|
|
77
|
+
clearWorkspace(workspaceId) {
|
|
78
|
+
this.releaseWorkspace(workspaceId);
|
|
79
|
+
this.codeNavigation.forget(workspaceId);
|
|
80
|
+
}
|
|
62
81
|
clear() {
|
|
63
82
|
this.snapshots.clearAll();
|
|
64
83
|
}
|
|
@@ -94,6 +113,14 @@ export class WorkspaceWorkbenchService {
|
|
|
94
113
|
const workspace = this.options.requireWorkspace(input.workspaceId);
|
|
95
114
|
return { search: await this.contentSearch.search(workspace, input) };
|
|
96
115
|
}
|
|
116
|
+
async symbolDefinition(input) {
|
|
117
|
+
const workspace = this.options.requireWorkspace(input.workspaceId);
|
|
118
|
+
return { definition: await this.codeNavigation.definition(workspace, input) };
|
|
119
|
+
}
|
|
120
|
+
async prepareSymbolIndex(input) {
|
|
121
|
+
const workspace = this.options.requireWorkspace(input.workspaceId);
|
|
122
|
+
return { preparation: await this.codeNavigation.prepare(workspace, input) };
|
|
123
|
+
}
|
|
97
124
|
async preflight(input) {
|
|
98
125
|
return {
|
|
99
126
|
uploads: await this.options.uploads.preflight(this.options.requireWorkspace(input.workspaceId), input)
|
package/dist/workspace.js
CHANGED
|
@@ -90,12 +90,51 @@ export class WorkspaceFileIndex {
|
|
|
90
90
|
expiryTimer;
|
|
91
91
|
refreshedAt = 0;
|
|
92
92
|
refreshAttemptedAt = 0;
|
|
93
|
+
lifecycleGeneration = 0;
|
|
93
94
|
constructor(workspaceRoot, limits = {}) {
|
|
94
95
|
this.workspaceRoot = workspaceRoot;
|
|
95
96
|
this.limits = limits;
|
|
96
97
|
}
|
|
97
|
-
async contentCandidates() {
|
|
98
|
-
await this.ensureLoaded();
|
|
98
|
+
async contentCandidates(options = {}) {
|
|
99
|
+
await this.ensureLoaded(options.requireFresh === true);
|
|
100
|
+
return this.currentContentCandidates();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns the current candidate snapshot without synchronously rebuilding an
|
|
104
|
+
* empty or explicitly invalidated index. A stale loaded snapshot schedules
|
|
105
|
+
* its normal single-flight refresh before it is returned.
|
|
106
|
+
*/
|
|
107
|
+
peekContentCandidates() {
|
|
108
|
+
if (this.stale || this.root === undefined)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (Date.now() - this.refreshAttemptedAt >=
|
|
111
|
+
(this.limits.freshnessTtlMs ?? FILE_INDEX_FRESHNESS_TTL_MS))
|
|
112
|
+
this.refreshInBackground();
|
|
113
|
+
return this.currentContentCandidates();
|
|
114
|
+
}
|
|
115
|
+
/** Starts a single candidate refresh without making the caller await it. */
|
|
116
|
+
refreshContentCandidates(requireFresh = false) {
|
|
117
|
+
if (this.loading !== undefined)
|
|
118
|
+
return this.loading;
|
|
119
|
+
if (this.stale || this.root === undefined) {
|
|
120
|
+
this.loading = this.rebuild().finally(() => {
|
|
121
|
+
this.loading = undefined;
|
|
122
|
+
});
|
|
123
|
+
return this.loading;
|
|
124
|
+
}
|
|
125
|
+
if (requireFresh) {
|
|
126
|
+
this.refreshAttemptedAt = Date.now();
|
|
127
|
+
this.loading = this.rebuild().finally(() => {
|
|
128
|
+
this.loading = undefined;
|
|
129
|
+
});
|
|
130
|
+
return this.loading;
|
|
131
|
+
}
|
|
132
|
+
if (Date.now() - this.refreshAttemptedAt >=
|
|
133
|
+
(this.limits.freshnessTtlMs ?? FILE_INDEX_FRESHNESS_TTL_MS))
|
|
134
|
+
this.refreshInBackground();
|
|
135
|
+
return this.loading ?? Promise.resolve();
|
|
136
|
+
}
|
|
137
|
+
currentContentCandidates() {
|
|
99
138
|
this.touch();
|
|
100
139
|
return {
|
|
101
140
|
entries: this.entries.filter((entry) => entry.kind === 'FILE'),
|
|
@@ -103,6 +142,7 @@ export class WorkspaceFileIndex {
|
|
|
103
142
|
};
|
|
104
143
|
}
|
|
105
144
|
dispose() {
|
|
145
|
+
this.lifecycleGeneration += 1;
|
|
106
146
|
if (this.expiryTimer !== undefined)
|
|
107
147
|
clearTimeout(this.expiryTimer);
|
|
108
148
|
this.expiryTimer = undefined;
|
|
@@ -150,11 +190,22 @@ export class WorkspaceFileIndex {
|
|
|
150
190
|
truncated: next !== undefined || this.truncated
|
|
151
191
|
};
|
|
152
192
|
}
|
|
153
|
-
async ensureLoaded() {
|
|
193
|
+
async ensureLoaded(requireFresh = false) {
|
|
154
194
|
if (!this.stale && this.root !== undefined) {
|
|
155
195
|
if (Date.now() - this.refreshAttemptedAt >=
|
|
156
|
-
(this.limits.freshnessTtlMs ?? FILE_INDEX_FRESHNESS_TTL_MS))
|
|
157
|
-
|
|
196
|
+
(this.limits.freshnessTtlMs ?? FILE_INDEX_FRESHNESS_TTL_MS)) {
|
|
197
|
+
if (requireFresh) {
|
|
198
|
+
if (this.loading === undefined) {
|
|
199
|
+
this.refreshAttemptedAt = Date.now();
|
|
200
|
+
this.loading = this.rebuild().finally(() => {
|
|
201
|
+
this.loading = undefined;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
await this.loading;
|
|
205
|
+
}
|
|
206
|
+
else
|
|
207
|
+
this.refreshInBackground();
|
|
208
|
+
}
|
|
158
209
|
return;
|
|
159
210
|
}
|
|
160
211
|
if (this.loading === undefined) {
|
|
@@ -171,14 +222,21 @@ export class WorkspaceFileIndex {
|
|
|
171
222
|
this.expiryTimer.unref();
|
|
172
223
|
}
|
|
173
224
|
async rebuild() {
|
|
225
|
+
const generation = this.lifecycleGeneration;
|
|
174
226
|
const root = await resolveRegisteredWorkspaceRoot(this.workspaceRoot);
|
|
227
|
+
if (generation !== this.lifecycleGeneration)
|
|
228
|
+
return;
|
|
175
229
|
const excludes = [...DEFAULT_QUICK_OPEN_EXCLUDES, ...(await workspaceExcludePatterns(root))];
|
|
230
|
+
if (generation !== this.lifecycleGeneration)
|
|
231
|
+
return;
|
|
176
232
|
const indexed = [];
|
|
177
233
|
const entryLimit = this.limits.entryLimit ?? FILE_INDEX_ENTRY_LIMIT;
|
|
178
234
|
let truncated = false;
|
|
179
235
|
const directories = ['.'];
|
|
180
236
|
let directoryCursor = 0;
|
|
181
|
-
while (
|
|
237
|
+
while (generation === this.lifecycleGeneration &&
|
|
238
|
+
directoryCursor < directories.length &&
|
|
239
|
+
!truncated) {
|
|
182
240
|
const relativeDirectory = directories[directoryCursor];
|
|
183
241
|
directoryCursor += 1;
|
|
184
242
|
const directory = await resolveWorkspaceEntryFromCanonicalRoot(root, relativeDirectory, true).catch(() => undefined);
|
|
@@ -191,6 +249,8 @@ export class WorkspaceFileIndex {
|
|
|
191
249
|
const appendPending = async () => {
|
|
192
250
|
const resolvedEntries = await Promise.all(pending);
|
|
193
251
|
pending = [];
|
|
252
|
+
if (generation !== this.lifecycleGeneration)
|
|
253
|
+
return;
|
|
194
254
|
for (const entry of resolvedEntries) {
|
|
195
255
|
if (entry === undefined)
|
|
196
256
|
continue;
|
|
@@ -208,6 +268,8 @@ export class WorkspaceFileIndex {
|
|
|
208
268
|
}
|
|
209
269
|
};
|
|
210
270
|
for await (const child of stream) {
|
|
271
|
+
if (generation !== this.lifecycleGeneration)
|
|
272
|
+
return;
|
|
211
273
|
const candidatePath = relativeDirectory === '.'
|
|
212
274
|
? child.name
|
|
213
275
|
: posix.join(relativeDirectory.replaceAll('\\', '/'), child.name);
|
|
@@ -224,6 +286,8 @@ export class WorkspaceFileIndex {
|
|
|
224
286
|
if (!truncated && pending.length > 0)
|
|
225
287
|
await appendPending();
|
|
226
288
|
}
|
|
289
|
+
if (generation !== this.lifecycleGeneration)
|
|
290
|
+
return;
|
|
227
291
|
this.root = root;
|
|
228
292
|
this.entries = indexed;
|
|
229
293
|
this.truncated = truncated;
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.96",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
|
-
"license": "Apache-2.0",
|
|
5
|
+
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist/**/*.js",
|
|
9
9
|
"vendor/ripgrep/**/*",
|
|
10
|
+
"vendor/ctags/**/*",
|
|
10
11
|
"LICENSE",
|
|
11
12
|
"NOTICE"
|
|
12
13
|
],
|
|
@@ -26,16 +27,18 @@
|
|
|
26
27
|
"minimatch": "^10.2.6",
|
|
27
28
|
"node-datachannel": "0.33.4",
|
|
28
29
|
"node-pty": "1.1.0",
|
|
30
|
+
"npm": "11.12.1",
|
|
29
31
|
"ws": "^8.21.3",
|
|
30
32
|
"zod": "4.4.3",
|
|
31
|
-
"@myagentroam/agent": "0.9.
|
|
32
|
-
"@myagentroam/protocol": "0.9.
|
|
33
|
+
"@myagentroam/agent": "0.9.96",
|
|
34
|
+
"@myagentroam/protocol": "0.9.96"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/ws": "^8.18.1"
|
|
36
38
|
},
|
|
37
39
|
"scripts": {
|
|
38
40
|
"build": "node ../scripts/clean-build-output.mjs dist && tsc -p tsconfig.json",
|
|
41
|
+
"stage:computer-use-runtime": "node ../scripts/stage-computer-use-runtime.mjs",
|
|
39
42
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
|
|
40
43
|
"dev": "tsx watch src/main.ts",
|
|
41
44
|
"start": "node dist/main.js",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
This directory distributes Universal Ctags 6.2.1 as a separate executable work.
|
|
2
|
+
|
|
3
|
+
Universal Ctags is licensed under GPL-2.0-or-later. Its complete corresponding
|
|
4
|
+
source is included at source/universal-ctags-6.2.1.tar.gz. The Linux x64 program
|
|
5
|
+
was built without source changes from that archive using configure --disable-xml
|
|
6
|
+
with optional pkg-config dependencies unavailable. The Windows x64 program is
|
|
7
|
+
the upstream universal-ctags/ctags-win32 v6.2.1 ctags-v6.2.1-x64.zip executable.
|
|
8
|
+
|
|
9
|
+
The Windows upstream package also distributes the MinGW-w64 runtime, jansson,
|
|
10
|
+
and libyaml license texts copied into licenses/.
|