@myagentroam/node 0.11.15 → 0.11.17
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/connector.js +13 -24
- package/dist/native-session-history.js +35 -832
- package/dist/native-transcript-adapters.js +9 -0
- package/dist/native-transcript-model.js +1 -0
- package/dist/runner/abstract-runner.js +12 -0
- package/dist/runner/claude/conversation-parser.js +80 -1
- package/dist/runner/claude/managed-run-controller.js +4 -2
- package/dist/runner/claude/message-parsers.js +37 -0
- package/dist/runner/claude/native-context-usage.js +90 -0
- package/dist/runner/claude/native-transcript.js +347 -0
- package/dist/runner/claude/tool-normalizer.js +12 -2
- package/dist/runner/claude-code-runner.js +13 -2
- package/dist/runner/codex/conversation-parser.js +67 -3
- package/dist/runner/codex/managed-run-controller.js +3 -2
- package/dist/runner/codex/native-context-usage.js +42 -0
- package/dist/runner/codex/native-transcript.js +575 -0
- package/dist/runner/codex-runner.js +18 -3
- package/dist/runner/mar-agent/managed-run-controller.js +1 -1
- package/dist/runner/mar-agent-runner.js +6 -0
- package/dist/runner/opencode/conversation-parser.js +1 -1
- package/dist/runner/opencode/managed-run-controller.js +1 -1
- package/dist/runner/opencode-runner.js +7 -1
- package/dist/runner/runner-registry.js +4 -0
- package/dist/runtime-state.js +1 -6
- package/dist/service/continuous-run-coordinator.js +1 -0
- package/dist/service/conversation-template-manifest.js +179 -0
- package/dist/service/conversation-template-node-service.js +10 -132
- package/dist/service/external-session-resume-service.js +15 -0
- package/dist/service/fake-managed-run-service.js +1 -1
- package/dist/service/goal-continuation-coordinator.js +4 -3
- package/dist/service/goal-runtime.js +0 -9
- package/dist/service/hosted-conversation-node-service.js +16 -179
- package/dist/service/hosted-conversation-store.js +2 -33
- package/dist/service/native-session-projection-service.js +7 -24
- package/dist/service/node-control-channel.js +1 -1
- package/dist/service/node-control-message-service.js +0 -8
- package/dist/service/node-request-service.js +1 -1
- package/dist/service/node-run-event-bridge.js +0 -37
- package/dist/service/node-terminal-channel.js +1 -1
- package/dist/service/recent-run-projection-service.js +2 -1
- package/dist/service/run-workbench-service.js +1 -1
- package/dist/service/runner-interaction-service.js +2 -1
- package/dist/service/runner-service.js +6 -4
- package/dist/service/session-identity-service.js +1 -0
- package/dist/service/session-message-service.js +1 -0
- package/dist/service/session-query-service.js +1 -1
- package/dist/service/workbench-event-projection.js +1 -1
- package/dist/service/workbench-event-service.js +1 -1
- package/dist/service/workspace-file-service.js +0 -2
- package/dist/service/workspace-queue-workbench-service.js +2 -2
- package/dist/util/node-operation-parsers.js +0 -45
- package/dist/util/runner-native-session-parsers.js +17 -194
- package/package.json +5 -5
|
@@ -77,6 +77,10 @@ export class RunnerRegistry {
|
|
|
77
77
|
return true;
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
80
|
+
migrateSessionIdentity(previousId, nativeSessionId) {
|
|
81
|
+
for (const runner of this.runners.values())
|
|
82
|
+
runner.migrateSessionIdentity(previousId, nativeSessionId);
|
|
83
|
+
}
|
|
80
84
|
decideApproval(input) {
|
|
81
85
|
let handled = false;
|
|
82
86
|
for (const runner of this.runners.values())
|
package/dist/runtime-state.js
CHANGED
|
@@ -478,6 +478,7 @@ export class NodeRuntimeState {
|
|
|
478
478
|
runner: session.runner,
|
|
479
479
|
clientMessageId: input.clientMessageId,
|
|
480
480
|
content: input.content,
|
|
481
|
+
...(input.goalInput === true ? { goalInput: true } : {}),
|
|
481
482
|
...(input.suppressNotifications === true ? { suppressNotifications: true } : {}),
|
|
482
483
|
attachments: input.attachments ?? [],
|
|
483
484
|
...(input.initiatedByUserId === undefined
|
|
@@ -1236,12 +1237,6 @@ export class NodeRuntimeState {
|
|
|
1236
1237
|
if (this.sessionHistoryMutations.has(sessionId))
|
|
1237
1238
|
throw new Error('SESSION_ACTIVE');
|
|
1238
1239
|
}
|
|
1239
|
-
reconcileRun(id, active) {
|
|
1240
|
-
// There is no cross-restart run recovery: this process-local state is
|
|
1241
|
-
// discarded on restart and cannot be reconciled from SQLite.
|
|
1242
|
-
void id;
|
|
1243
|
-
void active;
|
|
1244
|
-
}
|
|
1245
1240
|
replaceSession(id, patch) {
|
|
1246
1241
|
const value = this.requireSession(id);
|
|
1247
1242
|
const next = { ...value, ...patch };
|
|
@@ -50,6 +50,7 @@ export class ContinuousRunCoordinator {
|
|
|
50
50
|
sessionId: message.sessionId,
|
|
51
51
|
clientMessageId: message.clientMessageId,
|
|
52
52
|
content: message.content,
|
|
53
|
+
...(message.goalInput === true ? { goalInput: true } : {}),
|
|
53
54
|
...(message.suppressNotifications === true ? { suppressNotifications: true } : {}),
|
|
54
55
|
...(message.initiatedByUserId === undefined
|
|
55
56
|
? {}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { conversationTemplateDraftSchema, mcpRuntimeDescriptorSchema, mcpInstallationConfigurationSchema } from '@myagentroam/protocol';
|
|
4
|
+
const RUNTIME_LINK_ROOTS = ['/usr', '/bin', '/sbin', '/lib', '/lib64'];
|
|
5
|
+
const RESERVED_SANDBOX_ROOTS = [
|
|
6
|
+
'/workspace',
|
|
7
|
+
'/home/mar',
|
|
8
|
+
'/runtime',
|
|
9
|
+
'/usr',
|
|
10
|
+
'/bin',
|
|
11
|
+
'/sbin',
|
|
12
|
+
'/lib',
|
|
13
|
+
'/lib64',
|
|
14
|
+
'/proc',
|
|
15
|
+
'/dev',
|
|
16
|
+
'/run',
|
|
17
|
+
'/tmp'
|
|
18
|
+
];
|
|
19
|
+
export function parseTemplateVersionManifest(input) {
|
|
20
|
+
if (!record(input) ||
|
|
21
|
+
input['schemaVersion'] !== 2 ||
|
|
22
|
+
typeof input['sourceWorkspacePath'] !== 'string' ||
|
|
23
|
+
!Array.isArray(input['directories']) ||
|
|
24
|
+
!Array.isArray(input['entries']) ||
|
|
25
|
+
typeof input['contentHash'] !== 'string' ||
|
|
26
|
+
typeof input['mcpConfigHash'] !== 'string')
|
|
27
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
28
|
+
const sourceWorkspacePath = input['sourceWorkspacePath'];
|
|
29
|
+
if (!isAbsolute(sourceWorkspacePath) ||
|
|
30
|
+
resolve(sourceWorkspacePath) !== sourceWorkspacePath ||
|
|
31
|
+
sourceWorkspacePath === '/' ||
|
|
32
|
+
isReservedTemplateSourcePath(sourceWorkspacePath))
|
|
33
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
34
|
+
const draft = conversationTemplateDraftSchema.safeParse(input['draft']);
|
|
35
|
+
if (!draft.success)
|
|
36
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
37
|
+
const directories = input['directories'].map(safeManifestRelativePath);
|
|
38
|
+
if (new Set(directories).size !== directories.length ||
|
|
39
|
+
directories.includes('inbox') ||
|
|
40
|
+
directories.includes('output'))
|
|
41
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
42
|
+
const entries = input['entries'].map(manifestEntry);
|
|
43
|
+
if (new Set(entries.map((entry) => entry.relativePath)).size !== entries.length)
|
|
44
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
45
|
+
return {
|
|
46
|
+
schemaVersion: 2,
|
|
47
|
+
sourceWorkspacePath,
|
|
48
|
+
draft: draft.data,
|
|
49
|
+
directories,
|
|
50
|
+
entries,
|
|
51
|
+
contentHash: input['contentHash'],
|
|
52
|
+
mcpServers: publishedMcpServers(input['mcpServers']),
|
|
53
|
+
mcpConfigHash: input['mcpConfigHash']
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function isTemplateRuntimeLinkTarget(path) {
|
|
57
|
+
return isAbsolute(path) && RUNTIME_LINK_ROOTS.some((root) => path === root || within(root, path));
|
|
58
|
+
}
|
|
59
|
+
export function isReservedTemplateSourcePath(path) {
|
|
60
|
+
return RESERVED_SANDBOX_ROOTS.some((root) => path === root || within(root, path) || within(path, root));
|
|
61
|
+
}
|
|
62
|
+
export function templateMcpConfigHash(servers) {
|
|
63
|
+
return hashText(JSON.stringify(servers));
|
|
64
|
+
}
|
|
65
|
+
/** Hash the persisted text after removing exactly one optional trailing newline. */
|
|
66
|
+
export function templateManifestHash(manifestText) {
|
|
67
|
+
return hashText(manifestText.endsWith('\n') ? manifestText.slice(0, -1) : manifestText);
|
|
68
|
+
}
|
|
69
|
+
function manifestEntry(input) {
|
|
70
|
+
if (!record(input))
|
|
71
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
72
|
+
if (input['kind'] === 'FILE')
|
|
73
|
+
return manifestFile(input);
|
|
74
|
+
if (input['kind'] !== 'SYMLINK' ||
|
|
75
|
+
typeof input['linkTarget'] !== 'string' ||
|
|
76
|
+
input['linkTarget'].length === 0 ||
|
|
77
|
+
input['linkTarget'].includes('\0') ||
|
|
78
|
+
(input['targetKind'] !== 'SNAPSHOT' && input['targetKind'] !== 'RUNTIME') ||
|
|
79
|
+
typeof input['resolvedTarget'] !== 'string')
|
|
80
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
81
|
+
const relativePath = safeManifestRelativePath(input['relativePath']);
|
|
82
|
+
const size = safeNonNegativeInteger(input['size']);
|
|
83
|
+
const linkTarget = input['linkTarget'];
|
|
84
|
+
const targetKind = input['targetKind'];
|
|
85
|
+
const resolvedTarget = input['resolvedTarget'];
|
|
86
|
+
if (size !== Buffer.byteLength(linkTarget))
|
|
87
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
88
|
+
if (targetKind === 'SNAPSHOT')
|
|
89
|
+
safeManifestRelativePath(resolvedTarget);
|
|
90
|
+
else if (!isTemplateRuntimeLinkTarget(resolvedTarget))
|
|
91
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
92
|
+
return {
|
|
93
|
+
kind: 'SYMLINK',
|
|
94
|
+
relativePath,
|
|
95
|
+
size,
|
|
96
|
+
linkTarget,
|
|
97
|
+
targetKind,
|
|
98
|
+
resolvedTarget
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function manifestFile(input) {
|
|
102
|
+
if (!record(input) ||
|
|
103
|
+
input['kind'] !== 'FILE' ||
|
|
104
|
+
typeof input['executable'] !== 'boolean' ||
|
|
105
|
+
typeof input['sha256'] !== 'string' ||
|
|
106
|
+
!/^[a-f0-9]{64}$/u.test(input['sha256']))
|
|
107
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
108
|
+
return {
|
|
109
|
+
kind: 'FILE',
|
|
110
|
+
relativePath: safeManifestRelativePath(input['relativePath']),
|
|
111
|
+
size: safeNonNegativeInteger(input['size']),
|
|
112
|
+
executable: input['executable'],
|
|
113
|
+
sha256: input['sha256']
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function safeManifestRelativePath(input) {
|
|
117
|
+
if (typeof input !== 'string' ||
|
|
118
|
+
input.length === 0 ||
|
|
119
|
+
input.startsWith('/') ||
|
|
120
|
+
input.includes('\\') ||
|
|
121
|
+
input.split('/').some((segment) => segment.length === 0 || segment === '.' || segment === '..'))
|
|
122
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
123
|
+
return input;
|
|
124
|
+
}
|
|
125
|
+
function safeNonNegativeInteger(input) {
|
|
126
|
+
if (!Number.isSafeInteger(input) || input < 0)
|
|
127
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
128
|
+
return input;
|
|
129
|
+
}
|
|
130
|
+
export function templateContentHash(directories, entries) {
|
|
131
|
+
return hashText([
|
|
132
|
+
...directories.map((directory) => `DIRECTORY\0${directory}`),
|
|
133
|
+
...entries.map((entry) => entry.kind === 'FILE'
|
|
134
|
+
? `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.executable ? 1 : 0}\0${entry.sha256}`
|
|
135
|
+
: `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.linkTarget}\0${entry.targetKind}\0${entry.resolvedTarget}`)
|
|
136
|
+
].join('\n'));
|
|
137
|
+
}
|
|
138
|
+
function publishedMcpServers(value) {
|
|
139
|
+
if (!Array.isArray(value))
|
|
140
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
141
|
+
return value.map((candidate) => {
|
|
142
|
+
if (!record(candidate))
|
|
143
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
144
|
+
const keys = Object.keys(candidate).sort().join(',');
|
|
145
|
+
if (keys !== 'configuration,displayName,id,origin,runtime,serverName,transport,version')
|
|
146
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
147
|
+
const runtime = mcpRuntimeDescriptorSchema.safeParse(candidate.runtime);
|
|
148
|
+
const configuration = mcpInstallationConfigurationSchema.safeParse(candidate.configuration);
|
|
149
|
+
if (!runtime.success || !configuration.success)
|
|
150
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
151
|
+
if (typeof candidate.id !== 'string' ||
|
|
152
|
+
typeof candidate.serverName !== 'string' ||
|
|
153
|
+
typeof candidate.displayName !== 'string' ||
|
|
154
|
+
typeof candidate.version !== 'string' ||
|
|
155
|
+
(candidate.origin !== 'NODE' && candidate.origin !== 'WORKSPACE') ||
|
|
156
|
+
candidate.transport !== runtime.data.transport)
|
|
157
|
+
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
158
|
+
return {
|
|
159
|
+
id: candidate.id,
|
|
160
|
+
serverName: candidate.serverName,
|
|
161
|
+
displayName: candidate.displayName,
|
|
162
|
+
version: candidate.version,
|
|
163
|
+
origin: candidate.origin,
|
|
164
|
+
transport: runtime.data.transport,
|
|
165
|
+
runtime: runtime.data,
|
|
166
|
+
configuration: configuration.data
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function within(parent, candidate) {
|
|
171
|
+
const relation = relative(parent, candidate);
|
|
172
|
+
return relation.length > 0 && relation !== '..' && !relation.startsWith(`..${sep}`);
|
|
173
|
+
}
|
|
174
|
+
function hashText(value) {
|
|
175
|
+
return createHash('sha256').update(value).digest('hex');
|
|
176
|
+
}
|
|
177
|
+
function record(value) {
|
|
178
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
179
|
+
}
|
|
@@ -3,6 +3,7 @@ import { chmod, copyFile, lstat, mkdir, readFile, readlink, readdir, realpath, r
|
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { conversationTemplateDraftSchema, conversationTemplatePublishJobSchema, conversationTemplatePublishRequestSchema, conversationTemplatePublishResultSchema, conversationTemplatePublishStatusInputSchema, conversationTemplateSourceInspectInputSchema, conversationTemplateSourceInspectionSchema, conversationTemplateVersionSchema } from '@myagentroam/protocol';
|
|
5
5
|
import { safeErrorCode } from '../util/safe-error.js';
|
|
6
|
+
import { parseTemplateVersionManifest, templateContentHash, templateMcpConfigHash, templateManifestHash, isTemplateRuntimeLinkTarget, isReservedTemplateSourcePath } from './conversation-template-manifest.js';
|
|
6
7
|
const HOST_POLICY_VERSION = 1;
|
|
7
8
|
const FORCED_EXCLUDED_NAMES = new Set([
|
|
8
9
|
'.git',
|
|
@@ -17,21 +18,6 @@ const FORCED_EXCLUDED_NAMES = new Set([
|
|
|
17
18
|
const ROOT_EXCLUDED_NAMES = new Set(['credentials', 'secrets', 'readme.md', 'agents.md']);
|
|
18
19
|
const ROOT_TEMPLATE_INSTRUCTIONS_SOURCE = 'AGENTS.template.md';
|
|
19
20
|
const ROOT_TEMPLATE_INSTRUCTIONS_TARGET = 'AGENTS.md';
|
|
20
|
-
const RUNTIME_LINK_ROOTS = ['/usr', '/bin', '/sbin', '/lib', '/lib64'];
|
|
21
|
-
const RESERVED_SANDBOX_ROOTS = [
|
|
22
|
-
'/workspace',
|
|
23
|
-
'/home/mar',
|
|
24
|
-
'/runtime',
|
|
25
|
-
'/usr',
|
|
26
|
-
'/bin',
|
|
27
|
-
'/sbin',
|
|
28
|
-
'/lib',
|
|
29
|
-
'/lib64',
|
|
30
|
-
'/proc',
|
|
31
|
-
'/dev',
|
|
32
|
-
'/run',
|
|
33
|
-
'/tmp'
|
|
34
|
-
];
|
|
35
21
|
/** Handles the four closed template-management operations owned by Node. */
|
|
36
22
|
export class ConversationTemplateNodeService {
|
|
37
23
|
options;
|
|
@@ -196,7 +182,7 @@ export class ConversationTemplateNodeService {
|
|
|
196
182
|
const snapshot = await this.collectSnapshot(request.draft);
|
|
197
183
|
const entries = snapshot.entries;
|
|
198
184
|
const mcpServers = this.selectedMcpServers(request.draft);
|
|
199
|
-
const mcpConfigHash =
|
|
185
|
+
const mcpConfigHash = templateMcpConfigHash(mcpServers);
|
|
200
186
|
stage = 'ENUMERATING';
|
|
201
187
|
this.updateJob(request.jobId, stage, 'RUNNING');
|
|
202
188
|
await rm(candidate, { recursive: true, force: true });
|
|
@@ -237,7 +223,7 @@ export class ConversationTemplateNodeService {
|
|
|
237
223
|
const versionNumber = this.nextVersionNumber(request.templateId);
|
|
238
224
|
const versionId = this.id();
|
|
239
225
|
const publishedAt = this.now();
|
|
240
|
-
const contentHash =
|
|
226
|
+
const contentHash = templateContentHash(snapshot.directories, entries);
|
|
241
227
|
const manifest = {
|
|
242
228
|
schemaVersion: 2,
|
|
243
229
|
templateId: request.templateId,
|
|
@@ -273,7 +259,7 @@ export class ConversationTemplateNodeService {
|
|
|
273
259
|
stage = 'MANIFEST';
|
|
274
260
|
this.updateJob(request.jobId, stage, 'RUNNING');
|
|
275
261
|
const manifestJson = JSON.stringify(manifest);
|
|
276
|
-
const manifestHash =
|
|
262
|
+
const manifestHash = templateManifestHash(manifestJson);
|
|
277
263
|
await writeFile(join(candidate, 'manifest.json'), `${manifestJson}\n`, { mode: 0o600 });
|
|
278
264
|
await chmod(join(candidate, 'manifest.json'), 0o444);
|
|
279
265
|
sealed = this.options.store.templateVersionDirectory(storageKey);
|
|
@@ -506,16 +492,16 @@ export class ConversationTemplateNodeService {
|
|
|
506
492
|
const root = this.options.store.templateVersionDirectory(storageKey);
|
|
507
493
|
const manifestText = await readFile(join(root, 'manifest.json'), 'utf8');
|
|
508
494
|
const manifestJson = manifestText.endsWith('\n') ? manifestText.slice(0, -1) : manifestText;
|
|
509
|
-
if (
|
|
495
|
+
if (templateManifestHash(manifestText) !== stringValue(row, 'manifest_hash'))
|
|
510
496
|
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
511
|
-
const manifest =
|
|
497
|
+
const manifest = parseTemplateVersionManifest(JSON.parse(manifestJson));
|
|
512
498
|
if (manifest.mcpConfigHash !== stringValue(row, 'mcp_config_hash') ||
|
|
513
|
-
|
|
499
|
+
templateMcpConfigHash(manifest.mcpServers) !== manifest.mcpConfigHash ||
|
|
514
500
|
manifest.contentHash !== stringValue(row, 'content_hash') ||
|
|
515
501
|
manifest.entries.length !== numberValue(row, 'file_count') ||
|
|
516
502
|
manifest.entries.reduce((total, entry) => total + entry.size, 0) !==
|
|
517
503
|
numberValue(row, 'total_bytes') ||
|
|
518
|
-
|
|
504
|
+
templateContentHash(manifest.directories, manifest.entries) !== manifest.contentHash)
|
|
519
505
|
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
520
506
|
const expected = new Map(manifest.entries.map((entry) => [entry.relativePath, entry]));
|
|
521
507
|
const expectedDirectories = new Set([...manifest.directories, 'inbox', 'output']);
|
|
@@ -779,8 +765,7 @@ async function publishedSymlink(workspaceRoot, path, relativePath, metadata) {
|
|
|
779
765
|
resolvedTarget: targetRelative
|
|
780
766
|
};
|
|
781
767
|
}
|
|
782
|
-
|
|
783
|
-
if (runtimeTarget === undefined)
|
|
768
|
+
if (!isTemplateRuntimeLinkTarget(resolvedTarget))
|
|
784
769
|
throw new Error('CONVERSATION_TEMPLATE_SOURCE_LINK_INVALID');
|
|
785
770
|
return {
|
|
786
771
|
kind: 'SYMLINK',
|
|
@@ -792,110 +777,6 @@ async function publishedSymlink(workspaceRoot, path, relativePath, metadata) {
|
|
|
792
777
|
resolvedTarget
|
|
793
778
|
};
|
|
794
779
|
}
|
|
795
|
-
function templateVersionManifest(input) {
|
|
796
|
-
if (!record(input))
|
|
797
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
798
|
-
const mcpServers = Array.isArray(input['mcpServers'])
|
|
799
|
-
? input['mcpServers']
|
|
800
|
-
: undefined;
|
|
801
|
-
if (mcpServers === undefined ||
|
|
802
|
-
typeof input['mcpConfigHash'] !== 'string' ||
|
|
803
|
-
typeof input['contentHash'] !== 'string')
|
|
804
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
805
|
-
if (input['schemaVersion'] !== 2 ||
|
|
806
|
-
typeof input['sourceWorkspacePath'] !== 'string' ||
|
|
807
|
-
!Array.isArray(input['directories']) ||
|
|
808
|
-
!Array.isArray(input['entries']))
|
|
809
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
810
|
-
validateSourceWorkspacePath(input['sourceWorkspacePath']);
|
|
811
|
-
const directories = input['directories'].map((value) => safeManifestRelativePath(value));
|
|
812
|
-
if (new Set(directories).size !== directories.length ||
|
|
813
|
-
directories.includes('inbox') ||
|
|
814
|
-
directories.includes('output'))
|
|
815
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
816
|
-
const entries = input['entries'].map((value) => manifestEntry(value));
|
|
817
|
-
if (new Set(entries.map((entry) => entry.relativePath)).size !== entries.length)
|
|
818
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
819
|
-
return {
|
|
820
|
-
schemaVersion: 2,
|
|
821
|
-
sourceWorkspacePath: input['sourceWorkspacePath'],
|
|
822
|
-
directories,
|
|
823
|
-
entries,
|
|
824
|
-
contentHash: input['contentHash'],
|
|
825
|
-
mcpServers,
|
|
826
|
-
mcpConfigHash: input['mcpConfigHash']
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
function manifestEntry(input) {
|
|
830
|
-
if (!record(input))
|
|
831
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
832
|
-
if (input['kind'] === 'FILE')
|
|
833
|
-
return manifestFile(input, true);
|
|
834
|
-
if (input['kind'] !== 'SYMLINK' ||
|
|
835
|
-
typeof input['linkTarget'] !== 'string' ||
|
|
836
|
-
input['linkTarget'].length === 0 ||
|
|
837
|
-
input['linkTarget'].includes('\0') ||
|
|
838
|
-
(input['targetKind'] !== 'SNAPSHOT' && input['targetKind'] !== 'RUNTIME') ||
|
|
839
|
-
typeof input['resolvedTarget'] !== 'string')
|
|
840
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
841
|
-
const relativePath = safeManifestRelativePath(input['relativePath']);
|
|
842
|
-
const size = safeNonNegativeInteger(input['size']);
|
|
843
|
-
const linkTarget = input['linkTarget'];
|
|
844
|
-
const targetKind = input['targetKind'];
|
|
845
|
-
const resolvedTarget = input['resolvedTarget'];
|
|
846
|
-
if (size !== Buffer.byteLength(linkTarget))
|
|
847
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
848
|
-
if (targetKind === 'SNAPSHOT')
|
|
849
|
-
safeManifestRelativePath(resolvedTarget);
|
|
850
|
-
else if (!isAbsolute(resolvedTarget) ||
|
|
851
|
-
!RUNTIME_LINK_ROOTS.some((root) => resolvedTarget === root || within(root, resolvedTarget)))
|
|
852
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
853
|
-
return {
|
|
854
|
-
kind: 'SYMLINK',
|
|
855
|
-
relativePath,
|
|
856
|
-
size,
|
|
857
|
-
linkTarget,
|
|
858
|
-
targetKind,
|
|
859
|
-
resolvedTarget
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
function manifestFile(input, requireKind = false) {
|
|
863
|
-
if (!record(input) ||
|
|
864
|
-
(requireKind && input['kind'] !== 'FILE') ||
|
|
865
|
-
typeof input['executable'] !== 'boolean' ||
|
|
866
|
-
typeof input['sha256'] !== 'string' ||
|
|
867
|
-
!/^[a-f0-9]{64}$/u.test(input['sha256']))
|
|
868
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
869
|
-
return {
|
|
870
|
-
kind: 'FILE',
|
|
871
|
-
relativePath: safeManifestRelativePath(input['relativePath']),
|
|
872
|
-
size: safeNonNegativeInteger(input['size']),
|
|
873
|
-
executable: input['executable'],
|
|
874
|
-
sha256: input['sha256']
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
function safeManifestRelativePath(input) {
|
|
878
|
-
if (typeof input !== 'string' ||
|
|
879
|
-
input.length === 0 ||
|
|
880
|
-
input.startsWith('/') ||
|
|
881
|
-
input.includes('\\') ||
|
|
882
|
-
input.split('/').some((segment) => segment.length === 0 || segment === '.' || segment === '..'))
|
|
883
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
884
|
-
return input;
|
|
885
|
-
}
|
|
886
|
-
function safeNonNegativeInteger(input) {
|
|
887
|
-
if (!Number.isSafeInteger(input) || input < 0)
|
|
888
|
-
throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
|
|
889
|
-
return input;
|
|
890
|
-
}
|
|
891
|
-
function snapshotContentHash(directories, entries) {
|
|
892
|
-
return hashText([
|
|
893
|
-
...directories.map((directory) => `DIRECTORY\0${directory}`),
|
|
894
|
-
...entries.map((entry) => entry.kind === 'FILE'
|
|
895
|
-
? `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.executable ? 1 : 0}\0${entry.sha256}`
|
|
896
|
-
: `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.linkTarget}\0${entry.targetKind}\0${entry.resolvedTarget}`)
|
|
897
|
-
].join('\n'));
|
|
898
|
-
}
|
|
899
780
|
async function hostedFilesystemAvailableBytes(path) {
|
|
900
781
|
try {
|
|
901
782
|
const filesystem = await statfs(path);
|
|
@@ -913,16 +794,13 @@ async function hostedFilesystemAvailableBytes(path) {
|
|
|
913
794
|
function validateSourceWorkspacePath(path) {
|
|
914
795
|
if (!isAbsolute(path) || resolve(path) !== path || path === '/')
|
|
915
796
|
throw new Error('CONVERSATION_TEMPLATE_SOURCE_INVALID');
|
|
916
|
-
if (
|
|
797
|
+
if (isReservedTemplateSourcePath(path))
|
|
917
798
|
throw new Error('CONVERSATION_TEMPLATE_SOURCE_INVALID');
|
|
918
799
|
}
|
|
919
800
|
function within(parent, candidate) {
|
|
920
801
|
const relation = relative(parent, candidate);
|
|
921
802
|
return relation.length > 0 && relation !== '..' && !relation.startsWith(`..${sep}`);
|
|
922
803
|
}
|
|
923
|
-
function hashText(value) {
|
|
924
|
-
return createHash('sha256').update(value).digest('hex');
|
|
925
|
-
}
|
|
926
804
|
function hashBuffer(value) {
|
|
927
805
|
return createHash('sha256').update(value).digest('hex');
|
|
928
806
|
}
|
|
@@ -6,6 +6,21 @@ export class ExternalSessionResumeService {
|
|
|
6
6
|
constructor(options) {
|
|
7
7
|
this.options = options;
|
|
8
8
|
}
|
|
9
|
+
setActivity(sessionId, activity) {
|
|
10
|
+
this.options.state.externalActivity.set(sessionId, activity);
|
|
11
|
+
}
|
|
12
|
+
forgetSessions(directIds, projectionIds) {
|
|
13
|
+
for (const directId of directIds) {
|
|
14
|
+
this.options.state.externalActivity.delete(directId);
|
|
15
|
+
this.options.state.resumedByDirectId.delete(directId);
|
|
16
|
+
this.clearFailure(directId);
|
|
17
|
+
}
|
|
18
|
+
for (const [directId, resumedId] of this.options.state.resumedByDirectId)
|
|
19
|
+
if (projectionIds.has(resumedId))
|
|
20
|
+
this.options.state.resumedByDirectId.delete(directId);
|
|
21
|
+
for (const projectionId of projectionIds)
|
|
22
|
+
this.options.state.externalActivity.delete(projectionId);
|
|
23
|
+
}
|
|
9
24
|
transitioning(sessionId) {
|
|
10
25
|
return this.transitions.has(sessionId);
|
|
11
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isTerminalRunStatus } from '../
|
|
1
|
+
import { isTerminalRunStatus } from '../util/node-operation-parsers.js';
|
|
2
2
|
/** Test-only managed Run adapter; production Runner paths never depend on it. */
|
|
3
3
|
export class FakeManagedRunService {
|
|
4
4
|
options;
|
|
@@ -3,7 +3,6 @@ import { nodeLog } from '../operational.js';
|
|
|
3
3
|
import { isPlainRecord } from '../util/node-operation-parsers.js';
|
|
4
4
|
import { safeErrorCode } from '../util/safe-error.js';
|
|
5
5
|
import { NODE_MCP } from '../mcp/naming.js';
|
|
6
|
-
import { goalUnrestrictedAccess } from './goal-runtime.js';
|
|
7
6
|
import { composerResourceInputTags } from './composer-message-input.js';
|
|
8
7
|
/** Owns transient Goal orchestration, not the domain state or execution queue. */
|
|
9
8
|
export class GoalContinuationCoordinator {
|
|
@@ -527,9 +526,11 @@ export class GoalContinuationCoordinator {
|
|
|
527
526
|
return goalExit;
|
|
528
527
|
}
|
|
529
528
|
prepareGoalSession(session) {
|
|
530
|
-
const access =
|
|
529
|
+
const { access, accessChanged: changed } = this.options
|
|
530
|
+
.runners()
|
|
531
|
+
.require(session.runner)
|
|
532
|
+
.goalAccess(session.access);
|
|
531
533
|
const requiresUpdate = session.access !== access;
|
|
532
|
-
const changed = session.runner !== 'mar-agent' && requiresUpdate;
|
|
533
534
|
const prepared = requiresUpdate
|
|
534
535
|
? this.options.runtime.updateAgentSessionAccess(session.id, access)
|
|
535
536
|
: session;
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { NODE_MCP } from '../mcp/naming.js';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { parseComposerTags, serializeComposerTag } from '@myagentroam/protocol';
|
|
4
|
-
export function goalUnrestrictedAccess(runner, currentAccess) {
|
|
5
|
-
if (runner === 'mar-agent')
|
|
6
|
-
return currentAccess ?? 'full-access';
|
|
7
|
-
if (runner === 'codex')
|
|
8
|
-
return 'full-access';
|
|
9
|
-
if (runner === 'claude-code')
|
|
10
|
-
return 'bypass';
|
|
11
|
-
return 'auto';
|
|
12
|
-
}
|
|
13
4
|
export function publicGoalStartText(objective) {
|
|
14
5
|
return serializeComposerTag('mar_goal_start', { objective: objective.trim() });
|
|
15
6
|
}
|