@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.
Files changed (61) 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 +1 -0
  4. package/dist/codex-app-server.js +4 -2
  5. package/dist/connector.d.ts +1 -0
  6. package/dist/connector.js +16 -11
  7. package/dist/native-jsonl-reader.d.ts +21 -0
  8. package/dist/native-jsonl-reader.js +89 -0
  9. package/dist/native-session-history.d.ts +20 -3
  10. package/dist/native-session-history.js +245 -38
  11. package/dist/opencode-server.d.ts +1 -0
  12. package/dist/opencode-server.js +1 -0
  13. package/dist/runner/abstract-runner.d.ts +11 -2
  14. package/dist/runner/abstract-runner.js +4 -0
  15. package/dist/runner/claude/managed-run-controller.js +3 -0
  16. package/dist/runner/claude-code-runner.d.ts +2 -1
  17. package/dist/runner/claude-code-runner.js +4 -0
  18. package/dist/runner/codex/managed-run-controller.js +6 -0
  19. package/dist/runner/codex-runner.d.ts +2 -1
  20. package/dist/runner/codex-runner.js +13 -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.js +1 -0
  27. package/dist/service/conversation-history-service.d.ts +6 -5
  28. package/dist/service/conversation-history-service.js +151 -55
  29. package/dist/service/external-session-resume-service.d.ts +2 -0
  30. package/dist/service/external-session-resume-service.js +45 -34
  31. package/dist/service/native-session-projection-service.d.ts +1 -0
  32. package/dist/service/native-session-projection-service.js +4 -0
  33. package/dist/service/native-session-watch-service.d.ts +18 -0
  34. package/dist/service/native-session-watch-service.js +232 -34
  35. package/dist/service/run-workbench-service.d.ts +2 -4
  36. package/dist/service/run-workbench-service.js +2 -7
  37. package/dist/service/session-command-service.js +1 -1
  38. package/dist/service/session-identity-service.d.ts +1 -1
  39. package/dist/service/session-identity-service.js +1 -1
  40. package/dist/service/session-lifecycle-service.js +3 -0
  41. package/dist/service/session-message-service.d.ts +0 -1
  42. package/dist/service/session-message-service.js +15 -23
  43. package/dist/service/skill-directory-service.d.ts +1 -0
  44. package/dist/service/skill-directory-service.js +40 -6
  45. package/dist/service/workspace-domain-state.d.ts +3 -0
  46. package/dist/service/workspace-domain-state.js +1 -0
  47. package/dist/service/workspace-file-service.d.ts +1 -0
  48. package/dist/service/workspace-file-service.js +14 -1
  49. package/dist/service/workspace-queue-workbench-service.d.ts +17 -0
  50. package/dist/service/workspace-queue-workbench-service.js +66 -6
  51. package/dist/service/workspace-watch-service.d.ts +7 -2
  52. package/dist/service/workspace-watch-service.js +32 -6
  53. package/dist/service/workspace-workbench-service.d.ts +14 -0
  54. package/dist/service/workspace-workbench-service.js +215 -16
  55. package/dist/util/personal-instructions.d.ts +2 -0
  56. package/dist/util/personal-instructions.js +31 -0
  57. package/dist/util/runner-native-session-parsers.d.ts +1 -0
  58. package/dist/util/runner-native-session-parsers.js +76 -12
  59. package/dist/workspace.d.ts +14 -8
  60. package/dist/workspace.js +65 -42
  61. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { isImageAttachment, isRunnerImageAttachment, parseComposerAttachmentIds, parseMessageAttachments, sessionTitleFromFirstMessage, withNonImageAttachmentPrompt } from '../util/node-operation-parsers.js';
3
3
  import { parseSecretEnvironment } from '../util/secret-environment.js';
