@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,12 +1,9 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { projectConversationWorkProcesses } from './service/conversation-work-process.js';
2
+ import { RuntimeApprovals } from './session/runtime/approvals.js';
3
+ import { projectConversationEvent, conversationItemFromEvent, activeConversationItem, completedStatusForRun } from './session/runtime/conversation-projection.js';
3
4
  import { nodeLog } from './operational.js';
4
5
  import { safeErrorCode } from './util/safe-error.js';
5
- import { permissionStopSchema } from '@myagentroam/protocol';
6
6
  /** User inputs arrive with a durable Session identity, unlike execution-local output IDs. */
7
- export function runConversationItemId(runId, itemId, kind) {
8
- return kind === 'user_message' ? itemId : `${runId}:${itemId}`;
9
- }
10
7
  /**
11
8
  * Deliberately process-local runtime domain. No method in this class writes
12
9
  * into Node SQLite: a Node restart loses active runs, approvals, leases and
@@ -18,6 +15,26 @@ export class NodeRuntimeState {
18
15
  getWorkspace;
19
16
  now;
20
17
  onRunChanged;
18
+ activeExecutions = new Set();
19
+ interruptedExecutions = new Set();
20
+ executionAccepted(executionId) {
21
+ this.activeExecutions.add(executionId);
22
+ }
23
+ executionReleased(executionId) {
24
+ this.activeExecutions.delete(executionId);
25
+ }
26
+ isExecutionActive(executionId) {
27
+ return this.activeExecutions.has(executionId);
28
+ }
29
+ activeExecutionCount() {
30
+ return this.activeExecutions.size;
31
+ }
32
+ requestExecutionInterrupt(executionId) {
33
+ this.interruptedExecutions.add(executionId);
34
+ }
35
+ isExecutionInterruptRequested(executionId) {
36
+ return this.interruptedExecutions.has(executionId);
37
+ }
21
38
  static MAX_COMPLETED_TURNS_PER_SESSION = 10;
22
39
  sessions = new Map();
23
40
  runs = new Map();
@@ -33,7 +50,7 @@ export class NodeRuntimeState {
33
50
  turns = new Map();
34
51
  /** New Sessions retain their full history until the bounded Runtime window drops a Turn. */
35
52
  completeHistorySessions = new Set();
36
- approvals = new Map();
53
+ approvalState = new RuntimeApprovals((runId) => this.getRun(runId), (runId) => this.currentExecutionId(runId), (runId) => this.canonicalRunId(runId), () => this.now());
37
54
  /** A Session owns at most one running turn; Workspace sessions may run concurrently. */
38
55
  leases = new Map();
39
56
  /** Blocks new Runs or queued messages while native history is being destructively replaced. */
@@ -795,6 +812,21 @@ export class NodeRuntimeState {
795
812
  }));
796
813
  return value;
797
814
  }
