@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.
Files changed (66) hide show
  1. package/dist/claude-agent-sdk.d.ts +1 -0
  2. package/dist/claude-agent-sdk.js +9 -0
  3. package/dist/codex-app-server.d.ts +25 -0
  4. package/dist/codex-app-server.js +104 -7
  5. package/dist/connector.d.ts +1 -0
  6. package/dist/connector.js +21 -12
  7. package/dist/database.d.ts +2 -0
  8. package/dist/native-session-history.d.ts +3 -2
  9. package/dist/native-session-history.js +199 -30
  10. package/dist/opencode-server.d.ts +1 -0
  11. package/dist/opencode-server.js +1 -0
  12. package/dist/runner/abstract-runner.d.ts +11 -2
  13. package/dist/runner/abstract-runner.js +4 -0
  14. package/dist/runner/claude/managed-run-controller.js +3 -0
  15. package/dist/runner/claude-code-runner.d.ts +2 -1
  16. package/dist/runner/claude-code-runner.js +4 -0
  17. package/dist/runner/codex/managed-run-controller.d.ts +9 -0
  18. package/dist/runner/codex/managed-run-controller.js +63 -17
  19. package/dist/runner/codex-runner.d.ts +3 -1
  20. package/dist/runner/codex-runner.js +23 -2
  21. package/dist/runner/opencode/managed-run-controller.js +4 -0
  22. package/dist/runner/opencode-runner.d.ts +10 -3
  23. package/dist/runner/opencode-runner.js +87 -14
  24. package/dist/runner/runner-registry.js +16 -1
  25. package/dist/runtime-command-detector.js +1 -6
  26. package/dist/runtime-state.d.ts +2 -0
  27. package/dist/runtime-state.js +12 -7
  28. package/dist/service/conversation-history-service.d.ts +10 -1
  29. package/dist/service/conversation-history-service.js +272 -42
  30. package/dist/service/conversation-segment-service.js +7 -1
  31. package/dist/service/external-session-resume-service.d.ts +2 -0
  32. package/dist/service/external-session-resume-service.js +45 -34
  33. package/dist/service/native-session-projection-service.d.ts +1 -0
  34. package/dist/service/native-session-projection-service.js +4 -0
  35. package/dist/service/native-session-watch-service.d.ts +26 -2
  36. package/dist/service/native-session-watch-service.js +258 -42
  37. package/dist/service/node-request-service.js +2 -1
  38. package/dist/service/run-event-service.js +16 -5
  39. package/dist/service/run-workbench-service.d.ts +2 -4
  40. package/dist/service/run-workbench-service.js +2 -7
  41. package/dist/service/session-command-service.js +1 -1
  42. package/dist/service/session-identity-service.d.ts +1 -1
  43. package/dist/service/session-identity-service.js +1 -1
  44. package/dist/service/session-lifecycle-service.js +7 -12
  45. package/dist/service/session-message-service.d.ts +1 -1
  46. package/dist/service/session-message-service.js +104 -83
  47. package/dist/service/skill-directory-service.d.ts +1 -0
  48. package/dist/service/skill-directory-service.js +40 -6
  49. package/dist/service/workspace-change-service.js +1 -1
  50. package/dist/service/workspace-domain-state.d.ts +3 -0
  51. package/dist/service/workspace-domain-state.js +1 -0
  52. package/dist/service/workspace-file-service.d.ts +1 -0
  53. package/dist/service/workspace-file-service.js +14 -1
  54. package/dist/service/workspace-queue-workbench-service.d.ts +29 -2
  55. package/dist/service/workspace-queue-workbench-service.js +73 -8
  56. package/dist/service/workspace-watch-service.d.ts +7 -2
  57. package/dist/service/workspace-watch-service.js +32 -6
  58. package/dist/service/workspace-workbench-service.d.ts +14 -0
  59. package/dist/service/workspace-workbench-service.js +215 -16
  60. package/dist/util/personal-instructions.d.ts +2 -0
  61. package/dist/util/personal-instructions.js +31 -0
  62. package/dist/util/runner-native-session-parsers.d.ts +3 -1
  63. package/dist/util/runner-native-session-parsers.js +35 -17
  64. package/dist/workspace.d.ts +14 -8
  65. package/dist/workspace.js +98 -50
  66. 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.watchId}`;
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
- create(workspaceId, watchId, sessionHash, topics) {
47
- const key = `${sessionHash}:${watchId}`;
48
- this.remove(workspaceId, key);
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.expiresAt <= Date.now()) {
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 { renewed: true };
130
+ return this.accepted(watch);
49
131
  }
50
132
  const topics = this.options.watch.parseTopics(input);
51
- const snapshot = {};
52
- if (topics.sessions)
53
- snapshot.sessions = await this.options.listSessions(workspace.id);
54
- if (topics.files !== undefined)
55
- snapshot.files = { files: await this.options.files.list(workspace, topics.files) };
56
- if (topics.changes)
57
- snapshot.changes =
58
- workspace.kind === 'GIT_WORKSPACE' ? await this.options.changes.current(workspace) : null;
59
- if (topics.files !== undefined || topics.changes)
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
- this.options.watch.remove(input.workspaceId, this.options.watch.key(input));
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,2 @@
1
+ import type { RunnerName } from '@myagentroam/protocol';
2
+ export declare function personalInstructionsForRunner(value: unknown, runner: RunnerName): string | undefined;
@@ -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 turnId = `${session.id}:native:${firstIndex}`;
169
- const nativeItems = entries.map(({ entry, index }) => nativeTranscriptConversationItem(session, entry, index, turnId, runtimeTurns, registerImages));
170
- const clientMessageId = nativeItems.flatMap((item) => item.kind === 'user_message' && isPlainRecord(item.payload)
171
- ? [item.payload.clientMessageId]
172
- : [])[0];
173
- const runtimeTurn = typeof clientMessageId === 'string'
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 ?? firstIndex;
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].sort((left, right) => (left.startedAt ?? 0) - (right.startedAt ?? 0));
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 ?? index;
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
- if (runtimeCreatedAt !== null &&
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
- return null;
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)`
@@ -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
- readonly beforePreview?: BinaryDiffPreview;
181
- readonly afterPreview?: BinaryDiffPreview;
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 {