@myagentroam/agent 0.9.159 → 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 (128) 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 +12 -0
  61. package/dist/agent-core/model/openai-responses.js +73 -12
  62. package/dist/agent-core/model/text-blocks.d.ts +7 -0
  63. package/dist/agent-core/model/text-blocks.js +40 -0
  64. package/dist/agent-core/prompts/index.d.ts +6 -2
  65. package/dist/agent-core/prompts/index.js +38 -24
  66. package/dist/agent-core/runtime/context/gc-tokens.js +6 -1
  67. package/dist/agent-core/runtime/context/normalize.js +21 -1
  68. package/dist/agent-core/runtime/context/projection.js +35 -1
  69. package/dist/agent-core/runtime/context/request-budget.js +5 -2
  70. package/dist/agent-core/runtime/context/restore.d.ts +5 -0
  71. package/dist/agent-core/runtime/context/restore.js +47 -4
  72. package/dist/agent-core/runtime/context/usage.js +2 -3
  73. package/dist/agent-core/runtime/contracts.d.ts +2 -0
  74. package/dist/agent-core/runtime/execution.js +5 -2
  75. package/dist/agent-core/sdk/agent.js +177 -40
  76. package/dist/agent-core/sdk/extension-agents.d.ts +9 -0
  77. package/dist/agent-core/sdk/extension-agents.js +51 -0
  78. package/dist/agent-core/sdk/extension-models.d.ts +13 -0
  79. package/dist/agent-core/sdk/extension-models.js +128 -0
  80. package/dist/agent-core/sdk/extension-session.d.ts +30 -0
  81. package/dist/agent-core/sdk/extension-session.js +103 -0
  82. package/dist/agent-core/sdk/extension-tools.d.ts +16 -0
  83. package/dist/agent-core/sdk/extension-tools.js +126 -0
  84. package/dist/agent-core/sdk/session.d.ts +26 -2
  85. package/dist/agent-core/sdk/session.js +1215 -506
  86. package/dist/agent-core/sdk/types.d.ts +3 -0
  87. package/dist/agent-core/session/context-gc-checkpoint.js +1 -1
  88. package/dist/agent-core/session/jsonl-store.d.ts +28 -8
  89. package/dist/agent-core/session/jsonl-store.js +124 -84
  90. package/dist/agent-core/session/types.d.ts +4 -1
  91. package/dist/agent-core/storage/file-lock.d.ts +4 -0
  92. package/dist/agent-core/storage/file-lock.js +83 -0
  93. package/dist/agent-core/subagent/contracts.d.ts +11 -1
  94. package/dist/agent-core/subagent/scheduler.d.ts +8 -0
  95. package/dist/agent-core/subagent/session-controller.d.ts +22 -1
  96. package/dist/agent-core/subagent/session-controller.js +355 -98
  97. package/dist/agent-core/tools/agent/agent-start.d.ts +1 -1
  98. package/dist/agent-core/tools/code-mode/cells.js +5 -15
  99. package/dist/agent-core/tools/code-mode/dispatch.d.ts +1 -0
  100. package/dist/agent-core/tools/code-mode/dispatch.js +13 -1
  101. package/dist/agent-core/tools/code-mode/index.js +1 -2
  102. package/dist/agent-core/tools/code-mode/observations.d.ts +5 -0
  103. package/dist/agent-core/tools/code-mode/observations.js +16 -5
  104. package/dist/agent-core/tools/code-mode/quickjs-engine.js +10 -5
  105. package/dist/agent-core/tools/execution.d.ts +11 -2
  106. package/dist/agent-core/tools/execution.js +58 -21
  107. package/dist/agent-core/tools/input.d.ts +2 -0
  108. package/dist/agent-core/tools/input.js +36 -0
  109. package/dist/agent-core/tools/local/apply-patch.js +21 -17
  110. package/dist/agent-core/tools/local/exec.d.ts +4 -4
  111. package/dist/agent-core/tools/local/exec.js +234 -62
  112. package/dist/agent-core/tools/local/registry.d.ts +6 -0
  113. package/dist/agent-core/tools/local/registry.js +60 -9
  114. package/dist/agent-core/tools/mcp/catalog.d.ts +6 -2
  115. package/dist/agent-core/tools/mcp/catalog.js +4 -4
  116. package/dist/agent-core/tools/mcp/history.js +2 -2
  117. package/dist/agent-core/tools/mcp/tool-search.d.ts +3 -1
  118. package/dist/agent-core/tools/mcp/tool-search.js +2 -2
  119. package/dist/agent-core/tools/output-projection.d.ts +3 -1
  120. package/dist/agent-core/tools/output-projection.js +11 -2
  121. package/dist/agent-core/tools/runtime-context.d.ts +1 -0
  122. package/dist/agent-core/tools/runtime.d.ts +3 -1
  123. package/dist/agent-core/tools/runtime.js +22 -7
  124. package/dist/agent-core/tools/scheduler.d.ts +2 -0
  125. package/dist/agent-core/tools/scheduler.js +43 -6
  126. package/dist/index.d.ts +21 -0
  127. package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +5 -2
  128. package/package.json +1 -1
