@myagentroam/node 0.9.3 → 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 +25 -0
- package/dist/codex-app-server.js +104 -7
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +21 -12
- package/dist/database.d.ts +2 -0
- 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.d.ts +9 -0
- package/dist/runner/codex/managed-run-controller.js +63 -17
- package/dist/runner/codex-runner.d.ts +3 -1
- package/dist/runner/codex-runner.js +23 -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.d.ts +2 -0
- package/dist/runtime-state.js +12 -7
- package/dist/service/conversation-history-service.d.ts +10 -1
- package/dist/service/conversation-history-service.js +272 -42
- package/dist/service/conversation-segment-service.js +7 -1
- 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 +26 -2
- package/dist/service/native-session-watch-service.js +258 -42
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-event-service.js +16 -5
- 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 +7 -12
- package/dist/service/session-message-service.d.ts +1 -1
- package/dist/service/session-message-service.js +104 -83
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-change-service.js +1 -1
- 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 +29 -2
- package/dist/service/workspace-queue-workbench-service.js +73 -8
- 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 +3 -1
- package/dist/util/runner-native-session-parsers.js +35 -17
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +98 -50
- package/package.json +2 -2
|
@@ -36,20 +36,29 @@ export class WorkspaceWatchService {
|
|
|
36
36
|
return { sessions, changes, files };
|
|
37
37
|
}
|
|
38
38
|
key(input) {
|
|
39
|
-
return `${this.sessionHash(input)}:${input
|
|
39
|
+
return `${this.sessionHash(input)}:${this.connectionId(input)}`;
|
|
40
40
|
}
|
|
41
41
|
sessionHash(input) {
|
|
42
42
|
return typeof input.__workspaceWatchSessionHash === 'string'
|
|
43
43
|
? input.__workspaceWatchSessionHash
|
|
44
44
|
: 'local-test';
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
connectionId(input) {
|
|
47
|
+
if (typeof input.workbenchConnectionId !== 'string')
|
|
48
|
+
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
49
|
+
return input.workbenchConnectionId;
|
|
50
|
+
}
|
|
51
|
+
create(workspaceId, watchId, sessionHash, workbenchConnectionId, nodeGeneration, topics) {
|
|
52
|
+
const key = `${sessionHash}:${workbenchConnectionId}`;
|
|
53
|
+
for (const [activeWorkspaceId, watches] of this.state.watches)
|
|
54
|
+
if (watches.has(key))
|
|
55
|
+
this.remove(activeWorkspaceId, key);
|
|
49
56
|
const watch = {
|
|
50
57
|
workspaceId,
|
|
51
58
|
watchId,
|
|
52
59
|
sessionHash,
|
|
60
|
+
workbenchConnectionId,
|
|
61
|
+
nodeGeneration,
|
|
53
62
|
expiresAt: Date.now() + WATCH_TTL_MS,
|
|
54
63
|
topics,
|
|
55
64
|
timer: undefined
|
|
@@ -62,9 +71,11 @@ export class WorkspaceWatchService {
|
|
|
62
71
|
this.onSessionWatchRequired(workspaceId);
|
|
63
72
|
return watch;
|
|
64
73
|
}
|
|
65
|
-
renew(workspaceId, key) {
|
|
74
|
+
renew(workspaceId, key, watchId) {
|
|
66
75
|
const watch = this.state.watches.get(workspaceId)?.get(key);
|
|
67
|
-
if (watch === undefined || watch.
|
|
76
|
+
if (watch === undefined || watch.watchId !== watchId)
|
|
77
|
+
return undefined;
|
|
78
|
+
if (watch.expiresAt <= Date.now()) {
|
|
68
79
|
this.remove(workspaceId, key);
|
|
69
80
|
return undefined;
|
|
70
81
|
}
|
|
@@ -73,6 +84,14 @@ export class WorkspaceWatchService {
|
|
|
73
84
|
watch.timer = this.expiryTimer(workspaceId, key);
|
|
74
85
|
return watch;
|
|
75
86
|
}
|
|
87
|
+
matches(workspaceId, key, watchId) {
|
|
88
|
+
const watch = this.state.watches.get(workspaceId)?.get(key);
|
|
89
|
+
return watch !== undefined && watch.watchId === watchId && watch.expiresAt > Date.now();
|
|
90
|
+
}
|
|
91
|
+
existing(workspaceId, key) {
|
|
92
|
+
const watch = this.state.watches.get(workspaceId)?.get(key);
|
|
93
|
+
return watch !== undefined && watch.expiresAt > Date.now() ? watch : undefined;
|
|
94
|
+
}
|
|
76
95
|
remove(workspaceId, key) {
|
|
77
96
|
const watches = this.state.watches.get(workspaceId);
|
|
78
97
|
const watch = watches?.get(key);
|
|
@@ -106,9 +125,16 @@ export class WorkspaceWatchService {
|
|
|
106
125
|
this.state.watchRevisions.set(workspaceId, revision);
|
|
107
126
|
return revision;
|
|
108
127
|
}
|
|
128
|
+
nextSnapshotSequence() {
|
|
129
|
+
this.state.watchSnapshotSequence += 1;
|
|
130
|
+
return this.state.watchSnapshotSequence;
|
|
131
|
+
}
|
|
109
132
|
topicActive(workspaceId, topic) {
|
|
110
133
|
return [...(this.state.watches.get(workspaceId)?.values() ?? [])].some((watch) => topic === 'files' ? watch.topics.files !== undefined : watch.topics[topic]);
|
|
111
134
|
}
|
|
135
|
+
activeWatches(workspaceId) {
|
|
136
|
+
return [...(this.state.watches.get(workspaceId)?.values() ?? [])].filter((watch) => watch.expiresAt > Date.now());
|
|
137
|
+
}
|
|
112
138
|
expiryTimer(workspaceId, key) {
|
|
113
139
|
const timer = setTimeout(() => this.remove(workspaceId, key), WATCH_TTL_MS);
|
|
114
140
|
timer.unref();
|
|
@@ -12,14 +12,26 @@ export interface WorkspaceWorkbenchServiceOptions {
|
|
|
12
12
|
readonly changes: WorkspaceChangeService;
|
|
13
13
|
readonly listSessions: (workspaceId: string) => Promise<unknown>;
|
|
14
14
|
readonly invalidate: (workspaceId: string) => void;
|
|
15
|
+
readonly nodeId: () => string;
|
|
16
|
+
readonly nodeGeneration: () => string;
|
|
17
|
+
readonly emitWatchEvent: (payload: unknown) => void;
|
|
15
18
|
}
|
|
16
19
|
export declare class WorkspaceWorkbenchService {
|
|
17
20
|
private readonly options;
|
|
18
21
|
private readonly contentSearch;
|
|
22
|
+
private readonly topicRefreshes;
|
|
23
|
+
private readonly dirtyTopics;
|
|
19
24
|
constructor(options: WorkspaceWorkbenchServiceOptions);
|
|
20
25
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
26
|
+
refreshTopic(workspaceId: string, topic: 'sessions' | 'files' | 'changes'): Promise<void>;
|
|
27
|
+
private emitTopicFailure;
|
|
28
|
+
private performTopicRefresh;
|
|
21
29
|
private watch;
|
|
22
30
|
private unwatch;
|
|
31
|
+
private accepted;
|
|
32
|
+
private createInitialSnapshots;
|
|
33
|
+
private emitSnapshot;
|
|
34
|
+
private deliverSnapshot;
|
|
23
35
|
private listFiles;
|
|
24
36
|
private readFile;
|
|
25
37
|
private searchFiles;
|
|
@@ -31,6 +43,7 @@ export declare class WorkspaceWorkbenchService {
|
|
|
31
43
|
private completeUpload;
|
|
32
44
|
private cancelUpload;
|
|
33
45
|
private currentChanges;
|
|
46
|
+
private readFileContent;
|
|
34
47
|
private changeDiff;
|
|
35
48
|
private restoreChange;
|
|
36
49
|
private gitRepository;
|
|
@@ -39,6 +52,7 @@ export declare class WorkspaceWorkbenchService {
|
|
|
39
52
|
private gitHistory;
|
|
40
53
|
private gitCommitFiles;
|
|
41
54
|
private gitCommitFileDiff;
|
|
55
|
+
private gitCommitFileContent;
|
|
42
56
|
private diff;
|
|
43
57
|
private gitWorkspace;
|
|
44
58
|
}
|
|
@@ -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
|
+
}
|
|
@@ -15,12 +15,14 @@ export interface ConversationHistoryPage {
|
|
|
15
15
|
readonly source: ConversationHistorySource;
|
|
16
16
|
readonly readAt: number;
|
|
17
17
|
readonly latestVisibleAt: number;
|
|
18
|
+
readonly deferredOlderHistory?: boolean;
|
|
18
19
|
}
|
|
19
20
|
export declare function extractId(result: unknown, key: 'thread' | 'turn'): string;
|
|
20
21
|
export declare function settleWithin<T>(promise: Promise<T>, milliseconds: number): Promise<T | undefined>;
|
|
21
22
|
export declare function extractCodexThreads(result: unknown): readonly {
|
|
22
23
|
readonly id: string;
|
|
23
24
|
readonly source: string | undefined;
|
|
25
|
+
readonly derived: boolean;
|
|
24
26
|
readonly cwd: unknown;
|
|
25
27
|
readonly title: string | undefined;
|
|
26
28
|
}[];
|
|
@@ -50,7 +52,7 @@ export declare function nativeConversationPage(session: NodeAgentSession, histor
|
|
|
50
52
|
*/
|
|
51
53
|
export declare function nativeUserClientMessageId(entry: Extract<NativeSessionHistory['items'][number], {
|
|
52
54
|
readonly kind: 'message';
|
|
53
|
-
}>, createdAt: number, runtimeTurns: readonly NodeConversationTurn[]): string | null;
|
|
55
|
+
}>, createdAt: number | null, runtimeTurns: readonly NodeConversationTurn[]): string | null;
|
|
54
56
|
/**
|
|
55
57
|
* Normalize the documented App Server `thread/read(includeTurns: true)`
|
|
56
58
|
* result. Only items with a user-visible Workbench representation are kept;
|
|
@@ -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'
|
|
@@ -165,14 +172,18 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
165
172
|
.slice(start, end)
|
|
166
173
|
.map((entries, relativeIndex) => {
|
|
167
174
|
const firstIndex = entries[0]?.index ?? 0;
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
? runtimeTurns.find((turn) => runtimeTurnClientMessageId(turn) === clientMessageId)
|
|
175
|
+
const nativeUser = entries.find(({ entry }) => entry.kind === 'message' && entry.role === 'USER')?.entry;
|
|
176
|
+
const matchedClientMessageId = nativeUser?.kind === 'message'
|
|
177
|
+
? nativeUserClientMessageId(nativeUser, nativeUser.createdAt, runtimeTurns)
|
|
178
|
+
: null;
|
|
179
|
+
const runtimeTurn = typeof matchedClientMessageId === 'string'
|
|
180
|
+
? runtimeTurns.find((turn) => runtimeTurnClientMessageId(turn) === matchedClientMessageId)
|
|
175
181
|
: undefined;
|
|
182
|
+
const turnId = runtimeTurn?.id ?? `${session.id}:native:${firstIndex}`;
|
|
183
|
+
const nativeItems = entries.map(({ entry, index }) => ({
|
|
184
|
+
...nativeTranscriptConversationItem(session, entry, index, turnId, runtimeTurns, registerImages),
|
|
185
|
+
runId: runtimeTurn?.runId ?? null
|
|
186
|
+
}));
|
|
176
187
|
const runtimeErrors = (runtimeTurn?.items ?? [])
|
|
177
188
|
.filter((item) => item.kind === 'error')
|
|
178
189
|
.map((item) => ({ ...item, turnId, runId: runtimeTurn?.runId ?? null }));
|
|
@@ -180,7 +191,7 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
180
191
|
...nativeItems,
|
|
181
192
|
...runtimeErrors.filter((error) => !nativeItems.some((item) => item.id === error.id))
|
|
182
193
|
];
|
|
183
|
-
const startedAt = items[0]?.startedAt ??
|
|
194
|
+
const startedAt = items[0]?.startedAt ?? null;
|
|
184
195
|
const completedAt = items.at(-1)?.completedAt ?? startedAt;
|
|
185
196
|
const after = lastNativeItemId(entries);
|
|
186
197
|
return {
|
|
@@ -205,7 +216,7 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
205
216
|
completedAt
|
|
206
217
|
};
|
|
207
218
|
});
|
|
208
|
-
const combined = [...nativePageTurns, ...missingRuntimeErrorTurns]
|
|
219
|
+
const combined = [...nativePageTurns, ...missingRuntimeErrorTurns];
|
|
209
220
|
const dropped = combined.slice(0, Math.max(0, combined.length - limit));
|
|
210
221
|
const turns = combined.slice(-limit);
|
|
211
222
|
const nativePageIds = new Set(nativePageTurns.map((turn) => turn.id));
|
|
@@ -241,7 +252,7 @@ function nativeTranscriptTurns(items) {
|
|
|
241
252
|
return turns;
|
|
242
253
|
}
|
|
243
254
|
function nativeTranscriptConversationItem(session, entry, index, turnId, runtimeTurns, registerImages) {
|
|
244
|
-
const time = entry.createdAt
|
|
255
|
+
const time = entry.createdAt;
|
|
245
256
|
const isToolCall = entry.kind === 'tool_call';
|
|
246
257
|
const isUnknown = entry.kind === 'unknown';
|
|
247
258
|
const isReasoning = entry.kind === 'reasoning_summary';
|
|
@@ -336,6 +347,7 @@ function nativeTranscriptConversationItem(session, entry, index, turnId, runtime
|
|
|
336
347
|
* runtime user messages by content and creation time so native history replaces the optimistic Turn.
|
|
337
348
|
*/
|
|
338
349
|
export function nativeUserClientMessageId(entry, createdAt, runtimeTurns) {
|
|
350
|
+
const candidates = [];
|
|
339
351
|
for (const turn of runtimeTurns) {
|
|
340
352
|
const user = turn.items.find((item) => item.kind === 'user_message');
|
|
341
353
|
if (user === undefined || !isPlainRecord(user.payload))
|
|
@@ -346,13 +358,19 @@ export function nativeUserClientMessageId(entry, createdAt, runtimeTurns) {
|
|
|
346
358
|
if (clientMessageId === null || payload.text !== stripClaudePlanTag(entry.text))
|
|
347
359
|
continue;
|
|
348
360
|
const runtimeCreatedAt = user.startedAt ?? turn.startedAt;
|
|
349
|
-
|
|
350
|
-
runtimeCreatedAt !== undefined &&
|
|
351
|
-
Math.abs(createdAt - runtimeCreatedAt) > 60_000)
|
|
352
|
-
continue;
|
|
353
|
-
return clientMessageId;
|
|
361
|
+
candidates.push({ clientMessageId, createdAt: runtimeCreatedAt ?? null });
|
|
354
362
|
}
|
|
355
|
-
|
|
363
|
+
if (createdAt === null)
|
|
364
|
+
return candidates.length === 1 ? candidates[0].clientMessageId : null;
|
|
365
|
+
const timed = candidates
|
|
366
|
+
.filter((candidate) => candidate.createdAt !== null)
|
|
367
|
+
.map((candidate) => ({
|
|
368
|
+
...candidate,
|
|
369
|
+
distance: Math.abs(createdAt - candidate.createdAt)
|
|
370
|
+
}))
|
|
371
|
+
.filter((candidate) => candidate.distance <= 60_000)
|
|
372
|
+
.sort((left, right) => left.distance - right.distance);
|
|
373
|
+
return timed[0]?.clientMessageId ?? null;
|
|
356
374
|
}
|
|
357
375
|
/**
|
|
358
376
|
* Normalize the documented App Server `thread/read(includeTurns: true)`
|
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 {
|