@myagentroam/agent 0.9.158 → 0.9.160

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 (130) hide show
  1. package/dist/agent-core/extensions/agents.d.ts +69 -0
  2. package/dist/agent-core/extensions/agents.js +160 -0
  3. package/dist/agent-core/extensions/contracts.d.ts +260 -0
  4. package/dist/agent-core/extensions/contracts.js +1 -0
  5. package/dist/agent-core/extensions/data.d.ts +84 -0
  6. package/dist/agent-core/extensions/data.js +83 -0
  7. package/dist/agent-core/extensions/environment.d.ts +27 -0
  8. package/dist/agent-core/extensions/environment.js +52 -0
  9. package/dist/agent-core/extensions/files.d.ts +64 -0
  10. package/dist/agent-core/extensions/files.js +26 -0
  11. package/dist/agent-core/extensions/hooks.d.ts +32 -0
  12. package/dist/agent-core/extensions/hooks.js +135 -0
  13. package/dist/agent-core/extensions/manager.d.ts +39 -0
  14. package/dist/agent-core/extensions/manager.js +458 -0
  15. package/dist/agent-core/extensions/mcp.d.ts +99 -0
  16. package/dist/agent-core/extensions/mcp.js +433 -0
  17. package/dist/agent-core/extensions/model-request.d.ts +60 -0
  18. package/dist/agent-core/extensions/model-request.js +167 -0
  19. package/dist/agent-core/extensions/model-stream.d.ts +9 -0
  20. package/dist/agent-core/extensions/model-stream.js +101 -0
  21. package/dist/agent-core/extensions/models.d.ts +43 -0
  22. package/dist/agent-core/extensions/models.js +1 -0
  23. package/dist/agent-core/extensions/network.d.ts +32 -0
  24. package/dist/agent-core/extensions/network.js +97 -0
  25. package/dist/agent-core/extensions/operation.d.ts +29 -0
  26. package/dist/agent-core/extensions/operation.js +98 -0
  27. package/dist/agent-core/extensions/processes.d.ts +52 -0
  28. package/dist/agent-core/extensions/processes.js +283 -0
  29. package/dist/agent-core/extensions/prompt-composition.d.ts +24 -0
  30. package/dist/agent-core/extensions/prompt-composition.js +106 -0
  31. package/dist/agent-core/extensions/prompt-input.d.ts +23 -0
  32. package/dist/agent-core/extensions/prompt-input.js +46 -0
  33. package/dist/agent-core/extensions/resources.d.ts +27 -0
  34. package/dist/agent-core/extensions/resources.js +221 -0
  35. package/dist/agent-core/extensions/services.d.ts +31 -0
  36. package/dist/agent-core/extensions/services.js +61 -0
  37. package/dist/agent-core/extensions/session-input.d.ts +151 -0
  38. package/dist/agent-core/extensions/session-input.js +351 -0
  39. package/dist/agent-core/extensions/session-messages.d.ts +41 -0
  40. package/dist/agent-core/extensions/session-messages.js +76 -0
  41. package/dist/agent-core/extensions/session.d.ts +68 -0
  42. package/dist/agent-core/extensions/session.js +593 -0
  43. package/dist/agent-core/extensions/settings.d.ts +59 -0
  44. package/dist/agent-core/extensions/settings.js +115 -0
  45. package/dist/agent-core/extensions/store.d.ts +10 -0
  46. package/dist/agent-core/extensions/store.js +131 -0
  47. package/dist/agent-core/extensions/tools.d.ts +61 -0
  48. package/dist/agent-core/extensions/tools.js +266 -0
  49. package/dist/agent-core/host/contracts.d.ts +25 -0
  50. package/dist/agent-core/host/local-agent-host.d.ts +10 -1
  51. package/dist/agent-core/host/local-agent-host.js +132 -20
  52. package/dist/agent-core/host/local-file-access.d.ts +30 -0
  53. package/dist/agent-core/host/local-file-access.js +107 -0
  54. package/dist/agent-core/host/local-network-access.d.ts +6 -0
  55. package/dist/agent-core/host/local-network-access.js +97 -0
  56. package/dist/agent-core/host/local-process-executor.d.ts +5 -1
  57. package/dist/agent-core/host/local-session-environment.d.ts +18 -0
  58. package/dist/agent-core/host/local-session-environment.js +102 -0
  59. package/dist/agent-core/model/anthropic-messages.js +73 -23
  60. package/dist/agent-core/model/contracts.d.ts +21 -0
  61. package/dist/agent-core/model/openai-responses.d.ts +3 -1
  62. package/dist/agent-core/model/openai-responses.js +248 -97
  63. package/dist/agent-core/model/text-blocks.d.ts +7 -0
  64. package/dist/agent-core/model/text-blocks.js +40 -0
  65. package/dist/agent-core/prompts/index.d.ts +6 -2
  66. package/dist/agent-core/prompts/index.js +38 -24
  67. package/dist/agent-core/runtime/context/gc-tokens.js +6 -1
  68. package/dist/agent-core/runtime/context/normalize.js +21 -1
  69. package/dist/agent-core/runtime/context/projection.js +35 -1
  70. package/dist/agent-core/runtime/context/request-budget.js +5 -2
  71. package/dist/agent-core/runtime/context/restore.d.ts +5 -0
  72. package/dist/agent-core/runtime/context/restore.js +47 -4
  73. package/dist/agent-core/runtime/context/usage.js +2 -3
  74. package/dist/agent-core/runtime/contracts.d.ts +2 -0
  75. package/dist/agent-core/runtime/execution.js +5 -2
  76. package/dist/agent-core/sdk/agent.js +177 -40
  77. package/dist/agent-core/sdk/extension-agents.d.ts +9 -0
  78. package/dist/agent-core/sdk/extension-agents.js +51 -0
  79. package/dist/agent-core/sdk/extension-models.d.ts +13 -0
  80. package/dist/agent-core/sdk/extension-models.js +128 -0
  81. package/dist/agent-core/sdk/extension-session.d.ts +30 -0
  82. package/dist/agent-core/sdk/extension-session.js +103 -0
  83. package/dist/agent-core/sdk/extension-tools.d.ts +16 -0
  84. package/dist/agent-core/sdk/extension-tools.js +126 -0
  85. package/dist/agent-core/sdk/session.d.ts +26 -2
  86. package/dist/agent-core/sdk/session.js +1215 -506
  87. package/dist/agent-core/sdk/types.d.ts +3 -0
  88. package/dist/agent-core/session/context-gc-checkpoint.js +1 -1
  89. package/dist/agent-core/session/jsonl-store.d.ts +28 -8
  90. package/dist/agent-core/session/jsonl-store.js +124 -84
  91. package/dist/agent-core/session/types.d.ts +4 -1
  92. package/dist/agent-core/storage/file-lock.d.ts +4 -0
  93. package/dist/agent-core/storage/file-lock.js +83 -0
  94. package/dist/agent-core/subagent/contracts.d.ts +11 -1
  95. package/dist/agent-core/subagent/scheduler.d.ts +8 -0
  96. package/dist/agent-core/subagent/session-controller.d.ts +22 -1
  97. package/dist/agent-core/subagent/session-controller.js +355 -98
  98. package/dist/agent-core/tools/agent/agent-start.d.ts +1 -1
  99. package/dist/agent-core/tools/code-mode/cells.js +5 -15
  100. package/dist/agent-core/tools/code-mode/dispatch.d.ts +1 -0
  101. package/dist/agent-core/tools/code-mode/dispatch.js +13 -1
  102. package/dist/agent-core/tools/code-mode/index.js +1 -2
  103. package/dist/agent-core/tools/code-mode/observations.d.ts +5 -0
  104. package/dist/agent-core/tools/code-mode/observations.js +16 -5
  105. package/dist/agent-core/tools/code-mode/quickjs-engine.js +10 -5
  106. package/dist/agent-core/tools/execution.d.ts +11 -2
  107. package/dist/agent-core/tools/execution.js +58 -21
  108. package/dist/agent-core/tools/input.d.ts +2 -0
  109. package/dist/agent-core/tools/input.js +36 -0
  110. package/dist/agent-core/tools/local/apply-patch.js +21 -17
  111. package/dist/agent-core/tools/local/exec.d.ts +4 -4
  112. package/dist/agent-core/tools/local/exec.js +234 -62
  113. package/dist/agent-core/tools/local/registry.d.ts +6 -0
  114. package/dist/agent-core/tools/local/registry.js +60 -9
  115. package/dist/agent-core/tools/local/view-image.js +108 -2
  116. package/dist/agent-core/tools/mcp/catalog.d.ts +6 -2
  117. package/dist/agent-core/tools/mcp/catalog.js +4 -4
  118. package/dist/agent-core/tools/mcp/history.js +2 -2
  119. package/dist/agent-core/tools/mcp/tool-search.d.ts +3 -1
  120. package/dist/agent-core/tools/mcp/tool-search.js +2 -2
  121. package/dist/agent-core/tools/output-projection.d.ts +3 -1
  122. package/dist/agent-core/tools/output-projection.js +11 -2
  123. package/dist/agent-core/tools/runtime-context.d.ts +1 -0
  124. package/dist/agent-core/tools/runtime.d.ts +3 -1
  125. package/dist/agent-core/tools/runtime.js +22 -7
  126. package/dist/agent-core/tools/scheduler.d.ts +2 -0
  127. package/dist/agent-core/tools/scheduler.js +43 -6
  128. package/dist/index.d.ts +21 -0
  129. package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +5 -2
  130. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { MarAgentHost } from '../host/contracts.js';