4
+ import { personalInstructionsForRunner } from '../util/personal-instructions.js';
4
5
  export class SessionMessageService {
5
6
  options;
6
7
  pendingMessages = new Map();
@@ -29,11 +30,9 @@ export class SessionMessageService {
29
30
  if (inlineAttachments.length > 0 && attachmentIds.length > 0)
30
31
  throw new Error('MESSAGE_ATTACHMENTS_INVALID');
31
32
  const session = await this.options.resolve(input.sessionId);
32
- const intent = input.deliveryIntent;
33
- if (intent !== undefined &&
34
- intent !== 'SEND' &&
35
- intent !== 'QUEUE' &&
36
- intent !== 'REPLACE_CURRENT')
33
+ const personalInstructions = personalInstructionsForRunner(input.personalInstructions, session.runner);
34
+ const intent = input.deliveryIntent ?? 'SEND';
35
+ if (intent !== 'SEND' && intent !== 'QUEUE' && intent !== 'REPLACE_CURRENT')
37
36
  throw new Error('MESSAGE_INVALID');
38
37
  const existing = this.options.runtime.findMessageRun(session.id, clientMessageId);
39
38
  if (existing !== undefined)
@@ -67,8 +66,8 @@ export class SessionMessageService {
67
66
  .listRunsForSession(session.id)
68
67
  .find((run) => run.status === 'STARTING' || run.status === 'RUNNING');
69
68
  const replaceCurrentRun = intent === 'REPLACE_CURRENT' ? active : undefined;
70
- const queueWasPaused = this.options.runtime.sessionQueue(session.id).paused;
71
- const queuedBeforeCreate = this.options.runtime.hasActiveSessionLease(session.id) || intent === 'QUEUE';
69
+ const queueBeforeCreate = this.options.runtime.sessionQueue(session.id);
70
+ const leaseWasActive = this.options.runtime.hasActiveSessionLease(session.id);
72
71
  const created = this.options.queue.enqueue({
73
72
  runId,
74
73
  sessionId: session.id,
@@ -84,6 +83,7 @@ export class SessionMessageService {
84
83
  access: session.access,
85
84
  collaborationMode: planActive ? 'plan' : 'default',
86
85
  secretEnvironment,
86
+ ...(personalInstructions === undefined ? {} : { personalInstructions }),
87
87
  ...(runner.serviceTier() === 'fast' ? { serviceTier: 'fast' } : {})
88
88
  }, {
89
89
  runId,
@@ -109,25 +109,17 @@ export class SessionMessageService {
109
109
  this.options.emitSession(acceptedSession);
110
110
  this.options.attachments.cache(created.run.id, imageAttachments);
111
111
  void this.options.uploads.consume(attachmentIds).catch(() => undefined);
112
- if (queueWasPaused && active === undefined && intent !== 'QUEUE')
113
- this.options.runtime.prioritizeQueuedRun(created.run.id);
114
- if (!queueWasPaused || intent !== 'QUEUE')
115
- this.options.runtime.resumeSessionQueue(session.id);
116
- if (replaceCurrentRun !== undefined)
117
- this.options.queue.replace(created.run.id, replaceCurrentRun.id);
118
- else if (intent !== 'QUEUE' ||
119
- !queuedBeforeCreate ||
120
- !this.options.runtime.hasActiveSessionLease(session.id))
121
- this.options.queue.schedule(session.id);
122
- this.options.emitQueue(session.workspaceId, session.id);
112
+ const delivery = this.options.queue.settleEnqueue({
113
+ runId: created.run.id,
114
+ intent,
115
+ queueBefore: queueBeforeCreate,
116
+ leaseWasActive,
117
+ ...(replaceCurrentRun === undefined ? {} : { activeRunId: replaceCurrentRun.id })
118
+ });
123
119
  return {
124
120
  session: this.options.present(acceptedSession),
125
121
  run: created.run,
126
- delivery: replaceCurrentRun !== undefined
127
- ? 'RESTARTING'
128
- : queuedBeforeCreate
129
- ? 'QUEUED'
130
- : 'STARTED',
122
+ delivery,
131
123
  idempotent: false
132
124
  };
133
125
  }
@@ -4,6 +4,7 @@ export interface LocalSkillInspection {
4
4
  readonly name: string;
5
5
  readonly description: string;
6
6
  readonly localStatus: LocalSkillStatus;
7
+ readonly skillFilePath: string;
7
8
  readonly install?: SkillInstallMetadata;
8
9
  }
9
10
  export interface LocalSkillTargetInspection {
@@ -53,13 +53,25 @@ async function scanRoot(root, source) {
53
53
  const itemPath = path.join(root, entry.name);
54
54
  const stat = await lstat(itemPath);
55
55
  if (stat.isSymbolicLink() || (!stat.isDirectory() && process.platform === 'win32')) {
56
- results.push({ name: entry.name, description: '', localStatus: 'INVALID_LINK', source });
56
+ results.push({
57
+ name: entry.name,
58
+ description: '',
59
+ localStatus: 'INVALID_LINK',
60
+ skillFilePath: path.join(itemPath, 'SKILL.md'),
61
+ source
62
+ });
57
63
  continue;
58
64
  }
59
65
  if (!stat.isDirectory())
60
66
  continue;
61
67
  if (!SKILL_NAME.test(entry.name) || WINDOWS_DEVICE_NAME.test(entry.name)) {
62
- results.push({ name: entry.name, description: '', localStatus: 'INVALID', source });
68
+ results.push({
69
+ name: entry.name,
70
+ description: '',
71
+ localStatus: 'INVALID',
72
+ skillFilePath: path.join(itemPath, 'SKILL.md'),
73
+ source
74
+ });
63
75
  continue;
64
76
  }
65
77
  results.push({ ...(await inspectSkill(itemPath, entry.name)), source });
@@ -72,17 +84,28 @@ async function inspectSkill(directory, directoryName) {
72
84
  if (frontmatter.name !== directoryName ||
73
85
  !SKILL_NAME.test(frontmatter.name) ||
74
86
  WINDOWS_DEVICE_NAME.test(frontmatter.name))
75
- return { name: directoryName, description: frontmatter.description, localStatus: 'INVALID' };
87
+ return {
88
+ name: directoryName,
89
+ description: frontmatter.description,
90
+ localStatus: 'INVALID',
91
+ skillFilePath: path.join(directory, 'SKILL.md')
92
+ };
76
93
  const install = await readInstallMetadata(path.join(directory, '.mar-skill-install.json'));
77
94
  return {
78
95
  name: directoryName,
79
96
  description: frontmatter.description,
80
97
  localStatus: install === undefined ? 'UNKNOWN_VERSION' : 'VALID',
98
+ skillFilePath: path.join(directory, 'SKILL.md'),
81
99
  ...(install === undefined ? {} : { install })
82
100
  };
83
101
  }
84
102
  catch {
85
- return { name: directoryName, description: '', localStatus: 'INVALID' };
103
+ return {
104
+ name: directoryName,
105
+ description: '',
106
+ localStatus: 'INVALID',
107
+ skillFilePath: path.join(directory, 'SKILL.md')
108
+ };
86
109
  }
87
110
  }
88
111
  async function readInstallMetadata(file) {
@@ -149,7 +172,12 @@ function mergeWorkspaceCopies(agents, claude) {
149
172
  };
150
173
  if (copies.agents.localStatus !== copies.claude.localStatus ||
151
174
  JSON.stringify(copies.agents.install) !== JSON.stringify(copies.claude.install))
152
- return { name, description: copies.agents.description, localStatus: 'PARTIAL' };
175
+ return {
176
+ name,
177
+ description: copies.agents.description,
178
+ localStatus: 'PARTIAL',
179
+ skillFilePath: copies.agents.skillFilePath
180
+ };
153
181
  return withoutSource(copies.agents);
154
182
  });
155
183
  }
@@ -167,7 +195,12 @@ function mergeNodeCompatibilityRoots(agents, claude) {
167
195
  if (copies.agents.description !== copies.claude.description ||
168
196
  copies.agents.localStatus !== copies.claude.localStatus ||
169
197
  JSON.stringify(copies.agents.install) !== JSON.stringify(copies.claude.install))
170
- return { name, description: copies.agents.description, localStatus: 'PARTIAL' };
198
+ return {
199
+ name,
200
+ description: copies.agents.description,
201
+ localStatus: 'PARTIAL',
202
+ skillFilePath: copies.agents.skillFilePath
203
+ };
171
204
  return withoutSource(copies.agents);
172
205
  });
173
206
  }
@@ -176,6 +209,7 @@ function withoutSource(entry) {
176
209
  name: entry.name,
177
210
  description: entry.description,
178
211
  localStatus: entry.localStatus,
212
+ skillFilePath: entry.skillFilePath,
179
213
  ...(entry.install === undefined ? {} : { install: entry.install })
180
214
  };
181
215
  }
@@ -5,6 +5,8 @@ export interface WorkspaceWatch {
5
5
  readonly workspaceId: string;
6
6
  readonly watchId: string;
7
7
  readonly sessionHash: string;
8
+ readonly workbenchConnectionId: string;
9
+ readonly nodeGeneration: string;
8
10
  expiresAt: number;
9
11
  readonly topics: {
10
12
  readonly sessions: boolean;
@@ -56,6 +58,7 @@ export declare class WorkspaceDomainState {
56
58
  readonly fileIndexes: Map<string, WorkspaceFileIndex>;
57
59
  readonly watches: Map<string, Map<string, WorkspaceWatch>>;
58
60
  readonly watchRevisions: Map<string, number>;
61
+ watchSnapshotSequence: number;
59
62
  readonly sessionWatchTimers: Map<string, NodeJS.Timeout>;
60
63
  readonly sessionWatchSignatures: Map<string, string>;
61
64
  readonly sessionWatchReads: Map<string, Promise<void>>;
@@ -7,6 +7,7 @@ export class WorkspaceDomainState {
7
7
  fileIndexes = new Map();
8
8
  watches = new Map();
9
9
  watchRevisions = new Map();
10
+ watchSnapshotSequence = 0;
10
11
  sessionWatchTimers = new Map();
11
12
  sessionWatchSignatures = new Map();
12
13
  sessionWatchReads = new Map();
@@ -9,6 +9,7 @@ export declare class WorkspaceFileService {
9
9
  constructor(state: WorkspaceDomainState, allowedRoots: () => readonly string[], onInvalidated: (workspaceId: string) => void);
10
10
  list(workspace: NodeWorkspace, input: Record<string, unknown>): Promise<unknown>;
11
11
  read(workspace: NodeWorkspace, input: Record<string, unknown>): Promise<unknown>;
12
+ readContent(workspace: NodeWorkspace, input: Record<string, unknown>): Promise<unknown>;
12
13
  search(workspace: NodeWorkspace, input: Record<string, unknown>): Promise<unknown>;
13
14
  mutate(workspace: NodeWorkspace, input: Record<string, unknown>): Promise<unknown>;
14
15
  private serializeMutation;
@@ -1,5 +1,5 @@
1
1
  import { isAbsolute } from 'node:path';
2
- import { listWorkspaceFiles, mutateWorkspaceFile, readAllowedTextFile, readWorkspaceTextFile, WorkspaceFileIndex } from '../workspace.js';
2
+ import { listWorkspaceFiles, mutateWorkspaceFile, readWorkspaceFileContentRange, readAllowedTextFile, readWorkspaceTextFile, WorkspaceFileIndex } from '../workspace.js';
3
3
  const INDEX_CACHE_LIMIT = 8;
4
4
  export class WorkspaceFileService {
5
5
  state;
@@ -36,6 +36,19 @@ export class WorkspaceFileService {
36
36
  ? readAllowedTextFile(input.path, this.allowedRoots(), offset, limit)
37
37
  : readWorkspaceTextFile(workspace.path, input.path, offset, limit);
38
38
  }
39
+ async readContent(workspace, input) {
40
+ if (typeof input.path !== 'string' ||
41
+ (input.offset !== undefined &&
42
+ (!Number.isInteger(input.offset) || input.offset < 0)) ||
43
+ (input.limit !== undefined &&
44
+ (!Number.isInteger(input.limit) ||
45
+ input.limit < 1 ||
46
+ input.limit > 512 * 1024)))
47
+ throw new Error('FILE_RANGE_INVALID');
48
+ if (isAbsolute(input.path))
49
+ throw new Error('FILE_PATH_ESCAPE');
50
+ return readWorkspaceFileContentRange(workspace.path, input.path, typeof input.offset === 'number' ? input.offset : 0, typeof input.limit === 'number' ? input.limit : 512 * 1024);
51
+ }
39
52
  async search(workspace, input) {
40
53
  if (typeof input.query !== 'string' ||
41
54
  (input.cursor !== undefined && typeof input.cursor !== 'string') ||
@@ -18,6 +18,17 @@ export interface QueuedRunStart<TAttachment = unknown> {
18
18
  readonly collaborationMode: 'default' | 'plan';
19
19
  readonly serviceTier?: 'fast';
20
20
  readonly secretEnvironment: Readonly<Record<string, string>>;
21
+ readonly personalInstructions?: string;
22
+ }
23
+ interface EnqueuedRunDisposition {
24
+ readonly runId: string;
25
+ readonly intent: 'SEND' | 'QUEUE' | 'REPLACE_CURRENT';
26
+ readonly queueBefore: {
27
+ readonly paused: boolean;
28
+ readonly runIds: readonly string[];
29
+ };
30
+ readonly leaseWasActive: boolean;
31
+ readonly activeRunId?: string;
21
32
  }
22
33
  export interface WorkspaceQueueWorkbenchServiceOptions {
23
34
  readonly runtime: NodeRuntimeState;
@@ -35,6 +46,7 @@ export interface WorkspaceQueueWorkbenchServiceOptions {
35
46
  export declare class WorkspaceQueueWorkbenchService<TAttachment> {
36
47
  private readonly options;
37
48
  private readonly starts;
49
+ private readonly scheduledSessions;
38
50
  constructor(options: WorkspaceQueueWorkbenchServiceOptions);
39
51
  enqueue(start: QueuedRunStart<TAttachment>, message: {
40
52
  readonly runId: string;
@@ -50,7 +62,10 @@ export declare class WorkspaceQueueWorkbenchService<TAttachment> {
50
62
  pending(runId: string): QueuedRunStart<TAttachment> | undefined;
51
63
  remove(runId: string): void;
52
64
  clear(): void;
65
+ publish(sessionId: string): void;
53
66
  migrateSession(previousId: string, nextId: string): void;
67
+ cancelQueued(runId: string): void;
68
+ pauseForInterrupt(sessionId: string): void;
54
69
  operations(): Readonly<Record<string, NodeOperationHandler>>;
55
70
  private get;
56
71
  private pauseOrResume;
@@ -62,7 +77,9 @@ export declare class WorkspaceQueueWorkbenchService<TAttachment> {
62
77
  private requireRun;
63
78
  replace(queuedRunId: string, activeRunId: string, prioritize?: boolean): void;
64
79
  schedule(sessionId: string): void;
80
+ settleEnqueue(input: EnqueuedRunDisposition): 'STARTED' | 'QUEUED' | 'RESTARTING';
65
81
  dispatch(sessionId: string): Promise<void>;
66
82
  present(workspaceId: string, sessionId: string): unknown;
67
83
  private updatePending;
68
84
  }
85
+ export {};
@@ -5,6 +5,7 @@ import { safeErrorCode } from '../util/safe-error.js';
5
5
  export class WorkspaceQueueWorkbenchService {
6
6
  options;
7
7
  starts = new Map();
8
+ scheduledSessions = new Map();
8
9
  constructor(options) {
9
10
  this.options = options;
10
11
  }
@@ -24,12 +25,44 @@ export class WorkspaceQueueWorkbenchService {
24
25
  }
25
26
  clear() {
26
27
  this.starts.clear();
28
+ for (const timer of this.scheduledSessions.values())
29
+ clearTimeout(timer);
30
+ this.scheduledSessions.clear();
31
+ }
32
+ publish(sessionId) {
33
+ const session = this.options.runtime.getAgentSession(sessionId);
34
+ if (session === undefined)
35
+ throw new Error('SESSION_NOT_FOUND');
36
+ this.options.emit(session.workspaceId, session.id);
27
37
  }
28
38
  migrateSession(previousId, nextId) {
29
39
  for (const [runId, pending] of this.starts) {
30
40
  if (pending.sessionId === previousId)
31
41
  this.starts.set(runId, { ...pending, sessionId: nextId, externalSessionId: nextId });
32
42
  }
43
+ const timer = this.scheduledSessions.get(previousId);
44
+ if (timer !== undefined) {
45
+ clearTimeout(timer);
46
+ this.scheduledSessions.delete(previousId);
47
+ this.schedule(nextId);
48
+ }
49
+ }
50
+ cancelQueued(runId) {
51
+ const run = this.requireRun(runId);
52
+ if (!this.options.runtime.isQueuedRun(run.id))
53
+ throw new Error('QUEUE_ITEM_INVALID');
54
+ this.options.runtime.deleteQueuedRun(run.id);
55
+ this.remove(run.id);
56
+ this.options.clearImages(run.id);
57
+ this.options.cleanupAttachments(run.id);
58
+ this.options.emit(run.workspaceId, run.sessionId);
59
+ }
60
+ pauseForInterrupt(sessionId) {
61
+ const session = this.options.runtime.getAgentSession(sessionId);
62
+ if (session === undefined)
63
+ throw new Error('SESSION_NOT_FOUND');
64
+ this.options.runtime.pauseSessionQueue(session.id);
65
+ this.publish(session.id);
33
66
  }
34
67
  operations() {
35
68
  return {
@@ -82,11 +115,7 @@ export class WorkspaceQueueWorkbenchService {
82
115
  if (typeof input.runId !== 'string')
83
116
  throw new Error('QUEUE_INVALID');
84
117
  const run = this.requireRun(input.runId);
85
- this.options.runtime.deleteQueuedRun(run.id);
86
- this.remove(run.id);
87
- this.options.clearImages(run.id);
88
- this.options.cleanupAttachments(run.id);
89
- this.options.emit(run.workspaceId, run.sessionId);
118
+ this.cancelQueued(run.id);
90
119
  return { queue: this.present(run.workspaceId, run.sessionId) };
91
120
  }
92
121
  execute(input) {
@@ -105,7 +134,6 @@ export class WorkspaceQueueWorkbenchService {
105
134
  .listRunsForSession(session.id)
106
135
  .find((run) => run.status === 'STARTING' || run.status === 'RUNNING');
107
136
  this.options.runtime.prioritizeQueuedRun(queued.id);
108
- this.options.emit(queued.workspaceId, session.id);
109
137
  if (active !== undefined) {
110
138
  this.replace(queued.id, active.id, false);
111
139
  return { run: queued, delivery: 'RESTARTING' };
@@ -137,17 +165,46 @@ export class WorkspaceQueueWorkbenchService {
137
165
  throw new Error('QUEUE_ITEM_INVALID');
138
166
  if (prioritize)
139
167
  this.options.runtime.prioritizeQueuedRun(queued.id);
168
+ this.options.emit(queued.workspaceId, queued.sessionId);
140
169
  this.options.markInterrupted(active.id);
141
170
  this.options.runtime.transitionRun(active.id, 'CANCELLING');
142
171
  this.options.control('run.interrupt', { runId: active.id });
143
172
  }
144
173
  schedule(sessionId) {
174
+ if (this.scheduledSessions.has(sessionId))
175
+ return;
145
176
  const timer = setTimeout(() => {
177
+ this.scheduledSessions.delete(sessionId);
146
178
  if (!this.options.stopped())
147
179
  void this.dispatch(sessionId);
148
180
  }, 0);
181
+ this.scheduledSessions.set(sessionId, timer);
149
182
  timer.unref();
150
183
  }
184
+ settleEnqueue(input) {
185
+ const run = this.requireRun(input.runId);
186
+ const session = this.options.runtime.getAgentSession(run.sessionId);
187
+ if (session === undefined)
188
+ throw new Error('SESSION_NOT_FOUND');
189
+ if (input.queueBefore.paused && input.activeRunId === undefined && input.intent !== 'QUEUE')
190
+ this.options.runtime.prioritizeQueuedRun(run.id);
191
+ if (!input.queueBefore.paused || input.intent !== 'QUEUE')
192
+ this.options.runtime.resumeSessionQueue(session.id);
193
+ if (input.activeRunId !== undefined) {
194
+ this.replace(run.id, input.activeRunId);
195
+ return 'RESTARTING';
196
+ }
197
+ const leaseIsActive = input.leaseWasActive && this.options.runtime.hasActiveSessionLease(session.id);
198
+ const dispatchNow = !leaseIsActive && !(input.queueBefore.paused && input.intent === 'QUEUE');
199
+ const startsNow = dispatchNow &&
200
+ (input.queueBefore.runIds.length === 0 ||
201
+ (input.queueBefore.paused && input.intent !== 'QUEUE'));
202
+ if (dispatchNow)
203
+ this.schedule(session.id);
204
+ else
205
+ this.options.emit(session.workspaceId, session.id);
206
+ return startsNow ? 'STARTED' : 'QUEUED';
207
+ }
151
208
  async dispatch(sessionId) {
152
209
  const run = this.options.runtime.claimNextQueuedRun(sessionId);
153
210
  if (run === undefined)
@@ -186,6 +243,9 @@ export class WorkspaceQueueWorkbenchService {
186
243
  collaborationMode: pending.collaborationMode,
187
244
  ...(pending.serviceTier === undefined ? {} : { serviceTier: pending.serviceTier }),
188
245
  secretEnvironment: pending.secretEnvironment,
246
+ ...(pending.personalInstructions === undefined
247
+ ? {}
248
+ : { personalInstructions: pending.personalInstructions }),
189
249
  mcpInstallations: this.options.mcps(pending.workspaceId)
190
250
  });
191
251
  }
@@ -7,12 +7,17 @@ export declare class WorkspaceWatchService {
7
7
  parseTopics(input: Record<string, unknown>): WorkspaceWatch['topics'];
8
8
  key(input: Record<string, unknown>): string;
9
9
  sessionHash(input: Record<string, unknown>): string;
10
- create(workspaceId: string, watchId: string, sessionHash: string, topics: WorkspaceWatch['topics']): WorkspaceWatch;
11
- renew(workspaceId: string, key: string): WorkspaceWatch | undefined;
10
+ connectionId(input: Record<string, unknown>): string;
11
+ create(workspaceId: string, watchId: string, sessionHash: string, workbenchConnectionId: string, nodeGeneration: string, topics: WorkspaceWatch['topics']): WorkspaceWatch;
12
+ renew(workspaceId: string, key: string, watchId?: string): WorkspaceWatch | undefined;
13
+ matches(workspaceId: string, key: string, watchId: string): boolean;
14
+ existing(workspaceId: string, key: string): WorkspaceWatch | undefined;
12
15
  remove(workspaceId: string, key: string): void;
13
16
  clear(workspaceId: string): void;
14
17
  clearAll(): void;
15
18
  nextRevision(workspaceId: string): number;
19
+ nextSnapshotSequence(): number;
16
20
  topicActive(workspaceId: string, topic: 'files' | 'changes' | 'sessions'): boolean;
21
+ activeWatches(workspaceId: string): readonly WorkspaceWatch[];
17
22
  private expiryTimer;
18
23
  }
@@ -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
  }