@myagentroam/node 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +9 -0
- package/dist/codex-app-server.d.ts +25 -0
- package/dist/codex-app-server.js +104 -7
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +21 -12
- package/dist/database.d.ts +2 -0
- package/dist/native-session-history.d.ts +3 -2
- package/dist/native-session-history.js +199 -30
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +1 -0
- package/dist/runner/abstract-runner.d.ts +11 -2
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +3 -0
- package/dist/runner/claude-code-runner.d.ts +2 -1
- package/dist/runner/claude-code-runner.js +4 -0
- package/dist/runner/codex/managed-run-controller.d.ts +9 -0
- package/dist/runner/codex/managed-run-controller.js +63 -17
- package/dist/runner/codex-runner.d.ts +3 -1
- package/dist/runner/codex-runner.js +23 -2
- package/dist/runner/opencode/managed-run-controller.js +4 -0
- package/dist/runner/opencode-runner.d.ts +10 -3
- package/dist/runner/opencode-runner.js +87 -14
- package/dist/runner/runner-registry.js +16 -1
- package/dist/runtime-command-detector.js +1 -6
- package/dist/runtime-state.d.ts +2 -0
- package/dist/runtime-state.js +12 -7
- package/dist/service/conversation-history-service.d.ts +10 -1
- package/dist/service/conversation-history-service.js +272 -42
- package/dist/service/conversation-segment-service.js +7 -1
- package/dist/service/external-session-resume-service.d.ts +2 -0
- package/dist/service/external-session-resume-service.js +45 -34
- package/dist/service/native-session-projection-service.d.ts +1 -0
- package/dist/service/native-session-projection-service.js +4 -0
- package/dist/service/native-session-watch-service.d.ts +26 -2
- package/dist/service/native-session-watch-service.js +258 -42
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-event-service.js +16 -5
- package/dist/service/run-workbench-service.d.ts +2 -4
- package/dist/service/run-workbench-service.js +2 -7
- package/dist/service/session-command-service.js +1 -1
- package/dist/service/session-identity-service.d.ts +1 -1
- package/dist/service/session-identity-service.js +1 -1
- package/dist/service/session-lifecycle-service.js +7 -12
- package/dist/service/session-message-service.d.ts +1 -1
- package/dist/service/session-message-service.js +104 -83
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-change-service.js +1 -1
- package/dist/service/workspace-domain-state.d.ts +3 -0
- package/dist/service/workspace-domain-state.js +1 -0
- package/dist/service/workspace-file-service.d.ts +1 -0
- package/dist/service/workspace-file-service.js +14 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +29 -2
- package/dist/service/workspace-queue-workbench-service.js +73 -8
- package/dist/service/workspace-watch-service.d.ts +7 -2
- package/dist/service/workspace-watch-service.js +32 -6
- package/dist/service/workspace-workbench-service.d.ts +14 -0
- package/dist/service/workspace-workbench-service.js +215 -16
- package/dist/util/personal-instructions.d.ts +2 -0
- package/dist/util/personal-instructions.js +31 -0
- package/dist/util/runner-native-session-parsers.d.ts +3 -1
- package/dist/util/runner-native-session-parsers.js +35 -17
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +98 -50
- package/package.json +2 -2
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { isImageAttachment, isRunnerImageAttachment, parseComposerAttachmentIds, parseMessageAttachments, sessionTitleFromFirstMessage, withNonImageAttachmentPrompt } from '../util/node-operation-parsers.js';
|
|
2
3
|
import { parseSecretEnvironment } from '../util/secret-environment.js';
|
|
4
|
+
import { personalInstructionsForRunner } from '../util/personal-instructions.js';
|
|
3
5
|
export class SessionMessageService {
|
|
4
6
|
options;
|
|
7
|
+
pendingMessages = new Map();
|
|
5
8
|
constructor(options) {
|
|
6
9
|
this.options = options;
|
|
7
10
|
}
|
|
@@ -19,20 +22,19 @@ export class SessionMessageService {
|
|
|
19
22
|
typeof input.content !== 'string' ||
|
|
20
23
|
input.content.length === 0)
|
|
21
24
|
throw new Error('MESSAGE_INVALID');
|
|
25
|
+
const clientMessageId = input.clientMessageId;
|
|
26
|
+
const content = input.content;
|
|
22
27
|
const inlineAttachments = parseMessageAttachments(input.attachments);
|
|
23
28
|
const secretEnvironment = parseSecretEnvironment(input.secretEnvironment);
|
|
24
29
|
const attachmentIds = parseComposerAttachmentIds(input.attachmentIds);
|
|
25
30
|
if (inlineAttachments.length > 0 && attachmentIds.length > 0)
|
|
26
31
|
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
27
|
-
const session =
|
|
28
|
-
|
|
29
|
-
const intent = input.deliveryIntent;
|
|
30
|
-
if (intent !==
|
|
31
|
-
intent !== 'SEND' &&
|
|
32
|
-
intent !== 'QUEUE' &&
|
|
33
|
-
intent !== 'REPLACE_CURRENT')
|
|
32
|
+
const session = await this.options.resolve(input.sessionId);
|
|
33
|
+
const personalInstructions = personalInstructionsForRunner(input.personalInstructions, session.runner);
|
|
34
|
+
const intent = input.deliveryIntent ?? 'SEND';
|
|
35
|
+
if (intent !== 'SEND' && intent !== 'QUEUE' && intent !== 'REPLACE_CURRENT')
|
|
34
36
|
throw new Error('MESSAGE_INVALID');
|
|
35
|
-
const existing = this.options.runtime.findMessageRun(session.id,
|
|
37
|
+
const existing = this.options.runtime.findMessageRun(session.id, clientMessageId);
|
|
36
38
|
if (existing !== undefined)
|
|
37
39
|
return {
|
|
38
40
|
session: this.options.present(session),
|
|
@@ -40,84 +42,103 @@ export class SessionMessageService {
|
|
|
40
42
|
delivery: this.options.runtime.isQueuedRun(existing.id) ? 'QUEUED' : 'STARTED',
|
|
41
43
|
idempotent: true
|
|
42
44
|
};
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
45
|
+
const pendingKey = `${session.id}\u0000${clientMessageId}`;
|
|
46
|
+
const pending = this.pendingMessages.get(pendingKey);
|
|
47
|
+
if (pending !== undefined)
|
|
48
|
+
return pending;
|
|
49
|
+
const operation = (async () => {
|
|
50
|
+
const runId = randomUUID();
|
|
51
|
+
let enqueued = false;
|
|
52
|
+
try {
|
|
53
|
+
const uploaded = await this.options.uploads.resolve(session.workspaceId, attachmentIds);
|
|
54
|
+
const attachments = attachmentIds.length === 0
|
|
55
|
+
? inlineAttachments
|
|
56
|
+
: uploaded.map((attachment) => attachment.attachment);
|
|
57
|
+
const imageAttachments = attachments.filter(isImageAttachment);
|
|
58
|
+
const runnerImageAttachments = attachments.filter(isRunnerImageAttachment);
|
|
59
|
+
const attachmentPaths = await this.persistAttachments(session, runId, attachments);
|
|
60
|
+
const runnerInput = withNonImageAttachmentPrompt(content, attachmentPaths);
|
|
61
|
+
const planActive = this.options.commands
|
|
62
|
+
.list(session.id)
|
|
63
|
+
.some((state) => state.commandId === 'plan');
|
|
64
|
+
const runner = this.options.runners.require(session.runner);
|
|
65
|
+
const active = this.options.runtime
|
|
66
|
+
.listRunsForSession(session.id)
|
|
67
|
+
.find((run) => run.status === 'STARTING' || run.status === 'RUNNING');
|
|
68
|
+
const replaceCurrentRun = intent === 'REPLACE_CURRENT' ? active : undefined;
|
|
69
|
+
const queueBeforeCreate = this.options.runtime.sessionQueue(session.id);
|
|
70
|
+
const leaseWasActive = this.options.runtime.hasActiveSessionLease(session.id);
|
|
71
|
+
const created = this.options.queue.enqueue({
|
|
72
|
+
runId,
|
|
73
|
+
sessionId: session.id,
|
|
74
|
+
workspaceId: session.workspaceId,
|
|
75
|
+
runner: session.runner,
|
|
76
|
+
input: runner.prepareMessageInput(runnerInput, planActive ? 'plan' : 'default'),
|
|
77
|
+
attachments: runnerImageAttachments,
|
|
78
|
+
attachmentPaths,
|
|
79
|
+
cwd: session.cwd,
|
|
80
|
+
externalSessionId: session.externalSessionId,
|
|
81
|
+
model: session.model,
|
|
82
|
+
effort: session.effort,
|
|
83
|
+
access: session.access,
|
|
84
|
+
collaborationMode: planActive ? 'plan' : 'default',
|
|
85
|
+
secretEnvironment,
|
|
86
|
+
...(personalInstructions === undefined ? {} : { personalInstructions }),
|
|
87
|
+
...(runner.serviceTier() === 'fast' ? { serviceTier: 'fast' } : {})
|
|
88
|
+
}, {
|
|
89
|
+
runId,
|
|
90
|
+
sessionId: session.id,
|
|
91
|
+
clientMessageId,
|
|
92
|
+
content: runnerInput,
|
|
93
|
+
...(Number.isSafeInteger(input.__requestUserId)
|
|
94
|
+
? { initiatedByUserId: input.__requestUserId }
|
|
95
|
+
: {}),
|
|
96
|
+
...(attachments.length === 0 ? {} : { attachments })
|
|
97
|
+
});
|
|
98
|
+
enqueued = created.created;
|
|
99
|
+
if (!created.created) {
|
|
100
|
+
this.options.attachments.cleanup(runId);
|
|
101
|
+
return {
|
|
102
|
+
session: this.options.present(session),
|
|
103
|
+
run: created.run,
|
|
104
|
+
delivery: this.options.runtime.isQueuedRun(created.run.id) ? 'QUEUED' : 'STARTED',
|
|
105
|
+
idempotent: true
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const acceptedSession = this.options.runtime.setTemporaryTitleIfMissing(session.id, sessionTitleFromFirstMessage(content));
|
|
109
|
+
this.options.emitSession(acceptedSession);
|
|
110
|
+
this.options.attachments.cache(created.run.id, imageAttachments);
|
|
111
|
+
void this.options.uploads.consume(attachmentIds).catch(() => undefined);
|
|
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
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
session: this.options.present(acceptedSession),
|
|
121
|
+
run: created.run,
|
|
122
|
+
delivery,
|
|
123
|
+
idempotent: false
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (!enqueued) {
|
|
128
|
+
this.options.attachments.clear(runId);
|
|
129
|
+
this.options.attachments.cleanup(runId);
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
this.pendingMessages.set(pendingKey, operation);
|
|
70
135
|
try {
|
|
71
|
-
|
|
72
|
-
await this.options.uploads.consume(attachmentIds);
|
|
136
|
+
return await operation;
|
|
73
137
|
}
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
this.options.attachments.cleanup(created.run.id);
|
|
78
|
-
throw error;
|
|
138
|
+
finally {
|
|
139
|
+
if (this.pendingMessages.get(pendingKey) === operation)
|
|
140
|
+
this.pendingMessages.delete(pendingKey);
|
|
79
141
|
}
|
|
80
|
-
const runnerInput = withNonImageAttachmentPrompt(input.content, attachmentPaths);
|
|
81
|
-
this.options.runtime.updateQueuedMessage(created.run.id, runnerInput);
|
|
82
|
-
const planActive = this.options.commands
|
|
83
|
-
.list(session.id)
|
|
84
|
-
.some((state) => state.commandId === 'plan');
|
|
85
|
-
this.options.queue.remember({
|
|
86
|
-
runId: created.run.id,
|
|
87
|
-
sessionId: created.run.sessionId,
|
|
88
|
-
workspaceId: created.run.workspaceId,
|
|
89
|
-
runner: created.run.runner,
|
|
90
|
-
input: this.options.runners
|
|
91
|
-
.require(created.run.runner)
|
|
92
|
-
.prepareMessageInput(runnerInput, planActive ? 'plan' : 'default'),
|
|
93
|
-
attachments: runnerImageAttachments,
|
|
94
|
-
attachmentPaths,
|
|
95
|
-
cwd: session.cwd,
|
|
96
|
-
externalSessionId: session.externalSessionId,
|
|
97
|
-
model: session.model,
|
|
98
|
-
effort: session.effort,
|
|
99
|
-
access: session.access,
|
|
100
|
-
collaborationMode: planActive ? 'plan' : 'default',
|
|
101
|
-
secretEnvironment,
|
|
102
|
-
...(this.options.runners.require(created.run.runner).serviceTier() === 'fast'
|
|
103
|
-
? { serviceTier: 'fast' }
|
|
104
|
-
: {})
|
|
105
|
-
});
|
|
106
|
-
if (queueWasPaused && active === undefined && intent !== 'QUEUE')
|
|
107
|
-
this.options.runtime.prioritizeQueuedRun(created.run.id);
|
|
108
|
-
if (!queueWasPaused || intent !== 'QUEUE')
|
|
109
|
-
this.options.runtime.resumeSessionQueue(session.id);
|
|
110
|
-
this.options.emitQueue(session.workspaceId, session.id);
|
|
111
|
-
if (replaceCurrentRun !== undefined)
|
|
112
|
-
this.options.queue.replace(created.run.id, replaceCurrentRun.id);
|
|
113
|
-
else if (intent !== 'QUEUE' || !queuedBeforeCreate)
|
|
114
|
-
this.options.queue.schedule(session.id);
|
|
115
|
-
return {
|
|
116
|
-
session: this.options.present(session),
|
|
117
|
-
run: created.run,
|
|
118
|
-
delivery: replaceCurrentRun !== undefined ? 'RESTARTING' : queuedBeforeCreate ? 'QUEUED' : 'STARTED',
|
|
119
|
-
idempotent: false
|
|
120
|
-
};
|
|
121
142
|
}
|
|
122
143
|
async persistAttachments(session, runId, attachments) {
|
|
123
144
|
return this.options.attachments.persistFiles(session.cwd, runId, attachments.filter((attachment) => !isRunnerImageAttachment(attachment)));
|
|
@@ -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') ||
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
2
2
|
import type { NodeRuntimeState } from '../runtime-state.js';
|
|
3
3
|
import type { RunnerName } from '@myagentroam/protocol';
|
|
4
|
-
import type { NodeMcpInstallation } from '../database.js';
|
|
4
|
+
import type { NodeAgentRun, NodeMcpInstallation, NodeMessageAttachmentInput } from '../database.js';
|
|
5
5
|
export interface QueuedRunStart<TAttachment = unknown> {
|
|
6
6
|
readonly runId: string;
|
|
7
7
|
readonly sessionId: 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,12 +46,26 @@ 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: {
|
|
52
|
+
readonly runId: string;
|
|
53
|
+
readonly sessionId: string;
|
|
54
|
+
readonly clientMessageId: string;
|
|
55
|
+
readonly content: string;
|
|
56
|
+
readonly initiatedByUserId?: number;
|
|
57
|
+
readonly attachments?: readonly NodeMessageAttachmentInput[];
|
|
58
|
+
}): {
|
|
59
|
+
readonly run: NodeAgentRun;
|
|
60
|
+
readonly created: boolean;
|
|
61
|
+
};
|
|
40
62
|
pending(runId: string): QueuedRunStart<TAttachment> | undefined;
|
|
41
63
|
remove(runId: string): void;
|
|
42
64
|
clear(): void;
|
|
65
|
+
publish(sessionId: string): void;
|
|
43
66
|
migrateSession(previousId: string, nextId: string): void;
|
|
67
|
+
cancelQueued(runId: string): void;
|
|
68
|
+
pauseForInterrupt(sessionId: string): void;
|
|
44
69
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
45
70
|
private get;
|
|
46
71
|
private pauseOrResume;
|
|
@@ -52,7 +77,9 @@ export declare class WorkspaceQueueWorkbenchService<TAttachment> {
|
|
|
52
77
|
private requireRun;
|
|
53
78
|
replace(queuedRunId: string, activeRunId: string, prioritize?: boolean): void;
|
|
54
79
|
schedule(sessionId: string): void;
|
|
80
|
+
settleEnqueue(input: EnqueuedRunDisposition): 'STARTED' | 'QUEUED' | 'RESTARTING';
|
|
55
81
|
dispatch(sessionId: string): Promise<void>;
|
|
56
82
|
present(workspaceId: string, sessionId: string): unknown;
|
|
57
83
|
private updatePending;
|
|
58
84
|
}
|
|
85
|
+
export {};
|
|
@@ -5,11 +5,17 @@ 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
|
}
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
enqueue(start, message) {
|
|
13
|
+
if (message.runId !== start.runId || message.sessionId !== start.sessionId)
|
|
14
|
+
throw new Error('QUEUE_RUN_MISMATCH');
|
|
15
|
+
const created = this.options.runtime.createMessageRun(message);
|
|
16
|
+
if (created.created)
|
|
17
|
+
this.starts.set(start.runId, start);
|
|
18
|
+
return created;
|
|
13
19
|
}
|
|
14
20
|
pending(runId) {
|
|
15
21
|
return this.starts.get(runId);
|
|
@@ -19,12 +25,44 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
19
25
|
}
|
|
20
26
|
clear() {
|
|
21
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);
|
|
22
37
|
}
|
|
23
38
|
migrateSession(previousId, nextId) {
|
|
24
39
|
for (const [runId, pending] of this.starts) {
|
|
25
40
|
if (pending.sessionId === previousId)
|
|
26
41
|
this.starts.set(runId, { ...pending, sessionId: nextId, externalSessionId: nextId });
|
|
27
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);
|
|
28
66
|
}
|
|
29
67
|
operations() {
|
|
30
68
|
return {
|
|
@@ -77,11 +115,7 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
77
115
|
if (typeof input.runId !== 'string')
|
|
78
116
|
throw new Error('QUEUE_INVALID');
|
|
79
117
|
const run = this.requireRun(input.runId);
|
|
80
|
-
this.
|
|
81
|
-
this.remove(run.id);
|
|
82
|
-
this.options.clearImages(run.id);
|
|
83
|
-
this.options.cleanupAttachments(run.id);
|
|
84
|
-
this.options.emit(run.workspaceId, run.sessionId);
|
|
118
|
+
this.cancelQueued(run.id);
|
|
85
119
|
return { queue: this.present(run.workspaceId, run.sessionId) };
|
|
86
120
|
}
|
|
87
121
|
execute(input) {
|
|
@@ -100,7 +134,6 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
100
134
|
.listRunsForSession(session.id)
|
|
101
135
|
.find((run) => run.status === 'STARTING' || run.status === 'RUNNING');
|
|
102
136
|
this.options.runtime.prioritizeQueuedRun(queued.id);
|
|
103
|
-
this.options.emit(queued.workspaceId, session.id);
|
|
104
137
|
if (active !== undefined) {
|
|
105
138
|
this.replace(queued.id, active.id, false);
|
|
106
139
|
return { run: queued, delivery: 'RESTARTING' };
|
|
@@ -132,17 +165,46 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
132
165
|
throw new Error('QUEUE_ITEM_INVALID');
|
|
133
166
|
if (prioritize)
|
|
134
167
|
this.options.runtime.prioritizeQueuedRun(queued.id);
|
|
168
|
+
this.options.emit(queued.workspaceId, queued.sessionId);
|
|
135
169
|
this.options.markInterrupted(active.id);
|
|
136
170
|
this.options.runtime.transitionRun(active.id, 'CANCELLING');
|
|
137
171
|
this.options.control('run.interrupt', { runId: active.id });
|
|
138
172
|
}
|
|
139
173
|
schedule(sessionId) {
|
|
174
|
+
if (this.scheduledSessions.has(sessionId))
|
|
175
|
+
return;
|
|
140
176
|
const timer = setTimeout(() => {
|
|
177
|
+
this.scheduledSessions.delete(sessionId);
|
|
141
178
|
if (!this.options.stopped())
|
|
142
179
|
void this.dispatch(sessionId);
|
|
143
180
|
}, 0);
|
|
181
|
+
this.scheduledSessions.set(sessionId, timer);
|
|
144
182
|
timer.unref();
|
|
145
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
|
+
}
|
|
146
208
|
async dispatch(sessionId) {
|
|
147
209
|
const run = this.options.runtime.claimNextQueuedRun(sessionId);
|
|
148
210
|
if (run === undefined)
|
|
@@ -181,6 +243,9 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
181
243
|
collaborationMode: pending.collaborationMode,
|
|
182
244
|
...(pending.serviceTier === undefined ? {} : { serviceTier: pending.serviceTier }),
|
|
183
245
|
secretEnvironment: pending.secretEnvironment,
|
|
246
|
+
...(pending.personalInstructions === undefined
|
|
247
|
+
? {}
|
|
248
|
+
: { personalInstructions: pending.personalInstructions }),
|
|
184
249
|
mcpInstallations: this.options.mcps(pending.workspaceId)
|
|
185
250
|
});
|
|
186
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
|
}
|