@myagentroam/agent 0.10.60 → 0.10.61

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.
@@ -56,6 +56,7 @@ export interface SessionMeta {
56
56
  storageTransactionId?: string;
57
57
  resume?: {
58
58
  version: 1;
59
+ userTurns?: number;
59
60
  compactSequence?: number;
60
61
  gcSequence?: number;
61
62
  gcGeneration?: number;
@@ -12,7 +12,7 @@ export class SubagentScheduler {
12
12
  #disposed = false;
13
13
  #createdInExecution = 0;
14
14
  #runner;
15
- constructor(runner, limits = { maxTasks: 8, maxConcurrent: 4 }) {
15
+ constructor(runner, limits = { maxTasks: 16, maxConcurrent: 16 }) {
16
16
  this.limits = limits;
17
17
  this.#runner = runner;
18
18
  }
@@ -78,7 +78,7 @@ export interface SubagentSessionControllerBackend {
78
78
  }>;
79
79
  deleteSession(sessionId: string): Promise<void>;
80
80
  /** Returns children in durable creation order. */
81
- listChildren(parentSessionId: string): Promise<readonly PersistedSubagentSession[]>;
81
+ listChildren(parentSessionId: string): Promise<readonly Omit<PersistedSubagentSession, 'records'>[]>;
82
82
  loadChild(parentSessionId: string, sessionId: string): Promise<PersistedSubagentSession | undefined>;
83
83
  }
84
84
  export declare class SubagentSessionController implements SubagentController {
@@ -109,6 +109,9 @@ export declare class SubagentSessionController implements SubagentController {
109
109
  withOperation<T>(operation: SubagentParentOperation, run: () => T): T;
110
110
  inspect(agentId: string): Promise<SubagentTaskSnapshot>;
111
111
  inspectAll(): readonly SubagentTaskSnapshot[];
112
+ /** Full listing is explicit; ordinary opens and executions never require it. */
113
+ prepareAll(signal?: AbortSignal): Promise<void>;
114
+ prepareSessionResources(signal?: AbortSignal): Promise<void>;
112
115
  collectModelUpdates(): readonly {
113
116
  snapshot: SubagentTaskSnapshot;
114
117
  truncated: boolean;
@@ -4,12 +4,16 @@ import { randomUUID } from 'node:crypto';
4
4
  import { MarAgentError, describeMarAgentError } from '../error.js';
5
5
  import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
6
6
  import { parseAgentWaitTargets, waitForAgentCondition } from './wait.js';
7
+ const RECENT_TERMINAL_RESOURCE_LIMIT = 16;
7
8
  export class SubagentSessionController {
8
9
  parentSessionId;
9
10
  rootSessionId;
10
11
  backend;
11
12
  limits;
12
13
  #agents = new Map();
14
+ // Discovery order is independent of the order in which history is requested.
15
+ #discovered = new Map();
16
+ #loading = new Map();
13
17
  #queue = [];
14
18
  #completionWaiters = new Set();
15
19
  #activityWaiters = new Set();
@@ -35,7 +39,7 @@ export class SubagentSessionController {
35
39
  #retainedExecution = new AsyncLocalStorage();
36
40
  #execution;
37
41
  #pendingDeliveries = new Set();
38
- constructor(parentSessionId, rootSessionId, backend, limits = { maxTasks: 8, maxConcurrent: 4 }) {
42
+ constructor(parentSessionId, rootSessionId, backend, limits = { maxTasks: 16, maxConcurrent: 16 }) {
39
43
  this.parentSessionId = parentSessionId;
40
44
  this.rootSessionId = rootSessionId;
41
45
  this.backend = backend;
@@ -43,11 +47,12 @@ export class SubagentSessionController {
43
47
  }
44
48
  static async open(input) {
45
49
  const controller = new SubagentSessionController(input.parentSessionId, input.rootSessionId, input.backend, input.limits);
46
- const sessions = [...(await input.backend.listChildren(input.parentSessionId))].sort((a, b) => a.lastActivityAt.localeCompare(b.lastActivityAt));
47
- for (const session of sessions) {
48
- const state = restoreAgentState(session);
49
- state.resourceRevision = ++controller.#resourceRevision;
50
- controller.#agents.set(session.id, state);
50
+ const sessions = await input.backend.listChildren(input.parentSessionId);
51
+ // Map insertion order is creation order; values independently rank activity.
52
+ for (const session of sessions)
53
+ controller.#discovered.set(session.id, 0);
54
+ for (const session of [...sessions].sort((a, b) => a.lastActivityAt.localeCompare(b.lastActivityAt))) {
55
+ controller.#discovered.set(session.id, ++controller.#resourceRevision);
51
56
  }
52
57
  return controller;
53
58
  }
@@ -98,7 +103,49 @@ export class SubagentSessionController {
98
103
  inspectAll() {
99
104
  if (this.#disposed)
100
105
  throw new MarAgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
101
- return [...this.#agents.values()].map((state) => this.#snapshot(state, { full: true }));
106
+ return this.#orderedStates().map((state) => this.#snapshot(state, { full: true }));
107
+ }
108
+ /** Full listing is explicit; ordinary opens and executions never require it. */
109
+ async prepareAll(signal) {
110
+ for (const id of this.#discovered.keys()) {
111
+ signal?.throwIfAborted();
112
+ await this.#restoreDiscovered(id);
113
+ }
114
+ signal?.throwIfAborted();
115
+ }
116
+ async prepareSessionResources(signal) {
117
+ const candidates = [...this.#discovered]
118
+ .filter(([id]) => {
119
+ const state = this.#agents.get(id);
120
+ return !state || isTerminalState(state);
121
+ })
122
+ .sort(([a, ar], [b, br]) => (this.#agents.get(b)?.resourceRevision ?? br) -
123
+ (this.#agents.get(a)?.resourceRevision ?? ar));
124
+ let prepared = 0;
125
+ for (const [id] of candidates) {
126
+ signal?.throwIfAborted();
127
+ const state = await this.#restoreDiscovered(id);
128
+ if (state && isTerminalState(state) && ++prepared === RECENT_TERMINAL_RESOURCE_LIMIT)
129
+ break;
130
+ }
131
+ signal?.throwIfAborted();
132
+ }
133
+ async #restoreDiscovered(agentId) {
134
+ try {
135
+ return await this.#find(agentId);
136
+ }
137
+ catch (error) {
138
+ if (!(error instanceof MarAgentError) || error.code !== 'MAR_AGENT_SUBAGENT_NOT_FOUND')
139
+ throw error;
140
+ this.#discovered.delete(agentId);
141
+ return undefined;
142
+ }
143
+ }
144
+ #orderedStates() {
145
+ return [...this.#discovered.keys()].flatMap((id) => {
146
+ const state = this.#agents.get(id);
147
+ return state ? [state] : [];
148
+ });
102
149
  }
103
150
  collectModelUpdates() {
104
151
  return [...this.#agents.values()]
@@ -223,6 +270,7 @@ export class SubagentSessionController {
223
270
  if (!native)
224
271
  (this.#retainedExecution.getStore()?.budget ?? this.#creationBudget).count++;
225
272
  this.#agents.set(state.agentId, state);
273
+ this.#discovered.set(state.agentId, state.resourceRevision);
226
274
  const knownModel = execution.models.get(input.modelId ?? execution.modelId);
227
275
  if (knownModel) {
228
276
  state.modelId = knownModel.id;
@@ -335,7 +383,16 @@ export class SubagentSessionController {
335
383
  if (options.untilSettled)
336
384
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'An unbounded wait requires an agent id.');
337
385
  validateOutputOptions(options, this.#retainedExecution.getStore()?.native === true);
338
- const states = [...this.#agents.values()];
386
+ const hasLive = [...this.#agents.values()].some((state) => !isTerminalState(state));
387
+ if (!hasLive) {
388
+ for (const id of [...this.#discovered.keys()].reverse()) {
389
+ options.signal?.throwIfAborted();
390
+ if (await this.#restoreDiscovered(id))
391
+ break;
392
+ }
393
+ }
394
+ options.signal?.throwIfAborted();
395
+ const states = this.#orderedStates();
339
396
  if (states.length === 0)
340
397
  throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent was not found.');
341
398
  const live = states.filter((state) => state.status === 'queued' || state.status === 'running');
@@ -625,7 +682,7 @@ export class SubagentSessionController {
625
682
  const terminal = states
626
683
  .filter((state) => state.status !== 'queued' && state.status !== 'running')
627
684
  .sort((left, right) => right.resourceRevision - left.resourceRevision)
628
- .slice(0, 8);
685
+ .slice(0, RECENT_TERMINAL_RESOURCE_LIMIT);
629
686
  return [...active, ...terminal].map((state) => ({
630
687
  agentId: state.agentId,
631
688
  status: state.status,
@@ -946,6 +1003,7 @@ export class SubagentSessionController {
946
1003
  }
947
1004
  async #deleteUnstartedSession(agentId, error) {
948
1005
  this.#agents.delete(agentId);
1006
+ this.#discovered.delete(agentId);
949
1007
  try {
950
1008
  await this.backend.deleteSession(agentId);
951
1009
  }
@@ -957,15 +1015,41 @@ export class SubagentSessionController {
957
1015
  throw error;
958
1016
  }
959
1017
  async #find(agentId) {
1018
+ if (this.#disposed)
1019
+ throw new MarAgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
960
1020
  const known = this.#agents.get(agentId);
961
1021
  if (known)
962
1022
  return known;
963
- const persisted = await this.backend.loadChild(this.parentSessionId, agentId);
964
- if (!persisted)
965
- throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent was not found.');
966
- const restored = restoreAgentState(persisted);
967
- this.#agents.set(agentId, restored);
968
- return restored;
1023
+ const pending = this.#loading.get(agentId);
1024
+ if (pending)
1025
+ return pending;
1026
+ const load = (async () => {
1027
+ const persisted = await this.backend.loadChild(this.parentSessionId, agentId);
1028
+ if (this.#disposed)
1029
+ throw new MarAgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
1030
+ if (!persisted)
1031
+ throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent was not found.');
1032
+ const existing = this.#agents.get(agentId);
1033
+ if (existing)
1034
+ return existing;
1035
+ const restored = restoreAgentState(persisted);
1036
+ let revision = this.#discovered.get(agentId);
1037
+ if (revision === undefined) {
1038
+ revision = ++this.#resourceRevision;
1039
+ this.#discovered.set(agentId, revision);
1040
+ }
1041
+ restored.resourceRevision = revision;
1042
+ this.#agents.set(agentId, restored);
1043
+ return restored;
1044
+ })();
1045
+ this.#loading.set(agentId, load);
1046
+ try {
1047
+ return await load;
1048
+ }
1049
+ finally {
1050
+ if (this.#loading.get(agentId) === load)
1051
+ this.#loading.delete(agentId);
1052
+ }
969
1053
  }
970
1054
  #requireExecution() {
971
1055
  if (this.#disposed)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.10.60",
3
+ "version": "0.10.61",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",