@myagentroam/agent 0.11.34 → 0.11.36

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 (30) hide show
  1. package/dist/sandbox/linux/bubblewrap-launch.d.ts +11 -0
  2. package/dist/sandbox/linux/bubblewrap-launch.js +202 -0
  3. package/dist/sandbox/linux/bubblewrap-policy.d.ts +5 -0
  4. package/dist/sandbox/linux/bubblewrap-policy.js +285 -0
  5. package/dist/sandbox/linux/bubblewrap-probe.d.ts +11 -0
  6. package/dist/sandbox/linux/bubblewrap-probe.js +154 -0
  7. package/dist/sandbox/linux/bubblewrap-sandbox-executor.d.ts +3 -12
  8. package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +10 -632
  9. package/dist/sandbox/linux/bubblewrap-seccomp.d.ts +1 -0
  10. package/dist/sandbox/linux/bubblewrap-seccomp.js +43 -0
  11. package/dist/sandbox/linux/index.d.ts +2 -1
  12. package/dist/sandbox/linux/index.js +2 -1
  13. package/dist/sandbox/linux/sandbox-error.d.ts +4 -0
  14. package/dist/sandbox/linux/sandbox-error.js +14 -0
  15. package/dist/sandbox/linux/sandbox-mounts.d.ts +13 -0
  16. package/dist/sandbox/linux/sandbox-mounts.js +221 -0
  17. package/dist/sandbox/linux/sandbox-process.d.ts +3 -0
  18. package/dist/sandbox/linux/sandbox-process.js +32 -0
  19. package/dist/session/store/jsonl/fork-session.d.ts +19 -0
  20. package/dist/session/store/jsonl/fork-session.js +142 -0
  21. package/dist/session/store/jsonl/images.js +1 -1
  22. package/dist/session/store/jsonl/jsonl-migration.js +2 -2
  23. package/dist/session/store/jsonl/jsonl-store.js +70 -157
  24. package/dist/subagent/session-controller.d.ts +2 -63
  25. package/dist/subagent/session-controller.js +35 -453
  26. package/dist/subagent/task.d.ts +134 -0
  27. package/dist/subagent/task.js +436 -0
  28. package/dist/subagent/turn-contracts.d.ts +64 -0
  29. package/dist/subagent/turn-contracts.js +1 -0
  30. package/package.json +1 -1
@@ -4,295 +4,8 @@ import { randomUUID } from 'node:crypto';
4
4
  import { AgentError, describeAgentError } from '../error.js';
5
5
  import { SUBAGENT_LIMITS } from './limits.js';
6
6
  import { parseAgentWaitTargets, SubagentWaitCoordinator, waitForAgentGroup } from './wait.js';
7
+ import { SubagentTask, observeAgentEvent } from './task.js';
7
8
  const RECENT_TERMINAL_RESOURCE_LIMIT = 16;
