@myagentroam/node 0.11.14 → 0.11.16

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 (51) hide show
  1. package/dist/connector.js +13 -20
  2. package/dist/native-session-history.js +35 -832
  3. package/dist/native-transcript-adapters.js +9 -0
  4. package/dist/native-transcript-model.js +1 -0
  5. package/dist/runner/abstract-runner.js +12 -0
  6. package/dist/runner/claude/conversation-parser.js +80 -1
  7. package/dist/runner/claude/managed-run-controller.js +4 -2
  8. package/dist/runner/claude/message-parsers.js +37 -0
  9. package/dist/runner/claude/native-context-usage.js +90 -0
  10. package/dist/runner/claude/native-transcript.js +347 -0
  11. package/dist/runner/claude/tool-normalizer.js +12 -2
  12. package/dist/runner/claude-code-runner.js +13 -2
  13. package/dist/runner/codex/conversation-parser.js +67 -3
  14. package/dist/runner/codex/managed-run-controller.js +3 -2
  15. package/dist/runner/codex/native-context-usage.js +42 -0
  16. package/dist/runner/codex/native-transcript.js +575 -0
  17. package/dist/runner/codex-runner.js +18 -3
  18. package/dist/runner/mar-agent/managed-run-controller.js +1 -1
  19. package/dist/runner/mar-agent-runner.js +6 -0
  20. package/dist/runner/opencode/conversation-parser.js +1 -1
  21. package/dist/runner/opencode/managed-run-controller.js +1 -1
  22. package/dist/runner/opencode-runner.js +7 -1
  23. package/dist/runner/runner-registry.js +4 -0
  24. package/dist/runtime-state.js +1 -0
  25. package/dist/service/continuous-run-coordinator.js +1 -0
  26. package/dist/service/conversation-template-manifest.js +179 -0
  27. package/dist/service/conversation-template-node-service.js +10 -132
  28. package/dist/service/external-session-resume-service.js +15 -0
  29. package/dist/service/fake-managed-run-service.js +1 -1
  30. package/dist/service/goal-continuation-coordinator.js +4 -3
  31. package/dist/service/goal-runtime.js +0 -9
  32. package/dist/service/hosted-conversation-node-service.js +16 -179
  33. package/dist/service/native-session-projection-service.js +7 -24
  34. package/dist/service/node-control-channel.js +1 -1
  35. package/dist/service/node-request-service.js +1 -1
  36. package/dist/service/node-run-event-bridge.js +0 -24
  37. package/dist/service/node-terminal-channel.js +1 -1
  38. package/dist/service/recent-run-projection-service.js +2 -1
  39. package/dist/service/run-workbench-service.js +1 -1
  40. package/dist/service/runner-interaction-service.js +2 -1
  41. package/dist/service/runner-service.js +6 -4
  42. package/dist/service/session-identity-service.js +1 -0
  43. package/dist/service/session-message-service.js +1 -0
  44. package/dist/service/session-query-service.js +1 -1
  45. package/dist/service/workbench-event-projection.js +1 -1
  46. package/dist/service/workbench-event-service.js +1 -1
  47. package/dist/service/workspace-file-service.js +0 -2
  48. package/dist/service/workspace-queue-workbench-service.js +2 -2
  49. package/dist/util/node-operation-parsers.js +0 -45
  50. package/dist/util/runner-native-session-parsers.js +17 -194
  51. package/package.json +4 -4
@@ -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())
@@ -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
@@ -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 = hashText(JSON.stringify(mcpServers));
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 = snapshotContentHash(snapshot.directories, entries);
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 = hashText(manifestJson);
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 (hashText(manifestJson) !== stringValue(row, 'manifest_hash'))
495
+ if (templateManifestHash(manifestText) !== stringValue(row, 'manifest_hash'))
510
496
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
511
- const manifest = templateVersionManifest(JSON.parse(manifestJson));
497
+ const manifest = parseTemplateVersionManifest(JSON.parse(manifestJson));
512
498
  if (manifest.mcpConfigHash !== stringValue(row, 'mcp_config_hash') ||
513
- hashText(JSON.stringify(manifest.mcpServers)) !== manifest.mcpConfigHash ||
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
- snapshotContentHash(manifest.directories, manifest.entries) !== manifest.contentHash)
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
- const runtimeTarget = RUNTIME_LINK_ROOTS.find((root) => resolvedTarget === root || within(root, resolvedTarget));
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 (RESERVED_SANDBOX_ROOTS.some((root) => path === root || within(root, path) || within(path, root)))
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 '../runner/codex/conversation-parser.js';
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 = goalUnrestrictedAccess(session.runner, session.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
  }