@myagentroam/agent 0.9.82 → 0.9.84

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.
@@ -1,4 +1,13 @@
1
1
  import type { MarAgentModelConfiguration, MarAgentReasoningEffort, ModelCredentialAcquireReason } from './configuration.js';
2
+ export interface ModelContinuationCheckpoint {
3
+ readonly version: 1;
4
+ readonly protocol: 'OPENAI_RESPONSES';
5
+ readonly configurationHash: string;
6
+ readonly responseId: string;
7
+ readonly requestPropertiesHash: string;
8
+ readonly inputPrefixLength: number;
9
+ readonly inputPrefixHash: string;
10
+ }
2
11
  export interface ClientToolDefinition {
3
12
  name: string;
4
13
  description: string;
@@ -11,7 +20,7 @@ export interface ClientToolDefinition {
11
20
  export interface ModelMessage {
12
21
  role: 'user' | 'assistant' | 'tool' | 'tool_call' | 'provider';
13
22
  content: string;
14
- contextKind?: 'environment';
23
+ contextKind?: 'environment' | 'private';
15
24
  callId?: string;
16
25
  name?: string;
17
26
  arguments?: unknown;
@@ -146,11 +155,18 @@ export interface ModelAdapter {
146
155
  readonly configuration: MarAgentModelConfiguration;
147
156
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
148
157
  createTurnSession?(): ModelTurnSession;
158
+ /**
159
+ * Prepares an equivalent model rebind without mutating live state.
160
+ * The returned commit callback must be synchronous and non-throwing.
161
+ */
162
+ prepareReconfigure?(configuration: MarAgentModelConfiguration): (() => void) | undefined;
163
+ restoreContinuation?(checkpoint: ModelContinuationCheckpoint): boolean;
149
164
  close?(): Promise<void>;
150
165
  }
151
166
  export interface ModelTurnSession {
152
167
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
153
168
  /** Drops incremental response state after a local context replacement. */
154
169
  resetContinuation?(): void;
170
+ continuationCheckpoint?(): ModelContinuationCheckpoint | undefined;
155
171
  close(): Promise<void>;
156
172
  }
@@ -1,7 +1,7 @@
1
1
  import { MarAgentError } from '../error.js';
2
2
  import { type MarAgentModelConfiguration, type ModelCredentialAcquireReason } from './configuration.js';
3
3
  import WebSocket from 'ws';
4
- import type { ModelAdapter, ModelAttemptDiagnostic, ModelEvent, ModelRequest, ModelTurnSession } from './contracts.js';
4
+ import type { ModelAdapter, ModelAttemptDiagnostic, ModelContinuationCheckpoint, ModelEvent, ModelRequest, ModelTurnSession } from './contracts.js';
5
5
  export declare const OPENAI_RESPONSES_EARLY_EOF_REASON = "OPENAI_RESPONSES_EARLY_EOF";
6
6
  export declare const OPENAI_RESPONSES_WEBSOCKET_CONNECTION_LIMIT_REASON = "OPENAI_RESPONSES_WEBSOCKET_CONNECTION_LIMIT";
7
7
  type ResponsesRequestBody = Readonly<Record<string, unknown>> & {
@@ -11,6 +11,7 @@ interface ResponsesSessionState {
11
11
  socket?: WebSocket;
12
12
  endpoint?: string;
13
13
  credentialRevision?: number;
14
+ credentialFingerprint?: string;
14
15
  connectionId?: string;
15
16
  proxyType?: 'DIRECT' | 'SOCKS5';
16
17
  lastRequest?: ResponsesRequestBody;
@@ -18,18 +19,20 @@ interface ResponsesSessionState {
18
19
  readonly responseId: string;
19
20
  readonly outputItems: readonly Record<string, unknown>[];
20
21
  };
22
+ restoredContinuation?: ModelContinuationCheckpoint;
21
23
  }
22
24
  interface PreparedResponsesRequest {
23
25
  readonly fullBody: ResponsesRequestBody;
24
26
  readonly body: ResponsesRequestBody;
25
27
  readonly continuation: boolean;
26
- readonly requiresExistingSocket: boolean;
27
28
  }
28
29
  export declare class OpenAiResponsesAdapter implements ModelAdapter {
29
30
  #private;
30
- readonly configuration: MarAgentModelConfiguration;
31
31
  readonly prefixIdentity: `${string}-${string}-${string}-${string}-${string}`;
32
32
  constructor(configuration: MarAgentModelConfiguration);
33
+ get configuration(): MarAgentModelConfiguration;
34
+ prepareReconfigure(configuration: MarAgentModelConfiguration): (() => void) | undefined;
35
+ restoreContinuation(checkpoint: ModelContinuationCheckpoint): boolean;
33
36
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
34
37
  createTurnSession(): ModelTurnSession;
35
38
  close(): Promise<void>;
@@ -47,6 +50,7 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
47
50
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
48
51
  close(): Promise<void>;
49
52
  resetContinuation(): void;
53
+ continuationCheckpoint(): ModelContinuationCheckpoint | undefined;
50
54
  prepareRequest(request: ModelRequest): PreparedResponsesRequest;
51
55
  fullRequestBody(): ResponsesRequestBody;
52
56
  currentTurnState(): string | undefined;
@@ -61,7 +65,6 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
61
65
  readonly reason: ModelCredentialAcquireReason;
62
66
  readonly signal: AbortSignal;
63
67
  readonly continuation: boolean;
64
- readonly requiresExistingSocket: boolean;
65
68
  readonly credentialAcquired: (revision: number) => void;
66
69
  }): Promise<WebSocket>;
67
70
  private connectWebSocket;
@@ -13,13 +13,33 @@ const X_CODEX_TURN_STATE = 'x-codex-turn-state';
13
13
  const RESPONSES_LITE_HEADER = 'x-openai-internal-codex-responses-lite';
14
14
  const RESPONSES_LITE_METADATA = 'ws_request_header_x_openai_internal_codex_responses_lite';
15
15
  export class OpenAiResponsesAdapter {
16
- configuration;
17
16
  prefixIdentity = randomUUID();
18
17
  #cachedState = {};
19
18
  #cacheLeased = false;
20
19
  #closed = false;
20
+ #configuration;
21
21
  constructor(configuration) {
22
- this.configuration = configuration;
22
+ this.#configuration = configuration;
23
+ }
24
+ get configuration() {
25
+ return this.#configuration;
26
+ }
27
+ prepareReconfigure(configuration) {
28
+ if (!sameResponsesAdapterConfiguration(this.#configuration, configuration))
29
+ return undefined;
30
+ return () => {
31
+ this.#configuration = configuration;
32
+ };
33
+ }
34
+ restoreContinuation(checkpoint) {
35
+ if (this.#cacheLeased ||
36
+ this.#cachedState.lastRequest !== undefined ||
37
+ this.#cachedState.lastResponse !== undefined ||
38
+ checkpoint.protocol !== 'OPENAI_RESPONSES' ||
39
+ checkpoint.configurationHash !== responsesConfigurationHash(this.#configuration))
40
+ return false;
41
+ this.#cachedState.restoredContinuation = checkpoint;
42
+ return true;
23
43
  }
24
44
  async *start(request, signal) {
25
45
  const turn = new OpenAiResponsesTurnSession(this, {}, false, true);
@@ -53,14 +73,13 @@ export class OpenAiResponsesAdapter {
53
73
  return;
54
74
  }
55
75
  if (state.socket !== undefined && state.socket.readyState !== WebSocket.OPEN)
56
- invalidateResponsesWebSocket(state, true);
76
+ invalidateResponsesWebSocket(state, false);
57
77
  this.#cachedState = state;
58
78
  this.#cacheLeased = false;
59
79
  }
60
80
  async *startTurn(request, signal, turn, prepared) {
61
81
  let useContinuation = prepared.continuation;
62
82
  let requestBody = prepared.body;
63
- let requiresExistingSocket = prepared.requiresExistingSocket;
64
83
  let credentialReason = 'REQUEST';
65
84
  let unauthorizedRetryUsed = false;
66
85
  let invalidContinuationFallbackUsed = false;
@@ -87,7 +106,7 @@ export class OpenAiResponsesAdapter {
87
106
  credentialReason: attemptCredentialReason
88
107
  });
89
108
  try {
90
- for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, requiresExistingSocket, turn)) {
109
+ for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn)) {
91
110
  if (!sawSemanticEvent && !isSemanticEvent(event)) {
92
111
  prelude.push(event);
93
112
  continue;
@@ -143,7 +162,6 @@ export class OpenAiResponsesAdapter {
143
162
  invalidContinuationFallbackUsed = true;
144
163
  useContinuation = false;
145
164
  requestBody = prepared.fullBody;
146
- requiresExistingSocket = false;
147
165
  turn.invalidateWebSocket();
148
166
  credentialReason = 'RECONNECT';
149
167
  await reportFailure(true, {
@@ -192,14 +210,13 @@ export class OpenAiResponsesAdapter {
192
210
  : 'REQUEST';
193
211
  useContinuation = false;
194
212
  requestBody = prepared.fullBody;
195
- requiresExistingSocket = false;
196
213
  if (error instanceof MarAgentError && error.details?.transport === 'WEBSOCKET')
197
214
  turn.invalidateWebSocket();
198
215
  await waitForModelRetry(retryAfter(error), attempt, signal, delayMs);
199
216
  }
200
217
  }
201
218
  }
202
- async *startAttempt(request, signal, body, continuation, credentialReason, requiresExistingSocket, turn) {
219
+ async *startAttempt(request, signal, body, continuation, credentialReason, turn) {
203
220
  let latestCredentialRevision;
204
221
  const fetchResponse = async (requestBody) => {
205
222
  const serializedBody = JSON.stringify(requestBody);
@@ -233,7 +250,6 @@ export class OpenAiResponsesAdapter {
233
250
  reason: credentialReason,
234
251
  signal,
235
252
  continuation,
236
- requiresExistingSocket,
237
253
  turn,
238
254
  credentialAcquired: (revision) => (latestCredentialRevision = revision)
239
255
  });
@@ -517,6 +533,15 @@ export class OpenAiResponsesAdapter {
517
533
  });
518
534
  }
519
535
  }
536
+ function sameResponsesAdapterConfiguration(current, next) {
537
+ const { apiKey: currentApiKey, credentialProvider: currentCredentialProvider, imageGeneration: _currentImageGeneration, ...currentTransport } = current;
538
+ const { apiKey: nextApiKey, credentialProvider: nextCredentialProvider, imageGeneration: _nextImageGeneration, ...nextTransport } = next;
539
+ const credentialsAreRebindable = (currentCredentialProvider !== undefined && nextCredentialProvider !== undefined) ||
540
+ (currentApiKey !== undefined &&
541
+ nextApiKey !== undefined &&
542
+ currentApiKey.reveal() === nextApiKey.reveal());
543
+ return credentialsAreRebindable && isDeepStrictEqual(currentTransport, nextTransport);
544
+ }
520
545
  class OpenAiResponsesTurnSession {
521
546
  adapter;
522
547
  state;
@@ -561,7 +586,7 @@ class OpenAiResponsesTurnSession {
561
586
  for await (const event of this.adapter.startTurn({
562
587
  ...request,
563
588
  onAttemptDiagnostic: async (diagnostic) => request.onAttemptDiagnostic?.({ ...diagnostic, prewarm: true })
564
- }, signal, this, { fullBody: warmBody, body: warmBody, continuation: false, requiresExistingSocket: false })) {
589
+ }, signal, this, { fullBody: warmBody, body: warmBody, continuation: false })) {
565
590
  if (event.type === 'response.started' && event.responseId !== 'unknown')
566
591
  warmId = event.responseId;
567
592
  if (event.type === 'completed')
@@ -607,6 +632,7 @@ class OpenAiResponsesTurnSession {
607
632
  (outputItems.length > 0 || !sawUnrepresentedOutput)) {
608
633
  this.state.lastRequest = prepared.fullBody;
609
634
  this.state.lastResponse = { responseId, outputItems };
635
+ delete this.state.restoredContinuation;
610
636
  }
611
637
  else
612
638
  clearResponsesContinuation(this.state);
@@ -629,17 +655,31 @@ class OpenAiResponsesTurnSession {
629
655
  resetContinuation() {
630
656
  clearResponsesContinuation(this.state);
631
657
  }
658
+ continuationCheckpoint() {
659
+ const request = this.state.lastRequest;
660
+ const response = this.state.lastResponse;
661
+ if (request === undefined || response === undefined)
662
+ return this.state.restoredContinuation;
663
+ const baseline = [...request.input, ...response.outputItems];
664
+ return {
665
+ version: 1,
666
+ protocol: 'OPENAI_RESPONSES',
667
+ configurationHash: responsesConfigurationHash(this.adapter.configuration),
668
+ responseId: response.responseId,
669
+ requestPropertiesHash: responsesRequestPropertiesHash(request),
670
+ inputPrefixLength: baseline.length,
671
+ inputPrefixHash: hashResponsesValue(baseline)
672
+ };
673
+ }
632
674
  prepareRequest(request) {
633
675
  if (this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET' &&
634
676
  this.state.socket !== undefined &&
635
677
  this.state.socket.readyState !== WebSocket.OPEN)
636
- invalidateResponsesWebSocket(this.state, true);
678
+ invalidateResponsesWebSocket(this.state, false);
637
679
  const fullBody = responsesRequestBody(this.adapter.configuration, request, request.messages, this.adapter.prefixIdentity);
638
680
  if (this.adapter.configuration.responsesPreviousResponseId) {
639
681
  const incremental = incrementalResponsesInput(this.state, fullBody);
640
- if (incremental !== undefined &&
641
- (this.adapter.configuration.responsesTransport?.transport !== 'WEBSOCKET' ||
642
- this.state.socket?.readyState === WebSocket.OPEN)) {
682
+ if (incremental !== undefined) {
643
683
  const responseId = this.state.lastResponse.responseId;
644
684
  delete this.state.lastResponse;
645
685
  return {
@@ -649,8 +689,7 @@ class OpenAiResponsesTurnSession {
649
689
  input: incremental,
650
690
  previous_response_id: responseId
651
691
  },
652
- continuation: true,
653
- requiresExistingSocket: this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET'
692
+ continuation: true
654
693
  };
655
694
  }
656
695
  if (this.allowExternalContinuation &&
@@ -664,16 +703,14 @@ class OpenAiResponsesTurnSession {
664
703
  input: deltaBody.input,
665
704
  previous_response_id: request.continuation.previousResponseId
666
705
  },
667
- continuation: true,
668
- requiresExistingSocket: false
706
+ continuation: true
669
707
  };
670
708
  }
671
709
  }
672
710
  return {
673
711
  fullBody,
674
712
  body: fullBody,
675
- continuation: false,
676
- requiresExistingSocket: false
713
+ continuation: false
677
714
  };
678
715
  }
679
716
  fullRequestBody() {
@@ -714,14 +751,9 @@ class OpenAiResponsesTurnSession {
714
751
  async webSocketForRequest(input) {
715
752
  const endpoint = webSocketEndpoint(input.configuration.baseUrl);
716
753
  const existingSocket = this.state.socket;
717
- if (input.requiresExistingSocket &&
718
- (existingSocket?.readyState !== WebSocket.OPEN || this.state.endpoint !== endpoint)) {
719
- invalidateResponsesWebSocket(this.state, true);
720
- throw unavailableContinuationConnection();
721
- }
722
754
  if (existingSocket !== undefined &&
723
755
  (existingSocket.readyState !== WebSocket.OPEN || this.state.endpoint !== endpoint))
724
- invalidateResponsesWebSocket(this.state, true);
756
+ invalidateResponsesWebSocket(this.state, false);
725
757
  if (this.state.socket === undefined) {
726
758
  const handshakeCredential = await acquireResponsesCredential(input.configuration, input.reason);
727
759
  input.credentialAcquired(handshakeCredential.revision);
@@ -729,10 +761,9 @@ class OpenAiResponsesTurnSession {
729
761
  }
730
762
  const requestCredential = await acquireResponsesCredential(input.configuration, input.reason);
731
763
  input.credentialAcquired(requestCredential.revision);
732
- if (requestCredential.revision !== this.state.credentialRevision) {
733
- invalidateResponsesWebSocket(this.state, true);
734
- if (input.continuation)
735
- throw unavailableContinuationConnection();
764
+ if (requestCredential.revision !== this.state.credentialRevision ||
765
+ responsesCredentialFingerprint(requestCredential) !== this.state.credentialFingerprint) {
766
+ invalidateResponsesWebSocket(this.state, false);
736
767
  await this.connectWebSocket(endpoint, requestCredential, input.promptCacheKey, input.signal);
737
768
  }
738
769
  if (input.signal.aborted) {
@@ -787,6 +818,7 @@ class OpenAiResponsesTurnSession {
787
818
  this.state.socket = socket;
788
819
  this.state.endpoint = endpoint;
789
820
  this.state.credentialRevision = credential.revision;
821
+ this.state.credentialFingerprint = responsesCredentialFingerprint(credential);
790
822
  this.state.connectionId = connectionId;
791
823
  this.state.proxyType = proxyType;
792
824
  }
@@ -864,9 +896,18 @@ function uuidV5(namespace, value) {
864
896
  function incrementalResponsesInput(state, current) {
865
897
  const previous = state.lastRequest;
866
898
  const completion = state.lastResponse;
867
- if (previous === undefined ||
868
- completion === undefined ||
869
- !responsesRequestPropertiesMatch(previous, current))
899
+ if (previous === undefined || completion === undefined) {
900
+ const restored = state.restoredContinuation;
901
+ if (restored === undefined ||
902
+ restored.requestPropertiesHash !== responsesRequestPropertiesHash(current) ||
903
+ current.input.length < restored.inputPrefixLength ||
904
+ restored.inputPrefixHash !==
905
+ hashResponsesValue(current.input.slice(0, restored.inputPrefixLength)))
906
+ return undefined;
907
+ state.lastResponse = { responseId: restored.responseId, outputItems: [] };
908
+ return current.input.slice(restored.inputPrefixLength);
909
+ }
910
+ if (!responsesRequestPropertiesMatch(previous, current))
870
911
  return undefined;
871
912
  const baseline = [...previous.input, ...completion.outputItems];
872
913
  if (current.input.length < baseline.length)
@@ -877,31 +918,53 @@ function incrementalResponsesInput(state, current) {
877
918
  return current.input.slice(baseline.length);
878
919
  }
879
920
  function responsesRequestPropertiesMatch(previous, current) {
880
- const keys = [
881
- 'model',
882
- 'instructions',
883
- 'tools',
884
- 'tool_choice',
885
- 'parallel_tool_calls',
886
- 'reasoning',
887
- 'store',
888
- 'stream',
889
- 'include',
890
- 'prompt_cache_key',
891
- 'text',
892
- 'client_metadata'
893
- ];
894
- return keys.every((key) => isDeepStrictEqual(previous[key], current[key]));
921
+ return RESPONSES_REQUEST_PROPERTY_KEYS.every((key) => isDeepStrictEqual(previous[key], current[key]));
895
922
  }
896
923
  function clearResponsesContinuation(state) {
897
924
  delete state.lastRequest;
898
925
  delete state.lastResponse;
926
+ delete state.restoredContinuation;
927
+ }
928
+ const RESPONSES_REQUEST_PROPERTY_KEYS = [
929
+ 'model',
930
+ 'instructions',
931
+ 'tools',
932
+ 'tool_choice',
933
+ 'parallel_tool_calls',
934
+ 'reasoning',
935
+ 'store',
936
+ 'stream',
937
+ 'include',
938
+ 'prompt_cache_key',
939
+ 'text',
940
+ 'client_metadata'
941
+ ];
942
+ function responsesRequestPropertiesHash(request) {
943
+ return hashResponsesValue(Object.fromEntries(RESPONSES_REQUEST_PROPERTY_KEYS.map((key) => [key, request[key]])));
944
+ }
945
+ function hashResponsesValue(value) {
946
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
947
+ }
948
+ function responsesConfigurationHash(configuration) {
949
+ const { apiKey: _apiKey, credentialProvider: _credentialProvider, imageGeneration: _imageGeneration, ...identity } = configuration;
950
+ return hashResponsesValue(identity);
951
+ }
952
+ function responsesCredentialFingerprint(credential) {
953
+ return hashResponsesValue({
954
+ bearer: credential.bearer.reveal(),
955
+ headers: Object.fromEntries(Object.entries(credential.headers).map(([name, value]) => [
956
+ name,
957
+ typeof value === 'string' ? value : value.reveal()
958
+ ])),
959
+ socks5Url: credential.socks5Url?.reveal()
960
+ });
899
961
  }
900
962
  function invalidateResponsesWebSocket(state, clearContinuation) {
901
963
  const socket = state.socket;
902
964
  delete state.socket;
903
965
  delete state.endpoint;
904
966
  delete state.credentialRevision;
967
+ delete state.credentialFingerprint;
905
968
  delete state.connectionId;
906
969
  delete state.proxyType;
907
970
  if (clearContinuation)
@@ -911,16 +974,6 @@ function invalidateResponsesWebSocket(state, clearContinuation) {
911
974
  socket.readyState !== WebSocket.CLOSING)
912
975
  terminateWebSocket(socket);
913
976
  }
914
- function unavailableContinuationConnection() {
915
- return new MarAgentError('previous_response_not_found', 'The previous response is unavailable on the current WebSocket connection.', {
916
- details: {
917
- reason: 'OPENAI_RESPONSES_CONTINUATION_CONNECTION_UNAVAILABLE',
918
- transport: 'WEBSOCKET',
919
- continuation: true
920
- },
921
- retryable: true
922
- });
923
- }
924
977
  function turnStateFromEvent(event) {
925
978
  for (const headers of [
926
979
  record(event.headers),
@@ -958,7 +1011,6 @@ async function* webSocketResponseEvents(input) {
958
1011
  reason: input.reason,
959
1012
  signal: input.signal,
960
1013
  continuation: input.continuation,
961
- requiresExistingSocket: input.requiresExistingSocket,
962
1014
  credentialAcquired: input.credentialAcquired
963
1015
  });
964
1016
  const body = input.turn.withTurnStateMetadata(input.body);
@@ -9,7 +9,7 @@ Preserve, when present:
9
9
  - confirmed decisions, constraints, interfaces, invariants, assumptions that still matter, and exact identifiers;
10
10
  - completed actions and edits with exact file paths, symbols, commands, migrations, or external state changes;
11
11
  - verification actually run and its result, important tool evidence, reproducible errors, failed approaches and what they proved;
12
- - unresolved questions, blockers, risks, uncommitted changes, Todo/plan state, pending approvals, and live/background process or subagent state;
12
+ - unresolved questions, blockers, risks, uncommitted changes, Todo/plan state, and pending approvals;
13
13
  - the next concrete action and the evidence still required before completion.
14
14
 
15
15
  Distinguish clearly between completed, attempted, planned, inferred, and unverified work. Newer user instructions override older ones; retain superseded instructions only when needed to explain current state. Never invent completion, test results, file contents, permissions, or external effects.
@@ -17,6 +17,7 @@ Distinguish clearly between completed, attempted, planned, inferred, and unverif
17
17
  Remove aggressively:
18
18
  - greetings, acknowledgements, progress chatter, repeated instructions, repeated status, and conclusions already represented once;
19
19
  - user-level, project-level, or directory-level AGENTS.md instructions and constraints derived only from those files, because the next Execution reloads and replaces them separately;
20
+ - retained process/Subagent inventory, IDs, status, commands, cwd, and reuse instructions, because Runtime reloads and replaces that state separately;
20
21
  - raw long logs, large source excerpts, verbose tool payloads, duplicated search/read output, and implementation detail recoverable from named files or commands;
21
22
  - abandoned hypotheses and failed attempts that do not constrain the next action;
22
23
  - hidden reasoning, secrets, credentials, authorization values, and unrelated conversation.
@@ -52,7 +53,7 @@ branch changed -> inherited_evidence=unverified
52
53
  - direct user/caller authorization、prohibition、exclusion 保留 exact action/scope,不概括成更宽泛边界。
53
54
 
54
55
  2. 当前状态 / Current state
55
- - 对象:file、symbol、feature、migration、test、worktree、process、subagent、selected Skill、Todo/plan、approval、external side effect。
56
+ - 对象:file、symbol、feature、migration、test、worktree、selected Skill、Todo/plan、approval、external side effect。retained process/subagent inventory 由 Runtime 重建,不进入 IR。
56
57
  - 分离 completion(pending / in_progress / completed / blocked)、epistemic(observed / verified / inferred / proposed / disproved)、freshness(current / stale / superseded)、scope(turn / worktree / base / branch / external system);仅在消歧时标注。
57
58
  - 明确 completed、not started、unverified、active。相同精度下选择更短状态原子;中文、English 均可,如 根因已证、edit=pending、tests=not run。
58
59
  - selected Skill | 保留仍适用的 skill name + 续作必需约束;Todo/plan | 保留 revision、item/status、当前最小 step。
@@ -70,7 +71,7 @@ branch changed -> inherited_evidence=unverified
70
71
  - 下一 Agent 无法使用、仅供 local storage validation 的 integrity hash:删除。
71
72
 
72
73
  5. 未闭环 / Open loops
73
- - unresolved、unverified、blocker、pending approval、活动 process/subagent、Todo/plan current step、仍需 evidence。
74
+ - unresolved、unverified、blocker、pending approval、Todo/plan current step、仍需 evidence。
74
75
  - 只保留已确定的最小 next action + preconditions;不复制可重算的长 plan。
75
76
 
76
77
  编译规则:
@@ -85,7 +86,7 @@ observed HEAD、validated base、target commit、current worktree 不得折叠
85
86
 
86
87
  语言:标题/一般叙述跟随 latest active user instructions 和当前任务主语言;每条 record 按局部精度与密度选择中文、English 或 mixed phrasing。保留 established term/exact identifier,不为语言统一展开或翻译。English codebase 不等于 English handoff。
87
88
 
88
- 删除 greetings、progress chatter、过程叙事、重复事实、无剩余约束的 superseded hypothesis、长日志、source body、verbose payload、可从精确 path/command 廉价恢复的正文。删除 user/project/directory AGENTS.md 正文及仅由这些文件派生的 constraint,包括其中 exclusions;下一 Execution 会重新加载并整体替换。不得保留 hidden reasoning、secret、credential、无关内容。
89
+ 删除 greetings、progress chatter、过程叙事、重复事实、无剩余约束的 superseded hypothesis、长日志、source body、verbose payload、可从精确 path/command 廉价恢复的正文。删除 user/project/directory AGENTS.md 正文及仅由这些文件派生的 constraint,包括其中 exclusions;下一 Execution 会重新加载并整体替换。删除 retained process/Subagent inventory、ID、status、command、cwd 和 reuse instruction;Runtime 会重新加载并替换。不得保留 hidden reasoning、secret、credential、无关内容。
89
90
 
90
91
  只输出 compact Markdown IR。`;
91
92
  }
@@ -1,8 +1,7 @@
1
1
  import type { ExecutionMode } from '../sdk/types.js';
2
2
  import { type ExecutionBudgetPromptState } from './execution-budget.js';
3
- import { type RetainedSessionResources } from './resources.js';
4
3
  import { type CurrentAgentModel, type SubagentModelOption } from './subagent.js';
5
- export declare const MAR_AGENT_PROMPT_VERSION = "1.49";
4
+ export declare const MAR_AGENT_PROMPT_VERSION = "1.50";
6
5
  export declare function buildSystemPrompt(input: {
7
6
  mode: ExecutionMode;
8
7
  platform: string;
@@ -16,7 +15,6 @@ export declare function buildSystemPrompt(input: {
16
15
  tools: readonly string[];
17
16
  currentAgentModel?: CurrentAgentModel;
18
17
  subagentModels?: readonly SubagentModelOption[];
19
- retainedSessionResources?: RetainedSessionResources;
20
18
  executionBudget?: ExecutionBudgetPromptState;
21
19
  highDensityCompaction?: boolean;
22
20
  extension?: string | undefined;
@@ -3,10 +3,9 @@ import { environmentPrompt, identityPrompt, instructionPriorityPrompt, workspace
3
3
  import { executionBudgetPrompt } from './execution-budget.js';
4
4
  import { modePrompt } from './modes.js';
5
5
  import { outputStylePrompt } from './output.js';
6
- import { retainedSessionResourcesPrompt } from './resources.js';
7
6
  import { subagentModelOptionsPrompt, subagentPrompt, currentAgentModelPrompt } from './subagent.js';
8
7
  import { interactionPrompt, longRunningPrompt, safetyPrompt, toolUsagePrompt, workflowPrompt } from './workflow.js';
9
- export const MAR_AGENT_PROMPT_VERSION = '1.49';
8
+ export const MAR_AGENT_PROMPT_VERSION = '1.50';
10
9
  export function buildSystemPrompt(input) {
11
10
  const toolNames = new Set(input.tools);
12
11
  const hasLongRunningCapability = [
@@ -28,9 +27,6 @@ export function buildSystemPrompt(input) {
28
27
  environmentPrompt(input),
29
28
  input.tools.length > 0 ? toolUsagePrompt(input.tools) : '',
30
29
  hasLongRunningCapability ? longRunningPrompt(input.tools) : '',
31
- input.retainedSessionResources
32
- ? retainedSessionResourcesPrompt(input.retainedSessionResources)
33
- : '',
34
30
  input.executionBudget ? executionBudgetPrompt(input.executionBudget) : '',
35
31
  modePrompt(input.mode, {
36
32
  questionAvailable: toolNames.has('question'),
@@ -12,4 +12,5 @@ export interface RetainedSessionResources {
12
12
  description: string;
13
13
  }[];
14
14
  }
15
- export declare function retainedSessionResourcesPrompt(input: RetainedSessionResources): string;
15
+ export declare function retainedSessionResourcesSnapshot(input: RetainedSessionResources): string | undefined;
16
+ export declare function retainedSessionResourcesUpdate(snapshot: string | undefined, previousSnapshot: string | undefined, previousMayContainResources?: boolean): string | undefined;
@@ -1,31 +1,42 @@
1
- export function retainedSessionResourcesPrompt(input) {
1
+ export function retainedSessionResourcesSnapshot(input) {
2
2
  if (input.processes.length === 0 && input.subagents.length === 0)
3
- return '';
3
+ return undefined;
4
+ const processes = [...input.processes].sort((left, right) => left.processId.localeCompare(right.processId));
5
+ const subagents = [...input.subagents].sort((left, right) => left.agentId.localeCompare(right.agentId));
4
6
  return [
5
7
  '# Retained Session resources',
6
8
  'These resources belong to this Session and survived the previous Execution. Reuse their stable IDs instead of starting duplicate processes or subagents.',
7
- input.processes.length === 0
8
- ? ''
9
- : 'Cancelling a managed process releases that process resource.',
10
- input.subagents.length === 0
9
+ processes.length === 0 ? '' : 'Cancelling a managed process releases that process resource.',
10
+ subagents.length === 0
11
11
  ? ''
12
12
  : 'agent_cancel stops only the current child turn and retains its Session for later messages or explicit deletion.',
13
13
  input.idleTtlMs === undefined
14
14
  ? ''
15
15
  : `When the Session becomes idle again, unclaimed resources are retained for up to ${input.idleTtlMs} ms.`,
16
- input.processes.length === 0
16
+ processes.length === 0
17
17
  ? ''
18
18
  : [
19
19
  'Managed processes:',
20
- ...input.processes.map((process) => `- processId: ${process.processId}; status: ${process.status}; cwd: ${process.cwd}; command: ${process.command}`)
20
+ ...processes.map((process) => `- processId: ${process.processId}; status: ${process.status}; cwd: ${process.cwd}; command: ${process.command}`)
21
21
  ].join('\n'),
22
- input.subagents.length === 0
22
+ subagents.length === 0
23
23
  ? ''
24
24
  : [
25
25
  'Subagents:',
26
- ...input.subagents.map((task) => `- agentId: ${task.agentId}; status: ${task.status}; description: ${task.description}`)
26
+ ...subagents.map((task) => `- agentId: ${task.agentId}; status: ${task.status}; description: ${task.description}`)
27
27
  ].join('\n')
28
28
  ]
29
29
  .filter(Boolean)
30
30
  .join('\n');
31
31
  }
32
+ export function retainedSessionResourcesUpdate(snapshot, previousSnapshot, previousMayContainResources = false) {
33
+ if (!previousMayContainResources && snapshot === previousSnapshot)
34
+ return undefined;
35
+ if (!snapshot)
36
+ return previousSnapshot || previousMayContainResources
37
+ ? 'The previously provided retained Session resources no longer apply. No retained processes or subagents remain.'
38
+ : undefined;
39
+ return previousSnapshot || previousMayContainResources
40
+ ? `This replaces all previously provided retained Session resources.\n\n${snapshot}`
41
+ : snapshot;
42
+ }
@@ -95,7 +95,9 @@ export function buildNativeCompactionMessages(messages, input = {}) {
95
95
  const focus = input.focus?.trim();
96
96
  const contract = input.highDensityCompaction ? highDensityCompactionPrompt() : compactionPrompt();
97
97
  return [
98
- ...messages.map((message) => {
98
+ ...messages
99
+ .filter((message) => message.contextKind !== 'private')
100
+ .map((message) => {
99
101
  if (!message.images?.length)
100
102
  return message;
101
103
  const textOnly = { ...message };
@@ -139,6 +139,7 @@ export function restoreModelContext(payload, providerModelId) {
139
139
  role: 'provider',
140
140
  provider: 'ANTHROPIC_MESSAGES',
141
141
  providerModelId: modelId,
142
+ contextKind: 'private',
142
143
  content: '',
143
144
  item
144
145
  }
package/dist/sdk/agent.js CHANGED
@@ -18,6 +18,7 @@ import { DEFAULT_COMPACTION_FOCUS, CompactionOperation, buildCompactionInput, bu
18
18
  import { buildSystemPrompt, MAR_AGENT_PROMPT_VERSION } from '../prompts/index.js';
19
19
  import { buildEnvironmentContext } from '../prompts/core.js';
20
20
  import { modelOutputContinuationMessage } from '../prompts/output.js';
21
+ import { retainedSessionResourcesSnapshot, retainedSessionResourcesUpdate } from '../prompts/resources.js';
21
22
  import { environmentContextUpdate, restoreEnvironmentState } from '../runtime/environment-context.js';
22
23
  import { childSubagentInstruction } from '../prompts/subagent.js';
23
24
  import { sessionTitlePrompt } from '../prompts/title.js';
@@ -54,6 +55,7 @@ export async function createMarAgent(options) {
54
55
  throw new MarAgentError('MAR_AGENT_HOST_INCOMPATIBLE', 'Host mode does not match Agent mode.');
55
56
  const store = await JsonlSessionStore.open(description.homeDirectory);
56
57
  const active = new Map();
58
+ const activeExecutionAdapters = new Set();
57
59
  const executionResults = new Map();
58
60
  const sessionRuntimes = new Map();
59
61
  let disposed = false;
@@ -249,22 +251,24 @@ export async function createMarAgent(options) {
249
251
  const sessionState = await store.readExecutionState(sessionId);
250
252
  const todo = TodoStore.restore(sessionState.todo);
251
253
  const contextRecords = sessionState.contextRecords;
252
- const contextualInstructionGeneration = latestContextualInstructionGeneration(contextRecords);
253
- const restoredContextualInstructions = sessionRuntime.contextualInstructions;
254
- let contextualInstructions;
255
- if (restoredContextualInstructions === undefined ||
256
- restoredContextualInstructions.generation !== contextualInstructionGeneration) {
257
- contextualInstructions = {
258
- generation: contextualInstructionGeneration,
254
+ const contextualWorldStateGeneration = latestContextualWorldStateGeneration(contextRecords);
255
+ const restoredContextualWorldState = sessionRuntime.contextualWorldState;
256
+ let contextualWorldState;
257
+ if (restoredContextualWorldState === undefined ||
258
+ restoredContextualWorldState.generation !== contextualWorldStateGeneration) {
259
+ contextualWorldState = {
260
+ generation: contextualWorldStateGeneration,
259
261
  initialized: false,
260
- snapshot: undefined,
262
+ instructionSnapshot: undefined,
263
+ resourceSnapshot: undefined,
261
264
  entries: []
262
265
  };
263
- sessionRuntime.contextualInstructions = contextualInstructions;
266
+ sessionRuntime.contextualWorldState = contextualWorldState;
264
267
  }
265
268
  else
266
- contextualInstructions = restoredContextualInstructions;
269
+ contextualWorldState = restoredContextualWorldState;
267
270
  const compactMayContainAgentInstructions = contextRecords.some((record) => compactSummaryFromCheckpoint(record) !== undefined);
271
+ const compactMayContainRetainedResources = compactHistoryMayContainRetainedResources(contextRecords);
268
272
  const restored = await restoreMessages(store, sessionId, contextRecords, {
269
273
  includeImages: mode !== 'compact'
270
274
  });
@@ -351,6 +355,7 @@ export async function createMarAgent(options) {
351
355
  const titleModel = [...executionModels.values()].find((model) => model.titleGeneration) ??
352
356
  executionModels.get(input.modelId ?? executionDefaultModelId);
353
357
  titleAdapter = createAdapter(titleModel.id, executionModels);
358
+ activeExecutionAdapters.add(titleAdapter);
354
359
  titleTask = generateSessionTitle(titleAdapter, input.prompt, titleModel.defaultReasoningEffort, controller.signal, recordModelAttempt('TITLE')).catch(() => undefined);
355
360
  }
356
361
  }
@@ -367,12 +372,17 @@ export async function createMarAgent(options) {
367
372
  turnId
368
373
  });
369
374
  selectedAdapter = sessionAdapter(input.modelId ?? executionDefaultModelId, executionModels);
375
+ const selectedModel = executionModels.get(input.modelId ?? executionDefaultModelId);
376
+ const restoredContinuation = latestModelContinuationCheckpoint(contextRecords, selectedModel.id);
377
+ if (restoredContinuation !== undefined)
378
+ selectedAdapter.restoreContinuation?.(restoredContinuation);
370
379
  if (options.adapterFactory !== undefined &&
371
- selectedAdapter.createTurnSession === undefined)
380
+ selectedAdapter.createTurnSession === undefined) {
372
381
  executionScopedAdapters.add(selectedAdapter);
382
+ activeExecutionAdapters.add(selectedAdapter);
383
+ }
373
384
  const selected = createModelTurnSession(selectedAdapter);
374
385
  selectedTurn = selected;
375
- const selectedModel = executionModels.get(input.modelId ?? executionDefaultModelId);
376
386
  let lastServerUsage = latestModelUsage(contextRecords, selectedModel.id);
377
387
  const compactUserTokenLimit = Math.min(20_000, Math.floor(selectedModel.contextWindowTokens / 8));
378
388
  const reasoningEffort = input.reasoningEffort ?? selectedModel.defaultReasoningEffort;
@@ -462,29 +472,29 @@ export async function createMarAgent(options) {
462
472
  agentInstructionInsertIndex &&
463
473
  messages[agentInstructionInsertIndex - 1]?.contextKind === 'environment')
464
474
  agentInstructionInsertIndex--;
465
- if (!contextualInstructions.initialized ||
466
- contextualInstructions.snapshot !== currentInstructionSnapshot) {
467
- const content = renderAgentInstructions(instructionEntries, compactMayContainAgentInstructions || contextualInstructions.entries.length > 0);
468
- if (content !== undefined)
469
- contextualInstructions.entries.push({
470
- index: agentInstructionInsertIndex,
471
- content
472
- });
473
- contextualInstructions.snapshot = currentInstructionSnapshot;
474
- contextualInstructions.initialized = true;
475
- }
476
475
  const tools = withCodeModeResultTypes(availableTools.filter((tool) => tool.name !== 'skill' || (instructionContext?.skills.size ?? 0) > 0));
477
476
  const executionTools = tools;
478
- const hostResources = (await options.host.listSessionResources?.(sessionId)) ?? {
479
- processes: []
480
- };
481
- const retainedSessionResources = {
482
- ...(hostResources.idleTtlMs === undefined
483
- ? {}
484
- : { idleTtlMs: hostResources.idleTtlMs }),
485
- processes: hostResources.processes,
486
- subagents: subagents?.listSessionResources() ?? []
477
+ const loadRetainedSessionResources = async () => {
478
+ const hostResources = (await options.host.listSessionResources?.(sessionId)) ?? {
479
+ processes: []
480
+ };
481
+ return {
482
+ ...(hostResources.idleTtlMs === undefined
483
+ ? {}
484
+ : { idleTtlMs: hostResources.idleTtlMs }),
485
+ processes: hostResources.processes,
486
+ subagents: subagents?.listSessionResources() ?? []
487
+ };
487
488
  };
489
+ let currentResourceSnapshot = retainedSessionResourcesSnapshot(await loadRetainedSessionResources());
490
+ contextualWorldState = updateContextualWorldState(contextualWorldState, agentInstructionInsertIndex, {
491
+ instructionSnapshot: currentInstructionSnapshot,
492
+ instructionContent: renderAgentInstructions(instructionEntries, compactMayContainAgentInstructions ||
493
+ contextualWorldState.instructionSnapshot !== undefined),
494
+ resourceSnapshot: currentResourceSnapshot,
495
+ previousMayContainResources: compactMayContainRetainedResources
496
+ });
497
+ sessionRuntime.contextualWorldState = contextualWorldState;
488
498
  const buildExecutionSystemPrompt = (availableTools = executionTools, promptMode = mode) => buildSystemPrompt({
489
499
  mode: promptMode,
490
500
  platform: description.platform,
@@ -512,7 +522,6 @@ export async function createMarAgent(options) {
512
522
  reasoningEfforts: configuration.reasoningEfforts
513
523
  }))
514
524
  : [],
515
- retainedSessionResources,
516
525
  highDensityCompaction: selectedModel.highDensityCompaction,
517
526
  ...(rolloutBudget?.active
518
527
  ? {
@@ -537,7 +546,9 @@ export async function createMarAgent(options) {
537
546
  if (mode === 'compact') {
538
547
  const compactSystemPrompt = initialSystemPrompt;
539
548
  const compactHistory = [...messages];
540
- const projectedHistory = normalizeMessagesForModel(insertContextualInstructions(compactHistory, contextualInstructions.entries), { supportsImages: false });
549
+ const projectedHistory = normalizeMessagesForModel(insertContextualWorldState(compactHistory, contextualWorldState.entries, {
550
+ excludeResources: true
551
+ }), { supportsImages: false });
541
552
  const nativeMessages = buildNativeCompactionMessages(projectedHistory, {
542
553
  focus: input.prompt,
543
554
  highDensityCompaction: selectedModel.highDensityCompaction
@@ -593,7 +604,9 @@ export async function createMarAgent(options) {
593
604
  let compactSummary = '';
594
605
  const compactSystemPrompt = buildExecutionSystemPrompt(executionTools, 'run');
595
606
  const compactHistory = retainCurrent ? messages.slice(0, -1) : messages;
596
- const projectedHistory = normalizeMessagesForModel(insertContextualInstructions(compactHistory, contextualInstructions.entries), { supportsImages: false });
607
+ const projectedHistory = normalizeMessagesForModel(insertContextualWorldState(compactHistory, contextualWorldState.entries, {
608
+ excludeResources: true
609
+ }), { supportsImages: false });
597
610
  const nativeCompactMessages = buildNativeCompactionMessages(projectedHistory, {
598
611
  highDensityCompaction: selectedModel.highDensityCompaction
599
612
  });
@@ -686,8 +699,14 @@ export async function createMarAgent(options) {
686
699
  .filter((message) => message.kind === 'user_input')
687
700
  .map((message) => message.content);
688
701
  agentInstructionInsertIndex = Math.max(0, messages.length - 1);
689
- contextualInstructions = resetContextualInstructions(compactRecord.recordId, currentInstructionSnapshot, agentInstructionInsertIndex, renderAgentInstructions(instructionEntries, true));
690
- sessionRuntime.contextualInstructions = contextualInstructions;
702
+ currentResourceSnapshot = retainedSessionResourcesSnapshot(await loadRetainedSessionResources());
703
+ contextualWorldState = resetContextualWorldState(compactRecord.recordId, agentInstructionInsertIndex, {
704
+ instructionSnapshot: currentInstructionSnapshot,
705
+ instructionContent: renderAgentInstructions(instructionEntries, true),
706
+ resourceSnapshot: currentResourceSnapshot,
707
+ previousMayContainResources: compactSummary.includes('# Retained Session resources')
708
+ });
709
+ sessionRuntime.contextualWorldState = contextualWorldState;
691
710
  sessionRuntime.responsesChain = undefined;
692
711
  lastServerContextTokens = undefined;
693
712
  lastServerUsage = undefined;
@@ -726,7 +745,7 @@ export async function createMarAgent(options) {
726
745
  .update(stableJson({
727
746
  modelId: selectedModel.id,
728
747
  systemPrompt,
729
- contextualInstructions: contextualInstructions.entries,
748
+ contextualWorldState: contextualWorldState.entries,
730
749
  tools: roundTools,
731
750
  reasoningEffort
732
751
  }))
@@ -745,7 +764,7 @@ export async function createMarAgent(options) {
745
764
  : undefined;
746
765
  const modelMessages = mode === 'compact'
747
766
  ? normalizeMessagesForModel(requestContextMessages, { supportsImages: false })
748
- : normalizeMessagesForModel(insertContextualInstructions(requestContextMessages, contextualInstructions.entries), {
767
+ : normalizeMessagesForModel(insertContextualWorldState(requestContextMessages, contextualWorldState.entries), {
749
768
  supportsImages: selectedModel.inputCapabilities.includes('IMAGE')
750
769
  });
751
770
  let sawModelSemanticEvent = false;
@@ -807,6 +826,9 @@ export async function createMarAgent(options) {
807
826
  content: '',
808
827
  provider: event.provider,
809
828
  providerModelId: selectedModel.id,
829
+ ...(event.provider === 'ANTHROPIC_MESSAGES'
830
+ ? { contextKind: 'private' }
831
+ : {}),
810
832
  item: event.item
811
833
  };
812
834
  messages.push(message);
@@ -979,6 +1001,17 @@ export async function createMarAgent(options) {
979
1001
  };
980
1002
  else if (selectedModel.responsesPreviousResponseId)
981
1003
  sessionRuntime.responsesChain = undefined;
1004
+ const continuationCheckpoint = selected.continuationCheckpoint?.();
1005
+ if (mode !== 'compact' && continuationCheckpoint !== undefined)
1006
+ await store.append(sessionId, {
1007
+ type: 'responses.continuation',
1008
+ payload: {
1009
+ modelId: selectedModel.id,
1010
+ checkpoint: continuationCheckpoint
1011
+ },
1012
+ executionId,
1013
+ turnId
1014
+ });
982
1015
  if (pendingTools.length === 0 && mailbox.length > 0) {
983
1016
  await appendMailboxMessages();
984
1017
  finalAnswer = '';
@@ -1167,8 +1200,14 @@ export async function createMarAgent(options) {
1167
1200
  if (agentInstructionInsertIndex &&
1168
1201
  messages[agentInstructionInsertIndex - 1]?.contextKind === 'environment')
1169
1202
  agentInstructionInsertIndex--;
1170
- contextualInstructions = resetContextualInstructions(latestContextualInstructionGeneration(committed.records), currentInstructionSnapshot, agentInstructionInsertIndex, renderAgentInstructions(instructionEntries, committed.records.some((record) => compactSummaryFromCheckpoint(record) !== undefined)));
1171
- sessionRuntime.contextualInstructions = contextualInstructions;
1203
+ currentResourceSnapshot = retainedSessionResourcesSnapshot(await loadRetainedSessionResources());
1204
+ contextualWorldState = resetContextualWorldState(latestContextualWorldStateGeneration(committed.records), agentInstructionInsertIndex, {
1205
+ instructionSnapshot: currentInstructionSnapshot,
1206
+ instructionContent: renderAgentInstructions(instructionEntries, committed.records.some((record) => compactSummaryFromCheckpoint(record) !== undefined)),
1207
+ resourceSnapshot: currentResourceSnapshot,
1208
+ previousMayContainResources: compactHistoryMayContainRetainedResources(committed.records)
1209
+ });
1210
+ sessionRuntime.contextualWorldState = contextualWorldState;
1172
1211
  }
1173
1212
  }
1174
1213
  if (controller.signal.aborted) {
@@ -1252,7 +1291,16 @@ export async function createMarAgent(options) {
1252
1291
  await selectedTurn?.close().catch(() => undefined);
1253
1292
  if (titleAdapter !== undefined && titleAdapter !== selectedAdapter)
1254
1293
  executionScopedAdapters.add(titleAdapter);
1255
- await closeModelAdapters(executionScopedAdapters).catch(() => undefined);
1294
+ try {
1295
+ await closeModelAdapters(executionScopedAdapters);
1296
+ }
1297
+ catch {
1298
+ // Execution cleanup is best effort.
1299
+ }
1300
+ finally {
1301
+ for (const adapter of executionScopedAdapters)
1302
+ activeExecutionAdapters.delete(adapter);
1303
+ }
1256
1304
  subagents?.endParentExecution(executionId);
1257
1305
  await releaseSession?.();
1258
1306
  active.delete(`${sessionId}:${executionId}`);
@@ -1328,30 +1376,60 @@ export async function createMarAgent(options) {
1328
1376
  const activeSessions = activeSessionIds();
1329
1377
  if ([...activeSessions].some((sessionId) => sessionRuntimes.get(sessionId)?.sourceType !== 'subagent'))
1330
1378
  throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models cannot be reconfigured while a main execution is active.');
1331
- const activeAdapters = new Set();
1379
+ const activeAdapters = new Set(activeExecutionAdapters);
1332
1380
  for (const [sessionId, runtime] of sessionRuntimes)
1333
1381
  if (activeSessions.has(sessionId))
1334
1382
  for (const adapter of runtime.adapters.values())
1335
1383
  activeAdapters.add(adapter);
1336
1384
  const inactiveRuntimes = [...sessionRuntimes].flatMap(([sessionId, runtime]) => activeSessions.has(sessionId) ? [] : [runtime]);
1337
- const adapters = inactiveRuntimes
1338
- .flatMap((runtime) => [...runtime.adapters.values()])
1339
- .filter((adapter) => !activeAdapters.has(adapter));
1340
1385
  reconfiguring = true;
1341
1386
  try {
1387
+ const reconfigurationCommits = new Map();
1388
+ for (const runtime of inactiveRuntimes)
1389
+ for (const [modelId, adapter] of runtime.adapters) {
1390
+ if (activeAdapters.has(adapter))
1391
+ continue;
1392
+ const configuration = next.models.get(modelId);
1393
+ const commit = configuration === undefined ? undefined : adapter.prepareReconfigure?.(configuration);
1394
+ if (commit !== undefined)
1395
+ reconfigurationCommits.set(adapter, commit);
1396
+ }
1397
+ const retainedAdapters = new Set(reconfigurationCommits.keys());
1398
+ const adapters = [
1399
+ ...new Set(inactiveRuntimes.flatMap((runtime) => [...runtime.adapters.values()]))
1400
+ ].filter((adapter) => !activeAdapters.has(adapter) && !retainedAdapters.has(adapter));
1401
+ for (const runtime of inactiveRuntimes) {
1402
+ for (const [modelId, adapter] of runtime.adapters)
1403
+ if (!retainedAdapters.has(adapter))
1404
+ runtime.adapters.delete(modelId);
1405
+ if (runtime.responsesChain !== undefined &&
1406
+ !retainedAdapters.has(runtime.adapters.get(runtime.responsesChain.modelId)))
1407
+ runtime.responsesChain = undefined;
1408
+ }
1342
1409
  await closeModelAdapters(adapters);
1343
1410
  if (disposed)
1344
1411
  throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
1412
+ for (const commit of reconfigurationCommits.values())
1413
+ commit();
1345
1414
  models = next.models;
1346
1415
  defaultModelId = next.defaultModelId;
1347
1416
  modelGeneration++;
1348
1417
  for (const provider of next.credentialProviders)
1349
1418
  credentialProviders.add(provider);
1350
1419
  for (const runtime of inactiveRuntimes) {
1351
- runtime.responsesChain = undefined;
1352
- runtime.adapters.clear();
1353
1420
  runtime.adapterGeneration = modelGeneration;
1354
1421
  }
1422
+ const liveProviders = new Set(next.credentialProviders);
1423
+ for (const adapter of activeExecutionAdapters)
1424
+ addModelCredentialProviders(liveProviders, adapter.configuration);
1425
+ for (const runtime of sessionRuntimes.values())
1426
+ for (const adapter of runtime.adapters.values())
1427
+ addModelCredentialProviders(liveProviders, adapter.configuration);
1428
+ const obsoleteProviders = [...credentialProviders].filter((provider) => !liveProviders.has(provider));
1429
+ const providerCloseResults = await Promise.allSettled(obsoleteProviders.map((provider) => provider.close()));
1430
+ for (const [index, result] of providerCloseResults.entries())
1431
+ if (result.status === 'fulfilled')
1432
+ credentialProviders.delete(obsoleteProviders[index]);
1355
1433
  }
1356
1434
  finally {
1357
1435
  reconfiguring = false;
@@ -1494,9 +1572,46 @@ function createModelTurnSession(adapter) {
1494
1572
  }
1495
1573
  },
1496
1574
  resetContinuation: () => turn.resetContinuation?.(),
1575
+ continuationCheckpoint: () => turn.continuationCheckpoint?.(),
1497
1576
  close: () => turn.close()
1498
1577
  };
1499
1578
  }
1579
+ function latestModelContinuationCheckpoint(records, modelId) {
1580
+ let checkpoint;
1581
+ for (const record of records) {
1582
+ if (record.type === 'compact.completed' || record.type === 'context.gc.completed') {
1583
+ checkpoint = undefined;
1584
+ continue;
1585
+ }
1586
+ if (record.type !== 'responses.continuation')
1587
+ continue;
1588
+ const payload = record.payload;
1589
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload))
1590
+ continue;
1591
+ const candidateModelId = payload.modelId;
1592
+ const candidate = payload.checkpoint;
1593
+ if (candidateModelId !== modelId || !isModelContinuationCheckpoint(candidate))
1594
+ continue;
1595
+ checkpoint = candidate;
1596
+ }
1597
+ return checkpoint;
1598
+ }
1599
+ function isModelContinuationCheckpoint(value) {
1600
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
1601
+ return false;
1602
+ const checkpoint = value;
1603
+ return (checkpoint.version === 1 &&
1604
+ checkpoint.protocol === 'OPENAI_RESPONSES' &&
1605
+ isBoundedString(checkpoint.configurationHash, 64) &&
1606
+ isBoundedString(checkpoint.responseId, 1_024) &&
1607
+ isBoundedString(checkpoint.requestPropertiesHash, 64) &&
1608
+ Number.isSafeInteger(checkpoint.inputPrefixLength) &&
1609
+ checkpoint.inputPrefixLength >= 0 &&
1610
+ isBoundedString(checkpoint.inputPrefixHash, 64));
1611
+ }
1612
+ function isBoundedString(value, maximumLength) {
1613
+ return typeof value === 'string' && value.length > 0 && value.length <= maximumLength;
1614
+ }
1500
1615
  function modelAttemptFailureDiagnostic(diagnostic, summary) {
1501
1616
  if (summary === undefined)
1502
1617
  return diagnostic;
@@ -1508,7 +1623,16 @@ function modelAttemptFailureDiagnostic(diagnostic, summary) {
1508
1623
  };
1509
1624
  }
1510
1625
  async function closeModelAdapters(adapters) {
1511
- await Promise.all([...new Set(adapters)].flatMap((adapter) => adapter.close === undefined ? [] : [adapter.close()]));
1626
+ const results = await Promise.allSettled([...new Set(adapters)].flatMap((adapter) => adapter.close === undefined ? [] : [adapter.close()]));
1627
+ const failure = results.find((result) => result.status === 'rejected');
1628
+ if (failure !== undefined)
1629
+ throw failure.reason;
1630
+ }
1631
+ function addModelCredentialProviders(target, configuration) {
1632
+ if (configuration.credentialProvider !== undefined)
1633
+ target.add(configuration.credentialProvider);
1634
+ if (configuration.imageGeneration !== undefined)
1635
+ target.add(configuration.imageGeneration.credentialProvider);
1512
1636
  }
1513
1637
  function truncateUtf8(value, maximumBytes) {
1514
1638
  const bytes = Buffer.from(value);
@@ -1764,17 +1888,19 @@ function modelIdFromPayload(payload, key = 'modelId') {
1764
1888
  const value = payload[key];
1765
1889
  return typeof value === 'string' && value.length > 0 ? value : undefined;
1766
1890
  }
1767
- function insertContextualInstructions(messages, entries) {
1891
+ function insertContextualWorldState(messages, entries, options = {}) {
1768
1892
  const result = [...messages];
1769
1893
  let offset = 0;
1770
1894
  for (const entry of entries) {
1895
+ if (options.excludeResources && entry.kind === 'resources')
1896
+ continue;
1771
1897
  const insertionIndex = Math.max(0, Math.min(entry.index + offset, result.length));
1772
1898
  result.splice(insertionIndex, 0, { role: 'user', content: entry.content });
1773
1899
  offset++;
1774
1900
  }
1775
1901
  return result;
1776
1902
  }
1777
- function latestContextualInstructionGeneration(records) {
1903
+ function latestContextualWorldStateGeneration(records) {
1778
1904
  let generation;
1779
1905
  for (const record of records)
1780
1906
  if (record.type === 'context.gc.completed' ||
@@ -1782,12 +1908,39 @@ function latestContextualInstructionGeneration(records) {
1782
1908
  generation = record.recordId;
1783
1909
  return generation;
1784
1910
  }
1785
- function resetContextualInstructions(generation, snapshot, index, content) {
1911
+ function compactHistoryMayContainRetainedResources(records) {
1912
+ return records.some((record) => compactSummaryFromCheckpoint(record)?.includes('# Retained Session resources'));
1913
+ }
1914
+ function updateContextualWorldState(state, index, current) {
1915
+ const entries = [...state.entries];
1916
+ if (!state.initialized || state.instructionSnapshot !== current.instructionSnapshot) {
1917
+ if (current.instructionContent !== undefined)
1918
+ entries.push({ index, kind: 'instructions', content: current.instructionContent });
1919
+ }
1920
+ const resourceContent = retainedSessionResourcesUpdate(current.resourceSnapshot, state.initialized ? state.resourceSnapshot : undefined, !state.initialized && current.previousMayContainResources);
1921
+ if (resourceContent !== undefined)
1922
+ entries.push({ index, kind: 'resources', content: resourceContent });
1923
+ return {
1924
+ generation: state.generation,
1925
+ initialized: true,
1926
+ instructionSnapshot: current.instructionSnapshot,
1927
+ resourceSnapshot: current.resourceSnapshot,
1928
+ entries
1929
+ };
1930
+ }
1931
+ function resetContextualWorldState(generation, index, current) {
1932
+ const entries = [];
1933
+ if (current.instructionContent !== undefined)
1934
+ entries.push({ index, kind: 'instructions', content: current.instructionContent });
1935
+ const resourceContent = retainedSessionResourcesUpdate(current.resourceSnapshot, undefined, current.previousMayContainResources);
1936
+ if (resourceContent !== undefined)
1937
+ entries.push({ index, kind: 'resources', content: resourceContent });
1786
1938
  return {
1787
1939
  generation,
1788
1940
  initialized: true,
1789
- snapshot,
1790
- entries: content === undefined ? [] : [{ index, content }]
1941
+ instructionSnapshot: current.instructionSnapshot,
1942
+ resourceSnapshot: current.resourceSnapshot,
1943
+ entries
1791
1944
  };
1792
1945
  }
1793
1946
  function contextualInstructionInsertIndex(messages) {
@@ -302,7 +302,7 @@ export class JsonlSessionStore {
302
302
  });
303
303
  const retained = input.boundaryTurnId === null ? [] : records.slice(1, boundaryIndex + 1);
304
304
  for (const record of retained) {
305
- if (record.type === 'context.gc.completed')
305
+ if (record.type === 'context.gc.completed' || record.type === 'responses.continuation')
306
306
  continue;
307
307
  await this.append(target.id, {
308
308
  type: record.type,
@@ -368,7 +368,9 @@ export class JsonlSessionStore {
368
368
  : records.findLastIndex((record) => record.turnId === input.boundaryTurnId);
369
369
  if (input.boundaryTurnId !== null && boundaryIndex < 0)
370
370
  throw new MarAgentError('MAR_AGENT_ROLLBACK_TARGET_INVALID', 'Session rollback target was not found.');
371
- const retained = records.slice(0, boundaryIndex + 1);
371
+ const retained = records
372
+ .slice(0, boundaryIndex + 1)
373
+ .filter((record) => record.type !== 'responses.continuation');
372
374
  const staged = await stageRollbackSegments(this.sessionDirectory(sessionId), retained);
373
375
  const transactionId = randomUUID();
374
376
  let generatedTrash = [];
@@ -160,9 +160,9 @@ export class SubagentSessionController {
160
160
  if (waitFor === 'completion') {
161
161
  const completed = await this.#waitForCompletion(state, waitMs, options.signal);
162
162
  options.signal?.throwIfAborted();
163
- const includeLatestMessage = completed ? this.#hasUnreadMessage(state) : false;
163
+ const includeLatestMessage = this.#hasUnreadMessage(state);
164
164
  const includeTerminalDetails = completed ? this.#observeTerminal(state) : false;
165
- if (completed)
165
+ if (includeLatestMessage)
166
166
  this.#observeLatestMessage(state);
167
167
  return {
168
168
  ...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
@@ -244,12 +244,13 @@ export class SubagentSessionController {
244
244
  }
245
245
  const completed = await this.#waitForAnyCompletion(live, waitMs, options.signal);
246
246
  options.signal?.throwIfAborted();
247
- const includeLatestMessage = completed ? this.#hasUnreadMessage(completed) : false;
248
- const includeTerminalDetails = completed ? this.#observeTerminal(completed) : false;
249
- if (completed)
250
- this.#observeLatestMessage(completed);
247
+ const state = completed ?? this.#oldestUnreadMessage(live) ?? live.at(-1);
248
+ const includeLatestMessage = this.#hasUnreadMessage(state);
249
+ const includeTerminalDetails = completed ? this.#observeTerminal(state) : false;
250
+ if (includeLatestMessage)
251
+ this.#observeLatestMessage(state);
251
252
  return {
252
- ...this.#snapshot(completed ?? live.at(-1), {
253
+ ...this.#snapshot(state, {
253
254
  includeLatestMessage,
254
255
  includeTerminalDetails
255
256
  }),
@@ -3,7 +3,7 @@ import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
3
3
  export const agentWaitToolDefinition = {
4
4
  name: 'agent_wait',
5
5
  parallelSafety: 'serial',
6
- description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec with action:"poll" instead. Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Every result includes waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. A message or first terminal completion result includes the newly observed latest public commentary/final message; snapshot, timeout, and repeated terminal results omit previously observed message text. latestActivity can still identify a public message or bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. First terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
6
+ description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec with action:"poll" instead. Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Every result includes waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. A message, timeout with unread progress, or first terminal completion result includes the newly observed latest public commentary/final message; snapshots and repeated observations omit previously observed message text. latestActivity can still identify a public message or bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. First terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
7
7
  inputSchema: {
8
8
  type: 'object',
9
9
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.82",
3
+ "version": "0.9.84",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",