@@ -4,9 +4,14 @@ import type { EnvironmentContextState } from './contracts.js';
4
4
  import { type ContextProjection } from './projection.js';
5
5
  export declare function restoreMessages(store: JsonlSessionStore, sessionId: string, records: readonly import('../../session/jsonl-store.js').SessionRecord[], options: {
6
6
  includeImages: boolean;
7
+ strictImages?: boolean;
7
8
  currentTurnId?: string;
8
9
  projection?: ContextProjection;
9
10
  }): Promise<{
11
+ toolOutcomes: WeakMap<ModelMessage, {
12
+ isError: boolean;
13
+ result?: unknown;
14
+ }>;
10
15
  environmentState: EnvironmentContextState | undefined;
11
16
  userMessages: string[];
12
17
  messages: ModelMessage[];
@@ -2,9 +2,11 @@ import { MarAgentError } from '../../error.js';
2
2
  import { compactSummaryFromCheckpoint, replacementHistoryFromCheckpoint } from '../../session/jsonl-store.js';
3
3
  import { compactedHistoryMessages, continuationMessage } from '../compaction/history.js';
4
4
  import { environmentContextUpdate, restoreEnvironmentState } from './environment.js';
5
- import { buildContextProjection } from './projection.js';
5
+ import { buildContextProjection, restoreModelContext } from './projection.js';
6
6
  export async function restoreMessages(store, sessionId, records, options) {
7
7
  const messages = [];
8
+ const outcomes = new Map();
9
+ const toolOutcomes = new WeakMap();
8
10
  const modelProjection = new Map((options.projection ??
9
11
  buildContextProjection(records, (await store.getSession(sessionId)).workspace)).entries.map((entry) => [entry.record.recordId, entry.message]));
10
12
  let userMessages = [];
@@ -13,15 +15,30 @@ export async function restoreMessages(store, sessionId, records, options) {
13
15
  let environmentState;
14
16
  for (const record of records) {
15
17
  const payload = record.payload;
18
+ if ((record.type === 'tool.completed' || record.type === 'tool.failed') &&
19
+ typeof payload.callId === 'string') {
20
+ const result = record.type === 'tool.failed' ? payload.message : payload.data;
21
+ outcomes.set(JSON.stringify([record.executionId, payload.callId]), {
22
+ isError: record.type === 'tool.failed',
23
+ ...(result === undefined ? {} : { result: structuredClone(result) })
24
+ });
25
+ }
16
26
  const compactSummary = compactSummaryFromCheckpoint(record);
17
- if (record.type === 'turn.user' && typeof payload.prompt === 'string') {
27
+ if ((record.type === 'turn.user' || record.type === 'extension.input') &&
28
+ typeof payload.prompt === 'string') {
18
29
  userMessages.push(payload.prompt);
19
30
  let images = [];
20
31
  if (options.includeImages)
21
32
  try {
22
33
  images = await store.readInputImages(sessionId, payload.images);
34
+ if (options.strictImages &&
35
+ payload.images !== undefined &&
36
+ (!Array.isArray(payload.images) || images.length !== payload.images.length))
37
+ throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Session input images cannot be fully restored.');
23
38
  }
24
39
  catch (error) {
40
+ if (options.strictImages)
41
+ throw error;
25
42
  if (!(error instanceof MarAgentError) || error.code !== 'MAR_AGENT_SESSION_CORRUPT')
26
43
  throw error;
27
44
  ignoredImages += Array.isArray(payload.images) ? payload.images.length : 1;
@@ -30,9 +47,20 @@ export async function restoreMessages(store, sessionId, records, options) {
30
47
  currentTurnUserIndex = messages.length;
31
48
  messages.push({
32
49
  role: 'user',
33
- content: payload.prompt,
50
+ content: typeof payload.modelText === 'string' ? payload.modelText : payload.prompt,
34
51
  ...(images.length ? { images } : {})
35
52
  });
53
+ const context = payload.modelContext ?? payload.context;
54
+ if (payload.contextRecords !== true && Array.isArray(context))
55
+ for (const text of context)
56
+ if (typeof text === 'string')
57
+ messages.push({ role: 'user', content: text });
58
+ }
59
+ else if (record.type === 'input.context') {
60
+ if (Array.isArray(payload.modelContext))
61
+ for (const text of payload.modelContext)
62
+ if (typeof text === 'string')
63
+ messages.push({ role: 'user', content: text });
36
64
  }
37
65
  else if ((record.type === 'message.completed' || record.type === 'plan.completed') &&
38
66
  typeof payload.text === 'string')
@@ -49,6 +77,14 @@ export async function restoreMessages(store, sessionId, records, options) {
49
77
  messages.push(...(history
50
78
  ? compactedHistoryMessages(history)
51
79
  : [{ role: 'user', content: continuationMessage(compactSummary) }]));
80
+ if (Array.isArray(payload.retainedModelContext))
81
+ for (const item of payload.retainedModelContext) {
82
+ if (!item || typeof item !== 'object' || item.role !== 'user')
83
+ continue;
84
+ const message = restoreModelContext(item);
85
+ if (message)
86
+ messages.push(message);
87
+ }
52
88
  }
53
89
  else if (record.type === 'context.environment') {
54
90
  const current = restoreEnvironmentState(payload);
@@ -61,12 +97,19 @@ export async function restoreMessages(store, sessionId, records, options) {
61
97
  }
62
98
  else if (record.type === 'model.context') {
63
99
  const message = modelProjection.get(record.recordId);
64
- if (message)
100
+ if (message) {
65
101
  messages.push(message);
102
+ if (message.role === 'tool' && message.callId !== undefined) {
103
+ const outcome = outcomes.get(JSON.stringify([record.executionId, message.callId]));
104
+ if (outcome)
105
+ toolOutcomes.set(message, outcome);
106
+ }
107
+ }
66
108
  }
67
109
  }
68
110
  const completedToolCalls = new Set(messages.filter((message) => message.role === 'tool').map((message) => message.callId));
69
111
  return {
112
+ toolOutcomes,
70
113
  environmentState,
71
114
  userMessages,
72
115
  messages: messages.filter((message) => {
@@ -1,8 +1,7 @@
1
1
  import { isDeepStrictEqual } from 'node:util';
2
2
  import { marAgentReasoningEffortSchema } from '../../model/configuration.js';
3
3
  import { compactSummaryFromCheckpoint } from '../../session/jsonl-store.js';
4
- import { countModelTokens } from '../token-budget.js';
5
- import { stableJson } from './request-budget.js';
4
+ import { estimatedRequestTokens } from './request-budget.js';
6
5
  export function latestModelUsage(records, selectedModelId) {
7
6
  let usage;
8
7
  const toolCallIds = new Set();
@@ -104,7 +103,7 @@ export function shouldUseNativeCompaction(input) {
104
103
  const historyDelta = appendOnlyUsageDelta(input.latestUsage, input.historyMessages);
105
104
  if (historyDelta === undefined)
106
105
  return false;
107
- const appendedTokens = countModelTokens(stableJson([...historyDelta, ...input.appendedMessages]));
106
+ const appendedTokens = estimatedRequestTokens('', [...historyDelta, ...input.appendedMessages], []);
108
107
  return (input.latestUsage.contextInputTokens + input.latestUsage.outputTokens + appendedTokens <=
109
108
  input.contextWindowTokens);
110
109
  }
@@ -25,6 +25,8 @@ export interface ExecutionResult {
25
25
  executionId: string;
26
26
  status: 'completed' | 'failed' | 'cancelled';
27
27
  finalAnswer: string;
28
+ /** The input hook answered without admitting input; no model Turn was requested. */
29
+ inputHandled?: boolean;
28
30
  errorCode?: string;
29
31
  errorMessage?: string;
30
32
  }
@@ -302,6 +302,8 @@ export class ExecutionLoop {
302
302
  const message = {
303
303
  role: 'tool',
304
304
  callId: result.call.callId,
305
+ name: result.call.name,
306
+ isError: result.failed,
305
307
  content: result.content,
306
308
  ...(result.calls ? { calls: result.calls } : {}),
307
309
  ...(result.observations ? { observations: result.observations } : {}),
@@ -312,6 +314,8 @@ export class ExecutionLoop {
312
314
  await input.appendContext({
313
315
  role: message.role,
314
316
  callId: result.call.callId,
317
+ name: result.call.name,
318
+ isError: result.failed,
315
319
  content: message.content,
316
320
  ...(message.calls ? { calls: message.calls } : {}),
317
321
  ...(message.observations ? { observations: message.observations } : {}),
@@ -351,8 +355,7 @@ export class ExecutionLoop {
351
355
  throw aborted ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.');
352
356
  }
353
357
  input.rolloutBudget?.assertAvailable();
354
- if (input.mailbox.hasPending()) {
355
- await input.mailbox.append();
358
+ if (input.mailbox.hasPending() && (await input.mailbox.append())) {
356
359
  this.finalAnswer = '';
357
360
  continue;
358
361
  }
@@ -1,7 +1,10 @@
1
1
  import { addModelCredentialProviders, closeModelAdapters, resolveAgentModels } from './model-runtime.js';
2
2
  import { createSessionOpener } from './session.js';
3
3
  import { MarAgentError } from '../error.js';
4
+ import { ExtensionManager } from '../extensions/manager.js';
5
+ import { createExtensionModels } from './extension-models.js';
4
6
  import { negotiateHost } from '../host/contracts.js';
7
+ import { toolCallers } from '../tools/callers.js';
5
8
  import { AnthropicMessagesAdapter } from '../model/anthropic-messages.js';
6
9
  import { OpenAiResponsesAdapter } from '../model/openai-responses.js';
7
10
  import { resolveRolloutBudgetOptions } from '../runtime/rollout-budget.js';
@@ -29,6 +32,16 @@ export async function createMarAgentWithAdapter(options) {
29
32
  const sessionRuntimes = new Map();
30
33
  const sessionWriterOwner = {};
31
34
  let disposed = false;
35
+ let disposal;
36
+ const sessionLifecycles = new Set();
37
+ const trackSessionLifecycle = (operation) => {
38
+ if (disposed)
39
+ return Promise.reject(new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.'));
40
+ const pending = operation();
41
+ sessionLifecycles.add(pending);
42
+ void pending.finally(() => sessionLifecycles.delete(pending)).catch(() => undefined);
43
+ return pending;
44
+ };
32
45
  let reconfiguring = false;
33
46
  const createAdapter = (modelId, configuredModels = models) => {
34
47
  const configuration = configuredModels.get(modelId);
@@ -40,6 +53,85 @@ export async function createMarAgentWithAdapter(options) {
40
53
  : new AnthropicMessagesAdapter(configuration)));
41
54
  };
42
55
  const activeSessionIds = () => new Set([...active.keys()].map((key) => key.slice(0, key.indexOf(':'))));
56
+ const extensions = new ExtensionManager({
57
+ ...(options.host.extensionPorts === undefined
58
+ ? {}
59
+ : { hostPorts: options.host.extensionPorts }),
60
+ extensions: options.runtimeExtensions ?? [],
61
+ capabilities: [
62
+ 'tools',
63
+ 'prompts',
64
+ 'commands',
65
+ 'clock',
66
+ 'services',
67
+ 'hooks',
68
+ 'observability',
69
+ 'models',
70
+ 'data.state',
71
+ 'data.checkpoint',
72
+ 'data.store',
73
+ 'session',
74
+ 'agents',
75
+ 'settings',
76
+ ...(options.host.extensionPorts?.environment ? ['environment'] : []),
77
+ ...(options.host.extensionPorts?.files ? ['files'] : []),
78
+ ...(options.host.extensionPorts?.network ? ['network'] : []),
79
+ ...(options.host.extensionPorts?.mcp ? ['mcp'] : []),
80
+ ...(options.host.extensionPorts?.processes ? ['processes'] : [])
81
+ ],
82
+ models: createExtensionModels(() => ({ models, defaultModelId }), createAdapter, (snapshot, call) => {
83
+ const snapshots = sessionRuntimes.get(call.sessionId)?.requestSnapshots;
84
+ if (!snapshots)
85
+ throw new MarAgentError('MAR_AGENT_MODEL_SNAPSHOT_INVALID', 'Request snapshot is unavailable.');
86
+ return snapshots.resolve(snapshot);
87
+ }),
88
+ sessionBackend: (id) => sessionRuntimes.get(id)?.sessionBackend,
89
+ agentsBackend: (id) => sessionRuntimes.get(id)?.agentsBackend,
90
+ scheduleTool: (id) => (definition, run) => {
91
+ const schedule = sessionRuntimes.get(id)?.scheduleTool;
92
+ if (!schedule)
93
+ throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'Session tool scheduler is unavailable.');
94
+ return schedule(definition, run);
95
+ },
96
+ baseTools: (id, context) => {
97
+ const execution = sessionRuntimes.get(id)?.toolSources;
98
+ if (context?.executionId && execution?.executionId === context.executionId)
99
+ return execution.bindings;
100
+ return (options.host.extensionPorts?.tools?.catalog(id, context?.executionId) ?? []).map((source) => ({
101
+ definition: {
102
+ ...source.definition,
103
+ allowedCallers: [
104
+ ...toolCallers({
105
+ ...source.definition,
106
+ allowedCallers: source.definition.allowedCallers ??
107
+ (source.mcp ? ['direct', 'programmatic'] : ['direct'])
108
+ })
109
+ ]
110
+ },
111
+ source: source.source,
112
+ ...(source.mcp === undefined ? {} : { mcp: source.mcp }),
113
+ ...(source.available === undefined ? {} : { available: source.available.bind(source) }),
114
+ invoke: source.execute.bind(source)
115
+ }));
116
+ },
117
+ allowTool: (id, name, definition) => {
118
+ const runtime = sessionRuntimes.get(id);
119
+ return !(runtime?.sourceType === 'subagent' && definition?.agentScope === 'ROOT_ONLY');
120
+ },
121
+ checkpoint: {
122
+ read: async (extensionId, key, call) => {
123
+ call.signal.throwIfAborted();
124
+ return store.readExtensionCheckpoint(call.sessionId, extensionId, key);
125
+ },
126
+ write: (extensionId, key, value, expectedRevision, call) => store.writeExtensionCheckpoint(call.sessionId, extensionId, key, value, expectedRevision, {
127
+ owner: sessionWriterOwner,
128
+ signal: call.signal,
129
+ ...(call.executionId ? { executionId: call.executionId } : {}),
130
+ ...(call.turnId ? { turnId: call.turnId } : {})
131
+ })
132
+ },
133
+ busyExcept: (sessionId) => [...activeSessionIds()].some((id) => id !== sessionId)
134
+ });
43
135
  const activeAdaptersExcept = (sessionId) => {
44
136
  const activeAdapters = new Set();
45
137
  for (const [candidateSessionId, runtime] of sessionRuntimes)
@@ -61,6 +153,7 @@ export async function createMarAgentWithAdapter(options) {
61
153
  description,
62
154
  settings,
63
155
  store,
156
+ extensions,
64
157
  registry: {
65
158
  active,
66
159
  activeExecutionAdapters,
@@ -81,21 +174,40 @@ export async function createMarAgentWithAdapter(options) {
81
174
  }
82
175
  });
83
176
  return {
177
+ extensionDiagnostics: () => extensions.diagnostics(),
84
178
  createSession: async (input) => openSession((await store.createSession(input)).id, input.workspace),
85
179
  openSession: (id) => openSession(id),
86
180
  listSessions: (input) => store.list(input),
87
181
  getSession: (id) => store.getSession(id),
88
182
  setSessionTitle: (id, title, input) => store.updateTitle(id, title, { ...input, automatic: input?.automatic ?? false }, sessionWriterOwner),
89
- clearSession: async (id) => {
183
+ clearSession: (id) => trackSessionLifecycle(async () => {
90
184
  const runtime = sessionRuntimes.get(id);
91
185
  if ([...active.keys()].some((key) => key.startsWith(`${id}:`)))
92
186
  throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'A session cannot be cleared while an execution is active.');
93
- await runtime?.subagents?.dispose();
94
- await closeModelAdapters(runtime === undefined ? [] : [...runtime.adapters.values()]);
95
- sessionRuntimes.delete(id);
96
- await options.host.disposeSessionResources?.(id);
97
- await store.clearSession(id);
98
- },
187
+ await extensions.endSession(id, 'clear');
188
+ extensions.retireSession(id);
189
+ await completeAgentCleanup([
190
+ () => extensions.clearSession(id),
191
+ () => runtime?.inputs?.retire(),
192
+ () => runtime?.subagents?.dispose(),
193
+ () => closeModelAdapters(runtime === undefined ? [] : [...runtime.adapters.values()]),
194
+ () => {
195
+ // Existing SDK Session handles share this runtime and remain usable after clear.
196
+ if (runtime) {
197
+ runtime.adapters.clear();
198
+ runtime.adapterGeneration = modelGeneration;
199
+ delete runtime.todo;
200
+ delete runtime.subagents;
201
+ delete runtime.subagentsOpening;
202
+ delete runtime.contextualWorldState;
203
+ delete runtime.inputs;
204
+ delete runtime.requestSnapshots;
205
+ }
206
+ },
207
+ () => options.host.disposeSessionResources?.(id),
208
+ () => store.clearSession(id)
209
+ ]);
210
+ }),
99
211
  setSystemInstruction: (systemInstruction) => {
100
212
  if (disposed)
101
213
  throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
@@ -169,52 +281,77 @@ export async function createMarAgentWithAdapter(options) {
169
281
  reconfiguring = false;
170
282
  }
171
283
  },
172
- deleteSession: async (id) => {
284
+ deleteSession: (id) => trackSessionLifecycle(async () => {
173
285
  const sessionIds = await store.sessionTree(id);
174
286
  const sessionIdSet = new Set(sessionIds);
175
287
  const executionKeys = [...active.keys()].filter((key) => sessionIdSet.has(key.slice(0, key.indexOf(':'))));
176
288
  for (const key of executionKeys)
177
289
  active.get(key)?.abort();
290
+ await Promise.all(sessionIds.map((sessionId) => extensions.endSession(sessionId, 'other', false)));
291
+ for (const sessionId of sessionIds)
292
+ extensions.retireSession(sessionId);
178
293
  const runtimes = sessionIds.flatMap((sessionId) => {
179
294
  const runtime = sessionRuntimes.get(sessionId);
180
295
  sessionRuntimes.delete(sessionId);
181
296
  return runtime === undefined ? [] : [runtime];
182
297
  });
183
- await Promise.all(runtimes.map((runtime) => runtime.subagents?.dispose()));
184
- await Promise.allSettled(executionKeys.flatMap((key) => {
185
- const result = executionResults.get(key);
186
- return result === undefined ? [] : [result];
187
- }));
188
- await closeModelAdapters(runtimes.flatMap((runtime) => [...runtime.adapters.values()]));
189
- await Promise.all(sessionIds.map((sessionId) => options.host.disposeSessionResources?.(sessionId)));
190
- await store.delete(id);
191
- },
192
- dispose: async () => {
193
- if (disposed)
194
- return;
298
+ await completeAgentCleanup([
299
+ ...sessionIds.map((sessionId) => () => extensions.clearSession(sessionId)),
300
+ ...runtimes.map((runtime) => () => runtime.inputs?.retire()),
301
+ ...runtimes.map((runtime) => () => runtime.subagents?.dispose()),
302
+ () => Promise.allSettled(executionKeys.flatMap((key) => {
303
+ const result = executionResults.get(key);
304
+ return result === undefined ? [] : [result];
305
+ })),
306
+ () => closeModelAdapters(runtimes.flatMap((runtime) => [...runtime.adapters.values()])),
307
+ ...sessionIds.map((sessionId) => () => options.host.disposeSessionResources?.(sessionId)),
308
+ () => store.delete(id)
309
+ ]);
310
+ }),
311
+ dispose: () => {
312
+ if (disposal)
313
+ return disposal;
195
314
  disposed = true;
196
315
  for (const controller of active.values())
197
316
  controller.abort();
198
- await Promise.all([...sessionRuntimes.values()].map((runtime) => runtime.subagents?.dispose().catch(() => undefined)));
199
- await Promise.allSettled([...executionResults.values()]);
200
- await closeModelAdapters([...sessionRuntimes.values()].flatMap((runtime) => [...runtime.adapters.values()]));
201
- let disposalError;
202
- const providerResults = await Promise.allSettled([...credentialProviders].map((provider) => provider.close()));
203
- disposalError = providerResults.find((result) => result.status === 'rejected')?.reason;
204
- try {
205
- await options.host.dispose();
206
- }
207
- catch (error) {
208
- disposalError ??= error;
209
- }
210
- try {
211
- await store.close();
212
- }
213
- catch (error) {
214
- disposalError ??= error;
215
- }
216
- if (disposalError !== undefined)
217
- throw disposalError;
317
+ // Explicit end observers finish in a separate lifecycle frame before Session retirement.
318
+ const extensionDisposal = extensions.dispose();
319
+ void extensionDisposal.catch(() => undefined);
320
+ const runtimes = [...sessionRuntimes.values()];
321
+ disposal = completeAgentCleanup([
322
+ async () => {
323
+ const results = await Promise.allSettled([...sessionLifecycles]);
324
+ const failure = results.find((result) => result.status === 'rejected');
325
+ if (failure?.status === 'rejected')
326
+ throw failure.reason;
327
+ },
328
+ () => extensionDisposal,
329
+ ...runtimes.map((runtime) => () => runtime.inputs?.retire()),
330
+ ...runtimes.map((runtime) => () => runtime.subagents?.dispose()),
331
+ () => Promise.allSettled([...executionResults.values()]),
332
+ () => closeModelAdapters(runtimes.flatMap((runtime) => [...runtime.adapters.values()])),
333
+ ...[...credentialProviders].map((provider) => () => provider.close()),
334
+ () => options.host.dispose(),
335
+ () => store.close()
336
+ ]);
337
+ return disposal;
218
338
  }
219
339
  };
220
340
  }
341
+ /** Finish independent cleanup steps and preserve the first authoritative failure. */
342
+ async function completeAgentCleanup(steps) {
343
+ let failed = false;
344
+ let failure;
345
+ for (const step of steps) {
346
+ try {
347
+ await step();
348
+ }
349
+ catch (error) {
350
+ if (!failed)
351
+ failure = error;
352
+ failed = true;
353
+ }
354
+ }
355
+ if (failed)
356
+ throw failure;
357
+ }
@@ -0,0 +1,9 @@
1
+ import type { CoreAgentsBackend } from '../extensions/agents.js';
2
+ import type { CoreOperationContext } from '../extensions/operation.js';
3
+ import type { SubagentParentOperation, SubagentSessionController } from '../subagent/session-controller.js';
4
+ /** Uses the Session's sole task controller, including its durable children and queue. */
5
+ export declare function createExtensionAgentsBackend(input: {
6
+ readonly controller: () => Promise<SubagentSessionController>;
7
+ readonly existingController: () => SubagentSessionController | undefined;
8
+ readonly parent: (controller: SubagentSessionController, call: CoreOperationContext) => SubagentParentOperation;
9
+ }): CoreAgentsBackend;
@@ -0,0 +1,51 @@
1
+ import { MarAgentError } from '../error.js';
2
+ /** Uses the Session's sole task controller, including its durable children and queue. */
3
+ export function createExtensionAgentsBackend(input) {
4
+ const withController = async (call, run) => {
5
+ call.signal.throwIfAborted();
6
+ const controller = await input.controller();
7
+ call.signal.throwIfAborted();
8
+ return controller.withOperation(input.parent(controller, call), () => run(controller));
9
+ };
10
+ return {
11
+ own: (owner) => async () => {
12
+ await input.existingController()?.cancelOwned(owner);
13
+ },
14
+ spawn: (task, call) => withController(call, (controller) => controller.run({
15
+ description: task.description ?? 'Extension agent',
16
+ prompt: task.prompt,
17
+ role: task.role ?? 'general-purpose',
18
+ background: task.background ?? true,
19
+ ...(task.modelId === undefined ? {} : { modelId: task.modelId }),
20
+ ...(task.reasoningEffort === undefined ? {} : { reasoningEffort: task.reasoningEffort })
21
+ }, {
22
+ signal: call.signal,
23
+ ...(task.turnSignal === undefined ? {} : { turnSignal: task.turnSignal }),
24
+ ...(task.waitFor === undefined ? {} : { waitFor: task.waitFor }),
25
+ onCreated: (created) => {
26
+ if (created.status === 'failed')
27
+ throw new MarAgentError(created.errorCode ?? 'MAR_AGENT_SUBAGENT_FAILED', created.errorMessage ?? 'Agent could not start.');
28
+ }
29
+ })),
30
+ list: (call) => withController(call, async (controller) => controller.inspectAll()),
31
+ inspect: (target, call) => withController(call, (controller) => controller.inspect(target.agentId)),
32
+ wait: (target, call) => withController(call, (controller) => {
33
+ const options = {
34
+ waitMs: target.waitMs ?? 30_000,
35
+ ...(target.untilSettled === undefined ? {} : { untilSettled: target.untilSettled }),
36
+ ...(target.waitFor === undefined ? {} : { waitFor: target.waitFor }),
37
+ signal: call.signal
38
+ };
39
+ if (target.untilSettled && target.agentId === undefined)
40
+ throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'An unbounded wait requires an agent id.');
41
+ return target.agentId === undefined
42
+ ? controller.outputAny(options)
43
+ : controller.output(target.agentId, options);
44
+ }),
45
+ message: (target, call) => withController(call, (controller) => controller.message(target.agentId, target.message, {
46
+ ...(target.delivery === undefined ? {} : { delivery: target.delivery }),
47
+ signal: call.signal
48
+ })),
49
+ cancel: (target, call) => withController(call, (controller) => controller.cancel(target.agentId))
50
+ };
51
+ }
@@ -0,0 +1,13 @@
1
+ import type { CoreModelsBackend } from '../extensions/models.js';
2
+ import type { CoreOperationContext } from '../extensions/operation.js';
3
+ import type { CoreModelRequest, CoreRequestSnapshot } from '../extensions/model-request.js';
4
+ import type { ModelAdapter, ModelRequest } from '../model/contracts.js';
5
+ import type { ResolvedModelConfiguration } from './model-runtime.js';
6
+ export declare function createExtensionModels(state: () => {
7
+ models: ReadonlyMap<string, ResolvedModelConfiguration>;
8
+ defaultModelId: string;
9
+ }, createAdapter: (modelId: string, models: ReadonlyMap<string, ResolvedModelConfiguration>) => ModelAdapter, resolveSnapshot?: (snapshot: CoreRequestSnapshot, call: CoreOperationContext) => {
10
+ modelId: string;
11
+ request: ModelRequest;
12
+ view: CoreModelRequest;
13
+ }): CoreModelsBackend;