@myagentroam/node 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +9 -0
- package/dist/codex-app-server.d.ts +1 -0
- package/dist/codex-app-server.js +4 -2
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +16 -11
- package/dist/native-session-history.d.ts +3 -2
- package/dist/native-session-history.js +199 -30
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +1 -0
- package/dist/runner/abstract-runner.d.ts +11 -2
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +3 -0
- package/dist/runner/claude-code-runner.d.ts +2 -1
- package/dist/runner/claude-code-runner.js +4 -0
- package/dist/runner/codex/managed-run-controller.js +6 -0
- package/dist/runner/codex-runner.d.ts +2 -1
- package/dist/runner/codex-runner.js +11 -1
- package/dist/runner/opencode/managed-run-controller.js +4 -0
- package/dist/runner/opencode-runner.d.ts +10 -3
- package/dist/runner/opencode-runner.js +87 -14
- package/dist/runner/runner-registry.js +16 -1
- package/dist/runtime-command-detector.js +1 -6
- package/dist/runtime-state.js +1 -0
- package/dist/service/conversation-history-service.d.ts +5 -4
- package/dist/service/conversation-history-service.js +110 -52
- package/dist/service/external-session-resume-service.d.ts +2 -0
- package/dist/service/external-session-resume-service.js +45 -34
- package/dist/service/native-session-projection-service.d.ts +1 -0
- package/dist/service/native-session-projection-service.js +4 -0
- package/dist/service/native-session-watch-service.d.ts +18 -0
- package/dist/service/native-session-watch-service.js +232 -34
- package/dist/service/run-workbench-service.d.ts +2 -4
- package/dist/service/run-workbench-service.js +2 -7
- package/dist/service/session-command-service.js +1 -1
- package/dist/service/session-identity-service.d.ts +1 -1
- package/dist/service/session-identity-service.js +1 -1
- package/dist/service/session-lifecycle-service.js +3 -0
- package/dist/service/session-message-service.d.ts +0 -1
- package/dist/service/session-message-service.js +15 -23
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-domain-state.d.ts +3 -0
- package/dist/service/workspace-domain-state.js +1 -0
- package/dist/service/workspace-file-service.d.ts +1 -0
- package/dist/service/workspace-file-service.js +14 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +17 -0
- package/dist/service/workspace-queue-workbench-service.js +66 -6
- package/dist/service/workspace-watch-service.d.ts +7 -2
- package/dist/service/workspace-watch-service.js +32 -6
- package/dist/service/workspace-workbench-service.d.ts +14 -0
- package/dist/service/workspace-workbench-service.js +215 -16
- package/dist/util/personal-instructions.d.ts +2 -0
- package/dist/util/personal-instructions.js +31 -0
- package/dist/util/runner-native-session-parsers.d.ts +1 -0
- package/dist/util/runner-native-session-parsers.js +8 -1
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +65 -42
- package/package.json +2 -2
|
@@ -1,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
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
71
|
-
const
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
127
|
-
? 'RESTARTING'
|
|
128
|
-
: queuedBeforeCreate
|
|
129
|
-
? 'QUEUED'
|
|
130
|
-
: 'STARTED',
|
|
122
|
+
delivery,
|
|
131
123
|
idempotent: false
|
|
132
124
|
};
|
|
133
125
|
}
|
|
@@ -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({
|
|
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({
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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>>;
|
|
@@ -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.
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
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
|
}
|