@myagentroam/agent 0.9.173 → 0.9.174

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.
@@ -24,7 +24,7 @@ export interface CoreCompactContext {
24
24
  readonly messages: readonly CoreCompactMessage[];
25
25
  readonly focus?: string;
26
26
  readonly summaryTokenBudget: number;
27
- /** Return the Core compaction contract selected for this operation's main model snapshot. */
27
+ /** Return the Core's default structured handoff contract, independent of model configuration. */
28
28
  readonly getPrompt: () => string;
29
29
  /** Private, bounded telemetry only; reporting never changes the generation result. */
30
30
  readonly reportDiagnostic?: (diagnostic: CoreCompactDiagnostic) => Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { isDeepStrictEqual } from 'node:util';
3
3
  import { resolveModelTextBlocks } from '../model/text-blocks.js';
4
+ import { orderToolDefinitions, stableToolDefinition } from '../tools/definition-projection.js';
4
5
  import { MarAgentError } from '../error.js';
5
6
  /** Only actual SDK model dispatch records snapshots; public views carry no raw payload authority. */
6
7
  export class ExtensionRequestSnapshots {
@@ -151,6 +152,11 @@ export class ExtensionRequestBridge {
151
152
  };
152
153
  });
153
154
  const { toolChoice: _toolChoice, reasoningEffort: _reasoningEffort, images: _images, systemBlocks: _systemBlocks, ...retained } = this.original;
