@myagentroam/node 0.11.15 → 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
@@ -1,33 +1,19 @@
1
1
  import { assertMcpServerNames } from '../mcp/naming.js';
2
2
  import { modelUsageGrantSchema, steerComposerMessage } from '@myagentroam/protocol';
3
- import { createHash, randomUUID } from 'node:crypto';
3
+ import { randomUUID } from 'node:crypto';
4
4
  import { constants } from 'node:fs';
5
5
  import { lstat, mkdir, open, readFile, rename, rm, utimes } from 'node:fs/promises';
6
- import { isAbsolute, join, relative, resolve, sep } from 'node:path';
7
- import { toolResultResourceInputSchema, toolResultResourcePageSchema, conversationTurnSchema, conversationTemplateDraftSchema, conversationTemplateSummarySchema, hostedConversationConfigUpdateInputSchema, hostedConversationCreateInputSchema, hostedConversationDeleteResultSchema, hostedConversationFileDeleteInputSchema, hostedConversationFileDeleteResultSchema, hostedConversationFileReadInputSchema, hostedConversationFileReadResultSchema, hostedConversationFilesSchema, hostedConversationHistoryInputSchema, hostedConversationHistoryPageSchema, hostedConversationIdInputSchema, hostedConversationImageReadInputSchema, hostedConversationListInputSchema, hostedConversationListPageSchema, hostedConversationMessageInputSchema, hostedConversationMetadataUpdateInputSchema, hostedConversationRollbackInputSchema, hostedConversationSnapshotSchema, hostedConversationSummarySchema, hostedConversationUnwatchInputSchema, hostedConversationUploadChunkInputSchema, hostedConversationUploadControlInputSchema, hostedConversationUploadCreateInputSchema, hostedConversationUploadPreflightInputSchema, hostedConversationUploadPreflightResultSchema, hostedConversationUploadSchema, hostedConversationWatchInputSchema, marAgentRuntimeModelConfigurationSchema, mcpInstallationConfigurationSchema, mcpRuntimeDescriptorSchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
6
+ import { join } from 'node:path';
7
+ import { toolResultResourceInputSchema, toolResultResourcePageSchema, conversationTurnSchema, conversationTemplateSummarySchema, hostedConversationConfigUpdateInputSchema, hostedConversationCreateInputSchema, hostedConversationDeleteResultSchema, hostedConversationFileDeleteInputSchema, hostedConversationFileDeleteResultSchema, hostedConversationFileReadInputSchema, hostedConversationFileReadResultSchema, hostedConversationFilesSchema, hostedConversationHistoryInputSchema, hostedConversationHistoryPageSchema, hostedConversationIdInputSchema, hostedConversationImageReadInputSchema, hostedConversationListInputSchema, hostedConversationListPageSchema, hostedConversationMessageInputSchema, hostedConversationMetadataUpdateInputSchema, hostedConversationRollbackInputSchema, hostedConversationSnapshotSchema, hostedConversationSummarySchema, hostedConversationUnwatchInputSchema, hostedConversationUploadChunkInputSchema, hostedConversationUploadControlInputSchema, hostedConversationUploadCreateInputSchema, hostedConversationUploadPreflightInputSchema, hostedConversationUploadPreflightResultSchema, hostedConversationUploadSchema, hostedConversationWatchInputSchema, marAgentRuntimeModelConfigurationSchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
8
8
  import { hostedConversationGeneratedImagesDisplayDirectory, hostedConversationImageRunId, marAgentGeneratedImagesDirectory } from '../runner/mar-agent/generated-images.js';
9
9
  import { sessionTitleFromFirstMessage } from '../util/node-operation-parsers.js';
10
10
  import { safeErrorCode } from '../util/safe-error.js';
11
11
  import { SessionWatchSlotCoordinator } from './session-watch-slot-coordinator.js';
12
+ import { parseTemplateVersionManifest, templateContentHash, templateMcpConfigHash, templateManifestHash } from './conversation-template-manifest.js';
12
13
  const DEFAULT_HOSTED_CONVERSATION_TITLE = 'New conversation';
13
14
  const HOST_POLICY_VERSION = 1;
14
15
  const MAX_FILE_READ_BYTES = 512 * 1024;
15
16
  const WATCH_TTL_MS = 45_000;
16
- const RUNTIME_LINK_ROOTS = ['/usr', '/bin', '/sbin', '/lib', '/lib64'];
17
- const RESERVED_SANDBOX_ROOTS = [
18
- '/workspace',
19
- '/home/mar',
20
- '/runtime',
21
- '/usr',
22
- '/bin',
23
- '/sbin',
24
- '/lib',
25
- '/lib64',
26
- '/proc',
27
- '/dev',
28
- '/run',
29
- '/tmp'
30
- ];
31
17
  /** Implements the Node-owned hosted-conversation domain over an injected MAR Agent runtime. */
32
18
  export class HostedConversationNodeService {
33
19
  options;
@@ -1719,8 +1705,7 @@ export class HostedConversationNodeService {
1719
1705
  try {
1720
1706
  const manifestText = await readFile(manifestPath, 'utf8');
1721
1707
  const manifestJson = manifestText.endsWith('\n') ? manifestText.slice(0, -1) : manifestText;
1722
- if (manifestJson !== row.manifest ||
1723
- createHash('sha256').update(manifestJson).digest('hex') !== row.manifest_hash)
1708
+ if (manifestJson !== row.manifest || templateManifestHash(manifestText) !== row.manifest_hash)
1724
1709
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1725
1710
  persisted = JSON.parse(manifestJson);
1726
1711
  }
@@ -1729,16 +1714,14 @@ export class HostedConversationNodeService {
1729
1714
  }
1730
1715
  if (!record(persisted))
1731
1716
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1732
- const snapshot = publishedTemplateSnapshot(persisted);
1733
- const parsedDraft = publishedTemplateDraft(persisted.draft);
1734
- const draft = parsedDraft.draft;
1735
- const mcpServers = publishedMcpServers(persisted.mcpServers);
1736
- const contentHash = publishedTemplateContentHash(snapshot.directories, snapshot.entries);
1717
+ const snapshot = parseTemplateVersionManifest(persisted);
1718
+ const { allowedModels, defaultModelId, ...draft } = snapshot.draft;
1719
+ const mcpServers = snapshot.mcpServers;
1720
+ const contentHash = templateContentHash(snapshot.directories, snapshot.entries);
1737
1721
  const totalBytes = snapshot.entries.reduce((total, entry) => total + entry.size, 0);
1738
- if (persisted.mcpConfigHash !== row.mcp_config_hash ||
1739
- createHash('sha256').update(JSON.stringify(mcpServers)).digest('hex') !==
1740
- row.mcp_config_hash ||
1741
- persisted.contentHash !== row.content_hash ||
1722
+ if (snapshot.mcpConfigHash !== row.mcp_config_hash ||
1723
+ templateMcpConfigHash(mcpServers) !== row.mcp_config_hash ||
1724
+ snapshot.contentHash !== row.content_hash ||
1742
1725
  contentHash !== row.content_hash ||
1743
1726
  row.file_count !== snapshot.entries.length ||
1744
1727
  row.total_bytes !== totalBytes)
@@ -1756,7 +1739,10 @@ export class HostedConversationNodeService {
1756
1739
  templateDirectory: contentDirectory,
1757
1740
  sourceWorkspacePath: snapshot.sourceWorkspacePath,
1758
1741
  draft,
1759
- modelPolicy: parsedDraft.modelPolicy,
1742
+ modelPolicy: {
1743
+ allowedIds: allowedModels.map((model) => model.modelId),
1744
+ defaultId: defaultModelId
1745
+ },
1760
1746
  mcpServers
1761
1747
  };
1762
1748
  }
@@ -1825,160 +1811,11 @@ function selectModel(version, models, requestedModelId) {
1825
1811
  throw new Error('HOSTED_CONVERSATION_MODEL_NOT_AVAILABLE');
1826
1812
  return model;
1827
1813
  }
1828
- function publishedTemplateDraft(value) {
1829
- const current = conversationTemplateDraftSchema.safeParse(value);
1830
- if (current.success) {
1831
- const { allowedModels, defaultModelId, ...draft } = current.data;
1832
- return {
1833
- draft,
1834
- modelPolicy: {
1835
- allowedIds: allowedModels.map((model) => model.modelId),
1836
- defaultId: defaultModelId
1837
- }
1838
- };
1839
- }
1840
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1841
- }
1842
1814
  function networkModels(models, networkMode) {
1843
1815
  if (networkMode !== 'NONE')
1844
1816
  return models;
1845
1817
  return models.map((model) => model.hostedWebSearch ? { ...model, hostedWebSearch: false } : model);
1846
1818
  }
1847
- function publishedMcpServers(value) {
1848
- if (!Array.isArray(value))
1849
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1850
- return value.map((candidate) => {
1851
- if (!record(candidate))
1852
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1853
- const keys = Object.keys(candidate).sort().join(',');
1854
- if (keys !== 'configuration,displayName,id,origin,runtime,serverName,transport,version')
1855
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1856
- const runtime = mcpRuntimeDescriptorSchema.parse(candidate.runtime);
1857
- const configuration = mcpInstallationConfigurationSchema.parse(candidate.configuration);
1858
- if (typeof candidate.id !== 'string' ||
1859
- typeof candidate.serverName !== 'string' ||
1860
- typeof candidate.displayName !== 'string' ||
1861
- typeof candidate.version !== 'string' ||
1862
- (candidate.origin !== 'NODE' && candidate.origin !== 'WORKSPACE') ||
1863
- candidate.transport !== runtime.transport)
1864
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1865
- return {
1866
- id: candidate.id,
1867
- serverName: candidate.serverName,
1868
- displayName: candidate.displayName,
1869
- version: candidate.version,
1870
- origin: candidate.origin,
1871
- transport: runtime.transport,
1872
- runtime,
1873
- configuration
1874
- };
1875
- });
1876
- }
1877
- function publishedTemplateSnapshot(manifest) {
1878
- if (manifest.schemaVersion !== 2 ||
1879
- typeof manifest.sourceWorkspacePath !== 'string' ||
1880
- !Array.isArray(manifest.directories) ||
1881
- !Array.isArray(manifest.entries))
1882
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1883
- validatePublishedSourceWorkspacePath(manifest.sourceWorkspacePath);
1884
- const directories = manifest.directories.map(safePublishedTemplateRelativePath);
1885
- if (new Set(directories).size !== directories.length ||
1886
- directories.includes('inbox') ||
1887
- directories.includes('output'))
1888
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1889
- const entries = manifest.entries.map(publishedTemplateEntry);
1890
- if (new Set(entries.map((entry) => entry.relativePath)).size !== entries.length)
1891
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1892
- return {
1893
- schemaVersion: 2,
1894
- sourceWorkspacePath: manifest.sourceWorkspacePath,
1895
- directories,
1896
- entries
1897
- };
1898
- }
1899
- function publishedTemplateEntry(value) {
1900
- if (!record(value))
1901
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1902
- if (value.kind === 'FILE')
1903
- return publishedTemplateFile(value, true);
1904
- if (value.kind !== 'SYMLINK' ||
1905
- typeof value.linkTarget !== 'string' ||
1906
- value.linkTarget.length === 0 ||
1907
- value.linkTarget.includes('\0') ||
1908
- (value.targetKind !== 'SNAPSHOT' && value.targetKind !== 'RUNTIME') ||
1909
- typeof value.resolvedTarget !== 'string')
1910
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1911
- const relativePath = safePublishedTemplateRelativePath(value.relativePath);
1912
- const size = safePublishedTemplateSize(value.size);
1913
- const linkTarget = value.linkTarget;
1914
- const targetKind = value.targetKind;
1915
- const resolvedTarget = value.resolvedTarget;
1916
- if (size !== Buffer.byteLength(linkTarget))
1917
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1918
- if (targetKind === 'SNAPSHOT')
1919
- safePublishedTemplateRelativePath(resolvedTarget);
1920
- else if (!isAbsolute(resolvedTarget) ||
1921
- !RUNTIME_LINK_ROOTS.some((root) => resolvedTarget === root || within(root, resolvedTarget)))
1922
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1923
- return {
1924
- kind: 'SYMLINK',
1925
- relativePath,
1926
- size,
1927
- linkTarget,
1928
- targetKind,
1929
- resolvedTarget
1930
- };
1931
- }
1932
- function publishedTemplateFile(value, requireKind = false) {
1933
- if (!record(value) ||
1934
- (requireKind && value.kind !== 'FILE') ||
1935
- typeof value.executable !== 'boolean' ||
1936
- typeof value.sha256 !== 'string' ||
1937
- !/^[a-f0-9]{64}$/u.test(value.sha256))
1938
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1939
- return {
1940
- kind: 'FILE',
1941
- relativePath: safePublishedTemplateRelativePath(value.relativePath),
1942
- size: safePublishedTemplateSize(value.size),
1943
- executable: value.executable,
1944
- sha256: value.sha256
1945
- };
1946
- }
1947
- function safePublishedTemplateRelativePath(value) {
1948
- if (typeof value !== 'string' ||
1949
- value.length === 0 ||
1950
- value.startsWith('/') ||
1951
- value.includes('\\') ||
1952
- value.split('/').some((segment) => segment.length === 0 || segment === '.' || segment === '..'))
1953
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1954
- return value;
1955
- }
1956
- function safePublishedTemplateSize(value) {
1957
- if (!Number.isSafeInteger(value) || value < 0)
1958
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1959
- return value;
1960
- }
1961
- /** Current manifests hash directory and entry identities; changing the format requires a new version. */
1962
- function publishedTemplateContentHash(directories, entries) {
1963
- const content = [
1964
- ...directories.map((directory) => `DIRECTORY\0${directory}`),
1965
- ...entries.map((entry) => entry.kind === 'FILE'
1966
- ? `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.executable ? 1 : 0}\0${entry.sha256}`
1967
- : `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.linkTarget}\0${entry.targetKind}\0${entry.resolvedTarget}`)
1968
- ].join('\n');
1969
- return createHash('sha256').update(content).digest('hex');
1970
- }
1971
- function validatePublishedSourceWorkspacePath(path) {
1972
- if (!isAbsolute(path) ||
1973
- resolve(path) !== path ||
1974
- path === '/' ||
1975
- RESERVED_SANDBOX_ROOTS.some((root) => path === root || within(root, path) || within(path, root)))
1976
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1977
- }
1978
- function within(parent, candidate) {
1979
- const relation = relative(parent, candidate);
1980
- return relation.length > 0 && relation !== '..' && !relation.startsWith(`..${sep}`);
1981
- }
1982
1819
  function resolveMcpServers(version, environment) {
1983
1820
  const resolveReferences = (references) => {
1984
1821
  const resolved = {};
@@ -117,28 +117,21 @@ export class NativeSessionProjectionService {
117
117
  }
118
118
  resumedConfiguration(session) {
119
119
  const workspace = this.options.database()?.getWorkspace(session.workspaceId);
120
+ const runner = this.options.runners.require(session.runner);
120
121
  const saved = this.options
121
122
  .database()
122
123
  ?.getRunnerDefaults()
123
124
  .find((value) => value.runner === session.runner);
124
- const fallback = saved ?? this.options.runners.require(session.runner).defaultConfiguration(workspace);
125
+ const fallback = saved ?? runner.defaultConfiguration(workspace);
125
126
  const configuration = {
126
127
  runner: session.runner,
127
128
  model: typeof session.model === 'string' && session.model.trim().length > 0
128
129
  ? session.model
129
130
  : fallback.model,
130
131
  effort: typeof session.effort === 'string' ? session.effort : fallback.effort,
131
- access: typeof session.access === 'string' && session.access.trim().length > 0
132
- ? session.access
133
- : session.runner === 'mar-agent'
134
- ? 'full-access'
135
- : fallback.access
132
+ access: runner.restoreStoredAccess(session.access, fallback.access)
136
133
  };
137
- return session.runner === 'mar-agent' &&
138
- configuration.access !== 'auto' &&
139
- configuration.access !== 'full-access'
140
- ? { ...configuration, access: 'full-access' }
141
- : configuration;
134
+ return configuration;
142
135
  }
143
136
  ensureResumedConfiguration(session) {
144
137
  const configuration = this.resumedConfiguration(session);
@@ -315,32 +308,22 @@ export class NativeSessionProjectionService {
315
308
  runner.forgetNativeAlias(nativeId);
316
309
  }
317
310
  }
311
+ const directIds = new Set();
318
312
  for (const [directId, direct] of this.options.state.directNative) {
319
313
  if (direct.runner === session.runner &&
320
314
  direct.cwd === session.cwd &&
321
315
  direct.externalSessionId !== null &&
322
316
  nativeIds.has(direct.externalSessionId)) {
317
+ directIds.add(directId);
323
318
  this.options.state.directNative.delete(directId);
324
- this.options.state.externalActivity.delete(directId);
325
- this.options.state.resumedByDirectId.delete(directId);
326
- this.options.state.resumeFailures.delete(directId);
327
- this.options.state.messageResumeFailures.delete(directId);
328
- this.options.state.resumeFailureDetails.delete(directId);
329
- const timer = this.options.state.failureTimers.get(directId);
330
- if (timer !== undefined)
331
- clearTimeout(timer);
332
- this.options.state.failureTimers.delete(directId);
333
319
  }
334
320
  }
335
- for (const [directId, resumedId] of this.options.state.resumedByDirectId)
336
- if (projectionIds.has(resumedId))
337
- this.options.state.resumedByDirectId.delete(directId);
338
321
  for (const projectionId of projectionIds) {
339
322
  if (this.options.runtime.getAgentSession(projectionId) !== undefined)
340
323
  this.options.runtime.removeAgentSession(projectionId);
341
324
  this.options.commands.removeSession(projectionId);
342
- this.options.state.externalActivity.delete(projectionId);
343
325
  }
326
+ this.options.releaseResumeState(directIds, projectionIds);
344
327
  }
345
328
  requireDatabase() {
346
329
  const database = this.options.database();
@@ -1,7 +1,7 @@
1
1
  import WebSocket from 'ws';
2
2
  import { createEnvelope } from '@myagentroam/protocol';
3
3
  import { ControlOutboundScheduler } from '../control-outbound-scheduler.js';
4
- import { nodeConnectEndpoint } from '../runner/codex/conversation-parser.js';
4
+ import { nodeConnectEndpoint } from '../util/node-operation-parsers.js';
5
5
  export class NodeControlChannel {
6
6
  options;
7
7
  socket;
@@ -1,5 +1,5 @@
1
1
  import { nodeLog } from '../operational.js';
2
- import { validUserAuthorization } from '../runner/codex/conversation-parser.js';
2
+ import { validUserAuthorization } from '../util/node-operation-parsers.js';
3
3
  import { safePublicError } from '../util/safe-error.js';
4
4
  /** Validates and executes the closed Admin-facing Node operation envelope. */
5
5
  export class NodeRequestService {
@@ -27,30 +27,6 @@ export class NodeRunEventBridge {
27
27
  queue: this.options.queues.present(workspaceId, sessionId)
28
28
  });
29
29
  }
30
- migrateRunnerSessionIdentity(sessionId, nativeSessionId) {
31
- for (const [turnId, managed] of this.options.codex.execution.managedTurns) {
32
- if (managed.sessionId === sessionId)
33
- this.options.codex.execution.managedTurns.set(turnId, {
34
- ...managed,
35
- sessionId: nativeSessionId
36
- });
37
- }
38
- for (const [runId, managed] of this.options.codex.execution.runs) {
39
- if (managed.sessionId === sessionId)
40
- this.options.codex.execution.runs.set(runId, { ...managed, sessionId: nativeSessionId });
41
- }
42
- for (const [runId, managed] of this.options.claude.execution.runs) {
43
- if (managed.marSessionId === sessionId)
44
- this.options.claude.execution.runs.set(runId, {
45
- ...managed,
46
- marSessionId: nativeSessionId
47
- });
48
- }
49
- for (const [runId, managed] of this.options.openCode.execution) {
50
- if (managed.sessionId === sessionId)
51
- this.options.openCode.execution.set(runId, { ...managed, sessionId: nativeSessionId });
52
- }
53
- }
54
30
  reconcile(envelope) {
55
31
  const payload = envelope.payload;
56
32
  if (!Array.isArray(payload.runIds) || payload.runIds.some((id) => typeof id !== 'string')) {
@@ -1,6 +1,6 @@
1
1
  import WebSocket from 'ws';
2
2
  import { terminalDataFrameSchema } from '@myagentroam/protocol';
3
- import { nodeTerminalConnectEndpoint, isPlainRecord } from '../runner/codex/conversation-parser.js';
3
+ import { nodeTerminalConnectEndpoint, isPlainRecord } from '../util/node-operation-parsers.js';
4
4
  import { TerminalRelayState } from './terminal-relay-state.js';
5
5
  import { TerminalRelaySender } from '@myagentroam/marctl/src/terminal-relay-sender.mjs';
6
6
  export class NodeTerminalChannel {
@@ -1,3 +1,4 @@
1
+ import { composerVisibleText } from '@myagentroam/protocol';
1
2
  const TERMINAL_TTL_MS = 12 * 60 * 60_000;
2
3
  const CLEANUP_INTERVAL_MS = 5 * 60_000;
3
4
  const TERMINAL_LIMIT = 100;
@@ -91,7 +92,7 @@ function taskKey(userId, sessionId) {
91
92
  return `${userId}\0${sessionId}`;
92
93
  }
93
94
  function messagePreview(value) {
94
- const normalized = value?.trim().replace(/\s+/gu, ' ');
95
+ const normalized = value === undefined ? undefined : composerVisibleText(value).replace(/\s+/gu, ' ');
95
96
  return normalized === undefined || normalized.length === 0 ? undefined : normalized.slice(0, 500);
96
97
  }
97
98
  function terminal(status) {
@@ -1,7 +1,7 @@
1
1
  import { runImageReadInputSchema } from '@myagentroam/protocol';
2
2
  import { nodeOperationData } from '../util/node-operation-data.js';
3
3
  import { isTerminalRunStatus } from '../util/node-operation-parsers.js';
4
- import { isPlainRecord } from '../runner/codex/conversation-parser.js';
4
+ import { isPlainRecord } from '../util/node-operation-parsers.js';
5
5
  import { isHostedConversationImageRunId } from '../runner/mar-agent/generated-images.js';
6
6
  import { assertRunOwner } from './run-ownership-policy.js';
7
7
  export class RunWorkbenchService {
@@ -1,5 +1,6 @@
1
1
  import { denyPermission } from '../claude-agent-sdk.js';
2
- import { boundedConversationItemId, claudeUserInputQuestions, isTerminalRunStatus } from '../runner/codex/conversation-parser.js';
2
+ import { boundedConversationItemId, isTerminalRunStatus } from '../util/node-operation-parsers.js';
3
+ import { claudeUserInputQuestions } from '../runner/claude/conversation-parser.js';
3
4
  export class RunnerInteractionService {
4
5
  options;
5
6
  constructor(options) {
@@ -52,13 +52,15 @@ export class RunnerService {
52
52
  const defaults = this.database()
53
53
  .getRunnerDefaults()
54
54
  .map((configuration) => {
55
- if (configuration.runner !== 'mar-agent' ||
56
- configuration.access === 'auto' ||
57
- configuration.access === 'full-access')
55
+ const runner = this.runners.optional(configuration.runner);
56
+ if (runner === undefined)
57
+ return configuration;
58
+ const access = runner.restoreStoredAccess(configuration.access, configuration.access);
59
+ if (access === configuration.access)
58
60
  return configuration;
59
61
  const normalized = {
60
62
  ...configuration,
61
- access: 'full-access'
63
+ access
62
64
  };
63
65
  this.database().saveRunnerDefaults(normalized);
64
66
  return normalized;
@@ -28,6 +28,7 @@ export class SessionIdentityService {
28
28
  const session = this.options.runtime.promoteSessionIdentity(sessionId, nativeSessionId, {
29
29
  deferRunChanges: true
30
30
  });
31
+ this.options.migrateAttachments(sessionId, nativeSessionId);
31
32
  this.options.migrateGoal(sessionId, nativeSessionId);
32
33
  this.options.migrateCommands(sessionId, nativeSessionId);
33
34
  this.options.migrateQueue(sessionId, nativeSessionId);
@@ -178,6 +178,7 @@ export class SessionMessageService {
178
178
  sessionId: session.id,
179
179
  clientMessageId,
180
180
  content: publicContent,
181
+ ...(internalGoal || goalMessage?.activated === true ? { goalInput: true } : {}),
181
182
  ...(internalSchedule ? { source: 'SCHEDULED' } : {}),
182
183
  ...(input.__suppressNotifications === true ? { suppressNotifications: true } : {}),
183
184
  ...(Number.isSafeInteger(input.__requestUserId)
@@ -1,5 +1,5 @@
1
1
  import { marctlSessionDetail } from './session-recent-activity.js';
2
- import { isPlainRecord } from '../runner/codex/conversation-parser.js';
2
+ import { isPlainRecord } from '../util/node-operation-parsers.js';
3
3
  import { toolResultResourceInputSchema, toolResultResourcePageSchema } from '@myagentroam/protocol';
4
4
  export class SessionQueryService {
5
5
  options;
@@ -1,6 +1,6 @@
1
1
  import { conversationSegmentIdentityForItem, conversationSegments } from './conversation-segment-service.js';
2
2
  import { runConversationItemId } from '../runtime-state.js';
3
- import { isNodeAgentRun, isNodeConversationTurn, isPlainRecord } from '../runner/codex/conversation-parser.js';
3
+ import { isNodeAgentRun, isNodeConversationTurn, isPlainRecord } from '../util/node-operation-parsers.js';
4
4
  export function workbenchPayloadSessionId(getRun, category, payload) {
5
5
  if (!isPlainRecord(payload))
6
6
  return undefined;
@@ -1,5 +1,5 @@
1
1
  import { createEnvelope } from '@myagentroam/protocol';
2
- import { isPlainRecord } from '../runner/codex/conversation-parser.js';
2
+ import { isPlainRecord } from '../util/node-operation-parsers.js';
3
3
  import { projectWorkbenchPayload, workbenchPayloadSessionId } from './workbench-event-projection.js';
4
4
  export class WorkbenchEventService {
5
5
  options;
@@ -189,7 +189,6 @@ export class WorkspaceFileService {
189
189
  throw new Error('FILE_PATH_ESCAPE');
190
190
  return this.serializeMutation(workspace.id, async () => {
191
191
  const result = await writeWorkspaceTextFile(workspace.path, path, content, expectedRevision);
192
- this.dispose(workspace.id);
193
192
  this.onInvalidated(workspace.id);
194
193
  return result;
195
194
  });
@@ -221,7 +220,6 @@ export class WorkspaceFileService {
221
220
  throw new Error('FILE_PATH_ESCAPE');
222
221
  return this.serializeMutation(workspace.id, async () => {
223
222
  const result = await mutateWorkspaceFile(workspace.path, action === 'MOVE' ? { action, path, destination: destination } : { action, path });
224
- this.dispose(workspace.id);
225
223
  this.onInvalidated(workspace.id);
226
224
  return result;
227
225
  });
@@ -177,7 +177,7 @@ export class WorkspaceQueueWorkbenchService {
177
177
  }
178
178
  publicQueueItem(itemId) {
179
179
  const item = this.options.runtime.getQueueItem(itemId);
180
- return item?.clientMessageId.startsWith('goal:') ? undefined : item;
180
+ return item?.goalInput === true ? undefined : item;
181
181
  }
182
182
  queuedCount(sessionId) {
183
183
  return this.options.runtime
@@ -185,7 +185,7 @@ export class WorkspaceQueueWorkbenchService {
185
185
  .itemIds.filter((id) => this.publicQueueItem(id) !== undefined).length;
186
186
  }
187
187
  requireItem(itemId, workspaceId) {
188
- const item = this.options.runtime.getQueueItem(itemId);
188
+ const item = this.publicQueueItem(itemId);
189
189
  if (item === undefined)
190
190
  throw new Error('QUEUE_ITEM_INVALID');
191
191
  if (typeof workspaceId !== 'string' || item.workspaceId !== workspaceId)
@@ -104,15 +104,6 @@ export function compactRunnerValue(value, limit) {
104
104
  }
105
105
  return { text: compactRunnerText(text, limit), truncated: text.length > limit };
106
106
  }
107
- export function codexTimestamp(value) {
108
- if (typeof value === 'number' && Number.isFinite(value))
109
- return value < 10_000_000_000 ? value * 1_000 : value;
110
- if (typeof value === 'string') {
111
- const timestamp = Date.parse(value);
112
- return Number.isNaN(timestamp) ? null : timestamp;
113
- }
114
- return null;
115
- }
116
107
  export function nativePageEnd(cursor, sessionId, fallback) {
117
108
  if (cursor === undefined)
118
109
  return fallback;
@@ -128,42 +119,6 @@ export function nativePageEnd(cursor, sessionId, fallback) {
128
119
  return fallback;
129
120
  }
130
121
  }
131
- export function extractClaudeText(message) {
132
- return claudeContentBlocks(message)
133
- .flatMap((block) => block.type === 'text' && typeof block.text === 'string' ? [block.text] : [])
134
- .join('');
135
- }
136
- export function extractClaudeToolUses(message) {
137
- return claudeContentBlocks(message).flatMap((block) => {
138
- if (block.type !== 'tool_use' || typeof block.id !== 'string' || typeof block.name !== 'string')
139
- return [];
140
- const input = block.input;
141
- const command = isPlainRecord(input) && typeof input.command === 'string' ? input.command : undefined;
142
- return [
143
- {
144
- id: block.id,
145
- name: block.name,
146
- input,
147
- ...(command === undefined ? {} : { command }),
148
- inputTruncated: compactRunnerValue(input, 10_000).truncated
149
- }
150
- ];
151
- });
152
- }
153
- export function claudeContentBlocks(message) {
154
- const content = message.message;
155
- if (!isPlainRecord(content) || !Array.isArray(content.content))
156
- return [];
157
- return content.content.filter(isPlainRecord);
158
- }
159
- export function isClaudeCommandTool(name) {
160
- return name === 'Bash' || name === 'bash' || name === 'Shell';
161
- }
162
- export function nativeClaudeCommand(entry) {
163
- if (isPlainRecord(entry.input) && typeof entry.input.command === 'string')
164
- return compactRunnerText(entry.input.command, 10_000);
165
- return entry.inputSummary ?? entry.toolName;
166
- }
167
122
  export function boundedConversationItemId(prefix, value) {
168
123
  const candidate = `${prefix}:${value}`;
169
124
  if (candidate.length <= 88)