@myagentroam/node 0.11.35 → 0.11.37

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 (143) hide show
  1. package/dist/application/execution-lifecycle.js +54 -0
  2. package/dist/application/node-lifecycle.js +104 -0
  3. package/dist/application/node-operation-admission.js +51 -0
  4. package/dist/application/node-operation-contracts.js +1 -0
  5. package/dist/application/runner-composition.js +22 -0
  6. package/dist/application/session-projection-composition.js +26 -0
  7. package/dist/application/workspace-application.js +61 -0
  8. package/dist/application/workspace-composition.js +21 -0
  9. package/dist/capabilities.js +2 -2
  10. package/dist/codex-app-server.js +1 -471
  11. package/dist/connection/authorization.js +26 -0
  12. package/dist/{service → connection}/direct-transfer-service.js +1 -1
  13. package/dist/connection/endpoints.js +32 -0
  14. package/dist/connector.js +144 -252
  15. package/dist/database.js +34 -14
  16. package/dist/git-askpass.js +1 -1
  17. package/dist/git-execution-helper.js +1 -1
  18. package/dist/hosting/hosted-conversation-file-identity.js +27 -0
  19. package/dist/hosting/hosted-conversation-file-operations.js +44 -0
  20. package/dist/hosting/hosted-conversation-node-service.js +974 -0
  21. package/dist/hosting/hosted-conversation-runs.js +599 -0
  22. package/dist/hosting/hosted-conversation-templates.js +280 -0
  23. package/dist/hosting/hosted-conversation-uploads.js +344 -0
  24. package/dist/hosting/hosted-conversation-watch.js +194 -0
  25. package/dist/hosting/runtime-contracts.js +1 -0
  26. package/dist/marctl-guidance.js +1 -1
  27. package/dist/runner/abstract-runner.js +4 -13
  28. package/dist/{claude-agent-sdk.js → runner/claude/claude-agent-sdk.js} +2 -2
  29. package/dist/runner/claude/conversation-parser.js +2 -1
  30. package/dist/runner/claude/managed-run-controller.js +6 -7
  31. package/dist/runner/claude/message-parsers.js +2 -1
  32. package/dist/runner/claude/native-transcript.js +2 -1
  33. package/dist/runner/claude/tool-normalizer.js +1 -1
  34. package/dist/runner/claude-code-runner.js +2 -2
  35. package/dist/runner/codex/codex-app-server.js +471 -0
  36. package/dist/runner/codex/conversation-parser.js +4 -2
  37. package/dist/runner/codex/managed-run-controller.js +7 -9
  38. package/dist/runner/codex/native-transcript.js +1 -1
  39. package/dist/runner/codex/tool-normalizer.js +1 -1
  40. package/dist/runner/codex-runner.js +9 -27
  41. package/dist/runner/contracts.js +1 -0
  42. package/dist/runner/mar-agent/conversation-normalizer.js +2 -2
  43. package/dist/runner/mar-agent/history-projection.js +1 -1
  44. package/dist/runner/mar-agent/hosted-conversation-runtime.js +6 -10
  45. package/dist/runner/mar-agent/managed-run-controller.js +85 -61
  46. package/dist/runner/mar-agent-runner.js +1 -1
  47. package/dist/runner/normalization.js +42 -0
  48. package/dist/runner/opencode/conversation-parser.js +1 -1
  49. package/dist/runner/opencode/managed-run-controller.js +6 -7
  50. package/dist/{opencode-server.js → runner/opencode/opencode-server.js} +2 -2
  51. package/dist/runner/opencode/tool-normalizer.js +1 -1
  52. package/dist/runner/opencode-runner.js +4 -5
  53. package/dist/runner/runner-registry.js +4 -1
  54. package/dist/runtime-state.js +48 -372
  55. package/dist/service/computer-use-enablement-service.js +1 -1
  56. package/dist/service/continuous-run-coordinator.js +52 -7
  57. package/dist/service/conversation-history-service.js +4 -4
  58. package/dist/service/conversation-hosting-capability-service.js +1 -1
  59. package/dist/service/conversation-hosting-enablement-service.js +1 -1
  60. package/dist/service/conversation-hosting-lifecycle-service.js +3 -3
  61. package/dist/service/conversation-hosting-maintenance-service.js +1 -1
  62. package/dist/service/conversation-observation.js +1 -1
  63. package/dist/service/conversation-segment-service.js +1 -1
  64. package/dist/service/external-session-resume-service.js +47 -27
  65. package/dist/service/fake-managed-run-service.js +3 -7
  66. package/dist/service/git-operation-service.js +1 -1
  67. package/dist/service/goal-continuation-coordinator.js +14 -7
  68. package/dist/service/mar-agent-model-transport-test-service.js +1 -1
  69. package/dist/service/native-session-watch-service.js +1 -1
  70. package/dist/service/node-control-channel.js +1 -5
  71. package/dist/service/node-request-service.js +1 -1
  72. package/dist/service/node-terminal-channel.js +2 -1
  73. package/dist/service/node-workspace-coordinator.js +3 -2
  74. package/dist/service/notification-mcp-bridge.js +6 -2
  75. package/dist/service/recent-run-projection-service.js +1 -1
  76. package/dist/service/restart-session-restorer.js +1 -1
  77. package/dist/service/run-event-service.js +49 -42
  78. package/dist/service/run-workbench-service.js +3 -3
  79. package/dist/service/runner-interaction-service.js +6 -5
  80. package/dist/service/runner-service.js +1 -1
  81. package/dist/service/session-recent-activity.js +2 -1
  82. package/dist/service/skill-directory-service.js +1 -1
  83. package/dist/service/skill-install-service.js +2 -2
  84. package/dist/service/terminal-service.js +1 -1
  85. package/dist/service/workbench-event-projection.js +3 -2
  86. package/dist/service/workbench-event-service.js +1 -1
  87. package/dist/service/workspace-service.js +3 -2
  88. package/dist/session/attachments.js +110 -0
  89. package/dist/session/contracts.js +1 -0
  90. package/dist/{util → session}/conversation-identity.js +1 -1
  91. package/dist/session/input-validation.js +15 -0
  92. package/dist/{service → session}/native-session-projection-service.js +40 -11
  93. package/dist/session/pagination.js +77 -0
  94. package/dist/{util → session}/runner-native-session-parsers.js +2 -1
  95. package/dist/session/runtime/approvals.js +90 -0
  96. package/dist/session/runtime/conversation-projection.js +310 -0
  97. package/dist/session/runtime/type-guards.js +19 -0
  98. package/dist/{service → session}/session-catalog-service.js +5 -4
  99. package/dist/{service → session}/session-command-service.js +3 -3
  100. package/dist/{service → session}/session-identity-service.js +1 -1
  101. package/dist/{service → session}/session-lifecycle-service.js +18 -7
  102. package/dist/{service → session}/session-message-service.js +6 -5
  103. package/dist/{service → session}/session-presentation-service.js +1 -1
  104. package/dist/{service → session}/session-query-service.js +2 -2
  105. package/dist/{service → session}/session-schedule-service.js +2 -2
  106. package/dist/util/records.js +3 -0
  107. package/dist/workspace/contracts.js +1 -0
  108. package/dist/workspace/file-index.js +534 -0
  109. package/dist/workspace/files.js +466 -0
  110. package/dist/workspace/git.js +991 -0
  111. package/dist/workspace/path-access.js +184 -0
  112. package/dist/workspace/utf8.js +12 -0
  113. package/dist/{service → workspace}/workspace-change-service.js +45 -21
  114. package/dist/{service → workspace}/workspace-code-navigation-service.js +6 -6
  115. package/dist/{service → workspace}/workspace-file-manager-service.js +1 -1
  116. package/dist/{service → workspace}/workspace-file-service.js +16 -16
  117. package/dist/{service → workspace}/workspace-git-exclude-service.js +1 -1
  118. package/dist/{service → workspace}/workspace-queue-workbench-service.js +3 -2
  119. package/dist/{service → workspace}/workspace-session-service.js +45 -40
  120. package/dist/{service → workspace}/workspace-upload-service.js +28 -28
  121. package/dist/{service → workspace}/workspace-watch-service.js +29 -28
  122. package/dist/{service → workspace}/workspace-workbench-service.js +4 -3
  123. package/package.json +4 -4
  124. package/dist/service/hosted-conversation-node-service.js +0 -2252
  125. package/dist/service/run-domain-state.js +0 -4
  126. package/dist/service/session-domain-state.js +0 -9
  127. package/dist/service/workspace-domain-state.js +0 -41
  128. package/dist/util/node-operation-parsers.js +0 -320
  129. package/dist/workspace.js +0 -2168
  130. /package/dist/{connector → application}/node-connector-options.js +0 -0
  131. /package/dist/{util → connection}/node-operation-data.js +0 -0
  132. /package/dist/{connector → connection}/node-operation-router.js +0 -0
  133. /package/dist/{service → hosting}/hosted-conversation-sandbox-profile.js +0 -0
  134. /package/dist/{service → hosting}/hosted-conversation-store.js +0 -0
  135. /package/dist/{util → mcp}/secret-environment.js +0 -0
  136. /package/dist/{claude-channel.js → runner/claude/claude-channel.js} +0 -0
  137. /package/dist/{util → runner}/personal-instructions.js +0 -0
  138. /package/dist/{util → runner}/runner-error.js +0 -0
  139. /package/dist/{service/conversation-work-process.js → session/runtime/work-process.js} +0 -0
  140. /package/dist/{service → session}/session-context-service.js +0 -0
  141. /package/dist/{util → workspace}/git-credential-client.js +0 -0
  142. /package/dist/{service → workspace}/workspace-content-search-service.js +0 -0
  143. /package/dist/{service → workspace}/workspace-watch-snapshot-service.js +0 -0