155
+ // Middleware selects declarations; it must not reorder a retained discovery prefix.
156
+ const order = new Map(this.original.tools.map((tool, index) => [tool.name, index]));
157
+ const tools = orderToolDefinitions(structuredClone(view.tools))
158
+ .sort((left, right) => (order.get(left.name) ?? order.size) - (order.get(right.name) ?? order.size))
159
+ .map(stableToolDefinition);
154
160
  return {
155
161
  ...retained,
156
162
  system: view.system,
@@ -158,7 +164,7 @@ export class ExtensionRequestBridge {
158
164
  ? {}
159
165
  : { systemBlocks: resolveModelTextBlocks(view.system, view.systemBlocks) }),
160
166
  messages,
161
- tools: structuredClone(view.tools),
167
+ tools,
162
168
  ...(view.toolChoice ? { toolChoice: view.toolChoice } : {}),
163
169
  ...(view.reasoningEffort ? { reasoningEffort: view.reasoningEffort } : {}),
164
170
  ...(view.images ? { images: structuredClone(view.images) } : {})
@@ -1,4 +1,5 @@
1
1
  import { observationModelParts } from '../tools/code-mode/observations.js';
2
+ import { stableToolSchema } from '../tools/definition-projection.js';
2
3
  import { randomUUID } from 'node:crypto';
3
4
  import { MarAgentError } from '../error.js';
4
5
  import { resolveModelDefaultReasoningEffort } from './configuration.js';
@@ -97,7 +98,7 @@ export class AnthropicMessagesAdapter {
97
98
  : request.tools.map((tool) => ({
98
99
  name: tool.name,
99
100
  description: tool.description,
100
- input_schema: tool.inputSchema
101
+ input_schema: stableToolSchema(tool.inputSchema)
101
102
  }));
102
103
  const response = await fetchModelResponse(`${this.configuration.baseUrl.replace(/\/$/, '')}/v1/messages`, {
103
104
  method: 'POST',
@@ -28,7 +28,6 @@ export declare function serializeModel(model: CliModelConfiguration): {
28
28
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
29
29
  titleGeneration: boolean;
30
30
  codeMode: boolean;
31
- highDensityCompaction: boolean;
32
31
  fastMode?: boolean | undefined;
33
32
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
34
33
  hostedWebSearch: boolean;
@@ -55,7 +54,6 @@ export declare function serializeModel(model: CliModelConfiguration): {
55
54
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
56
55
  titleGeneration: boolean;
57
56
  codeMode: boolean;
58
- highDensityCompaction: boolean;
59
57
  fastMode?: boolean | undefined;
60
58
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
61
59
  hostedWebSearch: boolean;
@@ -52,6 +52,13 @@ function parseCliModelCatalog(input, options = {}) {
52
52
  return parseModelCatalog(input, options);
53
53
  const externalCredentialModels = new Set();
54
54
  const normalizedModels = input.models.map((model, index) => {
55
+ if (isRecord(model) && Object.hasOwn(model, 'highDensityCompaction')) {
56
+ if (typeof model.highDensityCompaction !== 'boolean')
57
+ throw invalidCatalog();
58
+ const { highDensityCompaction: _compaction, ...configuration } = model;
59
+ void _compaction;
60
+ model = configuration;
61
+ }
55
62
  if (isRecord(model) && Object.hasOwn(model, 'responsesEncoding')) {
56
63
  if (model.protocol !== 'OPENAI_RESPONSES' ||
57
64
  (model.responsesEncoding !== 'STANDARD' && model.responsesEncoding !== 'LITE'))
@@ -69,7 +69,6 @@ declare const rawModelSchema: z.ZodObject<{
69
69
  }>>;
70
70
  titleGeneration: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
71
71
  codeMode: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
72
- highDensityCompaction: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
73
72
  fastMode: z.ZodOptional<z.ZodBoolean>;
74
73
  serviceTier: z.ZodOptional<z.ZodEnum<{
75
74
  default: "default";
@@ -49,7 +49,6 @@ const rawModelSchema = z
49
49
  defaultReasoningEffort: marAgentReasoningEffortSchema.optional(),
50
50
  titleGeneration: z.boolean().optional().default(false),
51
51
  codeMode: z.boolean().optional().default(false),
52
- highDensityCompaction: z.boolean().optional().default(false),
53
52
  fastMode: z.boolean().optional(),
54
53
  serviceTier: z.enum(['auto', 'default', 'flex', 'priority', 'fast', 'ultrafast']).optional(),
55
54
  hostedWebSearch: z.boolean(),
@@ -51,6 +51,12 @@ export interface ModelAttemptDiagnostic {
51
51
  connectionId?: string;
52
52
  connectionReused?: boolean;
53
53
  proxyType?: 'DIRECT' | 'SOCKS5';
54
+ /** Hashes of the lowered full logical request; never include credentials or raw bodies. */
55
+ requestInstructionsHash?: string;
56
+ requestToolsHash?: string;
57
+ requestPropertiesHash?: string;
58
+ requestInputHash?: string;
59
+ requestInputItems?: number;
54
60
  /** Actual serialized WebSocket request size, including inline image Base64. */
55
61
  requestBytes?: number;
56
62
  imageCount?: number;
@@ -1,4 +1,5 @@
1
1
  import { observationModelParts } from '../tools/code-mode/observations.js';
2
+ import { stableToolSchema } from '../tools/definition-projection.js';
2
3
  import { isDeepStrictEqual } from 'node:util';
3
4
  import { createHash, randomUUID } from 'node:crypto';
4
5
  import { describeMarAgentError, MarAgentError } from '../error.js';
@@ -89,6 +90,15 @@ export class OpenAiResponsesAdapter {
89
90
  const attemptContinuation = useContinuation;
90
91
  const attemptCredentialReason = credentialReason;
91
92
  const attemptTransport = this.configuration.responsesTransport?.transport === 'WEBSOCKET' ? 'WEBSOCKET' : 'HTTP';
93
+ const requestPrefixDiagnostic = request.onAttemptDiagnostic
94
+ ? {
95
+ requestInstructionsHash: hashResponsesValue(prepared.fullBody.instructions),
96
+ requestToolsHash: hashResponsesValue(prepared.fullBody.tools),
97
+ requestPropertiesHash: hashResponsesValue({ ...prepared.fullBody, input: undefined }),
98
+ requestInputHash: hashResponsesValue(prepared.fullBody.input),
99
+ requestInputItems: prepared.fullBody.input.length
100
+ }
101
+ : {};
92
102
  const prelude = [];
93
103
  let sawSemanticEvent = false;
94
104
  let firstSemanticEventType;
@@ -100,7 +110,8 @@ export class OpenAiResponsesAdapter {
100
110
  attempt: attemptNumber,
101
111
  maxAttempts,
102
112
  continuation: attemptContinuation,
103
- credentialReason: attemptCredentialReason
113
+ credentialReason: attemptCredentialReason,
114
+ ...requestPrefixDiagnostic
104
115
  });
105
116
  try {
106
117
  for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn)) {
@@ -130,6 +141,7 @@ export class OpenAiResponsesAdapter {
130
141
  maxAttempts,
131
142
  continuation: attemptContinuation,
132
143
  credentialReason: attemptCredentialReason,
144
+ ...requestPrefixDiagnostic,
133
145
  semanticEventObserved: sawSemanticEvent,
134
146
  ...(firstSemanticEventType === undefined ? {} : { firstSemanticEventType }),
135
147
  ...turn.connectionDiagnostic(),
@@ -146,6 +158,7 @@ export class OpenAiResponsesAdapter {
146
158
  maxAttempts,
147
159
  continuation: attemptContinuation,
148
160
  credentialReason: attemptCredentialReason,
161
+ ...requestPrefixDiagnostic,
149
162
  semanticEventObserved: sawSemanticEvent,
150
163
  ...(firstSemanticEventType === undefined ? {} : { firstSemanticEventType }),
151
164
  ...turn.connectionDiagnostic(),
@@ -651,7 +664,7 @@ class OpenAiResponsesTurnSession {
651
664
  if (event.type === 'response.started' && event.responseId !== 'unknown')
652
665
  responseId = event.responseId;
653
666
  else if (event.type === 'response.item')
654
- outputItems.push(event.item);
667
+ outputItems.push(JSON.parse(JSON.stringify(event.item)));
655
668
  else if (event.type === 'text.delta' ||
656
669
  event.type === 'output.delta' ||
657
670
  event.type === 'text.completed' ||
@@ -706,7 +719,8 @@ class OpenAiResponsesTurnSession {
706
719
  invalidateResponsesWebSocket(this.state, false);
707
720
  clearResponsesContinuation(this.state);
708
721
  }
709
- const fullBody = responsesRequestBody(this.adapter.configuration, request, request.messages);
722
+ // The continuation baseline must not share mutable schemas or items with callers.
723
+ const fullBody = JSON.parse(JSON.stringify(responsesRequestBody(this.adapter.configuration, request, request.messages)));
710
724
  if (responsesContinuationEnabled(this.adapter.configuration)) {
711
725
  const incremental = incrementalResponsesInput(this.state, fullBody);
712
726
  if (incremental !== undefined) {
@@ -875,7 +889,7 @@ function responsesRequestBody(configuration, request, messages) {
875
889
  type: 'tool_search',
876
890
  execution: 'client',
877
891
  description: tool.description,
878
- parameters: strictFunctionSchema(tool.inputSchema)
892
+ parameters: stableToolSchema(strictFunctionSchema(stableToolSchema(tool.inputSchema)))
879
893
  }
880
894
  : responsesFunctionTool(tool));
881
895
  if (configuration.responsesToolNamespace && tools.length > 0) {
@@ -948,27 +962,13 @@ function incrementalResponsesInput(state, current) {
948
962
  if (current.input.length < baseline.length)
949
963
  return undefined;
950
964
  for (let index = 0; index < baseline.length; index++)
951
- if (!isDeepStrictEqual(baseline[index], current.input[index]))
965
+ if (JSON.stringify(baseline[index]) !== JSON.stringify(current.input[index]))
952
966
  return undefined;
953
967
  return current.input.slice(baseline.length);
954
968
  }
955
969
  function responsesContinuationPropertiesMatch(previous, current) {
956
- const properties = [
957
- 'model',
958
- 'service_tier',
959
- 'instructions',
960
- 'tools',
961
- 'tool_choice',
962
- 'max_output_tokens',
963
- 'parallel_tool_calls',
964
- 'reasoning',
965
- 'store',
966
- 'stream',
967
- 'include',
968
- 'prompt_cache_key',
969
- 'text'
970
- ];
971
- return properties.every((property) => isDeepStrictEqual(previous[property], current[property]));
970
+ return (JSON.stringify({ ...previous, input: undefined }) ===
971
+ JSON.stringify({ ...current, input: undefined }));
972
972
  }
973
973
  function clearResponsesContinuation(state) {
974
974
  delete state.lastRequest;
@@ -1508,11 +1508,11 @@ function responsesHeaders(credential, promptCacheKey, turnState) {
1508
1508
  authorization: `Bearer ${credential.bearer.reveal()}`,
1509
1509
  'content-type': 'application/json',
1510
1510
  accept: 'text/event-stream',
1511
+ 'x-client-request-id': randomUUID(),
1511
1512
  ...(promptCacheKey
1512
1513
  ? {
1513
1514
  'session-id': promptCacheKey,
1514
- 'thread-id': promptCacheKey,
1515
- 'x-client-request-id': promptCacheKey
1515
+ 'thread-id': promptCacheKey
1516
1516
  }
1517
1517
  : {})
1518
1518
  });
@@ -1927,7 +1927,7 @@ function responsesFunctionTool(tool) {
1927
1927
  type: 'function',
1928
1928
  name: tool.name,
1929
1929
  description: tool.description,
1930
- parameters: strict ? strictFunctionSchema(tool.inputSchema) : tool.inputSchema,
1930
+ parameters: stableToolSchema(strict ? strictFunctionSchema(stableToolSchema(tool.inputSchema)) : tool.inputSchema),
1931
1931
  strict
1932
1932
  };
1933
1933
  }
@@ -1,2 +1 @@
1
1
  export declare function compactionPrompt(): string;
2
- export declare function highDensityCompactionPrompt(): string;
@@ -24,59 +24,3 @@ Remove aggressively:
24
24
 
25
25
  Use compact Markdown with these headings when applicable: Objective, Instructions and constraints, Decisions and facts, Completed work, Verification and evidence, Active state, Open issues and risks, Next action. Omit empty headings. Prefer precise paths, IDs, error codes, and short evidence over narrative. Output only the handoff.`;
26
26
  }
27
- export function highDensityCompactionPrompt() {
28
- return `Compile the visible conversation into Textual Continuation IR that lets the next agent continue correctly after the transcript is discarded. Transfer state; do not recap, address the user, call tools, advance the task, or invent facts.
29
-
30
- Optimize for continuation fidelity first and density second. Prefer sufficient continuation state over brevity; never omit information required to resume correctly.
31
-
32
- ## Output contract
33
- Use these English section names when applicable; omit empty sections:
34
- 1. Goal contract
35
- 2. Current state
36
- 3. Settled matters
37
- 4. Necessary evidence
38
- 5. Open loops
39
-
40
- IR grammar and vocabulary are English: headings, keys, enums, state atoms, and control terms. Natural-language values may follow the task's dominant language. Preserve exact user-designated text and established domain terms; do not translate identifiers or invent localized IR terminology.
41
-
42
- Prefer records over prose:
43
- <object> | <key>=<value> | <key>=<value>
44
- <event/condition> -> <object>.<key>=<value> | <effect>
45
-
46
- Use | for parallel facts, = for current state, and -> only for causality, invalidation, or transition. Keep enums lowercase and semantic: required, forbidden, pending; never YES/NO. Omit empty or irrelevant unknown fields. Keep pending, not run, unverified, and failed distinct. Use a sentence only when records cannot express a multi-object cause, conflict, authorization, or safety boundary unambiguously.
47
-
48
- Examples:
49
- src/discount.js | cause=verified | edit=pending | tests=not run
50
- patch(src/discount.js) -> prior_read=stale | next=re-evidence
51
- authorization | action=<exact> | scope=<exact> | lifetime=<exact> | status=active
52
-
53
- ## Compiler
54
- 1. Resolve intent. The latest user correction overrides older intent. Extract the primary objective, current deliverable, completion criteria, non-goals, and boundaries.
55
- 2. Reduce time. Convert before -> event -> after into the latest evidenced state. Source mutation or base/branch change makes only affected evidence stale. History replacement invalidates Responses continuation, not factual evidence. Without an invalidating event, a verified claim remains current.
56
- 3. Build objects. Emit one current record per file, symbol, feature, migration, test, worktree axis, selected Skill, Todo/plan item, external decision, or external side effect. Deduplicate equivalent facts.
57
- 4. Separate status axes when needed:
58
- - completion=pending|in_progress|completed|blocked
59
- - epistemic=observed|verified|inferred|proposed|disproved
60
- - freshness=current|stale|superseded
61
- - scope=turn|worktree|base|branch|external
62
- 5. Bind evidence to the claim it proves. Prefer a verified conclusion over raw snapshots or intermediate hypotheses.
63
- 6. Select under budget. Protect, in order: objective, progress, boundaries and authorization, non-repeatable side effects, open loops, then necessary evidence. Remove provenance and recoverable detail only after those remain sufficient.
64
-
65
- ## Retention rules
66
- - Preserve active caller/user instructions outside filesystem-loaded AGENTS.md, exact authorization boundaries, confirmed decisions and exclusions, completed edits and side effects, verification actually run, reproducible failures, failed approaches that constrain the next step, unresolved risks, and the smallest established next action.
67
- - Authorization is valid only for its exact action, scope, and lifetime in the active request. Completed or superseded scopes cannot revive it. Object state, completion, tool availability, and convenience never imply permission.
68
- - Preserve decisive values, or an exact artifact path/command that deterministically recovers them, when a result controls later work. Counts or schemas alone are insufficient.
69
- - Preserve paths, symbols, APIs, tools, models, providers, JSON keys, protocol fields, commands, arguments, error codes, commit hashes, call IDs, sequences, versions, numbers, and user-designated text verbatim.
70
- - Preserve selected Skill names with only continuation-critical constraints. Preserve Todo/plan revision, item/status, and current minimum step.
71
- - Keep observed HEAD, validated base, target commit, and current worktree as separate axes. Evidence updates only what it directly proves; cross-axis, cross-base, and cross-branch inference remains unverified.
72
- - If evidence conflicts, use state=conflict | candidates=<short values> | next=re-evidence. Never choose a side without evidence.
73
-
74
- ## Exclusions
75
- Remove greetings, progress chatter, chronology that does not affect current state, duplicate facts, superseded hypotheses without remaining constraints, long logs, source bodies, verbose payloads, cheap-to-recover content, local-only integrity hashes, hidden reasoning, secrets, credentials, and unrelated content.
76
-
77
- Remove user/project/directory AGENTS.md text and constraints derived only from those files; the next Execution reloads them. Remove retained process/Subagent inventory, IDs, status, commands, cwd, and reuse instructions; Runtime reloads them.
78
-
79
- Before emitting, verify that objective, current progress, authorization/boundaries, completed versus unverified work, decisive evidence, open loops, and next action are explicit and non-contradictory.
80
-
81
- Output only compact Markdown IR.`;
82
- }
@@ -13,7 +13,6 @@ interface SystemPromptInput {
13
13
  workspace: string;
14
14
  tools: readonly string[];
15
15
  directTools?: readonly string[];
16
- highDensityCompaction?: boolean;
17
16
  extension?: string | undefined;
18
17
  }
19
18
  export declare function buildSystemPrompt(input: SystemPromptInput): string;
@@ -1,5 +1,5 @@
1
1
  import { renderPromptSections } from '../extensions/prompt-composition.js';
2
- import { compactionPrompt, highDensityCompactionPrompt } from './compact.js';
2
+ import { compactionPrompt } from './compact.js';
3
3
  import { environmentPrompt, identityPrompt, instructionPriorityPrompt, workspaceDisciplinePrompt } from './core.js';
4
4
  import { modePrompt } from './modes.js';
5
5
  import { outputStylePrompt } from './output.js';
@@ -36,14 +36,7 @@ export function buildSystemPromptSections(input) {
36
36
  questionAvailable: toolNames.has('question')
37
37
  })
38
38
  ],
39
- [
40
- 'compaction',
41
- input.mode === 'compact'
42
- ? input.highDensityCompaction
43
- ? highDensityCompactionPrompt()
44
- : compactionPrompt()
45
- : ''
46
- ],
39
+ ['compaction', input.mode === 'compact' ? compactionPrompt() : ''],
47
40
  ['subagent', toolNames.has('agent_start') ? subagentPrompt : ''],
48
41
  [
49
42
  'caller-instructions',
@@ -3,9 +3,8 @@ import type { CompactHistoryMessage, CompactUserInput } from '../../session/json
3
3
  export declare const DEFAULT_COMPACTION_FOCUS = "Create a durable continuation state for the next model invocation.";
4
4
  export declare function buildNativeCompactionMessages(messages: readonly ModelMessage[], input?: {
5
5
  focus?: string;
6
- highDensityCompaction?: boolean;
7
6
  }): ModelMessage[];
8
- export declare function buildCompactionMessages(evidence: string, highDensityCompaction?: boolean): ModelMessage[];
7
+ export declare function buildCompactionMessages(evidence: string): ModelMessage[];
9
8
  export declare function buildCompactionInput(messages: readonly ModelMessage[], input: {
10
9
  focus?: string;
11
10
  maxTokens: number;
@@ -1,9 +1,9 @@
1
1
  import { countModelTokens, truncateModelText } from '../token-budget.js';
2
- import { compactionPrompt, highDensityCompactionPrompt } from '../../prompts/compact.js';
2
+ import { compactionPrompt } from '../../prompts/compact.js';
3
3
  export const DEFAULT_COMPACTION_FOCUS = 'Create a durable continuation state for the next model invocation.';
4
4
  export function buildNativeCompactionMessages(messages, input = {}) {
5
5
  const focus = input.focus?.trim();
6
- const contract = input.highDensityCompaction ? highDensityCompactionPrompt() : compactionPrompt();
6
+ const contract = compactionPrompt();
7
7
  return [
8
8
  ...messages
9
9
  .filter((message) => message.contextKind !== 'private')
@@ -21,13 +21,13 @@ export function buildNativeCompactionMessages(messages, input = {}) {
21
21
  }
22
22
  ];
23
23
  }
24
- export function buildCompactionMessages(evidence, highDensityCompaction = false) {
24
+ export function buildCompactionMessages(evidence) {
25
25
  return [
26
26
  { role: 'user', content: evidence, contextKind: 'contextual' },
27
27
  {
28
28
  role: 'user',
29
29
  contextKind: 'compact_control',
30
- content: highDensityCompaction ? highDensityCompactionPrompt() : compactionPrompt()
30
+ content: compactionPrompt()
31
31
  }
32
32
  ];
33
33
  }
@@ -109,7 +109,6 @@ export function createSessionOpener(services) {
109
109
  .filter((tool) => !tool.name.startsWith('mcp__') &&
110
110
  allowsDirectTool(tool, availableTools.some((candidate) => candidate.name === 'code_exec')))
111
111
  .map((tool) => tool.name),
112
- highDensityCompaction: selectedModel.highDensityCompaction,
113
112
  extension: [
114
113
  executionSystemInstruction,
115
114
  skillCatalog,
@@ -1031,7 +1030,6 @@ export function createSessionOpener(services) {
1031
1030
  if (!coreExtensions.hooks.hasInterceptors('compact.generate'))
1032
1031
  return undefined;
1033
1032
  const summaryTokenBudget = Math.min(10_000, selectedModel.maxOutputTokens, Math.max(1, Math.floor(selectedModel.contextWindowTokens / 4)));
1034
- const highDensityCompaction = selectedModel.highDensityCompaction;
1035
1033
  let diagnosticReports = 0;
1036
1034
  let acceptsDiagnostics = true;
1037
1035
  try {
@@ -1041,7 +1039,7 @@ export function createSessionOpener(services) {
1041
1039
  messages: compactExtensionMessages(history),
1042
1040
  ...(focus === undefined ? {} : { focus }),
1043
1041
  summaryTokenBudget,
1044
- getPrompt: () => buildCompactionMessages('', highDensityCompaction)[1].content,
1042
+ getPrompt: () => buildCompactionMessages('')[1].content,
1045
1043
  reportDiagnostic: async (value) => {
1046
1044
  if (!acceptsDiagnostics || ++diagnosticReports > 8)
1047
1045
  return;
@@ -1082,17 +1080,16 @@ export function createSessionOpener(services) {
1082
1080
  const compactHistory = [...messages];
1083
1081
  const projectedHistory = normalizeMessagesForModel(insertContextualWorldState(compactHistory, contextualWorldState.entries), { supportsImages: false });
1084
1082
  const nativeMessages = buildNativeCompactionMessages(projectedHistory, {
1085
- focus: compactFocus,
1086
- highDensityCompaction: selectedModel.highDensityCompaction
1083
+ focus: compactFocus
1087
1084
  });
1088
- const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages('', selectedModel.highDensityCompaction), executionTools);
1085
+ const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages(''), executionTools);
1089
1086
  const compactInputTokens = selectedModel.contextWindowTokens - compactOverheadTokens;
1090
1087
  if (compactInputTokens <= 0)
1091
1088
  throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Compaction prompt exceeds the selected model context window.');
1092
1089
  const fallbackMessages = () => buildCompactionMessages(buildCompactionInput(compactHistory, {
1093
1090
  focus: compactFocus,
1094
1091
  maxTokens: compactInputTokens
1095
- }), selectedModel.highDensityCompaction);
1092
+ }));
1096
1093
  const useNative = shouldUseNativeCompaction({
1097
1094
  estimatedRequestTokens: estimatedRequestTokens(compactSystemPrompt, nativeMessages, executionTools),
1098
1095
  contextWindowTokens: selectedModel.contextWindowTokens,
@@ -1138,10 +1135,9 @@ export function createSessionOpener(services) {
1138
1135
  const compactSystemPrompt = await operation((call) => coreExtensions.compose(promptComposition(executionTools, 'run'), call));
1139
1136
  const projectedHistory = normalizeMessagesForModel(insertContextualWorldState(compactHistory, contextualWorldState.entries), { supportsImages: false });
1140
1137
  const nativeCompactMessages = buildNativeCompactionMessages(projectedHistory, {
1141
- ...(input.focus === undefined ? {} : { focus: input.focus }),
1142
- highDensityCompaction: selectedModel.highDensityCompaction
1138
+ ...(input.focus === undefined ? {} : { focus: input.focus })
1143
1139
  });
1144
- const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages('', selectedModel.highDensityCompaction), executionTools);
1140
+ const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages(''), executionTools);
1145
1141
  const compactInputTokens = Math.max(0, selectedModel.contextWindowTokens - compactOverheadTokens);
1146
1142
  let usingNativeMessages = shouldUseNativeCompaction({
1147
1143
  estimatedRequestTokens: estimatedRequestTokens(compactSystemPrompt, nativeCompactMessages, executionTools),
@@ -1153,7 +1149,7 @@ export function createSessionOpener(services) {
1153
1149
  const fallbackMessages = () => buildCompactionMessages(buildCompactionInput(compactHistory, {
1154
1150
  ...(input.focus === undefined ? {} : { focus: input.focus }),
1155
1151
  maxTokens: Math.max(1, compactInputTokens)
1156
- }), selectedModel.highDensityCompaction);
1152
+ }));
1157
1153
  let compactMessages = usingNativeMessages
1158
1154
  ? nativeCompactMessages
1159
1155
  : fallbackMessages();
@@ -31,7 +31,9 @@ export const agentStartToolDefinition = {
31
31
  export function agentStartToolDefinitionWithModels(models) {
32
32
  if (models.length === 0)
33
33
  return agentStartToolDefinition;
34
- const modelLines = models.map((model) => {
34
+ const modelLines = [...models]
35
+ .sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0))
36
+ .map((model) => {
35
37
  const efforts = model.reasoningEfforts.length === 0
36
38
  ? ''
37
39
  : `; reasoning efforts: ${model.reasoningEfforts.join(', ')}`;
@@ -7,6 +7,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
7
7
  import { MarAgentError, describeMarAgentError } from '../../error.js';
8
8
  import { directLocalProcessExecutor, signalProcessTree } from '../../host/local-process-executor.js';
9
9
  import { allowsDirectTool, allowsToolCaller } from '../callers.js';
10
+ import { orderToolDefinitions, stableToolSchema, stableToolDefinition } from '../definition-projection.js';
10
11
  import { CodeCells } from './cells.js';
11
12
  import { newObservationId, renderObservations } from './observations.js';
12
13
  import { parseCodeModeStateMutations } from './state.js';
@@ -100,16 +101,18 @@ export const codeExecWaitToolDefinition = {
100
101
  /** Deferred definitions travel in search results, not in the stable Code Mode description. */
101
102
  export function projectCodeModeTools(tools, residentTools = tools) {
102
103
  const codeModeEnabled = tools.some((tool) => tool.name === 'code_exec' && allowsToolCaller(tool, 'direct'));
103
- const direct = tools.filter((tool) => allowsDirectTool(tool, codeModeEnabled));
104
+ const direct = tools
105
+ .filter((tool) => allowsDirectTool(tool, codeModeEnabled))
106
+ .map(stableToolDefinition);
104
107
  if (!codeModeEnabled)
105
108
  return direct;
106
- const programmatic = residentTools.filter((tool) => allowsToolCaller(tool, 'programmatic'));
109
+ const programmatic = orderToolDefinitions(residentTools.filter((tool) => allowsToolCaller(tool, 'programmatic')));
107
110
  const directory = JSON.stringify({
108
111
  callableNames: programmatic.map((tool) => tool.name),
109
112
  programmaticOnly: programmatic.map((tool) => ({
110
113
  name: tool.name,
111
114
  description: tool.description,
112
- inputSchema: tool.inputSchema,
115
+ inputSchema: stableToolSchema(tool.inputSchema),
113
116
  inputMode: tool.inputMode ?? 'json'
114
117
  }))
115
118
  });
@@ -129,14 +132,16 @@ export function projectCodeModeSearchResult(message, codeModeEnabled) {
129
132
  if (!codeModeEnabled)
130
133
  return {
131
134
  ...message,
132
- toolSearchTools: message.toolSearchTools.filter((tool) => allowsToolCaller(tool, 'direct'))
135
+ toolSearchTools: message.toolSearchTools
136
+ .filter((tool) => allowsToolCaller(tool, 'direct'))
137
+ .map(stableToolDefinition)
133
138
  };
134
139
  const tools = message.toolSearchTools.map((tool) => ({
135
140
  name: tool.name,
136
141
  description: allowsToolCaller(tool, 'programmatic')
137
142
  ? `Call only as tools.${tool.name} inside code_exec, never as a direct model tool.\n${tool.description}`
138
143
  : tool.description,
139
- inputSchema: tool.inputSchema,
144
+ inputSchema: stableToolSchema(tool.inputSchema),
140
145
  inputMode: tool.inputMode ?? 'json',
141
146
  caller: allowsToolCaller(tool, 'programmatic') ? 'programmatic' : 'direct'
142
147
  }));
@@ -0,0 +1,7 @@
1
+ import type { ClientToolDefinition } from './contracts.js';
2
+ /** Stable model declarations, not a serializer for historical messages or tool results. */
3
+ export declare function stableToolSchema<T>(schema: T): T;
4
+ export declare function stableToolDefinition(definition: ClientToolDefinition): ClientToolDefinition;
5
+ export declare function orderToolDefinitions<T extends {
6
+ readonly name: string;
7
+ }>(definitions: readonly T[]): T[];
@@ -0,0 +1,26 @@
1
+ /** Stable model declarations, not a serializer for historical messages or tool results. */
2
+ export function stableToolSchema(schema) {
3
+ return stableSchemaValue(schema);
4
+ }
5
+ function stableSchemaValue(value) {
6
+ if (Array.isArray(value))
7
+ return value.map(stableSchemaValue);
8
+ if (value === null || typeof value !== 'object')
9
+ return value;
10
+ const prototype = Object.getPrototypeOf(value);
11
+ if (prototype !== Object.prototype && prototype !== null)
12
+ return value;
13
+ const fields = value;
14
+ return Object.fromEntries(Object.keys(fields)
15
+ .sort()
16
+ .map((key) => [key, stableSchemaValue(fields[key])]));
17
+ }
18
+ export function stableToolDefinition(definition) {
19
+ return {
20
+ ...definition,
21
+ inputSchema: stableToolSchema(definition.inputSchema)
22
+ };
23
+ }
24
+ export function orderToolDefinitions(definitions) {
25
+ return [...definitions].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
26
+ }
@@ -2,6 +2,7 @@ import { MarAgentError } from '../../error.js';
2
2
  import { countModelTokens } from '../../runtime/token-budget.js';
3
3
  import { projectCodeModeSearchResult, projectCodeModeTools } from '../code-mode/index.js';
4
4
  import { assertMcpToolCatalog } from './catalog.js';
5
+ import { orderToolDefinitions, stableToolDefinition } from '../definition-projection.js';
5
6
  export const TOOL_SEARCH_LIMITS = Object.freeze({
6
7
  defaultLimit: 5,
7
8
  maximumLimit: 20,
@@ -65,7 +66,11 @@ export class DeferredMcpRegistry {
65
66
  }
66
67
  update(catalog, eligible, options = {}) {
67
68
  assertMcpToolCatalog(catalog, options);
68
- this.#catalog = { ...catalog, tools: catalog.tools.filter(eligible) };
69
+ this.#catalog = {
70
+ ...catalog,
71
+ servers: [...catalog.servers].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
72
+ tools: catalog.tools.filter(eligible)
73
+ };
69
74
  const validSession = new Set(this.#catalog.tools
70
75
  .filter((tool) => tool.lifetime === 'session' && tool.exposure === 'deferred')
71
76
  .map((tool) => tool.definition.name));
@@ -128,12 +133,12 @@ export class DeferredMcpRegistry {
128
133
  });
129
134
  }
130
135
  projectDirect(base) {
131
- return [
136
+ return orderToolDefinitions([
132
137
  ...base,
133
138
  ...this.#catalog.tools
134
139
  .filter((tool) => tool.exposure === 'direct')
135
140
  .map((tool) => tool.definition)
136
- ];
141
+ ]).map(stableToolDefinition);
137
142
  }
138
143
  project(base, selection = this.#selected, executionSelection = this.#executionSelected) {
139
144
  const deferred = new Map(this.#catalog.tools
@@ -145,7 +150,10 @@ export class DeferredMcpRegistry {
145
150
  .map((name) => deferred.get(name))
146
151
  .filter((tool) => tool?.lifetime === 'execution')
147
152
  ];
148
- return [...this.projectDirect(base), ...active.map((tool) => tool.definition)];
153
+ return [
154
+ ...this.projectDirect(base),
155
+ ...active.map((tool) => stableToolDefinition(tool.definition))
156
+ ];
149
157
  }
150
158
  search(query, limit) {
151
159
  const exact = this.#catalog.tools.find((tool) => tool.definition.name === query);
package/dist/cli/main.js CHANGED
@@ -20,7 +20,7 @@ Usage:
20
20
  maragent run <prompt> [--reasoning-effort EFFORT] [--format text|json|stream-json] [--codex-auth-file PATH]
21
21
  maragent resume [sessionId] [--reasoning-effort EFFORT] [--codex-auth-file PATH]
22
22
  maragent sessions list|delete <sessionId>
23
- maragent models list|add|edit|remove|test [id] [--default-reasoning-effort EFFORT] [--code-mode|--no-code-mode] [--high-density-compaction|--no-high-density-compaction] [--fast-mode|--no-fast-mode] [--codex-auth-file PATH]
23
+ maragent models list|add|edit|remove|test [id] [--default-reasoning-effort EFFORT] [--code-mode|--no-code-mode] [--fast-mode|--no-fast-mode] [--codex-auth-file PATH]
24
24
  `;
25
25
  function takeOption(arguments_, name) {
26
26
  const index = arguments_.indexOf(name);
@@ -63,6 +63,9 @@ async function readApiKey() {
63
63
  return value.trim();
64
64
  }
65
65
  async function modelsCommand(home, arguments_, codexAuthFile) {
66
+ if (arguments_.includes('--high-density-compaction') ||
67
+ arguments_.includes('--no-high-density-compaction'))
68
+ throw new MarAgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', 'High-density compaction is no longer a model option.');
66
69
  if (arguments_.includes('--responses-encoding'))
67
70
  throw new MarAgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', 'Responses encoding is no longer configurable.');
68
71
  const action = arguments_[1];
@@ -164,8 +167,8 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
164
167
  : required(existing?.apiKey?.reveal(), '--api-key-stdin'))
165
168
  }),
166
169
  modelId: required(option('--model', existing?.modelId), '--model'),
167
- contextWindowTokens: integer('--context', existing?.contextWindowTokens ?? 200_000),
168
- maxOutputTokens: integer('--max-output', existing?.maxOutputTokens ?? 16_384),
170
+ contextWindowTokens: integer('--context', existing?.contextWindowTokens ?? 256_000),
171
+ maxOutputTokens: integer('--max-output', existing?.maxOutputTokens ?? 32_000),
169
172
  inputCapabilities: booleanFlag(arguments_, '--image', '--no-image', existing?.inputCapabilities.includes('IMAGE') ?? false)
170
173
  ? ['TEXT', 'IMAGE']
171
174
  : ['TEXT'],
@@ -179,7 +182,6 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
179
182
  : parseReasoningEffort(defaultReasoningEffortOption, '--default-reasoning-effort'),
180
183
  titleGeneration: existing?.titleGeneration ?? false,
181
184
  codeMode: booleanFlag(arguments_, '--code-mode', '--no-code-mode', existing?.codeMode ?? false),
182
- highDensityCompaction: booleanFlag(arguments_, '--high-density-compaction', '--no-high-density-compaction', existing?.highDensityCompaction ?? false),
183
185
  ...(credential === 'codex-auth-file' && fastMode ? { fastMode: true } : {}),
184
186
  ...(protocol === 'OPENAI_RESPONSES' && existing?.serviceTier !== undefined
185
187
  ? {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.173",
3
+ "version": "0.9.174",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",