@myagentroam/agent 0.9.150 → 0.9.152

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,3 +1,3 @@
1
1
  import type { RolloutBudgetStage } from '../runtime/rollout-budget.js';
2
- export type ExecutionBudgetEnvironmentStage = Exclude<RolloutBudgetStage, 'normal'> | 'inactive';
2
+ export type ExecutionBudgetEnvironmentStage = Exclude<RolloutBudgetStage, 'normal' | 'exhausted'> | 'inactive';
3
3
  export declare function executionBudgetEnvironmentEntry(stage: ExecutionBudgetEnvironmentStage): string;
@@ -2,10 +2,8 @@ export function executionBudgetEnvironmentEntry(stage) {
2
2
  if (stage === 'inactive')
3
3
  return '<execution_guidance status="inactive" />';
4
4
  const privateInstruction = 'This is internal runtime guidance. Never mention the budget, thresholds, remaining amount, or budget stage in user-visible progress or the final answer.';
5
- const guidance = stage === 'exhausted'
6
- ? 'The shared rollout budget is exhausted. Do not start or continue investigation, optional work, new tool exploration, or new subagents. Return a concise final answer now using the current verified staged result. Clearly distinguish completed work from anything still incomplete, without mentioning this budget.'
7
- : stage === 'finalize'
8
- ? 'Enter finalization now. Do not start new investigation or new subagents. Complete only the current atomic change, the minimum necessary verification, and the final answer as soon as possible.'
9
- : 'Stop expanding scope and stop optional work. Prefer completing the current objective with the evidence already gathered, and avoid new investigation unless it is strictly required for correctness.';
5
+ const guidance = stage === 'finalize'
6
+ ? 'Enter finalization now. Do not start new investigation or new subagents. Complete only the current atomic change, the minimum necessary verification, and the final answer as soon as possible.'
7
+ : 'Stop expanding scope and stop optional work. Prefer completing the current objective with the evidence already gathered, and avoid new investigation unless it is strictly required for correctness.';
10
8
  return `<execution_guidance stage="${stage}">\n${privateInstruction}\n\n${guidance}\n</execution_guidance>`;
11
9
  }