2
+ import type { CoreExtensionSource, CoreRuntimeExtension } from '../extensions/contracts.js';
2
3
  import type { MarAgentModelConfiguration } from '../model/configuration.js';
3
4
  import type { ExecutionResult, RolloutBudgetOptions, RunInput } from '../runtime/contracts.js';
4
5
  import type { MarAgentEvent } from '../runtime/events.js';
@@ -29,6 +30,7 @@ export interface MarAgentSession {
29
30
  cancel(executionId: string): Promise<void>;
30
31
  }
31
32
  export interface MarAgent {
33
+ extensionDiagnostics(): readonly import('../extensions/contracts.js').CoreExtensionDiagnostic[];
32
34
  createSession(input: CreateSessionInput): Promise<MarAgentSession>;
33
35
  openSession(sessionId: string): Promise<MarAgentSession>;
34
36
  listSessions(input?: SessionListInput): Promise<MarAgentSessionPage>;
@@ -59,5 +61,6 @@ export interface CreateMarAgentOptions {
59
61
  readonly physicalPath: string;
60
62
  };
61
63
  rolloutBudget?: false | RolloutBudgetOptions;
64
+ runtimeExtensions?: readonly CoreRuntimeExtension[] | CoreExtensionSource;
62
65
  }
63
66
  export type { MarAgentSessionSummary, SessionListInput, SessionSource };
