@myagentroam/node 0.9.4 → 0.9.6
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-jsonl-reader.d.ts +21 -0
- package/dist/native-jsonl-reader.js +89 -0
- package/dist/native-session-history.d.ts +20 -3
- package/dist/native-session-history.js +245 -38
- 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 +13 -2
- 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 +6 -5
- package/dist/service/conversation-history-service.js +151 -55
- 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 +76 -12
- 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
|
}[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { fileURLToPath } from 'node:url';
|
|
2
3
|
import { stripClaudePlanTag } from '../runner-command-engine.js';
|
|
3
4
|
import { compactRunnerText, isClaudeCommandTool, isPlainRecord, nativeClaudeCommand, nativePageEnd } from './node-operation-parsers.js';
|
|
@@ -49,12 +50,17 @@ export function extractCodexThreads(result) {
|
|
|
49
50
|
typeof value.source.kind === 'string'
|
|
50
51
|
? value.source.kind
|
|
51
52
|
: undefined;
|
|
53
|
+
const sourceRecord = isPlainRecord(value.source) ? value.source : undefined;
|
|
54
|
+
const derived = source === 'subagent' ||
|
|
55
|
+
sourceRecord?.kind === 'subagent' ||
|
|
56
|
+
isPlainRecord(sourceRecord?.subagent) ||
|
|
57
|
+
isPlainRecord(sourceRecord?.subAgent);
|
|
52
58
|
const title = typeof value.name === 'string' && value.name.trim().length > 0
|
|
53
59
|
? value.name.trim()
|
|
54
60
|
: typeof value.preview === 'string' && value.preview.trim().length > 0
|
|
55
61
|
? value.preview.trim()
|
|
56
62
|
: undefined;
|
|
57
|
-
return [{ id: value.id, source, cwd: value.cwd, title }];
|
|
63
|
+
return [{ id: value.id, source, derived, cwd: value.cwd, title }];
|
|
58
64
|
});
|
|
59
65
|
}
|
|
60
66
|
export function codexThreadActivity(result) {
|
|
@@ -106,6 +112,8 @@ export function claudeDiscoveredSession(value, fallbackCwd) {
|
|
|
106
112
|
if (typeof value !== 'object' || value === null)
|
|
107
113
|
return undefined;
|
|
108
114
|
const record = value;
|
|
115
|
+
if (record.isSidechain === true)
|
|
116
|
+
return undefined;
|
|
109
117
|
const externalSessionId = typeof record.sessionId === 'string'
|
|
110
118
|
? record.sessionId
|
|
111
119
|
: typeof record.id === 'string'
|
|
@@ -145,26 +153,27 @@ export function deduplicateDirectSessions(sessions) {
|
|
|
145
153
|
}
|
|
146
154
|
export function nativeConversationPage(session, history, input, runtimeTurns = [], registerImages = () => []) {
|
|
147
155
|
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
156
|
+
const effectiveCursor = history.cursorReset === true ? undefined : input.cursor;
|
|
148
157
|
const nativeTurns = nativeTranscriptTurns(history.items);
|
|
158
|
+
const nativeTurnIds = nativeTranscriptTurnIds(session.id, nativeTurns);
|
|
149
159
|
const representedClientMessageIds = new Set(nativeTurns.flatMap((entries) => entries.flatMap(({ entry, index }) => {
|
|
150
160
|
if (entry.kind !== 'message' || entry.role !== 'USER')
|
|
151
161
|
return [];
|
|
152
162
|
const clientMessageId = nativeUserClientMessageId(entry, entry.createdAt ?? index, runtimeTurns);
|
|
153
163
|
return clientMessageId === null ? [] : [clientMessageId];
|
|
154
164
|
})));
|
|
155
|
-
const missingRuntimeErrorTurns =
|
|
165
|
+
const missingRuntimeErrorTurns = effectiveCursor === undefined
|
|
156
166
|
? runtimeTurns.filter((turn) => {
|
|
157
167
|
const clientMessageId = runtimeTurnClientMessageId(turn);
|
|
158
168
|
return (turn.items.some((item) => item.kind === 'error') &&
|
|
159
169
|
(clientMessageId === undefined || !representedClientMessageIds.has(clientMessageId)));
|
|
160
170
|
})
|
|
161
171
|
: [];
|
|
162
|
-
const end = nativePageEnd(
|
|
172
|
+
const end = nativePageEnd(effectiveCursor, session.id, nativeTurns.length);
|
|
163
173
|
const start = Math.max(0, end - limit);
|
|
164
174
|
const nativePageTurns = nativeTurns
|
|
165
175
|
.slice(start, end)
|
|
166
176
|
.map((entries, relativeIndex) => {
|
|
167
|
-
const firstIndex = entries[0]?.index ?? 0;
|
|
168
177
|
const nativeUser = entries.find(({ entry }) => entry.kind === 'message' && entry.role === 'USER')?.entry;
|
|
169
178
|
const matchedClientMessageId = nativeUser?.kind === 'message'
|
|
170
179
|
? nativeUserClientMessageId(nativeUser, nativeUser.createdAt, runtimeTurns)
|
|
@@ -172,9 +181,10 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
172
181
|
const runtimeTurn = typeof matchedClientMessageId === 'string'
|
|
173
182
|
? runtimeTurns.find((turn) => runtimeTurnClientMessageId(turn) === matchedClientMessageId)
|
|
174
183
|
: undefined;
|
|
175
|
-
const turnId = runtimeTurn?.id ??
|
|
176
|
-
const
|
|
177
|
-
|
|
184
|
+
const turnId = runtimeTurn?.id ?? nativeTurnIds[start + relativeIndex];
|
|
185
|
+
const itemIdentities = nativeTranscriptItemIdentities(entries);
|
|
186
|
+
const nativeItems = entries.map(({ entry, index }, entryIndex) => ({
|
|
187
|
+
...nativeTranscriptConversationItem(session, entry, index, itemIdentities[entryIndex], turnId, runtimeTurns, registerImages),
|
|
178
188
|
runId: runtimeTurn?.runId ?? null
|
|
179
189
|
}));
|
|
180
190
|
const runtimeErrors = (runtimeTurn?.items ?? [])
|
|
@@ -214,11 +224,33 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
214
224
|
const turns = combined.slice(-limit);
|
|
215
225
|
const nativePageIds = new Set(nativePageTurns.map((turn) => turn.id));
|
|
216
226
|
const nextEnd = start + dropped.filter((turn) => nativePageIds.has(turn.id)).length;
|
|
227
|
+
const cursorBase = {
|
|
228
|
+
sessionId: session.id,
|
|
229
|
+
...(history.snapshotEnd === undefined ? {} : { snapshotEnd: history.snapshotEnd }),
|
|
230
|
+
...(history.windowEnd === undefined ? {} : { nextEnd: history.windowEnd }),
|
|
231
|
+
...(history.fileAnchor === undefined ? {} : { fileAnchor: history.fileAnchor }),
|
|
232
|
+
...(history.anchorBytes === undefined ? {} : { anchorBytes: history.anchorBytes }),
|
|
233
|
+
readLimit: history.windowReadLimit ?? limit
|
|
234
|
+
};
|
|
235
|
+
const precedingWindow = history.truncated === true &&
|
|
236
|
+
history.windowStart !== undefined &&
|
|
237
|
+
history.snapshotEnd !== undefined &&
|
|
238
|
+
history.fileAnchor !== undefined &&
|
|
239
|
+
history.anchorBytes !== undefined
|
|
240
|
+
? Buffer.from(JSON.stringify({
|
|
241
|
+
sessionId: session.id,
|
|
242
|
+
snapshotEnd: history.snapshotEnd,
|
|
243
|
+
nextEnd: history.windowStart,
|
|
244
|
+
fileAnchor: history.fileAnchor,
|
|
245
|
+
anchorBytes: history.anchorBytes,
|
|
246
|
+
readLimit: history.windowReadLimit ?? limit
|
|
247
|
+
})).toString('base64url')
|
|
248
|
+
: null;
|
|
217
249
|
return {
|
|
218
250
|
turns,
|
|
219
251
|
nextCursor: nextEnd > 0
|
|
220
|
-
? Buffer.from(JSON.stringify({
|
|
221
|
-
:
|
|
252
|
+
? Buffer.from(JSON.stringify({ ...cursorBase, end: nextEnd })).toString('base64url')
|
|
253
|
+
: precedingWindow
|
|
222
254
|
};
|
|
223
255
|
}
|
|
224
256
|
function runtimeTurnClientMessageId(turn) {
|
|
@@ -230,6 +262,38 @@ function runtimeTurnClientMessageId(turn) {
|
|
|
230
262
|
function lastNativeItemId(entries) {
|
|
231
263
|
return entries.findLast(({ entry }) => entry.nativeId !== undefined)?.entry.nativeId;
|
|
232
264
|
}
|
|
265
|
+
function nativeTranscriptTurnIds(sessionId, turns) {
|
|
266
|
+
const occurrences = new Map();
|
|
267
|
+
return turns.map((entries) => {
|
|
268
|
+
const user = entries.find(({ entry }) => entry.kind === 'message' && entry.role === 'USER')?.entry;
|
|
269
|
+
const first = user ?? entries[0]?.entry;
|
|
270
|
+
const base = first?.nativeId ?? nativeTranscriptItemFingerprint(first);
|
|
271
|
+
const occurrence = occurrences.get(base) ?? 0;
|
|
272
|
+
occurrences.set(base, occurrence + 1);
|
|
273
|
+
return `${sessionId}:native:${base}${occurrence === 0 ? '' : `:${occurrence}`}`;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function nativeTranscriptItemIdentities(entries) {
|
|
277
|
+
const nativeIdCounts = new Map();
|
|
278
|
+
for (const { entry } of entries)
|
|
279
|
+
if (entry.nativeId !== undefined)
|
|
280
|
+
nativeIdCounts.set(entry.nativeId, (nativeIdCounts.get(entry.nativeId) ?? 0) + 1);
|
|
281
|
+
const occurrences = new Map();
|
|
282
|
+
return entries.map(({ entry }) => {
|
|
283
|
+
const base = entry.nativeId !== undefined && nativeIdCounts.get(entry.nativeId) === 1
|
|
284
|
+
? entry.nativeId
|
|
285
|
+
: `${entry.nativeId === undefined ? '' : `${entry.nativeId}:`}${nativeTranscriptItemFingerprint(entry)}`;
|
|
286
|
+
const occurrence = occurrences.get(base) ?? 0;
|
|
287
|
+
occurrences.set(base, occurrence + 1);
|
|
288
|
+
return occurrence === 0 ? base : `${base}:${occurrence}`;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function nativeTranscriptItemFingerprint(entry) {
|
|
292
|
+
return createHash('sha256')
|
|
293
|
+
.update(JSON.stringify(entry ?? null))
|
|
294
|
+
.digest('base64url')
|
|
295
|
+
.slice(0, 24);
|
|
296
|
+
}
|
|
233
297
|
function nativeTranscriptTurns(items) {
|
|
234
298
|
const turns = [];
|
|
235
299
|
let current = [];
|
|
@@ -244,7 +308,7 @@ function nativeTranscriptTurns(items) {
|
|
|
244
308
|
turns.push(current);
|
|
245
309
|
return turns;
|
|
246
310
|
}
|
|
247
|
-
function nativeTranscriptConversationItem(session, entry, index, turnId, runtimeTurns, registerImages) {
|
|
311
|
+
function nativeTranscriptConversationItem(session, entry, index, itemIdentity, turnId, runtimeTurns, registerImages) {
|
|
248
312
|
const time = entry.createdAt;
|
|
249
313
|
const isToolCall = entry.kind === 'tool_call';
|
|
250
314
|
const isUnknown = entry.kind === 'unknown';
|
|
@@ -269,7 +333,7 @@ function nativeTranscriptConversationItem(session, entry, index, turnId, runtime
|
|
|
269
333
|
: '';
|
|
270
334
|
const planText = entry.kind === 'message' && entry.role !== 'USER' ? claudePlanText(entryText) : undefined;
|
|
271
335
|
const item = {
|
|
272
|
-
id: `${turnId}:item:${
|
|
336
|
+
id: `${turnId}:item:${itemIdentity}`,
|
|
273
337
|
sessionId: session.id,
|
|
274
338
|
turnId,
|
|
275
339
|
runId: null,
|
|
@@ -294,7 +358,7 @@ function nativeTranscriptConversationItem(session, entry, index, turnId, runtime
|
|
|
294
358
|
status: 'COMPLETED',
|
|
295
359
|
payload: isUserInputRequest
|
|
296
360
|
? {
|
|
297
|
-
requestId: `native:${entry.kind === 'tool_call' ? entry.toolUseId : `item-${
|
|
361
|
+
requestId: `native:${entry.kind === 'tool_call' ? entry.toolUseId : `item-${itemIdentity}`}`,
|
|
298
362
|
questions: userInputQuestions
|
|
299
363
|
}
|
|
300
364
|
: isReasoning
|
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 {
|