@@ -34,6 +34,7 @@ export class ExecutionLoop {
34
34
  const pendingTools = [];
35
35
  let incompleteToolCall = false;
36
36
  const round = await input.prepareRound(pendingOutputContinuation);
37
+ input.rolloutBudget?.assertAvailable();
37
38
  let sawModelSemanticEvent = false;
38
39
  let requestStartedAt;
39
40
  let firstModelResponseAt;
@@ -293,11 +294,6 @@ export class ExecutionLoop {
293
294
  }
294
295
  recoveringFromContextOverflow = false;
295
296
  input.observeUsage(roundServerUsage);
296
- if (pendingTools.length === 0 && input.mailbox.hasPending()) {
297
- await input.mailbox.append();
298
- this.finalAnswer = '';
299
- continue;
300
- }
301
297
  const toolResults = await executeRoundTools(pendingTools, round.toolScope);
302
298
  for (const result of toolResults) {
303
299
  if ('images' in result && result.images?.length)
@@ -335,11 +331,12 @@ export class ExecutionLoop {
335
331
  this.finalAnswer = '';
336
332
  }
337
333
  input.rolloutBudget?.recordUsage(latestUsage);
334
+ if (!input.signal.aborted)
335
+ input.rolloutBudget?.assertAvailable();
338
336
  if (mode !== 'compact' && stop === 'max_output') {
339
337
  const needsContinuation = !sawTool || incompleteToolCall;
340
338
  if (needsContinuation) {
341
- if (maxOutputContinuationUsed ||
342
- (input.rolloutBudget?.active && input.rolloutBudget.stage === 'exhausted'))
339
+ if (maxOutputContinuationUsed)
343
340
  throw new MarAgentError('MAR_AGENT_MODEL_OUTPUT_INCOMPLETE', 'Model output reached its limit before completing.');
344
341
  maxOutputContinuationUsed = true;
345
342
  pendingOutputContinuation = {
@@ -358,6 +355,7 @@ export class ExecutionLoop {
358
355
  const aborted = toolResults.find((result) => result.failed)?.error;
359
356
  throw aborted ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.');
360
357
  }
358
+ input.rolloutBudget?.assertAvailable();
361
359
  if (input.mailbox.hasPending()) {
362
360
  await input.mailbox.append();
363
361
  this.finalAnswer = '';
@@ -19,6 +19,7 @@ export declare class RolloutBudget {
19
19
  get stage(): RolloutBudgetStage;
20
20
  recordUsage(usage: ModelUsage | undefined): number;
21
21
  discard(): void;
22
+ assertAvailable(): void;
22
23
  }
23
24
  export declare function resolveRolloutBudgetOptions(options: false | RolloutBudgetOptions | undefined): ResolvedRolloutBudgetOptions | undefined;
24
25
  export declare function createRolloutBudget(options: false | RolloutBudgetOptions | undefined): RolloutBudget | undefined;
@@ -48,6 +48,10 @@ export class RolloutBudget {
48
48
  discard() {
49
49
  this.#active = false;
50
50
  }
51
+ assertAvailable() {
52
+ if (this.stage === 'exhausted')
53
+ throw new MarAgentError('MAR_AGENT_ROLLOUT_BUDGET_EXCEEDED', 'The shared execution token budget has been exhausted.');
54
+ }
51
55
  }
52
56
  export function resolveRolloutBudgetOptions(options) {
53
57
  if (options === false)
@@ -234,7 +234,10 @@ export function createSessionOpener(services) {
234
234
  let recordedEnvironment = restored.environmentState;
235
235
  let projectedExecutionBudgetStage;
236
236
  const appendExecutionBudgetEnvironment = () => {
237
+ rolloutBudget?.assertAvailable();
237
238
  const currentStage = rolloutBudget?.active ? rolloutBudget.stage : 'inactive';
239
+ if (currentStage === 'exhausted')
240
+ return;
238
241
  if (currentStage === projectedExecutionBudgetStage)
239
242
  return;
240
243
  const previousStage = projectedExecutionBudgetStage;
@@ -806,9 +809,7 @@ export function createSessionOpener(services) {
806
809
  executionEnvironment = { ...currentEnvironment, ...currentLocalTimeContext() };
807
810
  await recordEnvironment();
808
811
  }
809
- const toolsAllowed = mode !== 'compact' &&
810
- (!rolloutBudget?.active || rolloutBudget.stage !== 'exhausted');
811
- const roundTools = mode === 'compact' ? executionTools : toolsAllowed ? executionTools : [];
812
+ const roundTools = executionTools;
812
813
  const codeModeEnabled = roundTools.some((tool) => tool.name === 'code_exec');
813
814
  const residentTools = mcpRegistry.projectDirect(baseTools());
814
815
  const requestTools = (() => {
@@ -891,11 +892,7 @@ export function createSessionOpener(services) {
891
892
  system: systemPrompt,
892
893
  messages: modelMessages.map((message) => projectCodeModeSearchResult(message, codeModeEnabled)),
893
894
  tools: requestTools,
894
- ...(mode === 'compact'
895
- ? { toolChoice: 'none' }
896
- : toolsAllowed
897
- ? {}
898
- : { allowTools: false }),
895
+ ...(mode === 'compact' ? { toolChoice: 'none' } : {}),
899
896
  promptCacheKey: sessionId,
900
897
  reasoningEffort,
901
898
  ...(requestReasoningEffort === undefined ? {} : { requestReasoningEffort }),
@@ -11,6 +11,8 @@ import { contextGcBaseFromRecords, contextGcBaseSequence, contextGcMinimumUsageG
11
11
  import { beginSessionStorageTransaction, isUnjournaledTransactionFile, recoverSessionStorageTransaction } from './transaction-recovery.js';
12
12
  const MAX_SEGMENT_BYTES = 16 * 1024 * 1024;
13
13
  const REVERSE_READ_CHUNK_BYTES = 64 * 1024;
14
+ const MAX_HISTORY_PAGE_FILES = 32;
15
+ const MAX_HISTORY_PAGE_OFFSETS = 128;
14
16
  const MAX_INPUT_IMAGES = 5;
15
17
  const MAX_INPUT_IMAGE_BYTES = 5 * 1024 * 1024;
16
18
  const MAX_INPUT_IMAGES_BYTES = 10 * 1024 * 1024;
@@ -161,7 +163,8 @@ export class JsonlSessionStore {
161
163
  recordWrites: new Map(),
162
164
  writerLeases: new Map(),
163
165
  segments: new Map(),
164
- executionModelReads: new Map()
166
+ executionModelReads: new Map(),
167
+ historyPagePositions: new Map()
165
168
  };
166
169
  }
167
170
  /** Merge dirty metadata with disk membership; only createdIds publish locally new entries. */
@@ -1171,7 +1174,7 @@ export class JsonlSessionStore {
1171
1174
  if (segment !== undefined && segment.firstSequence >= before)
1172
1175
  continue;
1173
1176
  const needed = limit - page.length;
1174
- const window = await readRecordPageFromTail(path, before, needed);
1177
+ const window = await readRecordPageFromTail(path, before, needed, this.#shared.historyPagePositions);
1175
1178
  page.unshift(...window.records);
1176
1179
  if (window.hasEarlier) {
1177
1180
  hasEarlier = true;
@@ -2338,9 +2341,9 @@ async function repairSegmentTail(path) {
2338
2341
  await handle.close();
2339
2342
  }
2340
2343
  }
2341
- async function readRecordPageFromTail(path, beforeSequence, limit) {
2344
+ async function readRecordPageFromTail(path, beforeSequence, limit, positions) {
2342
2345
  const newestFirst = [];
2343
- for await (const record of readRecordsFromTail(path)) {
2346
+ for await (const record of readRecordsFromTail(path, { beforeSequence, limit, positions })) {
2344
2347
  if (!(record.sequence < beforeSequence))
2345
2348
  continue;
2346
2349
  newestFirst.push(record);
@@ -2353,14 +2356,42 @@ async function readRecordPageFromTail(path, beforeSequence, limit) {
2353
2356
  };
2354
2357
  }
2355
2358
  /** Decode complete records newest-first, releasing the file as soon as a reader stops. */
2356
- async function* readRecordsFromTail(path) {
2359
+ async function* readRecordsFromTail(path, page) {
2357
2360
  const handle = await open(path, 'r');
2358
2361
  try {
2359
- const size = (await handle.stat()).size;
2362
+ const metadata = await handle.stat({ bigint: true });
2363
+ const size = Number(metadata.size);
2360
2364
  let position = size;
2365
+ let cached;
2366
+ if (page !== undefined) {
2367
+ const signature = `${metadata.dev}:${metadata.ino}:${metadata.size}:${metadata.mtimeNs}:${metadata.ctimeNs}`;
2368
+ const previous = page.positions.get(path);
2369
+ cached = previous?.signature === signature ? previous : { signature, offsets: new Map() };
2370
+ page.positions.delete(path);
2371
+ page.positions.set(path, cached);
2372
+ if (page.positions.size > MAX_HISTORY_PAGE_FILES)
2373
+ page.positions.delete(page.positions.keys().next().value);
2374
+ let nearestSequence = Number.POSITIVE_INFINITY;
2375
+ for (const [sequence, offset] of cached.offsets) {
2376
+ if (sequence >= page.beforeSequence && sequence < nearestSequence) {
2377
+ nearestSequence = sequence;
2378
+ position = offset;
2379
+ }
2380
+ }
2381
+ }
2382
+ let accepted = 0;
2383
+ const remember = (record, offset) => {
2384
+ if (page === undefined || cached === undefined || record.sequence >= page.beforeSequence)
2385
+ return;
2386
+ if (++accepted !== page.limit)
2387
+ return;
2388
+ cached.offsets.set(record.sequence, offset);
2389
+ if (cached.offsets.size > MAX_HISTORY_PAGE_OFFSETS)
2390
+ cached.offsets.delete(cached.offsets.keys().next().value);
2391
+ };
2361
2392
  let pendingLineParts = [];
2362
2393
  let invalidFinalLineMayBeIgnored = false;
2363
- let inspectedFileEnd = false;
2394
+ let inspectedFileEnd = position !== size;
2364
2395
  const decode = (line, allowInvalidFinalLine) => {
2365
2396
  if (line.length === 0)
2366
2397
  return undefined;
@@ -2402,8 +2433,10 @@ async function* readRecordsFromTail(path) {
2402
2433
  invalidFinalLineMayBeIgnored = false;
2403
2434
  pendingLineParts = [];
2404
2435
  lineEnd = index;
2405
- if (record !== undefined)
2436
+ if (record !== undefined) {
2437
+ remember(record, start + index + 1);
2406
2438
  yield record;
2439
+ }
2407
2440
  }
2408
2441
  if (lineEnd > 0)
2409
2442
  pendingLineParts.unshift(Buffer.from(value.subarray(0, lineEnd)));
@@ -2411,8 +2444,10 @@ async function* readRecordsFromTail(path) {
2411
2444
  }
2412
2445
  if (pendingLineParts.length > 0) {
2413
2446
  const record = decode(Buffer.concat(pendingLineParts), invalidFinalLineMayBeIgnored);
2414
- if (record !== undefined)
2447
+ if (record !== undefined) {
2448
+ remember(record, 0);
2415
2449
  yield record;
2450
+ }
2416
2451
  }
2417
2452
  }
2418
2453
  finally {
@@ -50,7 +50,7 @@ export const execToolDefinition = {
50
50
  },
51
51
  cwd: {
52
52
  type: 'string',
53
- description: 'Optional relative/absolute start directory; defaults to workspace.'
53
+ description: 'Optional relative/absolute start directory; defaults to workspace. Command-internal relative paths resolve from this directory.'
54
54
  },
55
55
  timeoutMs: { type: 'integer', minimum: 1, maximum: TOOL_EXECUTION_LIMITS.execMaxTimeoutMs },
56
56
  yieldMs: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.150",
3
+ "version": "0.9.152",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",