@@ -659,7 +659,7 @@ function modelContextCallId(payload) {
659
659
  return typeof value === 'string' ? value : undefined;
660
660
  }
661
661
  function modelContextToolName(payload) {
662
- if (payload.role === 'tool_call' && typeof payload.name === 'string')
662
+ if ((payload.role === 'tool_call' || payload.role === 'tool') && typeof payload.name === 'string')
663
663
  return payload.name;
664
664
  if (payload.role !== 'provider' || !isRecord(payload.item))
665
665
  return undefined;
@@ -1,4 +1,5 @@
1
- import type { ModelUsage } from '../model/contracts.js';
1
+ import { type CoreCheckpoint, type CoreJsonValue } from '../extensions/data.js';
2
+ import type { ModelMessage, ModelUsage } from '../model/contracts.js';
2
3
  import type { EnvironmentContextState } from '../runtime/context/contracts.js';
3
4
  import { type CodeModeStateEntry } from '../tools/code-mode/state.js';
4
5
  import { type TodoSnapshot } from '../tools/agent/todo.js';
@@ -16,6 +17,13 @@ export interface SessionRecord {
16
17
  type: string;
17
18
  payload: unknown;
18
19
  }
20
+ interface SessionRecordInput {
21
+ type: string;
22
+ payload: unknown;
23
+ executionId?: string;
24
+ turnId?: string;
25
+ timestamp?: string;
26
+ }
19
27
  export interface SessionExecutionState {
20
28
  automaticTitlePending: boolean;
21
29
  todo: TodoSnapshot;
@@ -139,15 +147,19 @@ export declare class JsonlSessionStore {
139
147
  source: SessionSource;
140
148
  automaticTitlePending: boolean;
141
149
  }>;
142
- append(sessionId: string, input: {
143
- type: string;
144
- payload: unknown;
145
- executionId?: string;
146
- turnId?: string;
147
- timestamp?: string;
150
+ /** Serialize physical writes and honor cancellation before entering the writer. */
151
+ append(sessionId: string, input: SessionRecordInput, options?: {
152
+ readonly signal?: AbortSignal;
148
153
  }): Promise<SessionRecord>;
149
154
  read(sessionId: string): Promise<SessionRecord[]>;
150
155
  readCodeModeState(sessionId: string): Promise<CodeModeStateEntry[]>;
156
+ readExtensionCheckpoint(sessionId: string, extensionId: string, key: string): Promise<CoreCheckpoint>;
157
+ writeExtensionCheckpoint(sessionId: string, extensionId: string, key: string, value: CoreJsonValue | undefined, expectedRevision: number | undefined, input: {
158
+ owner: object;
159
+ signal: AbortSignal;
160
+ executionId?: string;
161
+ turnId?: string;
162
+ }): Promise<CoreCheckpoint>;
151
163
  commitCodeModeState(sessionId: string, mutations: unknown, input: {
152
164
  readonly executionId: string;
153
165
  readonly turnId: string;
@@ -168,6 +180,7 @@ export declare class JsonlSessionStore {
168
180
  compact(sessionId: string, input: {
169
181
  summary: string;
170
182
  replacementHistory?: readonly CompactHistoryMessage[];
183
+ retainedModelContext?: readonly ModelMessage[];
171
184
  environment?: EnvironmentContextState;
172
185
  automatic?: boolean;
173
186
  modelId?: string;
@@ -175,10 +188,14 @@ export declare class JsonlSessionStore {
175
188
  usage?: ModelUsage;
176
189
  executionId?: string;
177
190
  turnId?: string;
191
+ }, options?: {
192
+ readonly signal?: AbortSignal;
178
193
  }): Promise<SessionRecord>;
179
194
  /** A warm cell retains physical writer ownership between Executions of the same Agent. */
180
195
  acquireSessionLease(sessionId: string, owner?: object): Promise<() => Promise<void>>;
181
196
  hasRetainedSessionLease(sessionId: string, owner: object): boolean;
197
+ /** Native writes join their Agent's sole writer, without admitting another Execution. */
198
+ acquireSessionWriteLease(sessionId: string, owner: object): Promise<() => Promise<void>>;
182
199
  retainSessionLease(sessionId: string, owner: object): Promise<() => Promise<void>>;
183
200
  list(input?: SessionListInput): Promise<{
184
201
  nextCursor?: string;
@@ -208,7 +225,10 @@ export declare class JsonlSessionStore {
208
225
  readonly type: "subagent";
209
226
  readonly parentSessionId: string;
210
227
  readonly rootSessionId: string;
211
- readonly parentExecutionId: string;
228
+ readonly parentExecutionId?: string;
229
+ readonly parentOperationId?: string;
230
+ readonly ownerInstanceId?: string;
231
+ readonly extensionId?: string;
212
232
  readonly role: "general-purpose" | "explore";
213
233
  readonly description: string;
214
234
  };
@@ -2,9 +2,10 @@ import { createHash, randomUUID } from 'node:crypto';
2
2
  import { constants } from 'node:fs';
3
3
  import { copyFile, lstat, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
4
4
  import { basename, join } from 'node:path';
5
- import { setTimeout as delay } from 'node:timers/promises';
6
5
  import { isDeepStrictEqual } from 'node:util';
7
6
  import { MarAgentError } from '../error.js';
7
+ import { acquireFileLock } from '../storage/file-lock.js';
8
+ import { cloneCoreJson } from '../extensions/data.js';
8
9
  import { applyCodeModeStateMutations, parseCodeModeStateMutations, restoreCodeModeState } from '../tools/code-mode/state.js';
9
10
  import { todoItemsFromSnapshot } from '../tools/agent/todo.js';
10
11
  import { contextGcBaseFromRecords, contextGcBaseSequence, contextGcMinimumUsageGrowth, contextGcSourceProjectionHash, latestValidContextGcCheckpoint, normalizeContextGcReplacements, parseContextGcCheckpoint, sameContextGcBase, validateContextGcCheckpoint } from './context-gc-checkpoint.js';
@@ -153,6 +154,7 @@ export class JsonlSessionStore {
153
154
  return {
154
155
  root,
155
156
  index,
157
+ indexOperations: Promise.resolve(),
156
158
  pendingSessions: new Map(),
157
159
  references: 1,
158
160
  closed: false,
@@ -170,6 +172,14 @@ export class JsonlSessionStore {
170
172
  }
171
173
  /** Merge dirty metadata with disk membership; only createdIds publish locally new entries. */
172
174
  async #saveIndex(deletedIds, createdIds = []) {
175
+ return this.#serializeIndexOperation(() => this.#commitIndex(deletedIds, createdIds));
176
+ }
177
+ async #serializeIndexOperation(operation) {
178
+ const next = this.#shared.indexOperations.then(operation, operation);
179
+ this.#shared.indexOperations = next.then(() => undefined, () => undefined);
180
+ return next;
181
+ }
182
+ async #commitIndex(deletedIds, createdIds = []) {
173
183
  const release = await acquireFileLock(join(this.#shared.root, 'sessions', 'index.lock'));
174
184
  const path = join(this.#shared.root, 'sessions', 'index.json');
175
185
  try {
@@ -229,6 +239,9 @@ export class JsonlSessionStore {
229
239
  }
230
240
  }
231
241
  async #refreshIndex() {
242
+ return this.#serializeIndexOperation(() => this.#readCurrentIndex());
243
+ }
244
+ async #readCurrentIndex() {
232
245
  const disk = await readSessionIndex(join(this.#shared.root, 'sessions', 'index.json'));
233
246
  if (!disk)
234
247
  throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index is missing.');
@@ -874,7 +887,7 @@ export class JsonlSessionStore {
874
887
  installedPaths.push(item.path);
875
888
  }
876
889
  meta.lastActivityAt = clearedAt;
877
- meta.nextSequence = 2;
890
+ meta.nextSequence = retained.length + 1;
878
891
  meta.storageTransactionId = transactionId;
879
892
  meta.resume = { version: 1, todoRevision: 0, firstUserMessageIndexed: true };
880
893
  meta.segments = staged.segments;
@@ -945,8 +958,14 @@ export class JsonlSessionStore {
945
958
  automaticTitlePending: meta.automaticTitlePending === true
946
959
  };
947
960
  }
948
- async append(sessionId, input) {
949
- return this.#serializeRecordOperation(sessionId, () => this.#appendRecord(sessionId, input));
961
+ /** Serialize physical writes and honor cancellation before entering the writer. */
962
+ async append(sessionId, input, options = {}) {
963
+ this.#assertOpen();
964
+ options.signal?.throwIfAborted();
965
+ return this.#serializeRecordOperation(sessionId, () => {
966
+ options.signal?.throwIfAborted();
967
+ return this.#appendRecord(sessionId, input);
968
+ });
950
969
  }
951
970
  async #serializeRecordOperation(sessionId, action) {
952
971
  const operation = (this.#shared.recordWrites.get(sessionId) ?? Promise.resolve())
@@ -1058,6 +1077,42 @@ export class JsonlSessionStore {
1058
1077
  async readCodeModeState(sessionId) {
1059
1078
  return restoreCodeModeState(await this.read(sessionId));
1060
1079
  }
1080
+ async readExtensionCheckpoint(sessionId, extensionId, key) {
1081
+ return restoreExtensionCheckpoint(await this.read(sessionId), extensionId, key);
1082
+ }
1083
+ async writeExtensionCheckpoint(sessionId, extensionId, key, value, expectedRevision, input) {
1084
+ if (typeof key !== 'string' ||
1085
+ (expectedRevision !== undefined &&
1086
+ (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)))
1087
+ throw new MarAgentError('MAR_AGENT_EXTENSION_DATA_INVALID', 'Checkpoint key or expected revision is invalid.');
1088
+ const copied = value === undefined ? undefined : cloneCoreJson(value);
1089
+ input.signal.throwIfAborted();
1090
+ const release = await this.acquireSessionWriteLease(sessionId, input.owner);
1091
+ try {
1092
+ return await this.#serializeRecordOperation(sessionId, async () => {
1093
+ const current = restoreExtensionCheckpoint(await this.#readRecords(sessionId), extensionId, key);
1094
+ if (expectedRevision !== undefined && expectedRevision !== current.revision)
1095
+ throw new MarAgentError('MAR_AGENT_EXTENSION_DATA_CONFLICT', 'Checkpoint revision changed before commit.');
1096
+ input.signal.throwIfAborted();
1097
+ const revision = current.revision + 1;
1098
+ await this.#appendRecord(sessionId, {
1099
+ type: 'extension.checkpoint.updated',
1100
+ payload: {
1101
+ extensionId,
1102
+ key,
1103
+ revision,
1104
+ ...(copied === undefined ? { deleted: true } : { value: copied })
1105
+ },
1106
+ ...(input.executionId ? { executionId: input.executionId } : {}),
1107
+ ...(input.turnId ? { turnId: input.turnId } : {})
1108
+ });
1109
+ return { revision, ...(copied === undefined ? {} : { value: cloneCoreJson(copied) }) };
1110
+ });
1111
+ }
1112
+ finally {
1113
+ await release();
1114
+ }
1115
+ }
1061
1116
  async commitCodeModeState(sessionId, mutations, input) {
1062
1117
  const operation = (this.#shared.codeStateWrites.get(sessionId) ?? Promise.resolve())
1063
1118
  .catch(() => undefined)
@@ -1212,7 +1267,7 @@ export class JsonlSessionStore {
1212
1267
  const previousBeforeSequence = hasEarlier ? page[0]?.sequence : undefined;
1213
1268
  return { records: page, ...(previousBeforeSequence ? { previousBeforeSequence } : {}) };
1214
1269
  }
1215
- async compact(sessionId, input) {
1270
+ async compact(sessionId, input, options = {}) {
1216
1271
  if (!input.summary.trim())
1217
1272
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Compact summary is required.');
1218
1273
  const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
@@ -1225,6 +1280,9 @@ export class JsonlSessionStore {
1225
1280
  ...(input.replacementHistory === undefined
1226
1281
  ? {}
1227
1282
  : { replacementHistory: input.replacementHistory }),
1283
+ ...(input.retainedModelContext?.length
1284
+ ? { retainedModelContext: input.retainedModelContext }
1285
+ : {}),
1228
1286
  ...(input.environment === undefined ? {} : { environment: input.environment }),
1229
1287
  boundarySequence: meta.nextSequence - 1,
1230
1288
  automatic: input.automatic === true,
@@ -1236,7 +1294,7 @@ export class JsonlSessionStore {
1236
1294
  },
1237
1295
  ...(input.executionId ? { executionId: input.executionId } : {}),
1238
1296
  ...(input.turnId ? { turnId: input.turnId } : {})
1239
- });
1297
+ }, options);
1240
1298
  }
1241
1299
  /** A warm cell retains physical writer ownership between Executions of the same Agent. */
1242
1300
  async acquireSessionLease(sessionId, owner) {
@@ -1268,6 +1326,30 @@ export class JsonlSessionStore {
1268
1326
  const lease = this.#shared.writerLeases.get(sessionId);
1269
1327
  return lease?.owner === owner && lease.retained > 0;
1270
1328
  }
1329
+ /** Native writes join their Agent's sole writer, without admitting another Execution. */
1330
+ async acquireSessionWriteLease(sessionId, owner) {
1331
+ return this.#serializeRecordOperation(sessionId, async () => {
1332
+ this.#assertOpen();
1333
+ let lease = this.#shared.writerLeases.get(sessionId);
1334
+ if (lease && lease.owner !== owner)
1335
+ throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
1336
+ if (!lease) {
1337
+ const release = await this.#acquireSessionLease(sessionId);
1338
+ lease = { owner, executing: false, retained: 0, release };
1339
+ this.#shared.writerLeases.set(sessionId, lease);
1340
+ }
1341
+ lease.retained++;
1342
+ const current = lease;
1343
+ let released = false;
1344
+ return () => this.#serializeRecordOperation(sessionId, async () => {
1345
+ if (released)
1346
+ return;
1347
+ released = true;
1348
+ current.retained--;
1349
+ await this.#releaseUnusedWriter(sessionId, current);
1350
+ });
1351
+ });
1352
+ }
1271
1353
  async retainSessionLease(sessionId, owner) {
1272
1354
  return this.#serializeRecordOperation(sessionId, async () => {
1273
1355
  const lease = this.#shared.writerLeases.get(sessionId);
@@ -2167,9 +2249,20 @@ function normalizeSessionSource(value) {
2167
2249
  if (value === undefined || value.type === 'user')
2168
2250
  return { type: 'user' };
2169
2251
  if (value.type !== 'subagent' ||
2170
- !value.parentSessionId ||
2171
- !value.rootSessionId ||
2172
- !value.parentExecutionId ||
2252
+ typeof value.parentSessionId !== 'string' ||
2253
+ !value.parentSessionId.trim() ||
2254
+ typeof value.rootSessionId !== 'string' ||
2255
+ !value.rootSessionId.trim() ||
2256
+ (!value.parentExecutionId && !value.parentOperationId) ||
2257
+ [
2258
+ value.parentExecutionId,
2259
+ value.parentOperationId,
2260
+ value.ownerInstanceId,
2261
+ value.extensionId
2262
+ ].some((identity) => identity !== undefined && (typeof identity !== 'string' || !identity.trim())) ||
2263
+ ((value.ownerInstanceId !== undefined || value.extensionId !== undefined) &&
2264
+ value.parentOperationId === undefined) ||
2265
+ typeof value.description !== 'string' ||
2173
2266
  !value.description.trim() ||
2174
2267
  (value.role !== 'general-purpose' && value.role !== 'explore'))
2175
2268
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Session source is invalid.');
@@ -2177,7 +2270,14 @@ function normalizeSessionSource(value) {
2177
2270
  type: 'subagent',
2178
2271
  parentSessionId: value.parentSessionId,
2179
2272
  rootSessionId: value.rootSessionId,
2180
- parentExecutionId: value.parentExecutionId,
2273
+ ...(value.parentExecutionId === undefined
2274
+ ? {}
2275
+ : { parentExecutionId: value.parentExecutionId }),
2276
+ ...(value.parentOperationId === undefined
2277
+ ? {}
2278
+ : { parentOperationId: value.parentOperationId }),
2279
+ ...(value.ownerInstanceId === undefined ? {} : { ownerInstanceId: value.ownerInstanceId }),
2280
+ ...(value.extensionId === undefined ? {} : { extensionId: value.extensionId }),
2181
2281
  role: value.role,
2182
2282
  description: value.description.trim()
2183
2283
  };
@@ -2630,81 +2730,21 @@ function segmentNumberFromPath(path) {
2630
2730
  const match = /events-(\d{6})\.jsonl$/.exec(path);
2631
2731
  return match === null ? undefined : Number(match[1]);
2632
2732
  }
2633
- async function acquireFileLock(path, message = 'Session index is busy.', waitMs = 5000) {
2634
- const deadline = Date.now() + waitMs;
2635
- for (;;) {
2636
- let handle;
2637
- try {
2638
- handle = await open(path, 'wx', 0o600);
2639
- }
2640
- catch (cause) {
2641
- if (cause.code !== 'EEXIST')
2642
- throw cause;
2643
- if (await removeDeadOwner(path))
2644
- continue;
2645
- if (Date.now() >= deadline)
2646
- throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', message, { cause });
2647
- await delay(25 + Math.floor(Math.random() * 25));
2733
+ function restoreExtensionCheckpoint(records, extensionId, key) {
2734
+ for (const record of [...records].reverse()) {
2735
+ if (record.type !== 'extension.checkpoint.updated')
2648
2736
  continue;
2649
- }
2650
- try {
2651
- await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
2652
- }
2653
- catch (error) {
2654
- try {
2655
- await handle.close();
2656
- }
2657
- finally {
2658
- await rm(path, { force: true });
2659
- }
2660
- throw error;
2661
- }
2662
- let released = false;
2663
- return async () => {
2664
- if (released)
2665
- return;
2666
- released = true;
2667
- await handle.close();
2668
- await rm(path, { force: true });
2737
+ const payload = record.payload;
2738
+ if (!payload || payload.extensionId !== extensionId || payload.key !== key)
2739
+ continue;
2740
+ if (typeof payload.revision !== 'number' ||
2741
+ !Number.isSafeInteger(payload.revision) ||
2742
+ payload.revision < 1)
2743
+ throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Extension checkpoint revision is corrupt.');
2744
+ return {
2745
+ revision: payload.revision,
2746
+ ...(payload.deleted === true ? {} : { value: cloneCoreJson(payload.value) })
2669
2747
  };
2670
2748
  }
2671
- }
2672
- async function removeDeadOwner(path) {
2673
- const reclaimerPath = `${path}.reclaim`;
2674
- let reclaimer;
2675
- try {
2676
- // Only one process may unlink a dead owner's path; all other contenders wait.
2677
- reclaimer = await open(reclaimerPath, 'wx', 0o600);
2678
- }
2679
- catch (error) {
2680
- if (error.code === 'EEXIST')
2681
- return false;
2682
- throw error;
2683
- }
2684
- try {
2685
- const owner = JSON.parse(await readFile(path, 'utf8'));
2686
- if (typeof owner.pid !== 'number' || !Number.isSafeInteger(owner.pid))
2687
- return false;
2688
- try {
2689
- process.kill(owner.pid, 0);
2690
- return false;
2691
- }
2692
- catch (error) {
2693
- if (error.code !== 'ESRCH')
2694
- return false;
2695
- await rm(path, { force: true });
2696
- return true;
2697
- }
2698
- }
2699
- catch {
2700
- return false;
2701
- }
2702
- finally {
2703
- try {
2704
- await reclaimer.close();
2705
- }
2706
- finally {
2707
- await rm(reclaimerPath, { force: true });
2708
- }
2709
- }
2749
+ return { revision: 0 };
2710
2750
  }
@@ -5,7 +5,10 @@ export type SessionSource = {
5
5
  readonly type: 'subagent';
6
6
  readonly parentSessionId: string;
7
7
  readonly rootSessionId: string;
8
- readonly parentExecutionId: string;
8
+ readonly parentExecutionId?: string;
9
+ readonly parentOperationId?: string;
10
+ readonly ownerInstanceId?: string;
11
+ readonly extensionId?: string;
9
12
  readonly role: 'general-purpose' | 'explore';
10
13
  readonly description: string;
11
14
  };
@@ -0,0 +1,4 @@
1
+ export declare function acquireFileLock(path: string, message?: string, waitMs?: number, options?: {
2
+ readonly signal?: AbortSignal;
3
+ readonly code?: string;
4
+ }): Promise<() => Promise<void>>;
@@ -0,0 +1,83 @@
1
+ import { open, readFile, rm } from 'node:fs/promises';
2
+ import { setTimeout as delay } from 'node:timers/promises';
3
+ import { MarAgentError } from '../error.js';
4
+ export async function acquireFileLock(path, message = 'Session index is busy.', waitMs = 5000, options = {}) {
5
+ const deadline = Date.now() + waitMs;
6
+ for (;;) {
7
+ options.signal?.throwIfAborted();
8
+ let handle;
9
+ try {
10
+ handle = await open(path, 'wx', 0o600);
11
+ }
12
+ catch (cause) {
13
+ if (cause.code !== 'EEXIST')
14
+ throw cause;
15
+ if (await removeDeadOwner(path))
16
+ continue;
17
+ if (Date.now() >= deadline)
18
+ throw new MarAgentError(options.code ?? 'MAR_AGENT_SESSION_LOCKED', message, { cause });
19
+ await delay(25 + Math.floor(Math.random() * 25), undefined, options.signal ? { signal: options.signal } : {});
20
+ continue;
21
+ }
22
+ try {
23
+ await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
24
+ options.signal?.throwIfAborted();
25
+ }
26
+ catch (error) {
27
+ try {
28
+ await handle.close();
29
+ }
30
+ finally {
31
+ await rm(path, { force: true });
32
+ }
33
+ throw error;
34
+ }
35
+ let released = false;
36
+ return async () => {
37
+ if (released)
38
+ return;
39
+ released = true;
40
+ await handle.close();
41
+ await rm(path, { force: true });
42
+ };
43
+ }
44
+ }
45
+ async function removeDeadOwner(path) {
46
+ const reclaimerPath = `${path}.reclaim`;
47
+ let reclaimer;
48
+ try {
49
+ // Only one process may unlink a dead owner's path; all other contenders wait.
50
+ reclaimer = await open(reclaimerPath, 'wx', 0o600);
51
+ }
52
+ catch (error) {
53
+ if (error.code === 'EEXIST')
54
+ return false;
55
+ throw error;
56
+ }
57
+ try {
58
+ const owner = JSON.parse(await readFile(path, 'utf8'));
59
+ if (typeof owner.pid !== 'number' || !Number.isSafeInteger(owner.pid))
60
+ return false;
61
+ try {
62
+ process.kill(owner.pid, 0);
63
+ return false;
64
+ }
65
+ catch (error) {
66
+ if (error.code !== 'ESRCH')
67
+ return false;
68
+ await rm(path, { force: true });
69
+ return true;
70
+ }
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ finally {
76
+ try {
77
+ await reclaimer.close();
78
+ }
79
+ finally {
80
+ await rm(reclaimerPath, { force: true });
81
+ }
82
+ }
83
+ }
@@ -39,6 +39,8 @@ export type SubagentLatestActivity = {
39
39
  export interface SubagentTaskSnapshot extends Record<string, unknown> {
40
40
  agentId: string;
41
41
  description: string;
42
+ role?: SubagentTaskInput['role'];
43
+ extensionId?: string;
42
44
  status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled' | 'interrupted';
43
45
  modelId?: string;
44
46
  modelName?: string;
@@ -67,17 +69,25 @@ export interface SubagentMessageOptions {
67
69
  signal?: AbortSignal;
68
70
  }
69
71
  export interface SubagentRunOptions {
72
+ turnSignal?: AbortSignal;
73
+ waitFor?: 'start' | 'completion';
70
74
  signal?: AbortSignal;
71
75
  onCreated?: (task: SubagentTaskSnapshot) => Promise<void> | void;
72
76
  }
73
77
  export interface SubagentOutputOptions {
78
+ untilSettled?: true;
74
79
  waitMs?: number;
75
80
  waitFor?: SubagentWaitFor;
76
81
  signal?: AbortSignal;
77
82
  }
83
+ export interface SubagentHandledResult {
84
+ readonly outcome: 'handled';
85
+ readonly value?: unknown;
86
+ }
87
+ export type SubagentRunResult = SubagentTaskSnapshot | SubagentHandledResult;
78
88
  export interface SubagentController {
79
89
  beginExecution?(runner: (task: SubagentTaskInput, signal: AbortSignal) => Promise<SubagentTaskResult>): void;
80
- run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
90
+ run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentRunResult>;
81
91
  output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
82
92
  outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
83
93
  message(agentId: string, message: string, options?: SubagentMessageOptions): Promise<SubagentTaskSnapshot>;
@@ -16,6 +16,8 @@ export declare class SubagentScheduler implements SubagentController {
16
16
  waitOutcome: "snapshot";
17
17
  agentId: string;
18
18
  description: string;
19
+ role?: SubagentTaskInput["role"];
20
+ extensionId?: string;
19
21
  status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
20
22
  modelId?: string;
21
23
  modelName?: string;
@@ -39,6 +41,8 @@ export declare class SubagentScheduler implements SubagentController {
39
41
  waitOutcome: "completion" | "timeout";
40
42
  agentId: string;
41
43
  description: string;
44
+ role?: SubagentTaskInput["role"];
45
+ extensionId?: string;
42
46
  status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
43
47
  modelId?: string;
44
48
  modelName?: string;
@@ -63,6 +67,8 @@ export declare class SubagentScheduler implements SubagentController {
63
67
  waitOutcome: "snapshot";
64
68
  agentId: string;
65
69
  description: string;
70
+ role?: SubagentTaskInput["role"];
71
+ extensionId?: string;
66
72
  status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
67
73
  modelId?: string;
68
74
  modelName?: string;
@@ -86,6 +92,8 @@ export declare class SubagentScheduler implements SubagentController {
86
92
  waitOutcome: "completion" | "timeout";
87
93
  agentId: string;
88
94
  description: string;
95
+ role?: SubagentTaskInput["role"];
96
+ extensionId?: string;
89
97
  status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
90
98
  modelId?: string;
91
99
  modelName?: string;
@@ -1,3 +1,4 @@
1
+ import type { CoreOperationContext } from '../extensions/operation.js';
1
2
  import type { MarAgentReasoningEffort } from '../model/configuration.js';
2
3
  import type { ExecutionResult } from '../runtime/contracts.js';
3
4
  import type { MarAgentEvent } from '../runtime/events.js';
@@ -18,7 +19,10 @@ export interface SubagentTurnHandle extends AsyncIterable<MarAgentEvent> {
18
19
  }
19
20
  export interface SubagentMessageDelivery {
20
21
  readonly messageId: string;
22
+ readonly origin?: CoreOperationContext;
23
+ readonly signal?: AbortSignal;
21
24
  applied(): void;
25
+ settled?(status: 'cancelled' | 'failed'): void;
22
26
  }
23
27
  export interface SubagentMailboxMessage {
24
28
  readonly text: string;
@@ -39,8 +43,18 @@ export interface SubagentParentExecution {
39
43
  readonly initialMailbox: readonly SubagentMailboxMessage[];
40
44
  readonly turnId: string;
41
45
  readonly delivery?: SubagentMessageDelivery;
46
+ readonly origin?: CoreOperationContext;
42
47
  }): Promise<SubagentTurnHandle>;
43
48
  }
49
+ /** A native operation has its own provenance even when no parent Execution is active. */
50
+ export interface SubagentParentOperation extends Omit<SubagentParentExecution, 'executionId'> {
51
+ readonly operationId: string;
52
+ readonly executionId?: string;
53
+ readonly ownerInstanceId?: string;
54
+ readonly extensionId?: string;
55
+ readonly signal: AbortSignal;
56
+ readonly origin?: CoreOperationContext;
57
+ }
44
58
  export interface PersistedSubagentSession {
45
59
  readonly id: string;
46
60
  readonly createdAt: string;
@@ -85,8 +99,15 @@ export declare class SubagentSessionController implements SubagentController {
85
99
  }): Promise<SubagentSessionController>;
86
100
  beginParentExecution(execution: SubagentParentExecution): Promise<void>;
87
101
  /** A retained cell must not borrow a later model Execution's identity or task budget. */
88
- captureExecution(): SubagentController;
102
+ captureExecution(executionId?: string): SubagentController;
103
+ /** SDK physical tool ingress shares the same captured parent as its model controls. */
104
+ captureParentFrame(executionId?: string): <Value>(run: () => Value) => Value;
105
+ withOperation<T>(operation: SubagentParentOperation, run: () => T): T;
106
+ inspect(agentId: string): Promise<SubagentTaskSnapshot>;
107
+ inspectAll(): readonly SubagentTaskSnapshot[];
108
+ cancelOwned(ownerInstanceId: string): Promise<void>;
89
109
  endParentExecution(executionId: string): void;
110
+ parentExecution(executionId: string): SubagentParentExecution | undefined;
90
111
  run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
91
112
  output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
92
113
  outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;