8
- /** Owns the mutable state and transitions of one child task. */
9
- class SubagentTask {
10
- state;
11
- get detailsEvicted() {
12
- return this.state.detailsEvicted;
13
- }
14
- get restorable() {
15
- return this.state.restorable;
16
- }
17
- get permissionStop() {
18
- return this.state.permissionStop;
19
- }
20
- get agentId() {
21
- return this.state.agentId;
22
- }
23
- get description() {
24
- return this.state.description;
25
- }
26
- get role() {
27
- return this.state.role;
28
- }
29
- get ownerInstanceId() {
30
- return this.state.ownerInstanceId;
31
- }
32
- get extensionId() {
33
- return this.state.extensionId;
34
- }
35
- get status() {
36
- return this.state.status;
37
- }
38
- get modelId() {
39
- return this.state.modelId;
40
- }
41
- get modelName() {
42
- return this.state.modelName;
43
- }
44
- get reasoningEffort() {
45
- return this.state.reasoningEffort;
46
- }
47
- get latestMessage() {
48
- return this.state.latestMessage;
49
- }
50
- get latestActivity() {
51
- return this.state.latestActivity;
52
- }
53
- get messageRevision() {
54
- return this.state.messageRevision;
55
- }
56
- get observedMessageRevision() {
57
- return this.state.observedMessageRevision;
58
- }
59
- get latestMessageActivityRevision() {
60
- return this.state.latestMessageActivityRevision;
61
- }
62
- get summary() {
63
- return this.state.summary;
64
- }
65
- get changedFiles() {
66
- return this.state.changedFiles;
67
- }
68
- get verification() {
69
- return this.state.verification;
70
- }
71
- get evidence() {
72
- return this.state.evidence;
73
- }
74
- get errorCode() {
75
- return this.state.errorCode;
76
- }
77
- get errorMessage() {
78
- return this.state.errorMessage;
79
- }
80
- get terminalPending() {
81
- return this.state.terminalPending;
82
- }
83
- get observedTerminalRevision() {
84
- return this.state.observedTerminalRevision;
85
- }
86
- get terminalRevision() {
87
- return this.state.terminalRevision;
88
- }
89
- get resourceRevision() {
90
- return this.state.resourceRevision;
91
- }
92
- get turn() {
93
- return this.state.turn;
94
- }
95
- get controlTail() {
96
- return this.state.controlTail;
97
- }
98
- get receipt() {
99
- return this.state.receipt;
100
- }
101
- constructor(state) {
102
- this.state = state;
103
- }
104
- acknowledgeMessage(revision) {
105
- this.state.observedMessageRevision = Math.max(this.state.observedMessageRevision, revision);
106
- }
107
- acknowledgeTerminal(revision) {
108
- this.state.observedTerminalRevision = Math.max(this.state.observedTerminalRevision, revision);
109
- }
110
- selectModel(model) {
111
- this.state.modelId = model.id;
112
- this.state.modelName = model.name;
113
- if (model.reasoningEffort !== undefined)
114
- this.state.reasoningEffort = model.reasoningEffort;
115
- }
116
- setReceipt(receipt) {
117
- this.state.receipt = receipt;
118
- }
119
- updateDelivery(receipt, status) {
120
- if (status !== 'applied' && receipt.deliveryStatus !== 'accepted')
121
- return false;
122
- receipt.deliveryStatus = status;
123
- return true;
124
- }
125
- requestCancel(expected) {
126
- const turn = this.state.turn;
127
- if (turn !== expected)
128
- return;
129
- if (turn.handle)
130
- turn.handle.cancel();
131
- else
132
- turn.cancelRequested = true;
133
- }
134
- attachHandle(expected, handle) {
135
- const turn = this.state.turn;
136
- if (turn !== expected)
137
- return;
138
- turn.handle = handle;
139
- }
140
- cancelPending(expected) {
141
- const turn = this.state.turn;
142
- if (turn === expected && turn.cancelRequested)
143
- turn.handle.cancel();
144
- }
145
- takePendingMessages(expected) {
146
- const turn = this.state.turn;
147
- return turn === expected ? turn.pendingMessages.splice(0) : [];
148
- }
149
- settleHandleReady(expected) {
150
- if (this.state.turn === expected)
151
- this.state.turn.resolveHandleReady();
152
- }
153
- settleCompletion(expected) {
154
- if (this.state.turn === expected)
155
- this.state.turn.resolve();
156
- }
157
- appendMessage(message) {
158
- this.state.turn.pendingMessages.push(message);
159
- }
160
- control(operation) {
161
- const result = (this.state.controlTail ?? Promise.resolve()).then(operation);
162
- const tail = result.then(() => undefined, () => undefined);
163
- this.state.controlTail = tail;
164
- void tail.then(() => {
165
- if (this.state.controlTail === tail)
166
- delete this.state.controlTail;
167
- });
168
- return result;
169
- }
170
- cancelQueued(publish) {
171
- this.state.status = 'cancelled';
172
- if (publish)
173
- this.state.terminalPending = true;
174
- }
175
- queueTurn(input, onComplete) {
176
- let resolve;
177
- let resolveHandleReady;
178
- const completion = new Promise((done) => (resolve = done));
179
- const handleReady = new Promise((done) => (resolveHandleReady = done));
180
- const turn = {
181
- ...input,
182
- handleReady,
183
- completion,
184
- resolve: () => {
185
- onComplete();
186
- resolve();
187
- },
188
- resolveHandleReady
189
- };
190
- this.state.status = 'queued';
191
- this.state.restorable = false;
192
- delete this.state.summary;
193
- delete this.state.permissionStop;
194
- delete this.state.errorCode;
195
- delete this.state.errorMessage;
196
- this.state.changedFiles = [];
197
- this.state.verification = [];
198
- this.state.evidence = [];
199
- this.state.terminalPending = false;
200
- this.state.turn = turn;
201
- return turn;
202
- }
203
- markRunning() {
204
- this.state.status = 'running';
205
- }
206
- clearTurn(expected) {
207
- if (expected === undefined || this.state.turn === expected)
208
- delete this.state.turn;
209
- }
210
- observeEvent(event, revision) {
211
- if (!observeAgentEvent(this.state, event))
212
- return false;
213
- this.state.messageRevision++;
214
- this.state.latestMessageActivityRevision = revision;
215
- return true;
216
- }
217
- complete(result, terminal, persisted, previousActivity, previousRevision) {
218
- if (result.status === 'completed') {
219
- this.state.status = 'completed';
220
- this.state.summary = result.finalAnswer;
221
- if (result.permissionStop)
222
- this.state.permissionStop = result.permissionStop;
223
- else
224
- delete this.state.permissionStop;
225
- }
226
- else if (result.status === 'cancelled')
227
- this.state.status = 'cancelled';
228
- else {
229
- this.state.status = 'failed';
230
- this.state.errorCode = result.errorCode ?? 'MAR_AGENT_SUBAGENT_FAILED';
231
- this.state.errorMessage = result.errorMessage ?? 'Subagent execution failed.';
232
- }
233
- this.state.restorable =
234
- persisted &&
235
- terminal === this.state.status &&
236
- (previousActivity === undefined || this.state.latestActivity !== previousActivity) &&
237
- (this.state.latestMessage === undefined || this.state.messageRevision > previousRevision) &&
238
- (this.state.status !== 'completed' ||
239
- (this.state.latestMessage?.phase === 'final_answer' &&
240
- this.state.summary === this.state.latestMessage.text));
241
- this.state.terminalPending = true;
242
- }
243
- fail(error) {
244
- const diagnostic = describeAgentError(error, 'MAR_AGENT_SUBAGENT_FAILED');
245
- this.state.status = 'failed';
246
- this.state.restorable = false;
247
- this.state.errorCode = diagnostic.code;
248
- this.state.errorMessage = diagnostic.message;
249
- this.state.terminalPending = true;
250
- }
251
- touch(revision, terminal = false) {
252
- this.state.resourceRevision = revision;
253
- if (terminal)
254
- this.state.terminalRevision = revision;
255
- }
256
- terminalPublished(revision) {
257
- if (this.state.terminalRevision === revision)
258
- this.state.terminalPending = false;
259
- }
260
- evictDetails() {
261
- for (const key of DETAIL_FIELDS)
262
- delete this.state[key];
263
- this.state.changedFiles = [];
264
- this.state.verification = [];
265
- this.state.evidence = [];
266
- this.state.detailsEvicted = true;
267
- }
268
- restoreDetails(restored) {
269
- for (const key of DETAIL_FIELDS) {
270
- if (key in restored)
271
- Object.assign(this.state, { [key]: restored[key] });
272
- else
273
- delete this.state[key];
274
- }
275
- delete this.state.detailsEvicted;
276
- }
277
- copyWaitState() {
278
- return {
279
- ...this.state,
280
- changedFiles: [...this.state.changedFiles],
281
- verification: [...this.state.verification],
282
- evidence: this.state.evidence.map((item) => ({ ...item }))
283
- };
284
- }
285
- }
286
- const DETAIL_FIELDS = [
287
- 'latestMessage',
288
- 'latestActivity',
289
- 'summary',
290
- 'changedFiles',
291
- 'verification',
292
- 'evidence',
293
- 'permissionStop',
294
- 'errorMessage'
295
- ];
296
9
  export class SubagentSessionController {
297
10
  parentSessionId;
298
11
  rootSessionId;
@@ -872,20 +585,24 @@ export class SubagentSessionController {
872
585
  options.signal?.throwIfAborted();
873
586
  // Resolve before cancelling so an unavailable model does not stop valid work.
874
587
  const selected = resolveExistingModel(execution, state);
875
- if (options.delivery === 'replace')
876
- await this.#cancelState(state);
877
- this.#requireExecution();
878
- options.signal?.throwIfAborted();
879
588
  const turnId = state.turn?.turnId ?? randomUUID();
880
589
  const receipt = { messageId: randomUUID(), turnId, deliveryStatus: 'accepted' };
881
590
  const mutableReceipt = receipt;
882
- const delivery = this.#createDelivery(state, execution, mutableReceipt, options.signal);
591
+ const { delivery, waitSignal } = this.#createDelivery(state, execution, mutableReceipt, options.signal);
883
592
  try {
593
+ const sendSignal = options.signal === undefined
594
+ ? waitSignal
595
+ : AbortSignal.any([waitSignal, options.signal]);
596
+ sendSignal.throwIfAborted();
597
+ if (options.delivery === 'replace')
598
+ await waitForPromise(this.#cancelState(state), sendSignal);
599
+ this.#requireExecution();
600
+ sendSignal.throwIfAborted();
601
+ if (state.turn && state.status !== 'running' && state.status !== 'queued') {
602
+ await waitForPromise(state.turn.completion, sendSignal);
603
+ }
884
604
  if (state.status === 'running' && state.turn) {
885
605
  const turn = state.turn;
886
- const sendSignal = delivery.signal && options.signal
887
- ? AbortSignal.any([delivery.signal, options.signal])
888
- : (delivery.signal ?? options.signal);
889
606
  if (!turn.handle)
890
607
  await waitForPromise(turn.handleReady, sendSignal);
891
608
  this.#requireExecution();
@@ -949,10 +666,9 @@ export class SubagentSessionController {
949
666
  this.#touchResource(state, true);
950
667
  this.#closeReceipt(state);
951
668
  await this.#publishTerminal(state);
952
- state.settleHandleReady(turn);
953
- state.settleCompletion(turn);
669
+ state.settleQueuedTurn(turn);
954
670
  this.#notifyCompletion(state);
955
- state.clearTurn(turn);
671
+ state.releaseTurn(turn);
956
672
  this.#drain();
957
673
  }
958
674
  else if (state.status === 'running' && state.turn) {
@@ -991,12 +707,11 @@ export class SubagentSessionController {
991
707
  state.cancelQueued(false);
992
708
  this.#closeReceipt(state);
993
709
  if (turn) {
994
- state.settleHandleReady(turn);
995
- state.settleCompletion(turn);
710
+ state.settleQueuedTurn(turn);
996
711
  }
997
712
  this.#notifyCompletion(state);
998
713
  if (turn)
999
- state.clearTurn(turn);
714
+ state.releaseTurn(turn);
1000
715
  }
1001
716
  for (const state of this.#agents.values()) {
1002
717
  if (state.status !== 'running' || !state.turn)
@@ -1048,96 +763,43 @@ export class SubagentSessionController {
1048
763
  while (!this.#disposed && this.#running < this.limits.maxConcurrent && this.#queue.length > 0) {
1049
764
  const state = this.#queue.shift();
1050
765
  const turn = state.turn;
1051
- if (!turn || state.status !== 'queued')
766
+ if (!turn || !state.markRunning(turn))
1052
767
  continue;
1053
- state.markRunning();
1054
768
  this.#touchResource(state);
1055
769
  this.#running++;
1056
770
  void this.#startAndConsumeTurn(state, turn);
1057
771
  }
1058
772
  }
1059
773
  async #startAndConsumeTurn(state, turn) {
1060
- try {
1061
- const handle = await turn.start({
1062
- turnId: turn.turnId,
1063
- ...(turn.delivery === undefined ? {} : { delivery: turn.delivery }),
1064
- ...(turn.origin === undefined ? {} : { origin: turn.origin }),
1065
- sessionId: state.agentId,
1066
- prompt: turn.prompt,
1067
- modelId: state.modelId,
1068
- reasoningEffort: state.reasoningEffort,
1069
- role: state.role,
1070
- initialMailbox: state.takePendingMessages(turn)
1071
- });
1072
- state.attachHandle(turn, handle);
1073
- }
1074
- catch (error) {
1075
- state.settleHandleReady(turn);
1076
- await this.#failTurn(state, turn, error);
1077
- return;
1078
- }
1079
- state.cancelPending(turn);
1080
- state.settleHandleReady(turn);
1081
- await this.#consumeTurn(state, turn);
1082
- }
1083
- async #consumeTurn(state, turn) {
1084
- const previousActivity = state.latestActivity;
1085
- const previousMessageRevision = state.messageRevision;
1086
- let terminal;
1087
- try {
1088
- const consume = (async () => {
1089
- for await (const event of turn.handle) {
1090
- if (event.type === 'execution.completed')
1091
- terminal = 'completed';
1092
- else if (event.type === 'execution.failed')
1093
- terminal = 'failed';
1094
- else if (event.type === 'execution.cancelled')
1095
- terminal = 'cancelled';
1096
- if (!state.observeEvent(event, this.#activityRevision + 1))
1097
- continue;
1098
- this.#activityRevision++;
1099
- this.#touchResource(state);
1100
- this.#notifyActivity({ state, kind: 'message' });
1101
- }
1102
- })();
1103
- const [resultOutcome, consumeOutcome] = await Promise.allSettled([
1104
- Promise.resolve().then(() => turn.handle.result()),
1105
- consume
1106
- ]);
1107
- if (resultOutcome.status === 'rejected')
1108
- throw resultOutcome.reason;
1109
- if (consumeOutcome.status === 'rejected')
1110
- throw consumeOutcome.reason;
1111
- const result = resultOutcome.value;
1112
- state.complete(result, terminal, turn.handle.persistedEvents === true, previousActivity, previousMessageRevision);
774
+ const outcome = await state.runTurn(turn, () => this.#activityRevision + 1, () => {
775
+ this.#activityRevision++;
776
+ this.#touchResource(state);
777
+ this.#notifyActivity({ state, kind: 'message' });
778
+ }, () => {
1113
779
  this.#touchResource(state, true);
1114
780
  this.#closeReceipt(state);
781
+ });
782
+ if (outcome === 'opening-failed') {
783
+ this.#running--;
784
+ state.settleTurn(turn);
785
+ state.releaseTurn(turn);
786
+ this.#notifyCompletion(state);
1115
787
  await this.#publishTerminal(state);
788
+ this.#drain();
789
+ return;
1116
790
  }
1117
- catch (error) {
1118
- this.#setFailure(state, error);
1119
- this.#closeReceipt(state);
791
+ try {
1120
792
  await this.#publishTerminal(state);
1121
793
  }
1122
794
  finally {
1123
795
  this.#closeReceipt(state);
1124
796
  this.#running--;
1125
- state.settleCompletion(turn);
797
+ state.settleTurn(turn);
1126
798
  this.#notifyCompletion(state);
1127
- state.clearTurn(turn);
799
+ state.releaseTurn(turn);
1128
800
  this.#drain();
1129
801
  }
1130
802
  }
1131
- async #failTurn(state, turn, error) {
1132
- this.#setFailure(state, error);
1133
- this.#closeReceipt(state);
1134
- this.#running--;
1135
- state.settleCompletion(turn);
1136
- state.clearTurn(turn);
1137
- this.#notifyCompletion(state);
1138
- await this.#publishTerminal(state);
1139
- this.#drain();
1140
- }
1141
803
  #setFailure(state, error) {
1142
804
  state.fail(error);
1143
805
  this.#touchResource(state, true);
@@ -1255,7 +917,7 @@ export class SubagentSessionController {
1255
917
  sourceSignal?.addEventListener('abort', abort, { once: true });
1256
918
  if (sourceSignal?.aborted)
1257
919
  abort();
1258
- return delivery;
920
+ return { delivery, waitSignal: controller.signal };
1259
921
  }
1260
922
  #closeReceipt(state) {
1261
923
  for (const pending of this.#pendingDeliveries)
@@ -1601,88 +1263,11 @@ function restoreAgentState(session) {
1601
1263
  }
1602
1264
  return state;
1603
1265
  }
1604
- function observeAgentEvent(state, event) {
1605
- if (event.type === 'message.completed') {
1606
- state.latestMessage = latestMessageFromEvent(event);
1607
- state.latestActivity = {
1608
- kind: 'message',
1609
- timestamp: event.timestamp,
1610
- phase: event.phase
1611
- };
1612
- return true;
1613
- }
1614
- if (event.type === 'tool.started') {
1615
- state.latestActivity = {
1616
- kind: 'tool',
1617
- timestamp: event.timestamp,
1618
- toolName: event.toolName,
1619
- status: 'started'
1620
- };
1621
- return false;
1622
- }
1623
- if (event.type === 'tool.failed') {
1624
- state.latestActivity = {
1625
- kind: 'tool',
1626
- timestamp: event.timestamp,
1627
- toolName: event.toolName,
1628
- status: 'failed'
1629
- };
1630
- return false;
1631
- }
1632
- if (event.type !== 'tool.completed')
1633
- return false;
1634
- state.latestActivity = {
1635
- kind: 'tool',
1636
- timestamp: event.timestamp,
1637
- toolName: event.toolName,
1638
- status: 'completed'
1639
- };
1640
- if (event.toolName === 'apply_patch' && isRecord(event.data)) {
1641
- const data = event.data;
1642
- const files = Array.isArray(data.files) ? data.files : [];
1643
- for (const file of files) {
1644
- if (!isRecord(file))
1645
- continue;
1646
- for (const path of [file.path, file.targetPath])
1647
- if (typeof path === 'string' && path.length > 0 && !state.changedFiles.includes(path))
1648
- state.changedFiles.push(path);
1649
- }
1650
- }
1651
- else if (event.toolName === 'read' && isRecord(event.data)) {
1652
- const data = event.data;
1653
- const results = Array.isArray(data.results) ? data.results : [];
1654
- for (const result of results) {
1655
- if (!isRecord(result) || typeof result.path !== 'string' || result.error !== undefined)
1656
- continue;
1657
- const evidence = {
1658
- path: result.path,
1659
- ...(isPositiveInteger(result.startLine) ? { startLine: result.startLine } : {}),
1660
- ...(isPositiveInteger(result.endLine) ? { endLine: result.endLine } : {})
1661
- };
1662
- if (!state.evidence.some((item) => JSON.stringify(item) === JSON.stringify(evidence)))
1663
- state.evidence.push(evidence);
1664
- }
1665
- }
1666
- else if (event.toolName === 'exec')
1667
- state.verification.push(event.summary);
1668
- return false;
1669
- }
1670
1266
  function eventType(record) {
1671
1267
  return record && isRecord(record.payload) && typeof record.payload.type === 'string'
1672
1268
  ? record.payload.type
1673
1269
  : undefined;
1674
1270
  }
1675
- function latestMessageFromEvent(event) {
1676
- const text = event.text;
1677
- return {
1678
- itemId: event.itemId,
1679
- turnId: event.turnId ?? '',
1680
- phase: event.phase,
1681
- text,
1682
- timestamp: event.timestamp,
1683
- truncated: text !== event.text
1684
- };
1685
- }
1686
1271
  function isReasoningEffort(value) {
1687
1272
  return (value === 'low' ||
1688
1273
  value === 'medium' ||
@@ -1694,9 +1279,6 @@ function isReasoningEffort(value) {
1694
1279
  function isRecord(value) {
1695
1280
  return value !== null && typeof value === 'object' && !Array.isArray(value);
1696
1281
  }
1697
- function isPositiveInteger(value) {
1698
- return typeof value === 'number' && Number.isInteger(value) && value >= 1;
1699
- }
1700
1282
  function truncateUtf8(value, maximumBytes) {
1701
1283
  const bytes = Buffer.from(value);
1702
1284
  if (bytes.length <= maximumBytes)