@myagentroam/agent 0.9.74 → 0.9.76

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.
package/dist/cli/main.js CHANGED
@@ -19,7 +19,7 @@ Usage:
19
19
  maragent run <prompt> [--reasoning-effort EFFORT] [--format text|json|stream-json] [--codex-auth-file PATH]
20
20
  maragent resume [sessionId] [--reasoning-effort EFFORT] [--codex-auth-file PATH]
21
21
  maragent sessions list|delete <sessionId>
22
- maragent models list|add|edit|remove|test [id] [--default-reasoning-effort EFFORT] [--code-mode|--no-code-mode] [--responses-encoding standard|lite] [--codex-auth-file PATH]
22
+ maragent models list|add|edit|remove|test [id] [--default-reasoning-effort EFFORT] [--code-mode|--no-code-mode] [--high-density-compaction|--no-high-density-compaction] [--responses-encoding standard|lite] [--codex-auth-file PATH]
23
23
  `;
24
24
  function takeOption(arguments_, name) {
25
25
  const index = arguments_.indexOf(name);
@@ -181,6 +181,7 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
181
181
  : parseReasoningEffort(defaultReasoningEffortOption, '--default-reasoning-effort'),
182
182
  titleGeneration: existing?.titleGeneration ?? false,
183
183
  codeMode: booleanFlag(arguments_, '--code-mode', '--no-code-mode', existing?.codeMode ?? false),
184
+ highDensityCompaction: booleanFlag(arguments_, '--high-density-compaction', '--no-high-density-compaction', existing?.highDensityCompaction ?? false),
184
185
  hostedWebSearch: booleanFlag(arguments_, '--web-search', '--no-web-search', existing?.hostedWebSearch ?? false),
185
186
  ...(responsesTransport === undefined ? {} : { responsesTransport }),
186
187
  ...(responsesEncoding === undefined ? {} : { responsesEncoding }),
@@ -27,6 +27,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
27
27
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
28
28
  titleGeneration: boolean;
29
29
  codeMode: boolean;
30
+ highDensityCompaction: boolean;
30
31
  hostedWebSearch: boolean;
31
32
  responsesEncoding?: "STANDARD" | "LITE" | undefined;
32
33
  enabled: boolean;
@@ -50,6 +51,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
50
51
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
51
52
  titleGeneration: boolean;
52
53
  codeMode: boolean;
54
+ highDensityCompaction: boolean;
53
55
  hostedWebSearch: boolean;
54
56
  responsesEncoding?: "STANDARD" | "LITE" | undefined;
55
57
  enabled: boolean;
@@ -78,6 +78,7 @@ declare const rawModelSchema: z.ZodObject<{
78
78
  }>>;
79
79
  titleGeneration: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
80
80
  codeMode: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
81
+ highDensityCompaction: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
81
82
  hostedWebSearch: z.ZodBoolean;
82
83
  responsesTransport: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
83
84
  transport: z.ZodLiteral<"HTTP">;
@@ -49,6 +49,7 @@ 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),
52
53
  hostedWebSearch: z.boolean(),
53
54
  responsesTransport: rawResponsesTransportSchema.optional(),
54
55
  responsesEncoding: z.enum(['STANDARD', 'LITE']).optional(),
@@ -1 +1,2 @@
1
1
  export declare function compactionPrompt(): string;
2
+ export declare function highDensityCompactionPrompt(): string;
@@ -23,3 +23,69 @@ Remove aggressively:
23
23
 
24
24
  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.`;
25
25
  }
