@myagentroam/node 0.9.4 → 0.9.5
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.d.ts +1 -0
- package/dist/claude-agent-sdk.js +9 -0
- package/dist/codex-app-server.d.ts +1 -0
- package/dist/codex-app-server.js +4 -2
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +16 -11
- package/dist/native-session-history.d.ts +3 -2
- package/dist/native-session-history.js +199 -30
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +1 -0
- package/dist/runner/abstract-runner.d.ts +11 -2
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +3 -0
- package/dist/runner/claude-code-runner.d.ts +2 -1
- package/dist/runner/claude-code-runner.js +4 -0
- package/dist/runner/codex/managed-run-controller.js +6 -0
- package/dist/runner/codex-runner.d.ts +2 -1
- package/dist/runner/codex-runner.js +11 -1
- package/dist/runner/opencode/managed-run-controller.js +4 -0
- package/dist/runner/opencode-runner.d.ts +10 -3
- package/dist/runner/opencode-runner.js +87 -14
- package/dist/runner/runner-registry.js +16 -1
- package/dist/runtime-command-detector.js +1 -6
- package/dist/runtime-state.js +1 -0
- package/dist/service/conversation-history-service.d.ts +5 -4
- package/dist/service/conversation-history-service.js +110 -52
- package/dist/service/external-session-resume-service.d.ts +2 -0
- package/dist/service/external-session-resume-service.js +45 -34
- package/dist/service/native-session-projection-service.d.ts +1 -0
- package/dist/service/native-session-projection-service.js +4 -0
- package/dist/service/native-session-watch-service.d.ts +18 -0
- package/dist/service/native-session-watch-service.js +232 -34
- package/dist/service/run-workbench-service.d.ts +2 -4
- package/dist/service/run-workbench-service.js +2 -7
- package/dist/service/session-command-service.js +1 -1
- package/dist/service/session-identity-service.d.ts +1 -1
- package/dist/service/session-identity-service.js +1 -1
- package/dist/service/session-lifecycle-service.js +3 -0
- package/dist/service/session-message-service.d.ts +0 -1
- package/dist/service/session-message-service.js +15 -23
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-domain-state.d.ts +3 -0
- package/dist/service/workspace-domain-state.js +1 -0
- package/dist/service/workspace-file-service.d.ts +1 -0
- package/dist/service/workspace-file-service.js +14 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +17 -0
- package/dist/service/workspace-queue-workbench-service.js +66 -6
- package/dist/service/workspace-watch-service.d.ts +7 -2
- package/dist/service/workspace-watch-service.js +32 -6
- package/dist/service/workspace-workbench-service.d.ts +14 -0
- package/dist/service/workspace-workbench-service.js +215 -16
- package/dist/util/personal-instructions.d.ts +2 -0
- package/dist/util/personal-instructions.js +31 -0
- package/dist/util/runner-native-session-parsers.d.ts +1 -0
- package/dist/util/runner-native-session-parsers.js +8 -1
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +65 -42
- package/package.json +2 -2
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { listGitHistoryRefs, readGitHistory, readGitHistoryFileDiff, readGitHistoryFiles, readCurrentChangeDiff, readRestrictedDiff, resolveGitRepository, restoreCurrentChange } from '../workspace.js';
|
|
1
|
+
import { listGitHistoryRefs, readGitHistory, readGitHistoryFileDiff, readGitHistoryFileContentRange, readGitHistoryFiles, readCurrentChangeDiff, readRestrictedDiff, resolveGitRepository, restoreCurrentChange } from '../workspace.js';
|
|
2
2
|
import { WorkspaceContentSearchService } from './workspace-content-search-service.js';
|
|
3
3
|
export class WorkspaceWorkbenchService {
|
|
4
4
|
options;
|
|
5
5
|
contentSearch;
|
|
6
|
+
topicRefreshes = new Map();
|
|
7
|
+
dirtyTopics = new Set();
|
|
6
8
|
constructor(options) {
|
|
7
9
|
this.options = options;
|
|
8
10
|
this.contentSearch = new WorkspaceContentSearchService(options.files);
|
|
@@ -13,6 +15,7 @@ export class WorkspaceWorkbenchService {
|
|
|
13
15
|
'workspace.unwatch': (data) => this.unwatch(record(data)),
|
|
14
16
|
'workspace.files.list': (data) => this.listFiles(record(data)),
|
|
15
17
|
'workspace.file.read': (data) => this.readFile(record(data)),
|
|
18
|
+
'workspace.file.content.read': (data) => this.readFileContent(record(data)),
|
|
16
19
|
'workspace.files.search': (data) => this.searchFiles(record(data)),
|
|
17
20
|
'workspace.files.mutate': (data) => this.mutateFile(record(data)),
|
|
18
21
|
'workspace.content.search': (data) => this.searchContent(record(data)),
|
|
@@ -31,44 +34,220 @@ export class WorkspaceWorkbenchService {
|
|
|
31
34
|
'workspace.git.history.list': (data) => this.gitHistory(record(data)),
|
|
32
35
|
'workspace.git.commit.files': (data) => this.gitCommitFiles(record(data)),
|
|
33
36
|
'workspace.git.commit.file.diff': (data) => this.gitCommitFileDiff(record(data)),
|
|
37
|
+
'workspace.git.commit.file.content.read': (data) => this.gitCommitFileContent(record(data)),
|
|
34
38
|
'workspace.diff': (data) => this.diff(record(data))
|
|
35
39
|
};
|
|
36
40
|
}
|
|
41
|
+
async refreshTopic(workspaceId, topic) {
|
|
42
|
+
const key = `${workspaceId}:${topic}`;
|
|
43
|
+
const active = this.topicRefreshes.get(key);
|
|
44
|
+
if (active !== undefined) {
|
|
45
|
+
this.dirtyTopics.add(key);
|
|
46
|
+
return active;
|
|
47
|
+
}
|
|
48
|
+
const refresh = this.performTopicRefresh(workspaceId, topic).catch(() => {
|
|
49
|
+
this.emitTopicFailure(workspaceId, topic);
|
|
50
|
+
});
|
|
51
|
+
this.topicRefreshes.set(key, refresh);
|
|
52
|
+
try {
|
|
53
|
+
await refresh;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
if (this.topicRefreshes.get(key) === refresh)
|
|
57
|
+
this.topicRefreshes.delete(key);
|
|
58
|
+
if (this.dirtyTopics.delete(key))
|
|
59
|
+
void this.refreshTopic(workspaceId, topic);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
emitTopicFailure(workspaceId, topic) {
|
|
63
|
+
for (const watch of this.options.watch.activeWatches(workspaceId)) {
|
|
64
|
+
const subscribed = topic === 'files' ? watch.topics.files !== undefined : watch.topics[topic] === true;
|
|
65
|
+
if (!subscribed)
|
|
66
|
+
continue;
|
|
67
|
+
this.options.emitWatchEvent({
|
|
68
|
+
type: 'watch.failed',
|
|
69
|
+
workbenchConnectionId: watch.workbenchConnectionId,
|
|
70
|
+
watchType: 'workspace',
|
|
71
|
+
watchId: watch.watchId,
|
|
72
|
+
nodeGeneration: watch.nodeGeneration,
|
|
73
|
+
nodeId: this.options.nodeId(),
|
|
74
|
+
workspaceId,
|
|
75
|
+
topic,
|
|
76
|
+
code: 'WORKSPACE_WATCH_SNAPSHOT_FAILED',
|
|
77
|
+
message: 'Workspace watch snapshot failed.'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async performTopicRefresh(workspaceId, topic) {
|
|
82
|
+
const workspace = this.options.requireWorkspace(workspaceId);
|
|
83
|
+
const watches = this.options.watch.activeWatches(workspaceId);
|
|
84
|
+
if (topic === 'sessions') {
|
|
85
|
+
const subscribers = watches.filter((watch) => watch.topics.sessions);
|
|
86
|
+
if (subscribers.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
const payload = await this.options.listSessions(workspace.id);
|
|
89
|
+
for (const watch of subscribers)
|
|
90
|
+
this.deliverSnapshot(workspace, watch, topic, payload);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (topic === 'changes') {
|
|
94
|
+
const subscribers = watches.filter((watch) => watch.topics.changes);
|
|
95
|
+
if (subscribers.length === 0)
|
|
96
|
+
return;
|
|
97
|
+
const payload = workspace.kind === 'GIT_WORKSPACE' ? await this.options.changes.current(workspace) : null;
|
|
98
|
+
for (const watch of subscribers)
|
|
99
|
+
this.deliverSnapshot(workspace, watch, topic, payload);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const views = new Map();
|
|
103
|
+
for (const watch of watches) {
|
|
104
|
+
if (watch.topics.files === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
const key = JSON.stringify(watch.topics.files);
|
|
107
|
+
views.set(key, [...(views.get(key) ?? []), watch]);
|
|
108
|
+
}
|
|
109
|
+
await Promise.all([...views.values()].map(async (subscribers) => {
|
|
110
|
+
const view = subscribers[0]?.topics.files;
|
|
111
|
+
if (view === undefined)
|
|
112
|
+
return;
|
|
113
|
+
const payload = { files: await this.options.files.list(workspace, view) };
|
|
114
|
+
for (const watch of subscribers)
|
|
115
|
+
this.deliverSnapshot(workspace, watch, topic, payload);
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
37
118
|
async watch(input) {
|
|
38
119
|
if (typeof input.workspaceId !== 'string' ||
|
|
39
120
|
typeof input.watchId !== 'string' ||
|
|
121
|
+
typeof input.workbenchConnectionId !== 'string' ||
|
|
40
122
|
!/^[A-Za-z0-9_-]{8,128}$/.test(input.watchId) ||
|
|
41
123
|
(input.mode !== 'initial' && input.mode !== 'renew'))
|
|
42
124
|
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
43
125
|
const workspace = this.options.requireWorkspace(input.workspaceId);
|
|
44
126
|
if (input.mode === 'renew') {
|
|
45
|
-
const watch = this.options.watch.renew(workspace.id, this.options.watch.key(input));
|
|
127
|
+
const watch = this.options.watch.renew(workspace.id, this.options.watch.key(input), input.watchId);
|
|
46
128
|
if (watch === undefined)
|
|
47
129
|
throw new Error('WORKSPACE_WATCH_NOT_FOUND');
|
|
48
|
-
return
|
|
130
|
+
return this.accepted(watch);
|
|
49
131
|
}
|
|
50
132
|
const topics = this.options.watch.parseTopics(input);
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await this.options.files.index(workspace).observe();
|
|
61
|
-
const watch = this.options.watch.create(workspace.id, input.watchId, this.options.watch.sessionHash(input), topics);
|
|
62
|
-
return { watch: { expiresAt: watch.expiresAt }, snapshot };
|
|
133
|
+
const existing = this.options.watch.existing(workspace.id, this.options.watch.key(input));
|
|
134
|
+
if (existing?.watchId === input.watchId &&
|
|
135
|
+
JSON.stringify(existing.topics) === JSON.stringify(topics)) {
|
|
136
|
+
this.options.watch.renew(workspace.id, this.options.watch.key(input), input.watchId);
|
|
137
|
+
return this.accepted(existing);
|
|
138
|
+
}
|
|
139
|
+
const watch = this.options.watch.create(workspace.id, input.watchId, this.options.watch.sessionHash(input), input.workbenchConnectionId, this.options.nodeGeneration(), topics);
|
|
140
|
+
void this.createInitialSnapshots(workspace, watch);
|
|
141
|
+
return this.accepted(watch);
|
|
63
142
|
}
|
|
64
143
|
unwatch(input) {
|
|
65
144
|
if (typeof input.workspaceId !== 'string' ||
|
|
66
145
|
typeof input.watchId !== 'string' ||
|
|
146
|
+
typeof input.workbenchConnectionId !== 'string' ||
|
|
67
147
|
!/^[A-Za-z0-9_-]{8,128}$/.test(input.watchId))
|
|
68
148
|
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
69
|
-
|
|
149
|
+
const key = this.options.watch.key(input);
|
|
150
|
+
const watch = this.options.watch.renew(input.workspaceId, key, input.watchId);
|
|
151
|
+
if (watch !== undefined)
|
|
152
|
+
this.options.watch.remove(input.workspaceId, key);
|
|
70
153
|
return { stopped: true };
|
|
71
154
|
}
|
|
155
|
+
accepted(watch) {
|
|
156
|
+
return {
|
|
157
|
+
accepted: true,
|
|
158
|
+
watchId: watch.watchId,
|
|
159
|
+
nodeGeneration: watch.nodeGeneration,
|
|
160
|
+
expiresAt: watch.expiresAt
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async createInitialSnapshots(workspace, watch) {
|
|
164
|
+
const tasks = [];
|
|
165
|
+
const emit = async (topic, read) => {
|
|
166
|
+
try {
|
|
167
|
+
const payload = await read();
|
|
168
|
+
if (!this.options.watch.matches(workspace.id, `${watch.sessionHash}:${watch.workbenchConnectionId}`, watch.watchId))
|
|
169
|
+
return;
|
|
170
|
+
this.options.emitWatchEvent({
|
|
171
|
+
type: 'watch.workspace.snapshot',
|
|
172
|
+
workbenchConnectionId: watch.workbenchConnectionId,
|
|
173
|
+
watchId: watch.watchId,
|
|
174
|
+
nodeGeneration: watch.nodeGeneration,
|
|
175
|
+
nodeId: this.options.nodeId(),
|
|
176
|
+
workspaceId: workspace.id,
|
|
177
|
+
topic,
|
|
178
|
+
revision: this.options.watch.nextRevision(workspace.id),
|
|
179
|
+
snapshotSequence: this.options.watch.nextSnapshotSequence(),
|
|
180
|
+
expiresAt: watch.expiresAt,
|
|
181
|
+
payload
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
this.options.emitWatchEvent({
|
|
186
|
+
type: 'watch.failed',
|
|
187
|
+
workbenchConnectionId: watch.workbenchConnectionId,
|
|
188
|
+
watchType: 'workspace',
|
|
189
|
+
watchId: watch.watchId,
|
|
190
|
+
nodeGeneration: watch.nodeGeneration,
|
|
191
|
+
nodeId: this.options.nodeId(),
|
|
192
|
+
workspaceId: workspace.id,
|
|
193
|
+
topic,
|
|
194
|
+
code: 'WORKSPACE_WATCH_SNAPSHOT_FAILED',
|
|
195
|
+
message: 'Workspace watch snapshot failed.'
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
if (watch.topics.sessions)
|
|
200
|
+
tasks.push(emit('sessions', () => this.options.listSessions(workspace.id)));
|
|
201
|
+
if (watch.topics.files !== undefined)
|
|
202
|
+
tasks.push(emit('files', async () => ({
|
|
203
|
+
files: await this.options.files.list(workspace, watch.topics.files)
|
|
204
|
+
})));
|
|
205
|
+
if (watch.topics.changes)
|
|
206
|
+
tasks.push(emit('changes', async () => workspace.kind === 'GIT_WORKSPACE' ? this.options.changes.current(workspace) : null));
|
|
207
|
+
if (watch.topics.files !== undefined || watch.topics.changes)
|
|
208
|
+
void this.options.files
|
|
209
|
+
.index(workspace)
|
|
210
|
+
.observe()
|
|
211
|
+
.catch(() => undefined);
|
|
212
|
+
await Promise.all(tasks);
|
|
213
|
+
}
|
|
214
|
+
async emitSnapshot(workspace, watch, topic, read) {
|
|
215
|
+
try {
|
|
216
|
+
const payload = await read();
|
|
217
|
+
this.deliverSnapshot(workspace, watch, topic, payload);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
this.options.emitWatchEvent({
|
|
221
|
+
type: 'watch.failed',
|
|
222
|
+
workbenchConnectionId: watch.workbenchConnectionId,
|
|
223
|
+
watchType: 'workspace',
|
|
224
|
+
watchId: watch.watchId,
|
|
225
|
+
nodeGeneration: watch.nodeGeneration,
|
|
226
|
+
nodeId: this.options.nodeId(),
|
|
227
|
+
workspaceId: workspace.id,
|
|
228
|
+
topic,
|
|
229
|
+
code: 'WORKSPACE_WATCH_SNAPSHOT_FAILED',
|
|
230
|
+
message: 'Workspace watch snapshot failed.'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
deliverSnapshot(workspace, watch, topic, payload) {
|
|
235
|
+
if (!this.options.watch.matches(workspace.id, `${watch.sessionHash}:${watch.workbenchConnectionId}`, watch.watchId))
|
|
236
|
+
return;
|
|
237
|
+
this.options.emitWatchEvent({
|
|
238
|
+
type: 'watch.workspace.snapshot',
|
|
239
|
+
workbenchConnectionId: watch.workbenchConnectionId,
|
|
240
|
+
watchId: watch.watchId,
|
|
241
|
+
nodeGeneration: watch.nodeGeneration,
|
|
242
|
+
nodeId: this.options.nodeId(),
|
|
243
|
+
workspaceId: workspace.id,
|
|
244
|
+
topic,
|
|
245
|
+
revision: this.options.watch.nextRevision(workspace.id),
|
|
246
|
+
snapshotSequence: this.options.watch.nextSnapshotSequence(),
|
|
247
|
+
expiresAt: watch.expiresAt,
|
|
248
|
+
payload
|
|
249
|
+
});
|
|
250
|
+
}
|
|
72
251
|
async listFiles(input) {
|
|
73
252
|
const workspace = this.options.requireWorkspace(input.workspaceId);
|
|
74
253
|
return { files: await this.options.files.list(workspace, input) };
|
|
@@ -112,6 +291,13 @@ export class WorkspaceWorkbenchService {
|
|
|
112
291
|
async currentChanges(input) {
|
|
113
292
|
return this.options.changes.current(this.options.requireWorkspace(input.workspaceId));
|
|
114
293
|
}
|
|
294
|
+
async readFileContent(input) {
|
|
295
|
+
if (typeof input.workspaceId !== 'string')
|
|
296
|
+
throw new Error('WORKSPACE_NOT_FOUND');
|
|
297
|
+
return {
|
|
298
|
+
file: await this.options.files.readContent(this.options.requireWorkspace(input.workspaceId), input)
|
|
299
|
+
};
|
|
300
|
+
}
|
|
115
301
|
async changeDiff(input) {
|
|
116
302
|
if (typeof input.workspaceId !== 'string' ||
|
|
117
303
|
typeof input.path !== 'string' ||
|
|
@@ -184,6 +370,19 @@ export class WorkspaceWorkbenchService {
|
|
|
184
370
|
diff: await readGitHistoryFileDiff(await this.gitRepository(input), input.commit, input.path, this.refs(input), input.previousPath)
|
|
185
371
|
};
|
|
186
372
|
}
|
|
373
|
+
async gitCommitFileContent(input) {
|
|
374
|
+
if (typeof input.commit !== 'string' ||
|
|
375
|
+
typeof input.path !== 'string' ||
|
|
376
|
+
!Number.isSafeInteger(input.offset) ||
|
|
377
|
+
input.offset < 0 ||
|
|
378
|
+
!Number.isSafeInteger(input.limit) ||
|
|
379
|
+
input.limit < 1 ||
|
|
380
|
+
input.limit > 20 * 1024 * 1024)
|
|
381
|
+
throw new Error('FILE_RANGE_INVALID');
|
|
382
|
+
return {
|
|
383
|
+
file: await readGitHistoryFileContentRange(await this.gitRepository(input), input.commit, input.path, input.offset, input.limit, this.refs(input))
|
|
384
|
+
};
|
|
385
|
+
}
|
|
187
386
|
async diff(input) {
|
|
188
387
|
if (typeof input.workspaceId !== 'string' ||
|
|
189
388
|
typeof input.fromRef !== 'string' ||
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const MAX_CONTENT_LENGTH = 20_000;
|
|
2
|
+
export function personalInstructionsForRunner(value, runner) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
6
|
+
throw new Error('PERSONAL_INSTRUCTION_INVALID');
|
|
7
|
+
const record = value;
|
|
8
|
+
const instructions = {
|
|
9
|
+
common: content(record['common']),
|
|
10
|
+
codex: content(record['codex']),
|
|
11
|
+
claudeCode: content(record['claudeCode']),
|
|
12
|
+
openCode: content(record['openCode'])
|
|
13
|
+
};
|
|
14
|
+
const specialized = runner === 'codex'
|
|
15
|
+
? instructions.codex
|
|
16
|
+
: runner === 'claude-code'
|
|
17
|
+
? instructions.claudeCode
|
|
18
|
+
: instructions.openCode;
|
|
19
|
+
const merged = [instructions.common, specialized].filter((item) => item.length > 0).join('\n\n');
|
|
20
|
+
return merged.length === 0 ? undefined : merged;
|
|
21
|
+
}
|
|
22
|
+
function content(value) {
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
return '';
|
|
25
|
+
if (typeof value !== 'string')
|
|
26
|
+
throw new Error('PERSONAL_INSTRUCTION_INVALID');
|
|
27
|
+
const normalized = value.trim();
|
|
28
|
+
if (normalized.length > MAX_CONTENT_LENGTH)
|
|
29
|
+
throw new Error('PERSONAL_INSTRUCTION_INVALID');
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
@@ -22,6 +22,7 @@ export declare function settleWithin<T>(promise: Promise<T>, milliseconds: numbe
|
|
|
22
22
|
export declare function extractCodexThreads(result: unknown): readonly {
|
|
23
23
|
readonly id: string;
|
|
24
24
|
readonly source: string | undefined;
|
|
25
|
+
readonly derived: boolean;
|
|
25
26
|
readonly cwd: unknown;
|
|
26
27
|
readonly title: string | undefined;
|
|
27
28
|
}[];
|
|
@@ -49,12 +49,17 @@ export function extractCodexThreads(result) {
|
|
|
49
49
|
typeof value.source.kind === 'string'
|
|
50
50
|
? value.source.kind
|
|
51
51
|
: undefined;
|
|
52
|
+
const sourceRecord = isPlainRecord(value.source) ? value.source : undefined;
|
|
53
|
+
const derived = source === 'subagent' ||
|
|
54
|
+
sourceRecord?.kind === 'subagent' ||
|
|
55
|
+
isPlainRecord(sourceRecord?.subagent) ||
|
|
56
|
+
isPlainRecord(sourceRecord?.subAgent);
|
|
52
57
|
const title = typeof value.name === 'string' && value.name.trim().length > 0
|
|
53
58
|
? value.name.trim()
|
|
54
59
|
: typeof value.preview === 'string' && value.preview.trim().length > 0
|
|
55
60
|
? value.preview.trim()
|
|
56
61
|
: undefined;
|
|
57
|
-
return [{ id: value.id, source, cwd: value.cwd, title }];
|
|
62
|
+
return [{ id: value.id, source, derived, cwd: value.cwd, title }];
|
|
58
63
|
});
|
|
59
64
|
}
|
|
60
65
|
export function codexThreadActivity(result) {
|
|
@@ -106,6 +111,8 @@ export function claudeDiscoveredSession(value, fallbackCwd) {
|
|
|
106
111
|
if (typeof value !== 'object' || value === null)
|
|
107
112
|
return undefined;
|
|
108
113
|
const record = value;
|
|
114
|
+
if (record.isSidechain === true)
|
|
115
|
+
return undefined;
|
|
109
116
|
const externalSessionId = typeof record.sessionId === 'string'
|
|
110
117
|
? record.sessionId
|
|
111
118
|
: typeof record.id === 'string'
|
package/dist/workspace.d.ts
CHANGED
|
@@ -111,6 +111,13 @@ export declare function readWorkspaceTextFile(workspaceRoot: string, requestedPa
|
|
|
111
111
|
readonly truncated: boolean;
|
|
112
112
|
readonly nextOffset: number | null;
|
|
113
113
|
}>;
|
|
114
|
+
export declare function readWorkspaceFileContentRange(workspaceRoot: string, requestedPath: string, offset?: number, limit?: number): Promise<{
|
|
115
|
+
readonly path: string;
|
|
116
|
+
readonly size: number;
|
|
117
|
+
readonly offset: number;
|
|
118
|
+
readonly contentBase64: string;
|
|
119
|
+
readonly nextOffset: number | null;
|
|
120
|
+
}>;
|
|
114
121
|
/** Reads one absolute file only when the resolved target remains within a Node allowed root. */
|
|
115
122
|
export declare function readAllowedTextFile(requestedPath: string, allowedRoots: readonly string[], offset?: number, limit?: number): Promise<{
|
|
116
123
|
readonly path: string;
|
|
@@ -177,8 +184,13 @@ export declare function readGitHistoryFileDiff(repository: string, commit: strin
|
|
|
177
184
|
readonly text?: string;
|
|
178
185
|
readonly summary?: string;
|
|
179
186
|
readonly truncated: boolean;
|
|
180
|
-
|
|
181
|
-
|
|
187
|
+
}>;
|
|
188
|
+
export declare function readGitHistoryFileContentRange(repository: string, commit: string, requestedPath: string, offset: number, limit: number, refs?: readonly string[]): Promise<{
|
|
189
|
+
readonly path: string;
|
|
190
|
+
readonly size: number;
|
|
191
|
+
readonly offset: number;
|
|
192
|
+
readonly contentBase64: string;
|
|
193
|
+
readonly nextOffset: number | null;
|
|
182
194
|
}>;
|
|
183
195
|
export declare function readCurrentChanges(workspacePath: string): Promise<readonly {
|
|
184
196
|
readonly path: string;
|
|
@@ -192,15 +204,9 @@ export declare function readCurrentChangeDiff(workspacePath: string, requestedPa
|
|
|
192
204
|
readonly truncated: boolean;
|
|
193
205
|
readonly afterText?: string;
|
|
194
206
|
readonly afterTruncated?: boolean;
|
|
195
|
-
readonly beforePreview?: BinaryDiffPreview;
|
|
196
|
-
readonly afterPreview?: BinaryDiffPreview;
|
|
197
207
|
}>;
|
|
198
208
|
/** Current Changes must not expose an external link. */
|
|
199
209
|
export declare function isWorkspaceChangeVisible(workspacePath: string, requestedPath: string): Promise<boolean>;
|
|
200
|
-
interface BinaryDiffPreview {
|
|
201
|
-
readonly contentBase64?: string;
|
|
202
|
-
readonly truncated: boolean;
|
|
203
|
-
}
|
|
204
210
|
export declare function restoreCurrentChange(workspacePath: string, requestedPath: string, state: 'STAGED' | 'UNSTAGED' | 'UNTRACKED'): Promise<void>;
|
|
205
211
|
declare function gitChange(status: string): 'ADDED' | 'MODIFIED' | 'DELETED' | 'RENAMED' | 'COPIED' | 'TYPE_CHANGED';
|
|
206
212
|
export declare class GitCommandError extends Error {
|
package/dist/workspace.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { watch } from 'node:fs';
|
|
3
|
-
import { lstat, mkdir, opendir, readdir, readFile, realpath, rename, rm, stat, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { lstat, mkdir, opendir, open, readdir, readFile, realpath, rename, rm, stat, unlink, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir, platform as hostPlatform } from 'node:os';
|
|
5
5
|
import { dirname, isAbsolute, posix, relative, resolve, sep, win32 } from 'node:path';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { minimatch } from 'minimatch';
|
|
8
8
|
const GIT_TIMEOUT_MS = 5_000;
|
|
9
9
|
const GIT_OUTPUT_LIMIT = 512 * 1024;
|
|
10
|
-
const GIT_BINARY_PREVIEW_LIMIT = 4 * 1024 * 1024;
|
|
11
10
|
const FILE_READ_LIMIT = 512 * 1024;
|
|
12
11
|
const DIRECTORY_PAGE_LIMIT = 200;
|
|
13
12
|
const DIRECTORY_PICKER_LIMIT = 200;
|
|
@@ -665,6 +664,38 @@ export async function readWorkspaceTextFile(workspaceRoot, requestedPath, offset
|
|
|
665
664
|
nextOffset
|
|
666
665
|
};
|
|
667
666
|
}
|
|
667
|
+
export async function readWorkspaceFileContentRange(workspaceRoot, requestedPath, offset = 0, limit = FILE_READ_LIMIT) {
|
|
668
|
+
if (!Number.isSafeInteger(offset) ||
|
|
669
|
+
offset < 0 ||
|
|
670
|
+
!Number.isSafeInteger(limit) ||
|
|
671
|
+
limit < 1 ||
|
|
672
|
+
limit > FILE_READ_LIMIT)
|
|
673
|
+
throw new Error('FILE_RANGE_INVALID');
|
|
674
|
+
const root = await realpath(workspaceRoot);
|
|
675
|
+
const file = await resolveWorkspaceEntry(root, requestedPath, false);
|
|
676
|
+
const metadata = await lstat(file);
|
|
677
|
+
if (!metadata.isFile())
|
|
678
|
+
throw new Error('FILE_NOT_REGULAR');
|
|
679
|
+
if (offset > metadata.size)
|
|
680
|
+
throw new Error('FILE_RANGE_INVALID');
|
|
681
|
+
const length = Math.min(limit, metadata.size - offset);
|
|
682
|
+
const source = Buffer.allocUnsafe(length);
|
|
683
|
+
const handle = await open(file, 'r');
|
|
684
|
+
try {
|
|
685
|
+
const { bytesRead } = await handle.read(source, 0, length, offset);
|
|
686
|
+
const end = offset + bytesRead;
|
|
687
|
+
return {
|
|
688
|
+
path: relative(root, file).split(sep).join('/'),
|
|
689
|
+
size: metadata.size,
|
|
690
|
+
offset,
|
|
691
|
+
contentBase64: source.subarray(0, bytesRead).toString('base64'),
|
|
692
|
+
nextOffset: end < metadata.size ? end : null
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
finally {
|
|
696
|
+
await handle.close();
|
|
697
|
+
}
|
|
698
|
+
}
|
|
668
699
|
/** Reads one absolute file only when the resolved target remains within a Node allowed root. */
|
|
669
700
|
export async function readAllowedTextFile(requestedPath, allowedRoots, offset = 0, limit = FILE_READ_LIMIT) {
|
|
670
701
|
if (!isAbsolute(requestedPath))
|
|
@@ -1212,20 +1243,44 @@ export async function readGitHistoryFileDiff(repository, commit, requestedPath,
|
|
|
1212
1243
|
]);
|
|
1213
1244
|
const binary = result.output.includes(0) || result.text.includes('Binary files ');
|
|
1214
1245
|
if (binary) {
|
|
1215
|
-
const [beforePreview, afterPreview] = await Promise.all([
|
|
1216
|
-
readGitBinaryPreview(repository, `${commit}^`, previousPath ?? path),
|
|
1217
|
-
readGitBinaryPreview(repository, commit, path)
|
|
1218
|
-
]);
|
|
1219
1246
|
return {
|
|
1220
1247
|
binary: true,
|
|
1221
1248
|
summary: `binary diff sha256=${createHash('sha256').update(result.output).digest('hex')} bytes=${result.output.length}`,
|
|
1222
|
-
truncated: result.truncated
|
|
1223
|
-
...(beforePreview === undefined ? {} : { beforePreview }),
|
|
1224
|
-
...(afterPreview === undefined ? {} : { afterPreview })
|
|
1249
|
+
truncated: result.truncated
|
|
1225
1250
|
};
|
|
1226
1251
|
}
|
|
1227
1252
|
return { binary: false, text: result.text, truncated: result.truncated };
|
|
1228
1253
|
}
|
|
1254
|
+
export async function readGitHistoryFileContentRange(repository, commit, requestedPath, offset, limit, refs) {
|
|
1255
|
+
if (!Number.isSafeInteger(offset) ||
|
|
1256
|
+
offset < 0 ||
|
|
1257
|
+
!Number.isSafeInteger(limit) ||
|
|
1258
|
+
limit < 1 ||
|
|
1259
|
+
limit > 20 * 1024 * 1024 ||
|
|
1260
|
+
offset + limit > 20 * 1024 * 1024)
|
|
1261
|
+
throw new Error('FILE_RANGE_INVALID');
|
|
1262
|
+
await assertHistoryCommitReachable(repository, commit, refs);
|
|
1263
|
+
const path = safeGitWorkspacePath(requestedPath);
|
|
1264
|
+
const object = `${commit}:${path}`;
|
|
1265
|
+
const sizeResult = await runGit(repository, ['cat-file', '-s', object]);
|
|
1266
|
+
const size = Number(sizeResult.text.trim());
|
|
1267
|
+
if (!Number.isSafeInteger(size) || size < 0 || offset > size)
|
|
1268
|
+
throw new Error('FILE_RANGE_INVALID');
|
|
1269
|
+
if (offset === size)
|
|
1270
|
+
return { path, size, offset, contentBase64: '', nextOffset: null };
|
|
1271
|
+
const result = await runGitAllowFailure(repository, ['cat-file', 'blob', object], offset + limit);
|
|
1272
|
+
if (result.code !== 0)
|
|
1273
|
+
throw new GitCommandError(result.code, result.stderr);
|
|
1274
|
+
const content = result.output.subarray(offset, Math.min(size, offset + limit));
|
|
1275
|
+
const nextOffset = offset + content.length;
|
|
1276
|
+
return {
|
|
1277
|
+
path,
|
|
1278
|
+
size,
|
|
1279
|
+
offset,
|
|
1280
|
+
contentBase64: content.toString('base64'),
|
|
1281
|
+
nextOffset: nextOffset < size ? nextOffset : null
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1229
1284
|
export async function readCurrentChanges(workspacePath) {
|
|
1230
1285
|
return (await readCurrentChangesSummary(workspacePath)).changes;
|
|
1231
1286
|
}
|
|
@@ -1236,16 +1291,10 @@ export async function readCurrentChangeDiff(workspacePath, requestedPath) {
|
|
|
1236
1291
|
const after = await readWorkingTreeText(workspacePath, path);
|
|
1237
1292
|
const binary = result.output.includes(0) || result.text.includes('Binary files ') || after?.binary === true;
|
|
1238
1293
|
if (binary) {
|
|
1239
|
-
const [beforePreview, afterPreview] = await Promise.all([
|
|
1240
|
-
readGitBinaryPreview(workspacePath, 'HEAD', path),
|
|
1241
|
-
readWorkingTreeBinaryPreview(workspacePath, path)
|
|
1242
|
-
]);
|
|
1243
1294
|
return {
|
|
1244
1295
|
binary: true,
|
|
1245
1296
|
summary: `binary diff sha256=${createHash('sha256').update(result.output).digest('hex')} bytes=${result.output.length}`,
|
|
1246
|
-
truncated: result.truncated || after?.truncated === true
|
|
1247
|
-
...(beforePreview === undefined ? {} : { beforePreview }),
|
|
1248
|
-
...(afterPreview === undefined ? {} : { afterPreview })
|
|
1297
|
+
truncated: result.truncated || after?.truncated === true
|
|
1249
1298
|
};
|
|
1250
1299
|
}
|
|
1251
1300
|
return {
|
|
@@ -1315,32 +1364,6 @@ async function assertCurrentChangePathAccessible(workspacePath, path) {
|
|
|
1315
1364
|
return;
|
|
1316
1365
|
await resolveWorkspaceEntry(root, path, false);
|
|
1317
1366
|
}
|
|
1318
|
-
async function readGitBinaryPreview(repository, ref, path) {
|
|
1319
|
-
const result = await runGitAllowFailure(repository, ['show', `${ref}:${path}`], GIT_BINARY_PREVIEW_LIMIT);
|
|
1320
|
-
if (result.code !== 0)
|
|
1321
|
-
return undefined;
|
|
1322
|
-
return result.truncated
|
|
1323
|
-
? { truncated: true }
|
|
1324
|
-
: { contentBase64: result.output.toString('base64'), truncated: false };
|
|
1325
|
-
}
|
|
1326
|
-
async function readWorkingTreeBinaryPreview(workspacePath, path) {
|
|
1327
|
-
const root = await realpath(workspacePath);
|
|
1328
|
-
let file;
|
|
1329
|
-
try {
|
|
1330
|
-
file = await resolveWorkspaceEntry(root, path, false);
|
|
1331
|
-
}
|
|
1332
|
-
catch (error) {
|
|
1333
|
-
if (error instanceof Error && error.message === 'FILE_NOT_FOUND')
|
|
1334
|
-
return undefined;
|
|
1335
|
-
throw error;
|
|
1336
|
-
}
|
|
1337
|
-
const metadata = await lstat(file);
|
|
1338
|
-
if (!metadata.isFile())
|
|
1339
|
-
return undefined;
|
|
1340
|
-
if (metadata.size > GIT_BINARY_PREVIEW_LIMIT)
|
|
1341
|
-
return { truncated: true };
|
|
1342
|
-
return { contentBase64: (await readFile(file)).toString('base64'), truncated: false };
|
|
1343
|
-
}
|
|
1344
1367
|
async function readWorkingTreeText(workspacePath, path) {
|
|
1345
1368
|
const root = await realpath(workspacePath);
|
|
1346
1369
|
let file;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-pty": "1.1.0",
|
|
25
25
|
"ws": "^8.21.3",
|
|
26
26
|
"zod": "4.4.3",
|
|
27
|
-
"@myagentroam/protocol": "^0.9.
|
|
27
|
+
"@myagentroam/protocol": "^0.9.5"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/ws": "^8.18.1"
|