@myagentroam/agent 0.11.29 → 0.11.31

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,5 +1,7 @@
1
1
  export declare class AsyncQueue<T> implements AsyncIterable<T> {
2
2
  #private;
3
+ /** Best-effort live values are delivered only to an actively waiting consumer. */
4
+ pushIfWaiting(value: T): void;
3
5
  push(value: T): void;
4
6
  close(): void;
5
7
  discardIfUnobserved(): boolean;
@@ -6,6 +6,11 @@ export class AsyncQueue {
6
6
  #closed = false;
7
7
  #observed = false;
8
8
  #discarded = false;
9
+ /** Best-effort live values are delivered only to an actively waiting consumer. */
10
+ pushIfWaiting(value) {
11
+ if (this.#waiterOffset < this.#waiters.length)
12
+ this.push(value);
13
+ }
9
14
  push(value) {
10
15
  if (this.#closed || this.#discarded)
11
16
  return;
@@ -12,6 +12,7 @@ export function toolEventSummary(text) {
12
12
  /** Keep streaming deltas live-only; exec output has one durable copy in model context records. */
13
13
  export function eventForPersistence(event) {
14
14
  if (event.type === 'message.delta' ||
15
+ event.type === 'tool.output' ||
15
16
  event.type === 'input.received' ||
16
17
  (event.type === 'context.compacting' && event.async !== true && event.stage === undefined) ||
17
18
  event.type === 'model.performance' ||
@@ -91,6 +91,12 @@ export type MarAgentEvent = MarAgentEventBase & ({
91
91
  input?: unknown;
92
92
  data?: import('../tools/public-metadata.js').PublicToolMetadata;
93
93
  truncated?: boolean;
94
+ } | {
95
+ type: 'tool.output';
96
+ toolName: 'exec' | 'exec_wait';
97
+ callId: string;
98
+ parentCallId?: string;
99
+ output: import('../tools/contracts.js').ExecOutputSnapshot;
94
100
  } | {
95
101
  type: 'tool.completed';
96
102
  resultExtensions?: readonly import('../tools/result-extensions.js').ToolResultExtension[];
@@ -319,7 +319,10 @@ export function createSessionOpener(services) {
319
319
  messageSources.set(message, stored);
320
320
  }
321
321
  }
322
- queue.push(full);
322
+ if (full.type === 'tool.output')
323
+ queue.pushIfWaiting(full);
324
+ else
325
+ queue.push(full);
323
326
  compactRequests.publish(full);
324
327
  if (internal.publishHostEvents !== false) {
325
328
  try {
@@ -346,7 +349,9 @@ export function createSessionOpener(services) {
346
349
  }
347
350
  }
348
351
  });
349
- emitTail = operation.catch(() => undefined);
352
+ // Best-effort previews follow prior durable events without delaying later results.
353
+ if (full.type !== 'tool.output')
354
+ emitTail = operation.catch(() => undefined);
350
355
  return operation;
351
356
  };
352
357
  const recordModelAttempt = (purpose) => (diagnostic) => {
@@ -2006,6 +2011,7 @@ export function createSessionOpener(services) {
2006
2011
  }
2007
2012
  catch (error) {
2008
2013
  // Preserve the first failure/cancellation; successful executions still report release errors.
2014
+ // eslint-disable-next-line no-unsafe-finally -- Successful results must not hide writer lease release failures.
2009
2015
  if (!failedExecution)
2010
2016
  throw error;
2011
2017
  }
@@ -14,7 +14,7 @@ export type { HostExecutionExtensions, HostSessionResourceSnapshot, HostSkill, M
14
14
  export type { AccessMode, ExecutionMode, ExecutionResult, MarAgentImageMetadata, RolloutBudgetOptions, RunInput, RunInputImage } from '../runtime/contracts.js';
15
15
  export type { MarAgentEvent, MarAgentEventBase } from '../runtime/events.js';
16
16
  export type { CompactInput, ExecutionHandle } from '../runtime/execution-handle.js';
17
- export type { CodeCellEventData, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../tools/contracts.js';
17
+ export type { CodeCellEventData, ExecOutputSnapshot, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../tools/contracts.js';
18
18
  export interface SteerInput {
19
19
  readonly expectedExecutionId: string;
20
20
  readonly prompt: string;
@@ -4,7 +4,7 @@ import type { McpToolCallContext, ToolCall, ToolExecutionResult } from '../contr
4
4
  import type { ToolRuntimeContext } from '../runtime-context.js';
5
5
  import type { createSessionToolScheduler } from '../scheduler.js';
6
6
  type ToolEvent = Extract<MarAgentEvent, {
7
- type: 'tool.started' | 'tool.completed' | 'tool.failed';
7
+ type: 'tool.started' | 'tool.output' | 'tool.completed' | 'tool.failed';
8
8
  }>;
9
9
  type Payload<T = ToolEvent> = T extends ToolEvent ? Omit<T, 'eventId' | 'sequence' | 'timestamp' | 'sessionId' | 'executionId' | 'turnId'> : never;
10
10
  /** Capture only the cell's immutable dispatch identity, never the model loop or its AbortSignal. */
@@ -26,6 +26,7 @@ export function createCodeToolDispatch(input) {
26
26
  const expected = input.expectations.get(call.name);
27
27
  const result = await executeRegisteredTool(call.name, call.arguments, callId, {
28
28
  ...input.context,
29
+ emit: (event) => event.type === 'tool.output' ? input.emit(event) : input.context.emit(event),
29
30
  parentCallId: input.parentCallId,
30
31
  signal,
31
32
  ...(expected ? { expectedMcpTool: expected } : {})
@@ -30,6 +30,8 @@ export interface ToolExecutionContext {
30
30
  mode: ExecutionMode;
31
31
  signal: AbortSignal;
32
32
  waitInterrupt?: import('./wait.js').ToolWaitInterrupt | undefined;
33
+ /** Live-only process output; does not consume the eventual tool result. */
34
+ onOutput?: (output: ExecOutputSnapshot) => Promise<void>;
33
35
  sessionId: string;
34
36
  executionId: string;
35
37
  /** Current model identity for neutral Host-owned tool policies. */
@@ -90,6 +92,12 @@ export interface MarAgentToolImageArtifact {
90
92
  fileName: string;
91
93
  }
92
94
  export type MarAgentToolArtifact = MarAgentToolImageArtifact;
95
+ export interface ExecOutputSnapshot {
96
+ processId: string;
97
+ stdout: string;
98
+ stderr: string;
99
+ truncated: boolean;
100
+ }
93
101
  export interface ToolExecutionResult {
94
102
  calls?: readonly import('./code-mode/observations.js').CodeToolCallRecord[];
95
103
  content: string;
@@ -14,6 +14,8 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
14
14
  execMaxPollYieldMs: 300000;
15
15
  execTerminationGraceMs: 1000;
16
16
  execDefaultMaxOutputBytes: 1000000;
17
+ execProgressMaxOutputBytes: 8192;
18
+ execProgressIntervalMs: 100;
17
19
  maxProcesses: 64;
18
20
  execMaxCompletedResults: 16;
19
21
  execCompletedRetentionMs: 300000;
@@ -14,6 +14,8 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
14
14
  execMaxPollYieldMs: 300_000,
15
15
  execTerminationGraceMs: 1_000,
16
16
  execDefaultMaxOutputBytes: 1_000_000,
17
+ execProgressMaxOutputBytes: 8_192,
18
+ execProgressIntervalMs: 100,
17
19
  maxProcesses: 64,
18
20
  execMaxCompletedResults: 16,
19
21
  execCompletedRetentionMs: 300_000,
@@ -1,6 +1,6 @@
1
1
  import { type LocalProcessExecutor } from '../../host/local-process-executor.js';
2
2
  import type { ClientToolDefinition } from '../../model/contracts.js';
3
- import type { ExecResult, ToolExecutionResult } from '../contracts.js';
3
+ import type { ExecOutputSnapshot, ExecResult, ToolExecutionResult } from '../contracts.js';
4
4
  import { type ToolWaitInterrupt } from '../wait.js';
5
5
  import { WorkspacePathResolver } from '../shared/path-resolver.js';
6
6
  import { type CoreProcessInput, type CoreProcessHandle } from '../../extensions/processes.js';
@@ -40,7 +40,7 @@ export declare class ExecTool {
40
40
  private readonly shell?;
41
41
  private readonly processExecutor;
42
42
  constructor(paths: WorkspacePathResolver, maxOutputBytes?: number, maxProcesses?: number, environment?: NodeJS.ProcessEnv, shell?: string | undefined, processExecutor?: LocalProcessExecutor);
43
- execute(arguments_: unknown, signal?: AbortSignal, owner?: NonNullable<import('../../host/local-process-executor.js').LocalProcessSpawnRequest['owner']>, waitInterrupt?: ToolWaitInterrupt): Promise<ExecResult>;
43
+ execute(arguments_: unknown, signal?: AbortSignal, owner?: NonNullable<import('../../host/local-process-executor.js').LocalProcessSpawnRequest['owner']>, waitInterrupt?: ToolWaitInterrupt, onOutput?: (output: ExecOutputSnapshot) => Promise<void>): Promise<ExecResult>;
44
44
  spawn(input: CoreProcessInput, call: CoreOperationContext): Promise<CoreProcessHandle>;
45
45
  listSessionResources(sessionId: string): {
46
46
  processId: string;
@@ -30,7 +30,7 @@ export const execToolDefinition = {
30
30
  name: 'exec',
31
31
  allowedCallers: ['direct', 'programmatic'],
32
32
  parallelSafety: 'safe',
33
- description: 'Start a platform-native command, script, or program in workspace/default cwd. Returns a stable processId (p_ plus 12 lowercase hex characters), status, bounded stdout/stderr, cumulative byte counts, dropped-byte counts, exitCode and truncated. A completed status does not imply exitCode 0; inspect stderr/exitCode. For subsequent waiting, stdin writes or cancellation, call exec_wait directly with processId, not exec. Truncation does not stop the process. Cancelling the initial wait does not terminate the process. waitOutcome distinguishes snapshot, completion, timeout, and yield. A foreground wait may return interrupted with reason=steer; the process keeps running.',
33
+ description: 'Start a platform-native command, script, or program in workspace/default cwd. Returns a stable processId (p_ plus 12 lowercase hex characters), status, bounded stdout/stderr, cumulative byte counts, dropped-byte counts, exitCode and truncated. A completed status does not imply exitCode 0; inspect stderr/exitCode. For subsequent waiting, stdin writes or cancellation, call exec_wait directly with processId, not exec. Truncation does not stop the process. Cancelling the initial wait does not terminate the process. waitOutcome distinguishes snapshot, completion, and timeout. A foreground wait may return interrupted with reason=steer; the process keeps running.',
34
34
  outputSchema: {
35
35
  type: 'object',
36
36
  required: ['content', 'data'],
@@ -104,7 +104,7 @@ export const execWaitToolDefinition = {
104
104
  name: 'exec_wait',
105
105
  allowedCallers: ['direct', 'programmatic'],
106
106
  parallelSafety: 'safe',
107
- description: 'Wait for or control a managed process by processId. action defaults to poll: wait for new output or termination and return incremental stdout/stderr. write sends data to stdin; cancel terminates the process or removes its cached result. Terminal polls return a bounded snapshot marked cached for up to five minutes. Cancelling a wait does not terminate the process. Uses the same Session-owned processes as exec; cannot start a process or extend its timeout. Inspect stderr/exitCode; completed does not imply success. Supports direct and programmatic calls; in Code Mode use tools.exec_wait. Poll results include waitOutcome; interrupted with reason=steer ends only a foreground wait, never the process.',
107
+ description: 'Wait for or control a managed process by processId. action defaults to poll: collect output until termination or the wait window ends, then return incremental stdout/stderr. New output does not end the wait. write sends data to stdin; cancel terminates the process or removes its cached result. Terminal polls return a bounded snapshot marked cached for up to five minutes. Cancelling a wait does not terminate the process. Uses the same Session-owned processes as exec; cannot start a process or extend its timeout. Inspect stderr/exitCode; completed does not imply success. Supports direct and programmatic calls; in Code Mode use tools.exec_wait. Poll results include waitOutcome; interrupted with reason=steer ends only a foreground wait, never the process.',
108
108
  outputSchema: execToolDefinition.outputSchema,
109
109
  inputSchema: {
110
110
  type: 'object',
@@ -164,7 +164,7 @@ export class ExecTool {
164
164
  this.shell = shell;
165
165
  this.processExecutor = processExecutor;
166
166
  }
167
- async execute(arguments_, signal, owner, waitInterrupt) {
167
+ async execute(arguments_, signal, owner, waitInterrupt, onOutput) {
168
168
  if (signal?.aborted)
169
169
  throw executionAbortReason(signal);
170
170
  const input = parseExecInput(arguments_);
@@ -193,7 +193,7 @@ export class ExecTool {
193
193
  throw new MarAgentError('MAR_AGENT_PROCESS_NOT_FOUND', 'Managed process was not found.');
194
194
  let outcome;
195
195
  if (input.action === 'poll' && input.yieldMs)
196
- outcome = await waitForProcessChange(process, Math.min(Math.max(input.yieldMs, 0), TOOL_EXECUTION_LIMITS.execMaxPollYieldMs), signal, waitInterrupt);
196
+ outcome = await waitForProcessCompletion(input.processId, process, Math.min(Math.max(input.yieldMs, 0), TOOL_EXECUTION_LIMITS.execMaxPollYieldMs), signal, waitInterrupt, onOutput);
197
197
  if (input.action === 'write') {
198
198
  if (process.status !== 'running')
199
199
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Completed process cannot accept input.');
@@ -216,9 +216,7 @@ export class ExecTool {
216
216
  ? 'completion'
217
217
  : outcome === 'interrupted'
218
218
  ? 'interrupted'
219
- : outcome === 'ready'
220
- ? 'yield'
221
- : 'timeout')
219
+ : 'timeout')
222
220
  : {})
223
221
  };
224
222
  }
@@ -245,18 +243,7 @@ export class ExecTool {
245
243
  const yieldMs = Math.min(input.yieldMs ?? TOOL_EXECUTION_LIMITS.execDefaultYieldMs, TOOL_EXECUTION_LIMITS.execMaxYieldMs);
246
244
  const outcome = yieldMs === 0
247
245
  ? undefined
248
- : await waitForToolCondition({
249
- ready: () => managed.finalized,
250
- subscribe: (notify) => {
251
- managed.waiters.add(notify);
252
- return () => {
253
- managed.waiters.delete(notify);
254
- };
255
- },
256
- waitMs: yieldMs,
257
- signal,
258
- interrupt: waitInterrupt
259
- });
246
+ : await waitForProcessCompletion(id, managed, yieldMs, signal, waitInterrupt, onOutput);
260
247
  const result = this.#result(id, managed);
261
248
  if (managed.status !== 'running')
262
249
  this.#retainCompleted(id, managed);
@@ -802,27 +789,88 @@ function notifyProcessChange(managed) {
802
789
  for (const resolve of managed.waiters)
803
790
  resolve();
804
791
  }
805
- async function waitForProcessChange(managed, waitMs, signal, interrupt) {
806
- let changed = false;
807
- return waitForToolCondition({
808
- ready: () => managed.status !== 'running' ||
809
- managed.stdout.bytes > 0 ||
810
- managed.stderr.bytes > 0 ||
811
- changed,
812
- subscribe: (notify) => {
813
- const listener = () => {
814
- changed = true;
815
- notify();
816
- };
817
- managed.waiters.add(listener);
818
- return () => {
819
- managed.waiters.delete(listener);
820
- };
821
- },
822
- waitMs,
823
- signal,
824
- interrupt
825
- });
792
+ async function waitForProcessCompletion(processId, managed, waitMs, signal, interrupt, onOutput) {
793
+ const stopOutput = onOutput && watchProcessOutput(processId, managed, onOutput);
794
+ try {
795
+ return await waitForToolCondition({
796
+ ready: () => managed.finalized,
797
+ subscribe: (notify) => {
798
+ managed.waiters.add(notify);
799
+ return () => {
800
+ managed.waiters.delete(notify);
801
+ };
802
+ },
803
+ waitMs,
804
+ signal,
805
+ interrupt
806
+ });
807
+ }
808
+ finally {
809
+ stopOutput?.();
810
+ }
811
+ }
812
+ /** Coalesce display snapshots without consuming output or queueing behind a slow observer. */
813
+ function watchProcessOutput(processId, managed, onOutput) {
814
+ let stopped = false;
815
+ let dirty = false;
816
+ let timer;
817
+ let inFlight;
818
+ let failed = false;
819
+ const publish = () => {
820
+ dirty = false;
821
+ const preview = (output) => {
822
+ const bounded = createOutputBuffer();
823
+ for (const chunk of output.chunks)
824
+ appendOutput(bounded, chunk, TOOL_EXECUTION_LIMITS.execProgressMaxOutputBytes);
825
+ const result = consumeOutput(bounded);
826
+ return { ...result, droppedBytes: result.droppedBytes + output.droppedBytes };
827
+ };
828
+ const stdout = preview(managed.stdout);
829
+ const stderr = preview(managed.stderr);
830
+ inFlight = Promise.resolve()
831
+ .then(() => {
832
+ if (stopped)
833
+ return;
834
+ return onOutput({
835
+ processId,
836
+ stdout: stdout.value,
837
+ stderr: stderr.value,
838
+ truncated: stdout.droppedBytes > 0 || stderr.droppedBytes > 0
839
+ });
840
+ })
841
+ .catch(() => {
842
+ // Display delivery must not change the process result or wait deadline.
843
+ failed = true;
844
+ })
845
+ .finally(() => {
846
+ inFlight = undefined;
847
+ if (dirty)
848
+ schedule();
849
+ });
850
+ };
851
+ const schedule = () => {
852
+ if (stopped || failed || timer || inFlight)
853
+ return;
854
+ timer = setTimeout(() => {
855
+ timer = undefined;
856
+ publish();
857
+ }, TOOL_EXECUTION_LIMITS.execProgressIntervalMs);
858
+ timer.unref();
859
+ };
860
+ const changed = () => {
861
+ if (managed.stdout.bytes === 0 && managed.stderr.bytes === 0)
862
+ return;
863
+ dirty = true;
864
+ schedule();
865
+ };
866
+ managed.waiters.add(changed);
867
+ changed();
868
+ return () => {
869
+ stopped = true;
870
+ managed.waiters.delete(changed);
871
+ if (timer)
872
+ clearTimeout(timer);
873
+ };
826
874
  }
827
875
  function executionAbortReason(signal) {
828
876
  return (signal?.reason ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
@@ -90,7 +90,7 @@ export class LocalToolRegistry {
90
90
  ? {}
91
91
  : { ownerInstanceId: context.operation.ownerInstanceId })
92
92
  })
93
- }, context.waitInterrupt);
93
+ }, context.waitInterrupt, context.onOutput);
94
94
  return formatExecResult(result);
95
95
  }
96
96
  ])
@@ -4,6 +4,12 @@ import type { AgentSkill } from '../runtime/context/instructions.js';
4
4
  import type { SubagentController, SubagentLatestMessage } from '../subagent/scheduler.js';
5
5
  import type { TodoStore } from './agent/todo.js';
6
6
  export type ToolRuntimeEvent = {
7
+ type: 'tool.output';
8
+ toolName: 'exec' | 'exec_wait';
9
+ callId: string;
10
+ parentCallId?: string;
11
+ output: import('./contracts.js').ExecOutputSnapshot;
12
+ } | {
7
13
  type: 'sleep.started';
8
14
  callId: string;
9
15
  durationMs: number;
@@ -45,10 +45,22 @@ export async function executeCapturedTool(kind, call, context) {
45
45
  ...(context.expectedMcpTool ? { expectedTool: context.expectedMcpTool } : {})
46
46
  });
47
47
  }
48
+ const { name, callId } = call;
48
49
  return context.host.executeTool(call, {
49
50
  mode: context.mode,
50
51
  signal: context.signal,
51
52
  waitInterrupt: context.waitInterrupt,
53
+ ...((name === 'exec' || name === 'exec_wait') && callId !== undefined
54
+ ? {
55
+ onOutput: (output) => context.emit({
56
+ type: 'tool.output',
57
+ toolName: name,
58
+ callId,
59
+ ...(context.parentCallId === undefined ? {} : { parentCallId: context.parentCallId }),
60
+ output
61
+ })
62
+ }
63
+ : {}),
52
64
  sessionId: context.sessionId,
53
65
  executionId: context.executionId,
54
66
  ...(context.modelId === undefined ? {} : { modelId: context.modelId }),
package/dist/index.d.ts CHANGED
@@ -44,7 +44,7 @@ export { openMarAgentSessionCatalog, type MarAgentContextUsage, type MarAgentHis
44
44
  export type { SessionListInput, SessionSource } from './agent-core/session/types.js';
45
45
  export type { SessionStore, SessionResource, SessionStoreMaintenance, SessionStoreSession, SessionRecordInput, SessionStoreCompactInput, SessionRecord, SessionExecutionState, CompactHistoryMessage, CompactUserInput, SessionInputImage, SessionInputImageMetadata, SessionInputImageData, RestoredSessionInputImage, CodeModeStateEntry, TodoSnapshot, TodoItem, CodeToolCallRecord, CodeObservation, ModelMessage, ModelUsage, ModelInputMetadata, ModelTextBlock, ModelContextualBlock, EnvironmentContextState, ContextGcCheckpointDraft, ContextGcBase, ContextGcPlatform, ContextGcEconomicsPolicy, ContextGcMetrics, ContextGcReplacement, ContextGcTarget, ContextGcReadItemReplacement, ContextGcWebToolName, ContextGcSubagentToolName, AvailableToolResultResource } from './agent-core/session/store/contracts.js';
46
46
  export type { OpenMarAgentSessionCatalogInput } from './agent-core/session/catalog.js';
47
- export type { ApplyPatchFileResult, ApplyPatchResult, ApplyPatchPartialResult, ClientToolDefinition, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolCaller, ToolExecutionContext, ToolExecutionResult } from './agent-core/tools/contracts.js';
47
+ export type { ApplyPatchFileResult, ApplyPatchResult, ApplyPatchPartialResult, ExecOutputSnapshot, ClientToolDefinition, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolCaller, ToolExecutionContext, ToolExecutionResult } from './agent-core/tools/contracts.js';
48
48
  export { MCP_DEFINITION_LIMITS, assertMcpToolCatalog, assertMcpToolDefinition } from './agent-core/tools/mcp/catalog.js';
49
49
  export type { WorkspacePathMount } from './agent-core/tools/shared/path-resolver.js';
50
50
  export type { CodeModeEngine, CodeModeEngineFactory, CreateCodeModeEngine } from './agent-core/tools/code-mode/engine.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.11.29",
3
+ "version": "0.11.31",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",