26
+ export function highDensityCompactionPrompt() {
27
+ return `将 visible conversation 编译为供下一 Agent 直接续作的 Textual Continuation IR;原 transcript 将丢弃。This is factual state transfer, not recap, user reply, or new work. 不调用工具、不推进任务、不虚构完成。
28
+
29
+ 目标:在 handoff budget 内最大化 continuation accuracy、信息密度、可恢复性。Token reduction 仅在不损害续作后有价值;必要时约 10K tokens 可接受。
30
+
31
+ IR 不是 prose summary。默认使用最短且无歧义的事实短语、状态原子、精确引用;仅在短语无法说清因果、冲突或边界时使用完整句。优先两种形式:
32
+ <object> | <key>=<value> | <key>=<value>
33
+ <event/condition> -> <effect> | <effect>
34
+ 例如:
35
+ src/discount.js | cause=verified | edit=pending | comment=required | tests=not run
36
+ patch(src/discount.js) -> prior_read=stale | next=re-evidence
37
+ branch changed -> inherited_evidence=unverified
38
+
39
+ 语法:
40
+ - | 并列同一 object 的独立事实;= 表示 current fact/state/constraint;-> 仅表示 cause、invalidation、transition。
41
+ - key 使用最短且无歧义的 established term;value 可为状态原子或短语;enum 默认 lowercase。
42
+ - 使用 required/forbidden/pending 等语义值,不写 YES/NO。省略无续作价值的 unknown 和空字段。
43
+ - 同一 key 只保留已归约的 current value。无法消解:state=conflict | candidates=<short values> | next=re-evidence;不得任选一方。
44
+ - transition effect 优先 <object>.<key>=<value>;object 已唯一明确时可省略,不得省略到歧义。
45
+ - exact path/symbol/ID/command/number/user-designated text 原样保留。值含 |、换行或需逐字保存时,使用紧邻 record 的 Markdown code span/block,不发明 escaping protocol。
46
+ - pending、not run、unverified、failed 不得互换。
47
+
48
+ 按需覆盖五类信息职责,空块省略。双语名称仅解释职责;输出标题只选当前任务合适的一种语言,不同时复制两套。
49
+
50
+ 1. 目标契约 / Goal contract
51
+ - 主目标、当前交付、完成标准、非目标/边界。
52
+ - direct user/caller authorization、prohibition、exclusion 保留 exact action/scope,不概括成更宽泛边界。
53
+
54
+ 2. 当前状态 / Current state
55
+ - 对象:file、symbol、feature、migration、test、worktree、process、subagent、selected Skill、Todo/plan、approval、external side effect。
56
+ - 分离 completion(pending / in_progress / completed / blocked)、epistemic(observed / verified / inferred / proposed / disproved)、freshness(current / stale / superseded)、scope(turn / worktree / base / branch / external system);仅在消歧时标注。
57
+ - 明确 completed、not started、unverified、active。相同精度下选择更短状态原子;中文、English 均可,如 根因已证、edit=pending、tests=not run。
58
+ - selected Skill | 保留仍适用的 skill name + 续作必需约束;Todo/plan | 保留 revision、item/status、当前最小 step。
59
+ - verified claim 后无 source mutation/base/branch change 等 invalidating event:freshness=current | re-diagnosis=not needed。为 patch 获取当前行上下文的 targeted reread 仍可保留,不等同重新调查已证结论。
60
+
61
+ 3. 已定事项 / Settled matters
62
+ - confirmed decision/interface/constraint + 续作仍需的最短理由。
63
+ - disproved/rejected direction + scope/evidence。
64
+ - recovery path、event shape、implementation option、next action 未经 direct evidence 前保持 proposed/unverified。
65
+
66
+ 4. 必要证据 / Necessary evidence
67
+ - 仅保留不可廉价恢复、决定后续判断、记录不可重复 side effect,或精确值影响 correctness 的 evidence;紧邻 claim,绑定 scope/freshness。
68
+ - completed check/result 若决定后续判断:保留 decisive values,或可确定恢复这些值的 exact artifact path/command;不得只留 count/schema。
69
+ - path、symbol/class/function/variable、API/tool/model/provider、JSON key/enum/protocol field、command/arguments、error code、commit hash、callId、sequence、version、number、用户指定原文:原样保留,不翻译、不猜测。
70
+ - 下一 Agent 无法使用、仅供 local storage validation 的 integrity hash:删除。
71
+
72
+ 5. 未闭环 / Open loops
73
+ - unresolved、unverified、blocker、pending approval、活动 process/subagent、Todo/plan current step、仍需 evidence。
74
+ - 只保留已确定的最小 next action + preconditions;不复制可重算的长 plan。
75
+
76
+ 编译规则:
77
+ 1. latest user intent 定目标;newer correction 覆盖 older intent。
78
+ 2. 归约 before -> event -> after;source mutation 或 base/branch change 后 affected read/conclusion stale,除非已有 newer evidence;history replacement 仅使 Responses continuation invalid,不使 factual evidence 自动失效;无 invalidating event 的 verified claim 保持 current;current 取最后已证 after-state 或 later direct observation。
79
+ 3. 每个 continuation object 一条 current record;等价事实去重。
80
+ 4. 仅保留仍约束 open work 的 settled decision/exclusion。
81
+ 5. evidence 绑定 claim;verified conclusion 优先于 raw snapshot/intermediate hypothesis。
82
+ 6. 预算受压时依次保护:目标、当前进度、边界、不可重复副作用、未闭环、必要证据;再删 provenance/可恢复实现细节。
83
+
84
+ observed HEAD、validated base、target commit、current worktree 不得折叠;evidence 只更新直接证明的 axis。跨 axis/base/branch 推导无 direct evidence 时保持 unverified。状态冲突无法归约:保留 conflict + re-evidence,不替模型选择。
85
+
86
+ 语言:标题/一般叙述跟随 latest active user instructions 和当前任务主语言;每条 record 按局部精度与密度选择中文、English 或 mixed phrasing。保留 established term/exact identifier,不为语言统一展开或翻译。English codebase 不等于 English handoff。
87
+
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
+
90
+ 只输出 compact Markdown IR。`;
91
+ }
@@ -2,7 +2,7 @@ import type { ExecutionMode } from '../sdk/types.js';
2
2
  import { type ExecutionBudgetPromptState } from './execution-budget.js';