@@ -1,4 +1,4 @@
1
- import { isPlainRecord } from '../util/node-operation-parsers.js';
1
+ import { isPlainRecord } from '../util/records.js';
2
2
  import { composeConversationRuntimePage, convergeConversationWatchPage } from './conversation-history-service.js';
3
3
  const INITIAL_TURN_LIMIT = 30;
4
4
  const EMITTED_TURN_LIMIT = 10;
@@ -1,4 +1,4 @@
1
- import { projectConversationWorkProcesses } from './conversation-work-process.js';
1
+ import { projectConversationWorkProcesses } from '../session/runtime/work-process.js';
2
2
  const MAX_INITIAL_TURN_PAGE_SIZE = 30;
3
3
  function conversationSegmentPrefix(turn) {
4
4
  const user = turn.items.find((item) => item.kind === 'user_message');
@@ -1,25 +1,45 @@
1
1
  const RESUME_FAILURE_TTL_MS = 30 * 60_000;
2
2
  export class ExternalSessionResumeService {
3
3
  options;
4
+ externalActivity = new Map();
5
+ resumedByDirectId = new Map();
6
+ resumeFailures = new Map();
7
+ messageResumeFailures = new Map();
8
+ resumeFailureDetails = new Map();
9
+ failureTimers = new Map();
10
+ activity(sessionId) {
11
+ return this.externalActivity.get(sessionId);
12
+ }
4
13
  transitions = new Set();
5
14
  resumeTasks = new Map();
6
15
  constructor(options) {
7
16
  this.options = options;
8
17
  }
18
+ async close() {
19
+ await Promise.allSettled([...this.resumeTasks.values()]);
20
+ for (const timer of this.failureTimers.values())
21
+ clearTimeout(timer);
22
+ this.failureTimers.clear();
23
+ this.externalActivity.clear();
24
+ this.resumedByDirectId.clear();
25
+ this.resumeFailures.clear();
26
+ this.messageResumeFailures.clear();
27
+ this.resumeFailureDetails.clear();
28
+ }
9
29
  setActivity(sessionId, activity) {
10
- this.options.state.externalActivity.set(sessionId, activity);
30
+ this.externalActivity.set(sessionId, activity);
11
31
  }
12
32
  forgetSessions(directIds, projectionIds) {
13
33
  for (const directId of directIds) {
14
- this.options.state.externalActivity.delete(directId);
15
- this.options.state.resumedByDirectId.delete(directId);
34
+ this.externalActivity.delete(directId);
35
+ this.resumedByDirectId.delete(directId);
16
36
  this.clearFailure(directId);
17
37
  }
18
- for (const [directId, resumedId] of this.options.state.resumedByDirectId)
38
+ for (const [directId, resumedId] of this.resumedByDirectId)
19
39
  if (projectionIds.has(resumedId))
20
- this.options.state.resumedByDirectId.delete(directId);
40
+ this.resumedByDirectId.delete(directId);
21
41
  for (const projectionId of projectionIds)
22
- this.options.state.externalActivity.delete(projectionId);
42
+ this.externalActivity.delete(projectionId);
23
43
  }
24
44
  transitioning(sessionId) {
25
45
  return this.transitions.has(sessionId);
@@ -40,13 +60,13 @@ export class ExternalSessionResumeService {
40
60
  }
41
61
  async performResume(external) {
42
62
  this.clearFailure(external.id);
43
- const priorId = this.options.state.resumedByDirectId.get(external.id);
63
+ const priorId = this.resumedByDirectId.get(external.id);
44
64
  const prior = priorId === undefined ? undefined : this.options.runtime.getAgentSession(priorId);
45
65
  if (prior !== undefined)
46
66
  return this.options.ensureConfiguration(prior);
47
67
  const runner = this.options.runners.product(external.runner);
48
68
  if (runner === undefined) {
49
- this.options.state.externalActivity.set(external.id, 'IDLE');
69
+ this.externalActivity.set(external.id, 'IDLE');
50
70
  this.setFailure(external.id, 'RUNNER_UNAVAILABLE');
51
71
  return undefined;
52
72
  }
@@ -64,7 +84,7 @@ export class ExternalSessionResumeService {
64
84
  projectionFor: this.options.projectionFor,
65
85
  managedAliasFor: this.options.managedAliasFor,
66
86
  currentResumed: (sessionId) => {
67
- const resumedId = this.options.state.resumedByDirectId.get(sessionId);
87
+ const resumedId = this.resumedByDirectId.get(sessionId);
68
88
  return resumedId === undefined
69
89
  ? undefined
70
90
  : this.options.runtime.getAgentSession(resumedId);
@@ -104,24 +124,24 @@ export class ExternalSessionResumeService {
104
124
  releaseMutation();
105
125
  }
106
126
  if (result.activity !== undefined)
107
- this.options.state.externalActivity.set(external.id, result.activity);
127
+ this.externalActivity.set(external.id, result.activity);
108
128
  else if (result.failureCode === 'RUNNER_UNAVAILABLE')
109
- this.options.state.externalActivity.set(external.id, 'IDLE');
129
+ this.externalActivity.set(external.id, 'IDLE');
110
130
  if (result.session !== undefined) {
111
- this.options.state.resumedByDirectId.set(external.id, result.session.id);
131
+ this.resumedByDirectId.set(external.id, result.session.id);
112
132
  return result.session;
113
133
  }
114
134
  this.setFailure(external.id, result.failureCode ?? 'SESSION_RESUME_REJECTED', result.failureDetail);
115
- this.options.state.messageResumeFailures.set(external.id, result.messageFailureCode ?? result.failureCode ?? 'SESSION_RESUME_REJECTED');
135
+ this.messageResumeFailures.set(external.id, result.messageFailureCode ?? result.failureCode ?? 'SESSION_RESUME_REJECTED');
116
136
  return undefined;
117
137
  }
118
138
  failure(sessionId) {
119
- const code = this.options.state.resumeFailures.get(sessionId) ?? 'SESSION_RESUME_REJECTED';
120
- const detail = this.options.state.resumeFailureDetails.get(sessionId);
139
+ const code = this.resumeFailures.get(sessionId) ?? 'SESSION_RESUME_REJECTED';
140
+ const detail = this.resumeFailureDetails.get(sessionId);
121
141
  return detail === undefined ? { code } : { code, detail };
122
142
  }
123
143
  async resolveForMessage(sessionId) {
124
- const resumedId = this.options.state.resumedByDirectId.get(sessionId);
144
+ const resumedId = this.resumedByDirectId.get(sessionId);
125
145
  const current = this.options.runtime.getAgentSession(resumedId ?? sessionId) ??
126
146
  (await this.options.resolve(sessionId));
127
147
  if (current === undefined)
@@ -131,31 +151,31 @@ export class ExternalSessionResumeService {
131
151
  const resumed = await this.resume(current);
132
152
  if (resumed !== undefined)
133
153
  return resumed;
134
- if (this.options.state.externalActivity.get(current.id) === 'BUSY')
154
+ if (this.externalActivity.get(current.id) === 'BUSY')
135
155
  throw new Error('EXTERNAL_SESSION_ACTIVE');
136
- throw new Error(this.options.state.messageResumeFailures.get(current.id) ?? this.failure(current.id).code);
156
+ throw new Error(this.messageResumeFailures.get(current.id) ?? this.failure(current.id).code);
137
157
  }
138
158
  async acceptForMessage(sessionId) {
139
159
  return { session: await this.resolveForMessage(sessionId) };
140
160
  }
141
161
  clearFailure(sessionId) {
142
- const timer = this.options.state.failureTimers.get(sessionId);
162
+ const timer = this.failureTimers.get(sessionId);
143
163
  if (timer !== undefined)
144
164
  clearTimeout(timer);
145
- this.options.state.failureTimers.delete(sessionId);
146
- this.options.state.resumeFailures.delete(sessionId);
147
- this.options.state.resumeFailureDetails.delete(sessionId);
148
- this.options.state.messageResumeFailures.delete(sessionId);
165
+ this.failureTimers.delete(sessionId);
166
+ this.resumeFailures.delete(sessionId);
167
+ this.resumeFailureDetails.delete(sessionId);
168
+ this.messageResumeFailures.delete(sessionId);
149
169
  }
150
170
  setFailure(sessionId, code, detail) {
151
171
  this.clearFailure(sessionId);
152
- this.options.state.resumeFailures.set(sessionId, code);
172
+ this.resumeFailures.set(sessionId, code);
153
173
  if (detail === undefined)
154
- this.options.state.resumeFailureDetails.delete(sessionId);
174
+ this.resumeFailureDetails.delete(sessionId);
155
175
  else
156
- this.options.state.resumeFailureDetails.set(sessionId, detail);
176
+ this.resumeFailureDetails.set(sessionId, detail);
157
177
  const timer = setTimeout(() => this.clearFailure(sessionId), RESUME_FAILURE_TTL_MS);
158
178
  timer.unref();
159
- this.options.state.failureTimers.set(sessionId, timer);
179
+ this.failureTimers.set(sessionId, timer);
160
180
  }
161
181
  }
@@ -1,4 +1,3 @@
1
- import { isTerminalRunStatus } from '../util/node-operation-parsers.js';
2
1
  /** Test-only managed Run adapter; production Runner paths never depend on it. */
3
2
  export class FakeManagedRunService {
4
3
  options;
@@ -7,7 +6,7 @@ export class FakeManagedRunService {
7
6
  }
8
7
  start(envelope) {
9
8
  const payload = envelope.payload;
10
- if (typeof payload.runId !== 'string' || this.options.state.active.has(payload.runId))
9
+ if (typeof payload.runId !== 'string' || this.options.runtime.isExecutionActive(payload.runId))
11
10
  return;
12
11
  const runId = payload.runId;
13
12
  const cwd = typeof payload.cwd === 'string' ? payload.cwd : undefined;
@@ -25,14 +24,11 @@ export class FakeManagedRunService {
25
24
  cwd
26
25
  });
27
26
  }
28
- this.options.state.active.add(runId);
27
+ this.options.runtime.executionAccepted(runId);
29
28
  void this.options.runner
30
29
  .start(runId, this.options.script(payload.input, runId), {
31
30
  event: (eventType, eventPayload, status) => {
32
31
  this.options.emitRun(runId, eventType, eventPayload, status);
33
- if (status !== undefined && isTerminalRunStatus(status)) {
34
- this.options.state.active.delete(runId);
35
- }
36
32
  },
37
33
  approval: (approvalId, approvalPayload, expiresAt) => {
38
34
  try {
@@ -55,6 +51,6 @@ export class FakeManagedRunService {
55
51
  },
56
52
  disconnect: this.options.disconnect
57
53
  })
58
- .finally(() => this.options.state.active.delete(runId));
54
+ .finally(() => this.options.runtime.executionReleased(runId));
59
55
  }
60
56
  }
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { personalGitRuntimeSchema } from '@myagentroam/protocol';
3
- import { parseSecretEnvironment } from '../util/secret-environment.js';
3
+ import { parseSecretEnvironment } from '../mcp/secret-environment.js';
4
4
  export const gitOperationEnvironment = new AsyncLocalStorage();
5
5
  /** Binds authenticated operations to the same lease mechanism as Runner queues. */
6
6
  export class GitOperationService {
@@ -1,6 +1,6 @@
1
1
  import { composerVisibleText } from '@myagentroam/protocol';
2
2
  import { nodeLog } from '../operational.js';
3
- import { isPlainRecord } from '../util/node-operation-parsers.js';
3
+ import { isPlainRecord } from '../util/records.js';
4
4
  import { safeErrorCode } from '../util/safe-error.js';
5
5
  import { NODE_MCP } from '../mcp/naming.js';
6
6
  import { composerResourceInputTags } from './composer-message-input.js';
@@ -77,8 +77,10 @@ export class GoalContinuationCoordinator {
77
77
  return;
78
78
  if (permissionStop !== undefined) {
79
79
  this.retireGoalContinuation(pending.sessionId);
80
- this.options.queue().removeGoalQueue(pending.sessionId);
81
- return;
80
+ const changed = this.options
81
+ .queue()
82
+ .removeGoalQueue(pending.sessionId, undefined, undefined, false);
83
+ return changed ? { queueChangedSessionId: pending.sessionId } : undefined;
82
84
  }
83
85
  const disposition = this.options.goals.recordExecutionResult(pending.sessionId, {
84
86
  failed: status === 'FAILED',
@@ -87,12 +89,17 @@ export class GoalContinuationCoordinator {
87
89
  if (disposition === 'blocked' || disposition === 'completed') {
88
90
  this.nativeGoalBindings.delete(pending.goal.id);
89
91
  this.retireGoalContinuation(pending.sessionId);
90
- this.options.queue().removeGoalQueue(pending.sessionId);
92
+ const changed = this.options
93
+ .queue()
94
+ .removeGoalQueue(pending.sessionId, undefined, undefined, false);
91
95
  const terminalGoal = this.options.goals.get(pending.sessionId);
92
96
  const session = this.options.runtime.getAgentSession(pending.sessionId);
93
- if (terminalGoal !== undefined && session !== undefined)
94
- this.publishGoalState(session, terminalGoal);
95
- return;
97
+ return {
98
+ ...(terminalGoal !== undefined && session !== undefined
99
+ ? { goalUpdate: { session, goal: terminalGoal } }
100
+ : {}),
101
+ ...(changed ? { queueChangedSessionId: pending.sessionId } : {})
102
+ };
96
103
  }
97
104
  if (status !== 'FAILED' || started)
98
105
  return;
@@ -1,6 +1,6 @@
1
1
  import { testAgentModelTransport } from '@myagentroam/agent';
2
2
  import { marAgentModelTransportTestInputSchema, marAgentModelTransportTestResultSchema } from '@myagentroam/protocol';
3
- import { runnerErrorProjection } from '../util/runner-error.js';
3
+ import { runnerErrorProjection } from '../runner/runner-error.js';
4
4
  export class MarAgentModelTransportTestService {
5
5
  options;
6
6
  #timeoutMs;
@@ -1,4 +1,4 @@
1
- import { isPlainRecord } from '../util/node-operation-parsers.js';
1
+ import { isPlainRecord } from '../util/records.js';
2
2
  import { ConversationObservation } from './conversation-observation.js';
3
3
  import { safeErrorCode } from '../util/safe-error.js';
4
4
  import { SessionWatchSlotCoordinator } from './session-watch-slot-coordinator.js';
@@ -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 '../util/node-operation-parsers.js';
4
+ import { nodeConnectEndpoint } from '../connection/endpoints.js';
5
5
  export class NodeControlChannel {
6
6
  options;
7
7
  socket;
@@ -50,10 +50,6 @@ export class NodeControlChannel {
50
50
  connected() {
51
51
  return this.socket?.readyState === WebSocket.OPEN;
52
52
  }
53
- /** Keeps transport injection at the transport boundary for contract tests. */
54
- replaceSocketForTesting(socket) {
55
- this.socket = socket;
56
- }
57
53
  close(code, reason) {
58
54
  this.socket?.close(code, reason);
59
55
  }
@@ -1,5 +1,5 @@
1
1
  import { nodeLog } from '../operational.js';
2
- import { validUserAuthorization } from '../util/node-operation-parsers.js';
2
+ import { validUserAuthorization } from '../connection/authorization.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 {
@@ -1,6 +1,7 @@
1
1
  import WebSocket from 'ws';
2
2
  import { terminalDataFrameSchema } from '@myagentroam/protocol';
3
- import { nodeTerminalConnectEndpoint, isPlainRecord } from '../util/node-operation-parsers.js';
3
+ import { nodeTerminalConnectEndpoint } from '../connection/endpoints.js';
4
+ import { isPlainRecord } from '../util/records.js';
4
5
  import { TerminalRelayState } from './terminal-relay-state.js';
5
6
  import { TerminalRelaySender } from '@myagentroam/marctl/src/terminal-relay-sender.mjs';
6
7
  export class NodeTerminalChannel {
@@ -1,7 +1,8 @@
1
1
  import { realpath } from 'node:fs/promises';
2
2
  import { nodeLog } from '../operational.js';
3
- import { inspectWorkspace, normalizeWorkspacePath } from '../workspace.js';
4
- import { ensureWorkspaceGitExclude } from './workspace-git-exclude-service.js';
3
+ import { inspectWorkspace } from '../workspace/git.js';
4
+ import { normalizeWorkspacePath } from '../workspace/path-access.js';
5
+ import { ensureWorkspaceGitExclude } from '../workspace/workspace-git-exclude-service.js';
5
6
  /** Coordinates Workspace invalidation, inspection and persisted Terminal summaries. */
6
7
  export class NodeWorkspaceCoordinator {
7
8
  options;
@@ -7,7 +7,7 @@ import { tmpdir } from 'node:os';
7
7
  import { join } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { notificationInputSchema, notificationChannelsRequestSchema, mailNotificationRequestSchema } from '@myagentroam/protocol';
10
- import { sessionGroup } from './session-presentation-service.js';
10
+ import { sessionGroup } from '../session/session-presentation-service.js';
11
11
  export class NotificationMcpBridge {
12
12
  resolve;
13
13
  send;
@@ -146,7 +146,11 @@ export class NotificationMcpBridge {
146
146
  typeof callId !== 'string')
147
147
  throw new Error('MAIL_INPUT_INVALID');
148
148
  const parsed = mailNotificationRequestSchema.safeParse({
149
- ...current,
149
+ executionId: current.executionId,
150
+ sessionId: current.sessionId,
151
+ workspaceId: current.workspaceId,
152
+ userId: current.userId,
153
+ source: current.source,
150
154
  templateId: templateId ?? '',
151
155
  ...(title === undefined ? {} : { title }),
152
156
  ...(recipientEmail === undefined ? {} : { recipientEmail }),
@@ -1,4 +1,4 @@
1
- import { sessionGroup } from './session-presentation-service.js';
1
+ import { sessionGroup } from '../session/session-presentation-service.js';
2
2
  import { composerVisibleText } from '@myagentroam/protocol';
3
3
  const TERMINAL_TTL_MS = 12 * 60 * 60_000;
4
4
  const CLEANUP_INTERVAL_MS = 5 * 60_000;
@@ -1,5 +1,5 @@
1
1
  import { parseComposerTags, serializeComposerTag } from '@myagentroam/protocol';
2
- import { parseMessageAttachments } from '../util/node-operation-parsers.js';
2
+ import { parseMessageAttachments } from '../session/attachments.js';
3
3
  import { RUNNER_EXECUTION_CONTEXT } from '../runner/execution-context.js';
4
4
  import { RESTART_NOTICE } from './planned-restart-handoff.js';
5
5
  /** Rebuild inputs through the existing preparation and FIFO boundary. */
@@ -1,7 +1,8 @@
1
- import { sessionGroup } from './session-presentation-service.js';
1
+ import { sessionGroup } from '../session/session-presentation-service.js';
2
2
  import { permissionStopSchema, runPerformanceSchema } from '@myagentroam/protocol';
3
3
  import { nodeLog } from '../operational.js';
4
- import { isPlainRecord, isTerminalRunStatus } from '../util/node-operation-parsers.js';
4
+ import { isPlainRecord } from '../util/records.js';
5
+ import { isTerminalRunStatus } from '../session/runtime/type-guards.js';
5
6
  const COALESCE_MS = 200;
6
7
  const MAX_BATCH_CHARS = 16_384;
7
8
  export class RunEventService {
@@ -18,7 +19,7 @@ export class RunEventService {
18
19
  const parsed = runPerformanceSchema.safeParse({ ...payload, runId: run.id });
19
20
  if (parsed.success && this.options.runtime.updateRunPerformance(runId, parsed.data)) {
20
21
  this.flushText(runId);
21
- this.options.emitWorkbench('conversation', {
22
+ this.emitWorkbench('conversation', {
22
23
  sessionId: run.sessionId,
23
24
  performance: parsed.data
24
25
  });
@@ -35,6 +36,13 @@ export class RunEventService {
35
36
  this.flushText(runId);
36
37
  this.emitNow(runId, eventType, payload, status);
37
38
  }
39
+ emitWorkbench(category, payload) {
40
+ if (category === 'conversation' && isPlainRecord(payload) && 'turn' in payload) {
41
+ this.options.emitWorkbench(category, { turn: payload.turn });
42
+ return;
43
+ }
44
+ this.options.emitWorkbench(category, payload);
45
+ }
38
46
  clear() {
39
47
  for (const pending of this.pendingText.values())
40
48
  clearTimeout(pending.timer);
@@ -104,59 +112,58 @@ export class RunEventService {
104
112
  if (status === 'FAILED')
105
113
  this.options.metrics.runnerFailed();
106
114
  let recorded = undefined;
107
- try {
108
- recorded = this.options.runtime.appendRunEvent({
109
- runId: executionId,
115
+ let finalStatus;
116
+ let manuallyInterrupted = false;
117
+ if (terminalExecution) {
118
+ const parsedStop = isPlainRecord(payload)
119
+ ? permissionStopSchema.safeParse(payload.permissionStop)
120
+ : undefined;
121
+ const result = this.options.acceptExecutionTerminal({
122
+ executionId,
110
123
  sequence: event.sequence,
111
124
  eventType,
112
125
  payload,
113
- ...(status === undefined ? {} : { status })
126
+ status: status,
127
+ ...(parsedStop?.success ? { permissionStop: parsedStop.data } : {})
114
128
  });
129
+ recorded = result.recorded;
130
+ if (recorded === undefined)
131
+ return;
132
+ finalStatus = result.finalStatus;
133
+ manuallyInterrupted = result.manuallyInterrupted;
134
+ if (result.queueChangedSessionId !== undefined)
135
+ this.options.publishQueueChange(result.queueChangedSessionId);
136
+ if (result.goalUpdate !== undefined)
137
+ this.options.publishGoalUpdate(result.goalUpdate);
115
138
  }
116
- catch {
117
- /* A stale Runner callback may race process-local Run cleanup. */
139
+ else {
140
+ try {
141
+ recorded = this.options.runtime.appendRunEvent({
142
+ runId: executionId,
143
+ sequence: event.sequence,
144
+ eventType,
145
+ payload,
146
+ ...(status === undefined ? {} : { status })
147
+ });
148
+ }
149
+ catch {
150
+ /* A stale Runner callback may race process-local Run cleanup. */
151
+ }
118
152
  }
119
153
  if (eventType === 'run.started' || eventType === 'run.running')
120
154
  this.options.executionStarted?.(executionId);
121
155
  if (!terminalExecution)
122
156
  this.options.refreshAttachments?.(run.sessionId);
123
- let finalStatus;
124
- let manuallyInterrupted = false;
125
- if (terminalCurrentExecution) {
126
- const parsedStop = isPlainRecord(payload)
127
- ? permissionStopSchema.safeParse(payload.permissionStop)
128
- : undefined;
129
- const permissionStop = parsedStop?.success ? parsedStop.data : undefined;
130
- if (permissionStop !== undefined)
131
- this.options.runtime.pauseSessionQueue(run.sessionId);
132
- this.options.executionTerminating?.(executionId, status, permissionStop);
133
- this.options.state.active.delete(executionId);
134
- const interrupted = this.options.state.interruptRequested.delete(executionId);
135
- manuallyInterrupted = interrupted;
136
- const queue = this.options.runtime.sessionQueue(run.sessionId);
137
- const continues = run.status !== 'CANCELLING' &&
138
- permissionStop === undefined &&
139
- ((!queue.paused && queue.itemIds.length > 0) ||
140
- this.options.keepRunActive?.(run.sessionId) === true);
141
- if (!continues) {
142
- finalStatus =
143
- status === 'CANCELLED' || interrupted ? 'INTERRUPTED' : status;
144
- if (finalStatus === 'SUCCEEDED' ||
145
- finalStatus === 'FAILED' ||
146
- finalStatus === 'INTERRUPTED')
147
- this.options.runtime.finishRun(runId, finalStatus);
148
- }
149
- }
150
157
  const publicStatus = terminalExecution ? finalStatus : status;
151
158
  const notificationPayload = this.notificationPayload(runId, eventType, payload, publicStatus, manuallyInterrupted || !targetsCurrentTurn);
152
159
  if (publicStatus !== undefined) {
153
160
  const currentRun = this.options.runtime.getRun(runId);
154
- this.options.emitWorkbench('run', { run: currentRun });
161
+ this.emitWorkbench('run', { run: currentRun });
155
162
  const session = currentRun === undefined
156
163
  ? undefined
157
164
  : this.options.runtime.getAgentSession(currentRun.sessionId);
158
165
  if (session !== undefined)
159
- this.options.emitWorkbench('session', { session: this.options.presentSession(session) });
166
+ this.emitWorkbench('session', { session: this.options.presentSession(session) });
160
167
  }
161
168
  const createdTurn = targetsCurrentTurn && targetTurn === undefined && currentTurn === undefined
162
169
  ? this.options.runtime.conversationTurnForRun(runId)
@@ -169,7 +176,7 @@ export class RunEventService {
169
176
  (previousGroups.get(item.id)?.id !== item.processGroup?.id ||
170
177
  previousGroups.get(item.id)?.isStart !== item.processGroup?.isStart));
171
178
  if (targetsCurrentTurn)
172
- this.options.emitWorkbench('conversation', createdTurn === undefined && !correctedGroups
179
+ this.emitWorkbench('conversation', createdTurn === undefined && !correctedGroups
173
180
  ? {
174
181
  event: {
175
182
  ...event,
@@ -181,7 +188,7 @@ export class RunEventService {
181
188
  else {
182
189
  const projectedTurn = this.options.runtime.conversationTurnForExecution(executionId);
183
190
  if (projectedTurn !== undefined)
184
- this.options.emitWorkbench('conversation', { turn: projectedTurn });
191
+ this.emitWorkbench('conversation', { turn: projectedTurn });
185
192
  }
186
193
  const occurredAt = recorded?.createdAt ?? Date.now();
187
194
  const terminalNotification = notificationPayload !== undefined && terminalCurrentExecution && finalStatus !== undefined;
@@ -198,7 +205,7 @@ export class RunEventService {
198
205
  targetTurn?.items.some((item) => item.status === 'PENDING' || item.status === 'IN_PROGRESS'))) {
199
206
  const turn = this.options.runtime.conversationTurnForRun(runId);
200
207
  if (turn !== undefined)
201
- this.options.emitWorkbench('conversation', { turn });
208
+ this.emitWorkbench('conversation', { turn });
202
209
  }
203
210
  this.options.cleanupAttachments(executionId);
204
211
  this.options.expireImages(executionId);
@@ -225,7 +232,7 @@ export class RunEventService {
225
232
  }
226
233
  emitNotification(runId, event, payload, occurredAt) {
227
234
  nodeLog('run.event', { runId, eventType: 'notification' });
228
- this.options.emitWorkbench('conversation', {
235
+ this.emitWorkbench('conversation', {
229
236
  event: {
230
237
  runId,
231
238
  sequence: event.sequence,
@@ -1,7 +1,7 @@
1
1
  import { modelResourceReadInputSchema, runImageReadInputSchema } from '@myagentroam/protocol';
2
- import { nodeOperationData } from '../util/node-operation-data.js';
3
- import { isTerminalRunStatus } from '../util/node-operation-parsers.js';
4
- import { isPlainRecord } from '../util/node-operation-parsers.js';
2
+ import { nodeOperationData } from '../connection/node-operation-data.js';
3
+ import { isTerminalRunStatus } from '../session/runtime/type-guards.js';
4
+ import { isPlainRecord } from '../util/records.js';
5
5
  import { isHostedConversationImageRunId } from '../runner/mar-agent/generated-images.js';
6
6
  import { assertRunOwner } from './run-ownership-policy.js';
7
7
  import { imagePreviews } from './image-preview-service.js';
@@ -1,5 +1,6 @@
1
- import { denyPermission } from '../claude-agent-sdk.js';
2
- import { boundedConversationItemId, isTerminalRunStatus } from '../util/node-operation-parsers.js';
1
+ import { denyPermission } from '../runner/claude/claude-agent-sdk.js';
2
+ import { boundedConversationItemId } from '../runner/normalization.js';
3
+ import { isTerminalRunStatus } from '../session/runtime/type-guards.js';
3
4
  import { claudeUserInputQuestions } from '../runner/claude/conversation-parser.js';
4
5
  export class RunnerInteractionService {
5
6
  options;
@@ -223,7 +224,6 @@ export class RunnerInteractionService {
223
224
  finally {
224
225
  codex.execution.runs.delete(runId);
225
226
  codex.releaseEnvironment(runId);
226
- this.options.state.active.delete(runId);
227
227
  }
228
228
  }
229
229
  respondUserInput(runId, requestId, rawAnswers) {
@@ -260,13 +260,14 @@ export class RunnerInteractionService {
260
260
  if (run === undefined)
261
261
  return false;
262
262
  if (run.status === 'CANCELLING') {
263
- const status = this.options.state.interruptRequested.has(runId) ? 'INTERRUPTED' : 'CANCELLED';
263
+ const status = this.options.runtime.isExecutionInterruptRequested(runId)
264
+ ? 'INTERRUPTED'
265
+ : 'CANCELLED';
264
266
  this.options.emitRun(runId, 'run.completed', { status }, status);
265
267
  }
266
268
  else if (!isTerminalRunStatus(run.status))
267
269
  return false;
268
270
  this.options.codex().execution.runs.delete(runId);
269
- this.options.state.active.delete(runId);
270
271
  return true;
271
272
  }
272
273
  respondCodexUserInput(runId, requestId, rawAnswers) {
@@ -1,5 +1,5 @@
1
1
  import { runnerDefaultConfigurationSchema } from '@myagentroam/protocol';
2
- import { parseSecretEnvironment } from '../util/secret-environment.js';
2
+ import { parseSecretEnvironment } from '../mcp/secret-environment.js';
3
3
  export class RunnerService {
4
4
  database;
5
5
  capabilities;
@@ -1,4 +1,5 @@
1
- import { isPlainRecord, isTerminalRunStatus } from '../util/node-operation-parsers.js';
1
+ import { isPlainRecord } from '../util/records.js';
2
+ import { isTerminalRunStatus } from '../session/runtime/type-guards.js';
2
3
  /** Projects only already-observed, process-local public facts. Never reads native history. */
3
4
  function activity(runtime, sessionId) {
4
5
  const candidate = runtime.activeSessionRun(sessionId);
@@ -1,7 +1,7 @@
1
1
  import { lstat, open, readdir, readlink, realpath } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { skillInstallMetadataSchema } from '@myagentroam/protocol';
4
- import { hasBrokenWorkspaceSkillGitExclude } from './workspace-git-exclude-service.js';
4
+ import { hasBrokenWorkspaceSkillGitExclude } from '../workspace/workspace-git-exclude-service.js';
5
5
  const MAX_METADATA_BYTES = 64 * 1024;
6
6
  const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
7
7
  const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/u;
@@ -4,8 +4,8 @@ import { promisify } from 'node:util';
4
4
  import { lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, symlink, writeFile } from 'node:fs/promises';
5
5
  import path from 'node:path';
6
6
  import { skillInstallMetadataSchema } from '@myagentroam/protocol';
7
- import { GitCommandError, resolveWorkspaceGitRoot } from '../workspace.js';
8
- import { rewriteWorkspaceGitExclude, updateWorkspaceSkillGitExclude } from './workspace-git-exclude-service.js';
7
+ import { GitCommandError, resolveWorkspaceGitRoot } from '../workspace/git.js';
8
+ import { rewriteWorkspaceGitExclude, updateWorkspaceSkillGitExclude } from '../workspace/workspace-git-exclude-service.js';
9
9
  export class SkillInstallService {
10
10
  async recoverNodeHome(home) {
11
11
  const agentsRoot = path.join(home, '.agents', 'skills');
@@ -1,4 +1,4 @@
1
- import { parseSecretEnvironment } from '../util/secret-environment.js';
1
+ import { parseSecretEnvironment } from '../mcp/secret-environment.js';
2
2
  export class TerminalService {
3
3
  database;
4
4
  terminalManager;
@@ -1,6 +1,7 @@
1
1
  import { conversationSegmentIdentityForItem, conversationSegments } from './conversation-segment-service.js';
2
- import { runConversationItemId } from '../runtime-state.js';
3
- import { isNodeAgentRun, isNodeConversationTurn, isPlainRecord } from '../util/node-operation-parsers.js';
2
+ import { runConversationItemId } from '../session/runtime/conversation-projection.js';
3
+ import { isNodeAgentRun, isNodeConversationTurn } from '../session/runtime/type-guards.js';
4
+ import { isPlainRecord } from '../util/records.js';
4
5
  export function workbenchPayloadSessionId(getRun, category, payload) {
5
6
  if (!isPlainRecord(payload))
6
7
  return undefined;
@@ -1,5 +1,5 @@
1
1
  import { createEnvelope } from '@myagentroam/protocol';
2
- import { isPlainRecord } from '../util/node-operation-parsers.js';
2
+ import { isPlainRecord } from '../util/records.js';
3
3
  import { projectWorkbenchPayload, workbenchPayloadSessionId } from './workbench-event-projection.js';
4
4
  export class WorkbenchEventService {
5
5
  options;