815
+ acceptExecutionTerminal(input) {
816
+ const run = this.getRun(input.runId);
817
+ if (run === undefined)
818
+ return { recorded: undefined, current: false, interrupted: false };
819
+ const currentId = this.currentExecutionId(run.id);
820
+ const current = currentId === input.runId || (currentId === undefined && input.runId === run.id);
821
+ const recorded = this.appendRunEvent(input);
822
+ if (recorded === undefined)
823
+ return { recorded, current, interrupted: false };
824
+ if (current && input.pauseQueue)
825
+ this.pauseSessionQueue(run.sessionId);
826
+ this.activeExecutions.delete(input.runId);
827
+ const interrupted = this.interruptedExecutions.delete(input.runId);
828
+ return { recorded, current, interrupted: current && interrupted };
829
+ }
798
830
  appendRunEvent(input) {
799
831
  const execution = this.executions.get(input.runId);
800
832
  const runId = execution?.runId ?? input.runId;
@@ -985,65 +1017,22 @@ export class NodeRuntimeState {
985
1017
  return completedTurn;
986
1018
  }
987
1019
  createApproval(input) {
988
- const run = this.getRun(input.runId);
989
- if (run === undefined)
990
- throw new Error('RUN_NOT_FOUND');
991
- if (input.runId !== run.id && this.currentExecutionId(run.id) !== input.runId)
992
- throw new Error('RUN_EXECUTION_NOT_CURRENT');
993
- if (this.approvals.has(input.approvalId))
994
- throw new Error('APPROVAL_ALREADY_EXISTS');
995
- if (input.expiresAt <= this.now())
996
- throw new Error('APPROVAL_EXPIRED');
997
- const value = {
998
- id: input.approvalId,
999
- runId: input.runId,
1000
- status: 'PENDING',
1001
- payload: input.payload,
1002
- expiresAt: input.expiresAt,
1003
- createdAt: this.now(),
1004
- decidedAt: null
1005
- };
1006
- this.approvals.set(value.id, value);
1007
- return value;
1020
+ return this.approvalState.createApproval(input);
1008
1021
  }
1009
1022
  getApproval(id) {
1010
- const value = this.approvals.get(id);
1011
- if (value?.status === 'PENDING' && value.expiresAt <= this.now())
1012
- return this.expireApproval(id);
1013
- return value;
1023
+ return this.approvalState.getApproval(id);
1014
1024
  }
1015
1025
  listApprovals(runId) {
1016
- const canonicalRunId = this.canonicalRunId(runId);
1017
- return [...this.approvals.values()]
1018
- .filter((value) => this.canonicalRunId(value.runId) === canonicalRunId)
1019
- .map((value) => this.getApproval(value.id));
1026
+ return this.approvalState.listApprovals(runId);
1020
1027
  }
1021
1028
  expireApproval(id) {
1022
- const value = this.requireApproval(id);
1023
- if (value.status !== 'PENDING')
1024
- return value;
1025
- if (value.expiresAt > this.now())
1026
- throw new Error('APPROVAL_NOT_EXPIRED');
1027
- const next = { ...value, status: 'EXPIRED', decidedAt: this.now() };
1028
- this.approvals.set(id, next);
1029
- return next;
1029
+ return this.approvalState.expireApproval(id);
1030
1030
  }
1031
1031
  decideApproval(id, decision) {
1032
- const value = this.getApproval(id);
1033
- if (value === undefined)
1034
- throw new Error('APPROVAL_NOT_FOUND');
1035
- if (value.status === 'EXPIRED')
1036
- throw new Error('APPROVAL_EXPIRED');
1037
- if (value.status !== 'PENDING')
1038
- throw new Error('APPROVAL_ALREADY_DECIDED');
1039
- const next = { ...value, status: decision, decidedAt: this.now() };
1040
- this.approvals.set(id, next);
1041
- return next;
1032
+ return this.approvalState.decideApproval(id, decision);
1042
1033
  }
1043
1034
  expireDueApprovals() {
1044
- return [...this.approvals.values()]
1045
- .filter((value) => value.status === 'PENDING' && value.expiresAt <= this.now())
1046
- .map((value) => this.expireApproval(value.id));
1035
+ return this.approvalState.expireDueApprovals();
1047
1036
  }
1048
1037
  listNonterminalRuns() {
1049
1038
  return [...this.runs.values()].filter((run) => !terminal(run.status));
@@ -1100,9 +1089,7 @@ export class NodeRuntimeState {
1100
1089
  this.messageSubmissions.delete(key);
1101
1090
  this.rememberTerminalMessageSubmission(key, runId);
1102
1091
  }
1103
- for (const [approvalId, approval] of this.approvals)
1104
- if (approval.runId === runId || executionIds.has(approval.runId))
1105
- this.approvals.delete(approvalId);
1092
+ this.approvalState.removeRun(runId, executionIds);
1106
1093
  }
1107
1094
  clearTerminalMessageSubmissions(sessionId) {
1108
1095
  for (const key of this.terminalMessageSubmissions.keys())
@@ -1153,9 +1140,7 @@ export class NodeRuntimeState {
1153
1140
  for (const key of this.failedMessageSubmissions.keys())
1154
1141
  if (key.startsWith(`${sessionId}\u0000`))
1155
1142
  this.failedMessageSubmissions.delete(key);
1156
- for (const [approvalId, approval] of this.approvals)
1157
- if (runIds.has(approval.runId) || executionIds.has(approval.runId))
1158
- this.approvals.delete(approvalId);
1143
+ this.approvalState.removeRuns(runIds, executionIds);
1159
1144
  }
1160
1145
  beginSessionHistoryMutation(sessionId, options) {
1161
1146
  if (this.sessionHistoryMutations.has(sessionId))
@@ -1226,9 +1211,7 @@ export class NodeRuntimeState {
1226
1211
  if (separator >= 0 && sessionIds.has(key.slice(0, separator)))
1227
1212
  this.failedMessageSubmissions.delete(key);
1228
1213
  }
1229
- for (const [approvalId, approval] of this.approvals)
1230
- if (runIds.has(approval.runId) || executionIds.has(approval.runId))
1231
- this.approvals.delete(approvalId);
1214
+ this.approvalState.removeRuns(runIds, executionIds);
1232
1215
  for (const sessionId of sessionIds) {
1233
1216
  const queued = this.queuedItems.get(sessionId) ?? [];
1234
1217
  this.queuedItems.delete(sessionId);
@@ -1310,12 +1293,6 @@ export class NodeRuntimeState {
1310
1293
  throw new Error('QUEUE_ITEM_INVALID');
1311
1294
  return value;
1312
1295
  }
1313
- requireApproval(id) {
1314
- const value = this.approvals.get(id);
1315
- if (value === undefined)
1316
- throw new Error('APPROVAL_NOT_FOUND');
1317
- return value;
1318
- }
1319
1296
  updateTurnForRun(runId, update) {
1320
1297
  const run = this.requireRun(runId);
1321
1298
  const current = this.turns.get(run.sessionId) ?? [];
@@ -1438,146 +1415,7 @@ export class NodeRuntimeState {
1438
1415
  this.currentTurnForRun(event.runId) === undefined &&
1439
1416
  conversationItemFromEvent(event) !== undefined)
1440
1417
  this.createItemOnlyTurn(run, event.createdAt);
1441
- let needsProcessProjection = false;
1442
- const update = (turn) => {
1443
- if (event.eventType === 'text.delta' &&
1444
- isRecord(event.payload) &&
1445
- typeof event.payload.text === 'string') {
1446
- const nativeItemId = typeof event.payload.itemId === 'string' && event.payload.itemId.length <= 88
1447
- ? event.payload.itemId
1448
- : undefined;
1449
- const stableItemId = nativeItemId === undefined ? undefined : `${event.runId}:${nativeItemId}`;
1450
- const lastItem = turn.items.at(-1);
1451
- const existing = stableItemId === undefined
1452
- ? lastItem?.kind === 'assistant_message' && activeConversationItem(lastItem.status)
1453
- ? lastItem
1454
- : undefined
1455
- : turn.items.find((item) => item.id === stableItemId && item.kind === 'assistant_message');
1456
- const text = `${existing === undefined ? '' : readableText(existing.payload)}${event.payload.text}`;
1457
- const sourceItemId = conversationSourceItemId(event.payload) ?? existing?.sourceItemId;
1458
- needsProcessProjection =
1459
- existing === undefined ||
1460
- existing.sourceItemId !== sourceItemId ||
1461
- (!readableText(existing.payload).trim() && !!text.trim());
1462
- const item = {
1463
- id: existing?.id ?? stableItemId ?? `${event.runId}:assistant:${event.sequence}`,
1464
- ...(sourceItemId === undefined ? {} : { sourceItemId }),
1465
- sessionId: run.sessionId,
1466
- turnId: turn.id,
1467
- runId: event.runId,
1468
- parentItemId: null,
1469
- sourceSequence: existing?.sourceSequence ?? event.sequence,
1470
- revision: event.sequence,
1471
- kind: 'assistant_message',
1472
- ...(existing?.processGroup === undefined ? {} : { processGroup: existing.processGroup }),
1473
- updatedAt: event.createdAt,
1474
- status: 'IN_PROGRESS',
1475
- payload: { text, format: 'markdown', model: null },
1476
- startedAt: existing?.startedAt ?? event.createdAt,
1477
- completedAt: null
1478
- };
1479
- return {
1480
- ...turn,
1481
- status: 'RUNNING',
1482
- startedAt: turn.startedAt ?? event.createdAt,
1483
- items: existing === undefined
1484
- ? [...turn.items, item]
1485
- : turn.items.map((value) => (value.id === item.id ? item : value))
1486
- };
1487
- }
1488
- const projected = conversationItemFromEvent(event);
1489
- if (projected !== undefined) {
1490
- const itemId = runConversationItemId(event.runId, projected.itemId, projected.kind);
1491
- const trailingItem = turn.items.at(-1);
1492
- const orderedItems = projected.kind !== 'user_message' &&
1493
- trailingItem?.kind === 'assistant_message' &&
1494
- activeConversationItem(trailingItem.status)
1495
- ? turn.items.map((item) => item.id === trailingItem.id
1496
- ? {
1497
- ...item,
1498
- status: 'COMPLETED',
1499
- completedAt: event.createdAt,
1500
- updatedAt: event.createdAt
1501
- }
1502
- : item)
1503
- : turn.items;
1504
- const existing = orderedItems.find((item) => item.id === itemId) ??
1505
- (projected.kind === 'assistant_message' &&
1506
- projected.itemId.startsWith('codex:') &&
1507
- trailingItem?.kind === 'assistant_message' &&
1508
- trailingItem.id.startsWith(`${event.runId}:assistant:`) &&
1509
- activeConversationItem(trailingItem.status)
1510
- ? trailingItem
1511
- : undefined);
1512
- const status = projected.status;
1513
- const sourceItemId = projected.sourceItemId ?? existing?.sourceItemId;
1514
- const parentItemId = projected.parentItemId === undefined
1515
- ? (existing?.parentItemId ?? null)
1516
- : runConversationItemId(event.runId, projected.parentItemId);
1517
- needsProcessProjection =
1518
- existing === undefined ||
1519
- existing.kind !== projected.kind ||
1520
- projected.kind === 'context_compaction' ||
1521
- existing.parentItemId !== parentItemId ||
1522
- existing.sourceItemId !== sourceItemId ||
1523
- (projected.kind === 'assistant_message' &&
1524
- !!readableText(existing.payload).trim() !== !!readableText(projected.payload).trim()) ||
1525
- (projected.kind === 'reasoning' && existing.processGroup === undefined);
1526
- const item = {
1527
- id: itemId,
1528
- sessionId: run.sessionId,
1529
- turnId: turn.id,
1530
- runId: event.runId,
1531
- parentItemId,
1532
- ...(sourceItemId === undefined ? {} : { sourceItemId }),
1533
- sourceSequence: existing?.sourceSequence ?? event.sequence,
1534
- revision: (existing?.revision ?? -1) + 1,
1535
- kind: projected.kind,
1536
- ...(existing?.processGroup === undefined ? {} : { processGroup: existing.processGroup }),
1537
- updatedAt: event.createdAt,
1538
- status,
1539
- payload: mergeConversationItemPayload(existing?.payload, projected),
1540
- ...(projected.permissionStop === undefined
1541
- ? {}
1542
- : { permissionStop: projected.permissionStop }),
1543
- startedAt: existing?.startedAt ?? event.createdAt,
1544
- completedAt: completedConversationItem(status) ? event.createdAt : null
1545
- };
1546
- return {
1547
- ...turn,
1548
- status: terminal(run.status) ? run.status : 'RUNNING',
1549
- startedAt: turn.startedAt ?? event.createdAt,
1550
- items: existing === undefined
1551
- ? [...orderedItems, item]
1552
- : orderedItems.map((value) => (value.id === existing.id ? item : value))
1553
- };
1554
- }
1555
- if (event.eventType === 'run.started' || event.eventType === 'run.running')
1556
- return { ...turn, status: 'RUNNING', startedAt: turn.startedAt ?? event.createdAt };
1557
- if (event.eventType === 'run.completed' ||
1558
- event.eventType === 'run.failed' ||
1559
- event.eventType === 'run.rejected') {
1560
- const status = event.status ?? this.requireRun(event.runId).status;
1561
- return {
1562
- ...turn,
1563
- status,
1564
- completedAt: event.createdAt,
1565
- items: turn.items.map((item) => activeConversationItem(item.status)
1566
- ? {
1567
- ...item,
1568
- status: completedStatusForRun(status),
1569
- completedAt: event.createdAt
1570
- }
1571
- : item)
1572
- };
1573
- }
1574
- return turn;
1575
- };
1576
- const project = (turn) => {
1577
- const next = update(turn);
1578
- // Ordinary token/output updates retain membership; only structural input needs a scan.
1579
- return needsProcessProjection ? projectConversationWorkProcesses(next) : next;
1580
- };
1418
+ const project = (turn) => projectConversationEvent(turn, run, event, terminal(run.status));
1581
1419
  if (turnId === undefined)
1582
1420
  this.updateCurrentTurnForRun(event.runId, project);
1583
1421
  else {
@@ -1601,168 +1439,6 @@ function transitionAllowed(from, to) {
1601
1439
  function isRecord(value) {
1602
1440
  return typeof value === 'object' && value !== null;
1603
1441
  }
1604
- function readableText(value) {
1605
- return isRecord(value) && typeof value.text === 'string' ? value.text : '';
1606
- }
1607
- function conversationItemFromEvent(event) {
1608
- if (isRecord(event.payload)) {
1609
- const approvalId = event.payload.approvalId;
1610
- if (typeof approvalId === 'string' && approvalId.length > 0) {
1611
- if (event.eventType === 'approval.request')
1612
- return {
1613
- itemId: `approval:${approvalId}`,
1614
- kind: 'approval_request',
1615
- status: 'PENDING',
1616
- payload: {
1617
- approvalId,
1618
- approvalStatus: 'PENDING',
1619
- approval: event.payload.payload,
1620
- expiresAt: event.payload.expiresAt
1621
- },
1622
- merge: false
1623
- };
1624
- if (event.eventType === 'approval.decided') {
1625
- const decision = event.payload.decision;
1626
- if (decision === 'APPROVED' || decision === 'REJECTED')
1627
- return {
1628
- itemId: `approval:${approvalId}`,
1629
- kind: 'approval_request',
1630
- status: decision === 'APPROVED' ? 'COMPLETED' : 'DECLINED',
1631
- payload: { approvalId, approvalStatus: decision },
1632
- merge: true
1633
- };
1634
- }
1635
- if (event.eventType === 'approval.expired')
1636
- return {
1637
- itemId: `approval:${approvalId}`,
1638
- kind: 'approval_request',
1639
- status: 'CANCELLED',
1640
- payload: { approvalId, approvalStatus: 'EXPIRED' },
1641
- merge: true
1642
- };
1643
- }
1644
- }
1645
- if (event.eventType !== 'conversation.item' || !isRecord(event.payload))
1646
- return undefined;
1647
- const itemId = event.payload.itemId;
1648
- const kind = event.payload.kind;
1649
- const status = event.payload.status;
1650
- const payload = event.payload.payload;
1651
- if (typeof itemId !== 'string' ||
1652
- itemId.length === 0 ||
1653
- itemId.length > 88 ||
1654
- typeof kind !== 'string' ||
1655
- !projectableConversationKind(kind) ||
1656
- typeof status !== 'string' ||
1657
- !projectableConversationStatus(status) ||
1658
- !isRecord(payload))
1659
- return undefined;
1660
- const append = event.payload.append;
1661
- const sourceItemId = conversationSourceItemId(event.payload);
1662
- const permissionStop = permissionStopSchema.safeParse(event.payload.permissionStop);
1663
- return {
1664
- itemId,
1665
- kind,
1666
- status,
1667
- payload,
1668
- ...(sourceItemId === undefined ? {} : { sourceItemId }),
1669
- ...(typeof event.payload.parentItemId === 'string' &&
1670
- event.payload.parentItemId.length > 0 &&
1671
- event.payload.parentItemId.length <= 88
1672
- ? { parentItemId: event.payload.parentItemId }
1673
- : {}),
1674
- ...(permissionStop.success ? { permissionStop: permissionStop.data } : {}),
1675
- merge: event.payload.merge === true,
1676
- ...(append === 'sections' || append === 'outputPreview' || append === 'outputSummary'
1677
- ? { append }
1678
- : {})
1679
- };
1680
- }
1681
- function conversationSourceItemId(payload) {
1682
- return typeof payload.sourceItemId === 'string' &&
1683
- payload.sourceItemId.length > 0 &&
1684
- payload.sourceItemId.length <= 1024
1685
- ? payload.sourceItemId
1686
- : undefined;
1687
- }
1688
- function projectableConversationKind(value) {
1689
- return (value === 'user_message' ||
1690
- value === 'assistant_message' ||
1691
- value === 'reasoning' ||
1692
- value === 'tool_call' ||
1693
- value === 'command_execution' ||
1694
- value === 'approval_request' ||
1695
- value === 'user_input_request' ||
1696
- value === 'plan' ||
1697
- value === 'todo_list' ||
1698
- value === 'file_change' ||
1699
- value === 'context_compaction' ||
1700
- value === 'unknown' ||
1701
- value === 'warning' ||
1702
- value === 'error');
1703
- }
1704
- function projectableConversationStatus(value) {
1705
- return [
1706
- 'PENDING',
1707
- 'IN_PROGRESS',
1708
- 'COMPLETED',
1709
- 'FAILED',
1710
- 'DECLINED',
1711
- 'CANCELLED',
1712
- 'INTERRUPTED'
1713
- ].includes(value);
1714
- }
1715
- function mergeConversationItemPayload(existing, next) {
1716
- const base = next.merge && isRecord(existing) ? existing : {};
1717
- const value = { ...base, ...next.payload };
1718
- if (next.append === 'sections') {
1719
- const prior = Array.isArray(base.sections) ? base.sections : [];
1720
- const incoming = Array.isArray(next.payload.sections) ? next.payload.sections : [];
1721
- const sections = new Map();
1722
- for (const section of [...prior, ...incoming]) {
1723
- if (!isRecord(section) ||
1724
- typeof section.index !== 'number' ||
1725
- !Number.isInteger(section.index) ||
1726
- section.index < 0 ||
1727
- typeof section.text !== 'string')
1728
- continue;
1729
- sections.set(section.index, appendProjectionText(sections.get(section.index) ?? '', section.text));
1730
- }
1731
- value.sections = [...sections]
1732
- .sort(([left], [right]) => left - right)
1733
- .map(([index, text]) => ({ index, text }));
1734
- }
1735
- if (next.append === 'outputPreview') {
1736
- const prior = typeof base.outputPreview === 'string' ? base.outputPreview : '';
1737
- const incoming = typeof next.payload.outputPreview === 'string' ? next.payload.outputPreview : '';
1738
- value.outputPreview = appendProjectionText(prior, incoming);
1739
- }
1740
- if (next.append === 'outputSummary') {
1741
- const prior = typeof base.outputSummary === 'string' ? base.outputSummary : '';
1742
- const incoming = typeof next.payload.outputSummary === 'string' ? next.payload.outputSummary : '';
1743
- value.outputSummary = appendProjectionText(prior, incoming);
1744
- }
1745
- return value;
1746
- }
1747
- function appendProjectionText(previous, next) {
1748
- const text = `${previous}${next}`;
1749
- return text.length <= 10_000 ? text : `${text.slice(0, 9_999)}…`;
1750
- }
1751
- function activeConversationItem(status) {
1752
- return status === 'PENDING' || status === 'IN_PROGRESS';
1753
- }
1754
- function completedConversationItem(status) {
1755
- return !activeConversationItem(status);
1756
- }
1757
- function completedStatusForRun(status) {
1758
- if (status === 'INTERRUPTED')
1759
- return 'INTERRUPTED';
1760
- if (status === 'CANCELLED')
1761
- return 'CANCELLED';
1762
- if (status === 'FAILED')
1763
- return 'FAILED';
1764
- return 'COMPLETED';
1765
- }
1766
1442
  function executionStatusForRun(status) {
1767
1443
  if (status === 'FAILED')
1768
1444
  return 'FAILED';
@@ -1,6 +1,6 @@
1
1
  import { computerUseDisableInputSchema, computerUseEnableInputSchema, computerUseEnableResultSchema } from '@myagentroam/protocol';
2
2
  import { computerUseRuntimeExecutable, saveNodeConfig } from '../config.js';
3
- import { nodeOperationData } from '../util/node-operation-data.js';
3
+ import { nodeOperationData } from '../connection/node-operation-data.js';
4
4
  /** Persists Computer Use authorization and owns its asynchronous capability probe. */
5
5
  export class ComputerUseEnablementService {
6
6
  options;
@@ -1,6 +1,6 @@
1
- import { withNonImageAttachmentPrompt } from '../util/node-operation-parsers.js';
1
+ import { withNonImageAttachmentPrompt } from '../session/attachments.js';
2
2
  import { safeErrorCode } from '../util/safe-error.js';
3
- import { withSessionPrompt } from '../util/personal-instructions.js';
3
+ import { withSessionPrompt } from '../runner/personal-instructions.js';
4
4
  import { prepareComposerResourceInput } from './composer-message-input.js';
5
5
  import { nodeLog } from '../operational.js';
6
6
  export class ContinuousRunCoordinator {
@@ -18,6 +18,46 @@ export class ContinuousRunCoordinator {
18
18
  constructor(options) {
19
19
  this.options = options;
20
20
  }
21
+ acceptExecutionTerminal(input) {
22
+ const run = this.options.runtime.getRun(input.executionId);
23
+ if (run === undefined)
24
+ return { recorded: undefined, manuallyInterrupted: false };
25
+ let accepted;
26
+ try {
27
+ accepted = this.options.runtime.acceptExecutionTerminal({
28
+ runId: input.executionId,
29
+ sequence: input.sequence,
30
+ eventType: input.eventType,
31
+ payload: input.payload,
32
+ status: input.status,
33
+ pauseQueue: input.permissionStop !== undefined
34
+ });
35
+ }
36
+ catch {
37
+ return { recorded: undefined, manuallyInterrupted: false };
38
+ }
39
+ const { recorded, interrupted: manuallyInterrupted } = accepted;
40
+ if (recorded === undefined || !accepted.current)
41
+ return { recorded, manuallyInterrupted };
42
+ const goalResult = this.options.executionTerminating(input.executionId, input.status, input.permissionStop);
43
+ const queue = this.options.runtime.sessionQueue(run.sessionId);
44
+ const continues = run.status !== 'CANCELLING' &&
45
+ input.permissionStop === undefined &&
46
+ ((!queue.paused && queue.itemIds.length > 0) || this.options.keepRunActive(run.sessionId));
47
+ let finalStatus;
48
+ if (!continues) {
49
+ finalStatus =
50
+ input.status === 'CANCELLED' || manuallyInterrupted ? 'INTERRUPTED' : input.status;
51
+ if (finalStatus === 'SUCCEEDED' || finalStatus === 'FAILED' || finalStatus === 'INTERRUPTED')
52
+ this.options.runtime.finishRun(run.id, finalStatus);
53
+ }
54
+ return {
55
+ recorded,
56
+ manuallyInterrupted,
57
+ ...(finalStatus === undefined ? {} : { finalStatus }),
58
+ ...goalResult
59
+ };
60
+ }
21
61
  suspend() {
22
62
  this.suspended = true;
23
63
  for (const timer of this.scheduledSessions.values())
@@ -430,7 +470,7 @@ export class ContinuousRunCoordinator {
430
470
  : `${pending.precedingInput}\n\n${resources}`, pending.attachmentPaths, pending.attachmentFiles)
431
471
  });
432
472
  }
433
- deleteQueueItem(itemId) {
473
+ deleteQueueItem(itemId, publish = true) {
434
474
  const item = this.options.runtime.getQueueItem(itemId);
435
475
  if (item === undefined)
436
476
  throw new Error('QUEUE_ITEM_INVALID');
@@ -438,7 +478,8 @@ export class ContinuousRunCoordinator {
438
478
  this.remove(item.id);
439
479
  this.options.clearImages(item.id);
440
480
  this.options.cleanupAttachments(item.id);
441
- this.options.emitQueue(item.workspaceId, item.sessionId);
481
+ if (publish)
482
+ this.options.emitQueue(item.workspaceId, item.sessionId);
442
483
  }
443
484
  publishCurrentTurn(runId) {
444
485
  const turn = this.options.runtime.conversationTurnForRun(runId);
@@ -725,7 +766,8 @@ export class ContinuousRunCoordinator {
725
766
  if (lease !== undefined)
726
767
  this.environmentLeases.set(nextId, lease);
727
768
  }
728
- removeGoalQueue(sessionId, preservedGoal, preservedSubmissionId) {
769
+ removeGoalQueue(sessionId, preservedGoal, preservedSubmissionId, publish = true) {
770
+ let changed = false;
729
771
  for (const [id, pending] of this.preparedInputs) {
730
772
  if (id === preservedSubmissionId)
731
773
  continue;
@@ -737,9 +779,12 @@ export class ContinuousRunCoordinator {
737
779
  if (pending.goal.id === preservedGoal?.id && pending.goal.revision === preservedGoal.revision)
738
780
  continue;
739
781
  const item = this.options.runtime.getQueueItem(id);
740
- if (item !== undefined)
741
- this.deleteQueueItem(id);
782
+ if (item !== undefined) {
783
+ this.deleteQueueItem(id, publish);
784
+ changed = true;
785
+ }
742
786
  }
787
+ return changed;
743
788
  }
744
789
  removePendingQueue(sessionId, preservedItemId) {
745
790
  for (const itemId of [...this.options.runtime.sessionQueue(sessionId).itemIds]) {
@@ -1,8 +1,8 @@
1
- import { projectConversationWorkProcesses } from './conversation-work-process.js';
1
+ import { projectConversationWorkProcesses } from '../session/runtime/work-process.js';
2
2
  import { discoverNativeSessions, readNativeSession } from '../native-session-history.js';
3
- import { nativeConversationPage } from '../util/runner-native-session-parsers.js';
4
- import { isPlainRecord } from '../util/node-operation-parsers.js';
5
- import { conversationClientMessageId } from '../util/conversation-identity.js';
3
+ import { nativeConversationPage } from '../session/runner-native-session-parsers.js';
4
+ import { isPlainRecord } from '../util/records.js';
5
+ import { conversationClientMessageId } from '../session/conversation-identity.js';
6
6
  import { nodeLog } from '../operational.js';
7
7
  export function hasManagedActiveRun(runtime, session) {
8
8
  const current = runtime.getAgentSession(session.id) ?? session;
@@ -3,7 +3,7 @@ import { mkdir, mkdtemp, rm } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { safeErrorCode } from '../util/safe-error.js';
6
- import { hostedConversationSandboxProfile } from './hosted-conversation-sandbox-profile.js';
6
+ import { hostedConversationSandboxProfile } from '../hosting/hosted-conversation-sandbox-profile.js';
7
7
  const CONVERSATION_HOSTING_API_VERSION = 1;
8
8
  /** Runs the same bounded Bubblewrap policy used by hosted local processes. */
9
9
  export async function probeBubblewrap() {
@@ -1,6 +1,6 @@
1
1
  import { conversationHostingDisableInputSchema, conversationHostingEnableInputSchema, conversationHostingEnableResultSchema } from '@myagentroam/protocol';
2
2
  import { saveNodeConfig } from '../config.js';
3
- import { nodeOperationData } from '../util/node-operation-data.js';
3
+ import { nodeOperationData } from '../connection/node-operation-data.js';
4
4
  /** Owns management operations that persist and hot-apply hosting configuration. */
5
5
  export class ConversationHostingEnablementService {
6
6
  options;
@@ -1,11 +1,11 @@
1
1
  import { hostedConversationStorePath, nodeDataDirectory } from '../config.js';
2
- import { HostedConversationStore } from './hosted-conversation-store.js';
2
+ import { HostedConversationStore } from '../hosting/hosted-conversation-store.js';
3
3
  import { ConversationTemplateNodeService } from './conversation-template-node-service.js';
4
- import { HostedConversationNodeService } from './hosted-conversation-node-service.js';
4
+ import { HostedConversationNodeService } from '../hosting/hosted-conversation-node-service.js';
5
5
  import { ConversationHostingMaintenanceService } from './conversation-hosting-maintenance-service.js';
6
6
  import { MarAgentHostedConversationRuntime } from '../runner/mar-agent/hosted-conversation-runtime.js';
7
7
  import { BubblewrapSandboxExecutor } from '@myagentroam/agent/sandbox/linux';
8
- import { hostedConversationSandboxProfile } from './hosted-conversation-sandbox-profile.js';
8
+ import { hostedConversationSandboxProfile } from '../hosting/hosted-conversation-sandbox-profile.js';
9
9
  import { conversationHostingCapability } from './conversation-hosting-capability-service.js';
10
10
  import { nodeLog } from '../operational.js';
11
11
  import { safeErrorCode } from '../util/safe-error.js';
@@ -1,6 +1,6 @@
1
1
  import { statfs } from 'node:fs/promises';
2
2
  import { conversationHostingCleanupExecuteInputSchema, conversationHostingCleanupPreviewInputSchema, conversationHostingCleanupPreviewSchema, conversationHostingCleanupResultSchema, conversationHostingStorageCapacitySchema } from '@myagentroam/protocol';
3
- import { nodeOperationData } from '../util/node-operation-data.js';
3
+ import { nodeOperationData } from '../connection/node-operation-data.js';
4
4
  const DAY_MS = 24 * 60 * 60 * 1_000;
5
5
  const AUTOMATIC_CLEANUP_INTERVAL_MS = DAY_MS;
6
6
  /** Owns aggregate storage watermarks and permanent hosted-conversation cleanup. */