3
3
  import { type RetainedSessionResources } from './resources.js';
4
4
  import { type CurrentAgentModel, type SubagentModelOption } from './subagent.js';
5
- export declare const MAR_AGENT_PROMPT_VERSION = "1.42";
5
+ export declare const MAR_AGENT_PROMPT_VERSION = "1.47";
6
6
  export declare function buildSystemPrompt(input: {
7
7
  mode: ExecutionMode;
8
8
  platform: string;
@@ -18,5 +18,6 @@ export declare function buildSystemPrompt(input: {
18
18
  subagentModels?: readonly SubagentModelOption[];
19
19
  retainedSessionResources?: RetainedSessionResources;
20
20
  executionBudget?: ExecutionBudgetPromptState;
21
+ highDensityCompaction?: boolean;
21
22
  extension?: string | undefined;
22
23
  }): string;
@@ -1,4 +1,4 @@
1
- import { compactionPrompt } from './compact.js';
1
+ import { compactionPrompt, highDensityCompactionPrompt } from './compact.js';
2
2
  import { environmentPrompt, identityPrompt, instructionPriorityPrompt, workspaceDisciplinePrompt } from './core.js';
3
3
  import { executionBudgetPrompt } from './execution-budget.js';
4
4
  import { modePrompt } from './modes.js';
@@ -6,7 +6,7 @@ import { outputStylePrompt } from './output.js';
6
6
  import { retainedSessionResourcesPrompt } from './resources.js';
7
7
  import { subagentModelOptionsPrompt, subagentPrompt, currentAgentModelPrompt } from './subagent.js';
8
8
  import { interactionPrompt, longRunningPrompt, safetyPrompt, toolUsagePrompt, workflowPrompt } from './workflow.js';
9
- export const MAR_AGENT_PROMPT_VERSION = '1.42';
9
+ export const MAR_AGENT_PROMPT_VERSION = '1.47';
10
10
  export function buildSystemPrompt(input) {
11
11
  const toolNames = new Set(input.tools);
12
12
  const hasLongRunningCapability = [
@@ -36,7 +36,11 @@ export function buildSystemPrompt(input) {
36
36
  questionAvailable: toolNames.has('question'),
37
37
  forceFinalize: input.executionBudget?.stage === 'exhausted'
38
38
  }),
39
- input.mode === 'compact' ? compactionPrompt() : '',
39
+ input.mode === 'compact'
40
+ ? input.highDensityCompaction
41
+ ? highDensityCompactionPrompt()
42
+ : compactionPrompt()
43
+ : '',
40
44
  toolNames.has('agent_start') ? subagentPrompt : '',
41
45
  toolNames.has('agent_start') ? currentAgentModelPrompt(input.currentAgentModel) : '',
42
46
  toolNames.has('agent_start') ? subagentModelOptionsPrompt(input.subagentModels ?? []) : '',
@@ -1,2 +1,2 @@
1
- export declare const outputStylePrompt = "# Output and communication style\nBe direct, natural, and concise. Match detail and structure to the user's needs and the task's complexity. Lead with the result; include actual verification, material risks, and unfinished work when relevant. Avoid repeating progress, dumping logs, or adding empty sections. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Never expose hidden reasoning.";
1
+ export declare const outputStylePrompt = "# Output and communication style\nBe direct, natural, and concise. Default to a brief teammate update rather than a report; expand when the user requests detail or when complexity and multiple distinct findings need grouping or explanation. Lead with the result and report only details that materially support or qualify it, including actual verification, material risks, and unfinished work when relevant. Use plain sentences or a short list for simple results. Avoid unnecessary repetition, repeating progress, dumping logs, or adding empty sections. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Never expose hidden reasoning.";
2
2
  export declare const modelOutputContinuationMessage = "Continue the previous response from exactly where it stopped. Do not repeat completed content.";
@@ -1,2 +1,2 @@
1
- export const outputStylePrompt = "# Output and communication style\nBe direct, natural, and concise. Match detail and structure to the user's needs and the task's complexity. Lead with the result; include actual verification, material risks, and unfinished work when relevant. Avoid repeating progress, dumping logs, or adding empty sections. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Never expose hidden reasoning.";
1
+ export const outputStylePrompt = '# Output and communication style\nBe direct, natural, and concise. Default to a brief teammate update rather than a report; expand when the user requests detail or when complexity and multiple distinct findings need grouping or explanation. Lead with the result and report only details that materially support or qualify it, including actual verification, material risks, and unfinished work when relevant. Use plain sentences or a short list for simple results. Avoid unnecessary repetition, repeating progress, dumping logs, or adding empty sections. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Never expose hidden reasoning.';
2
2
  export const modelOutputContinuationMessage = 'Continue the previous response from exactly where it stopped. Do not repeat completed content.';
@@ -10,7 +10,7 @@ export function toolUsagePrompt(tools) {
10
10
  if (names.has('exec'))
11
11
  clauses.push('Use exec for platform-native commands, scripts, verification, and mechanical edits. Prefer rg or rg --files for search. Batch independent lookups and already-decided commands, preserving failure ordering. Use scripts for mechanical substitutions instead of reproducing long unchanged text in a patch; inspect actual effects afterward.');
12
12
  if (names.has('read'))
13
- clauses.push('Use read to batch known files and ranges. Batch only already-located, bounded ranges that are likely to fit together; for large files or documents, locate relevant sections before reading them. Read only what can affect the next decision, continue only relevant incomplete results using returned metadata, and avoid rereading unchanged content.');
13
+ clauses.push('Use read to batch known files and ranges. Batch only already-located, bounded ranges that are likely to fit together; for large files or documents, locate relevant sections before reading them. Read only what can affect the next decision, continue only relevant incomplete results using returned metadata, and avoid rereading unchanged content. Do not edit from an incomplete oversized-line preview. Obtain exact source with exec; if it still cannot fit, use a preconditioned deterministic replacement and verify the result.');
14
14
  if (names.has('apply_patch'))
15
15
  clauses.push('Prefer apply_patch for precise local edits. After a conflict, use the reported evidence to refresh only the affected context before retrying.');
16
16
  if (names.has('apply_patch') || names.has('exec'))
@@ -22,7 +22,7 @@ export declare class CompactionOperation {
22
22
  private report;
23
23
  }
24
24
  export declare const DEFAULT_COMPACTION_FOCUS = "Create a durable continuation state for the next model invocation.";
25
- export declare function buildCompactionMessages(evidence: string): ModelMessage[];
25
+ export declare function buildCompactionMessages(evidence: string, highDensityCompaction?: boolean): ModelMessage[];
26
26
  export declare function buildCompactionInput(messages: readonly ModelMessage[], input: {
27
27
  focus?: string;
28
28
  maxTokens: number;
@@ -1,6 +1,6 @@
1
1
  import { MarAgentError } from '../error.js';
2
2
  import { countModelTokens, truncateModelText } from './token-budget.js';
3
- import { compactionPrompt } from '../prompts/compact.js';
3
+ import { compactionPrompt, highDensityCompactionPrompt } from '../prompts/compact.js';
4
4
  const COMPACTION_TIMEOUT_MS = 600_000;
5
5
  const COMPACTION_PROGRESS_INTERVAL_MS = 30_000;
6
6
  /** One deadline spans all model attempts and recovery waits for a single compaction. */
@@ -91,10 +91,13 @@ export class CompactionOperation {
91
91
  }
92
92
  }
93
93
  export const DEFAULT_COMPACTION_FOCUS = 'Create a durable continuation state for the next model invocation.';
94
- export function buildCompactionMessages(evidence) {
94
+ export function buildCompactionMessages(evidence, highDensityCompaction = false) {
95
95
  return [
96
96
  { role: 'user', content: evidence },
97
- { role: 'user', content: compactionPrompt() }
97
+ {
98
+ role: 'user',
99
+ content: highDensityCompaction ? highDensityCompactionPrompt() : compactionPrompt()
100
+ }
98
101
  ];
99
102
  }
100
103
  export function buildCompactionInput(messages, input) {
@@ -2,8 +2,12 @@ export declare const MODEL_TOKEN_ESTIMATOR: "UTF8_BYTES_V1";
2
2
  /** Local budget estimate only; provider usage remains authoritative. */
3
3
  export declare function countModelTokens(value: string): number;
4
4
  export declare function modelTokenBudgetBytes(maximumTokens: number): number;
5
- export declare function truncateModelText(value: string, maximumTokens: number, omittedLabel?: string): {
5
+ export declare function truncateModelText(value: string, maximumTokens: number, omittedLabel?: string, reported?: {
6
+ readonly originalTokenCount?: number;
7
+ readonly totalLines?: number;
8
+ }): {
6
9
  content: string;
7
10
  truncated: boolean;
8
11
  omittedTokens: number;
9
12
  };
13
+ export declare function countTextLines(value: string): number;
@@ -7,13 +7,13 @@ export function countModelTokens(value) {
7
7
  export function modelTokenBudgetBytes(maximumTokens) {
8
8
  return Math.max(0, Math.floor(maximumTokens)) * BYTES_PER_TOKEN;
9
9
  }
10
- export function truncateModelText(value, maximumTokens, omittedLabel = 'tool output') {
10
+ export function truncateModelText(value, maximumTokens, omittedLabel = 'tool output', reported) {
11
11
  const tokens = countModelTokens(value);
12
12
  if (tokens <= maximumTokens)
13
13
  return { content: value, truncated: false, omittedTokens: 0 };
14
14
  const maximumBytes = modelTokenBudgetBytes(maximumTokens);
15
15
  const bytes = Buffer.from(value, 'utf8');
16
- const warning = `Warning: truncated output (original token count: ${tokens})\nTotal output lines: ${lineCount(value)}\n\n`;
16
+ const warning = `Warning: truncated output (original token count: ${reported?.originalTokenCount ?? tokens})\nTotal output lines: ${reported?.totalLines ?? countTextLines(value)}\n\n`;
17
17
  let retainedBytes = Math.max(0, maximumBytes - 16 * BYTES_PER_TOKEN);
18
18
  for (let attempt = 0; attempt < 8; attempt++) {
19
19
  let head = Math.ceil(retainedBytes / 2);
@@ -48,7 +48,7 @@ export function truncateModelText(value, maximumTokens, omittedLabel = 'tool out
48
48
  omittedTokens: tokens
49
49
  };
50
50
  }
51
- function lineCount(value) {
51
+ export function countTextLines(value) {
52
52
  let lines = value.length === 0 ? 0 : 1;
53
53
  for (let index = 0; index < value.length; index++)
54
54
  if (value.charCodeAt(index) === 10)
package/dist/sdk/agent.js CHANGED
@@ -12,7 +12,7 @@ import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
12
12
  import { RolloutBudget, resolveRolloutBudgetOptions } from '../runtime/rollout-budget.js';
13
13
  import { ContextGcRuntime } from '../runtime/context-gc-runtime.js';
14
14
  import { buildContextProjection } from '../runtime/context-projection.js';
15
- import { countModelTokens, truncateModelText } from '../runtime/token-budget.js';
15
+ import { countModelTokens, countTextLines, truncateModelText } from '../runtime/token-budget.js';
16
16
  import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
17
17
  import { DEFAULT_COMPACTION_FOCUS, CompactionOperation, buildCompactionInput, buildCompactionMessages, buildCompactedHistory, compactedHistoryMessages, continuationMessage } from '../runtime/compact.js';
18
18
  import { buildSystemPrompt, MAR_AGENT_PROMPT_VERSION } from '../prompts/index.js';
@@ -486,6 +486,7 @@ export async function createMarAgent(options) {
486
486
  }))
487
487
  : [],
488
488
  retainedSessionResources,
489
+ highDensityCompaction: selectedModel.highDensityCompaction,
489
490
  ...(rolloutBudget?.active
490
491
  ? {
491
492
  executionBudget: {
@@ -504,14 +505,14 @@ export async function createMarAgent(options) {
504
505
  });
505
506
  const initialSystemPrompt = buildExecutionSystemPrompt();
506
507
  if (mode === 'compact') {
507
- const compactOverheadTokens = estimatedRequestTokens(initialSystemPrompt, buildCompactionMessages(''), []);
508
+ const compactOverheadTokens = estimatedRequestTokens(initialSystemPrompt, buildCompactionMessages('', selectedModel.highDensityCompaction), []);
508
509
  const compactInputTokens = selectedModel.contextWindowTokens - compactOverheadTokens;
509
510
  if (compactInputTokens <= 0)
510
511
  throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Compaction prompt exceeds the selected model context window.');
511
512
  messages.splice(0, messages.length, ...buildCompactionMessages(buildCompactionInput(messages, {
512
513
  focus: input.prompt,
513
514
  maxTokens: compactInputTokens
514
- })));
515
+ }), selectedModel.highDensityCompaction));
515
516
  }
516
517
  await store.append(sessionId, {
517
518
  type: 'execution.header',
@@ -539,9 +540,10 @@ export async function createMarAgent(options) {
539
540
  platform: description.platform,
540
541
  workspace: logicalWorkspace,
541
542
  tools: [],
543
+ highDensityCompaction: selectedModel.highDensityCompaction,
542
544
  extension: options.systemInstruction
543
545
  });
544
- const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages(''), []);
546
+ const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages('', selectedModel.highDensityCompaction), []);
545
547
  const compactInputTokens = Math.max(0, selectedModel.contextWindowTokens - compactOverheadTokens);
546
548
  if (compactInputTokens === 0)
547
549
  throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Compaction prompt exceeds the selected model context window.');
@@ -561,7 +563,7 @@ export async function createMarAgent(options) {
561
563
  for await (const event of operation.start(selected, {
562
564
  retryState: compactionRetryState,
563
565
  system: compactSystemPrompt,
564
- messages: buildCompactionMessages(compactSource),
566
+ messages: buildCompactionMessages(compactSource, selectedModel.highDensityCompaction),
565
567
  tools: [],
566
568
  allowTools: false,
567
569
  promptCacheKey: sessionId,
@@ -930,7 +932,7 @@ export async function createMarAgent(options) {
930
932
  }
931
933
  })
932
934
  });
933
- const modelOutput = boundedModelToolOutput(output.content);
935
+ const modelOutput = boundedModelToolOutput(call.name, output.content, output.data);
934
936
  const eventSummary = output.content.slice(0, AGENT_EXECUTION_POLICY.toolEventSummaryCharacters);
935
937
  const structuredData = structuredToolEventData(call.name, output.data);
936
938
  const artifacts = structuredToolArtifacts(call.name, output.artifacts);
@@ -1660,8 +1662,17 @@ function estimatedRequestTokens(system, messages, tools, images) {
1660
1662
  return (countModelTokens(`${system}${stableJson(textMessages)}${stableJson(tools)}`) +
1661
1663
  transientImageCount * AGENT_EXECUTION_POLICY.estimatedTokensPerImage);
1662
1664
  }
1663
- function boundedModelToolOutput(value) {
1664
- const result = truncateModelText(value, AGENT_EXECUTION_POLICY.modelToolOutputTokens);
1665
+ function boundedModelToolOutput(toolName, value, data) {
1666
+ const execDiagnostics = toolName === 'exec' &&
1667
+ isRecord(data) &&
1668
+ typeof data.stdout === 'string' &&
1669
+ typeof data.stderr === 'string'
1670
+ ? {
1671
+ originalTokenCount: countModelTokens(data.stdout) + countModelTokens(data.stderr),
1672
+ totalLines: countTextLines(data.stdout) + countTextLines(data.stderr)
1673
+ }
1674
+ : undefined;
1675
+ const result = truncateModelText(value, AGENT_EXECUTION_POLICY.modelToolOutputTokens, 'tool output', execDiagnostics);
1665
1676
  return { content: result.content, truncated: result.truncated };
1666
1677
  }
1667
1678
  function stableJson(value) {
@@ -208,6 +208,30 @@ function boundedUtf8Prefix(value, maximumBytes) {
208
208
  }
209
209
  return { value: `${result}${marker}`, truncated: true };
210
210
  }
211
+ function oversizedLinePreview(value, retainedBytes, lineNumber) {
212
+ const bytes = Buffer.from(value, 'utf8');
213
+ if (retainedBytes >= bytes.length)
214
+ return { value, shownBytes: bytes.length, omittedBytes: 0 };
215
+ const headTarget = Math.floor(retainedBytes / 2);
216
+ const tailTarget = retainedBytes - headTarget;
217
+ let head = Math.min(headTarget, bytes.length);
218
+ while (head > 0 && isUtf8ContinuationByte(bytes[head]))
219
+ head--;
220
+ let tail = Math.max(head, bytes.length - tailTarget);
221
+ while (tail < bytes.length && isUtf8ContinuationByte(bytes[tail]))
222
+ tail++;
223
+ const shownBytes = head + bytes.length - tail;
224
+ const omittedBytes = bytes.length - shownBytes;
225
+ const marker = `...[${omittedBytes} bytes omitted from line ${lineNumber}]...`;
226
+ return {
227
+ value: `${bytes.toString('utf8', 0, head)}${marker}${bytes.toString('utf8', tail)}`,
228
+ shownBytes,
229
+ omittedBytes
230
+ };
231
+ }
232
+ function isUtf8ContinuationByte(value) {
233
+ return value !== undefined && (value & 0xc0) === 0x80;
234
+ }
211
235
  function formatBatchReadResult(result, index, maximumBytes) {
212
236
  const visiblePath = boundedUtf8Prefix(result.path, 128);
213
237
  const path = escapeAttribute(visiblePath.value);
@@ -265,8 +289,59 @@ function formatBatchReadResult(result, index, maximumBytes) {
265
289
  high = middle - 1;
266
290
  }
267
291
  const content = render(low);
268
- if (Buffer.byteLength(content) <= maximumBytes)
292
+ if (low > 0 && Buffer.byteLength(content) <= maximumBytes)
269
293
  return { content, previewComplete: low === lines.length };
294
+ if (lines.length > 0) {
295
+ const lineNumber = result.startLine;
296
+ const prefix = `${lineNumber}\t`;
297
+ const formattedLine = lines[0];
298
+ const sourceLine = formattedLine.startsWith(prefix)
299
+ ? formattedLine.slice(prefix.length)
300
+ : formattedLine;
301
+ const sourceLineBytes = Buffer.byteLength(sourceLine);
302
+ const renderOversizedLine = (retainedBytes) => {
303
+ const preview = oversizedLinePreview(sourceLine, retainedBytes, lineNumber);
304
+ const metadata = {
305
+ path: visiblePath.value,
306
+ ...(visiblePath.truncated ? { pathTruncated: true } : {}),
307
+ encoding: result.encoding,
308
+ newline: result.newline,
309
+ startLine: lineNumber,
310
+ endLine: lineNumber,
311
+ eof: false,
312
+ truncated: true,
313
+ previewComplete: false,
314
+ oversizedLine: {
315
+ line: lineNumber,
316
+ bytes: sourceLineBytes,
317
+ shownBytes: preview.shownBytes,
318
+ omittedBytes: preview.omittedBytes
319
+ }
320
+ };
321
+ return [
322
+ `<read_result index="${index}" path="${path}"${pathTruncatedAttribute}>`,
323
+ `${prefix}${preview.value}`,
324
+ `<read_metadata>${JSON.stringify(metadata)}</read_metadata>`,
325
+ '</read_result>'
326
+ ].join('\n');
327
+ };
328
+ let minimum = renderOversizedLine(0);
329
+ if (Buffer.byteLength(minimum) <= maximumBytes) {
330
+ let retainedLow = 0;
331
+ let retainedHigh = sourceLineBytes;
332
+ while (retainedLow < retainedHigh) {
333
+ const middle = Math.ceil((retainedLow + retainedHigh) / 2);
334
+ const candidate = renderOversizedLine(middle);
335
+ if (Buffer.byteLength(candidate) <= maximumBytes) {
336
+ retainedLow = middle;
337
+ minimum = candidate;
338
+ }
339
+ else
340
+ retainedHigh = middle - 1;
341
+ }
342
+ return { content: minimum, previewComplete: false };
343
+ }
344
+ }
270
345
  return {
271
346
  content: `<read_error index="${index}" code="MAR_AGENT_TOOL_OUTPUT_LIMIT">Model-visible read metadata exceeds this item's output share.</read_error>`,
272
347
  previewComplete: false
@@ -46,7 +46,7 @@ export const readToolDefinition = {
46
46
  }
47
47
  }
48
48
  },
49
- description: `Read one or more known text files/ranges. Strict UTF-8 with optional BOM is the default; BOM-declared UTF-16LE/UTF-16BE is supported. Invalid text returns an item error instead of replacement characters. Always pass reads; use one item for a single file and up to ${TOOL_EXECUTION_LIMITS.readMaxBatchItems} items for a batch. Batch only already-located, bounded ranges whose combined content is likely to fit the model-visible output budget; for exploration, locate relevant files, symbols, or headings first, then read the smallest useful ranges. Results retain input order, use 1-based line numbers, and include <read_metadata> with path, encoding, newline, exact visible range, eof, truncation, nextOffset, and whether previewComplete. Items fail independently and run with bounded concurrency. Each item returns at most ${TOOL_EXECUTION_LIMITS.readMaxLines} lines/${TOOL_EXECUTION_LIMITS.readMaxBytes} bytes; the structured call shares a ${TOOL_EXECUTION_LIMITS.readMaxBatchBytes}-byte budget, while ordinary model-visible content fairly shares the common tool-output budget across all items. Structured data.results items provide text as decoded source for the returned range, preserving line endings and excluding BOM; content is numbered display text. Large files support bounded range reads. If previewComplete is false, continue only the still-relevant item from nextOffset. Relative paths use the workspace; absolute paths are allowed. Prefer exec with rg for search and read for known files.`,
49
+ description: `Read one or more known text files/ranges. Strict UTF-8 with optional BOM is the default; BOM-declared UTF-16LE/UTF-16BE is supported. Invalid text returns an item error instead of replacement characters. Always pass reads; use one item for a single file and up to ${TOOL_EXECUTION_LIMITS.readMaxBatchItems} items for a batch. Batch only already-located, bounded ranges whose combined content is likely to fit the model-visible output budget; for exploration, locate relevant files, symbols, or headings first, then read the smallest useful ranges. Results retain input order, use 1-based line numbers, and include <read_metadata> with path, encoding, newline, exact visible range, eof, truncation, nextOffset, and whether previewComplete. Items fail independently and run with bounded concurrency. Each item returns at most ${TOOL_EXECUTION_LIMITS.readMaxLines} lines/${TOOL_EXECUTION_LIMITS.readMaxBytes} bytes; the structured call shares a ${TOOL_EXECUTION_LIMITS.readMaxBatchBytes}-byte budget, while ordinary model-visible content fairly shares the common tool-output budget across all items. Structured data.results items provide text as decoded source for the returned range, preserving line endings and excluding BOM; content is numbered display text. Large files support bounded range reads. If previewComplete is false and nextOffset is present, continue only the still-relevant item from nextOffset. An oversized single line instead returns a head/tail preview with no line continuation cursor; use exec to obtain exact source before editing omitted content. Relative paths use the workspace; absolute paths are allowed. Prefer exec with rg for search and read for known files.`,
50
50
  inputSchema: {
51
51
  type: 'object',
52
52
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.74",
3
+ "version": "0.9.76",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -38,6 +38,7 @@
38
38
  "build": "node ../scripts/clean-build-output.mjs dist && tsc -p tsconfig.json",
39
39
  "typecheck": "tsc -p tsconfig.json --noEmit",
40
40
  "test:unit": "vitest run --config vitest.config.ts test/unit",
41
+ "benchmark:compaction": "node test/e2e/compaction-benchmark.mjs",
41
42
  "test:performance": "vitest run --config vitest.performance.config.ts",
42
43
  "test:memory": "node --expose-gc ./node_modules/vitest/vitest.mjs run --config vitest.memory.config.ts",
43
44
  "test:e2e": "vitest run --config vitest.config.ts test/e2e --no-file-parallelism",