@myagentroam/agent 0.10.26 → 0.10.28
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/agent-core/extensions/agents.d.ts +14 -4
- package/dist/agent-core/extensions/agents.js +29 -13
- package/dist/agent-core/extensions/contracts.d.ts +2 -2
- package/dist/agent-core/model/anthropic-messages.js +2 -0
- package/dist/agent-core/model/openai-responses.js +2 -0
- package/dist/agent-core/model/product-user-agent.d.ts +5 -0
- package/dist/agent-core/model/product-user-agent.js +22 -0
- package/dist/agent-core/runtime/events.d.ts +1 -1
- package/dist/agent-core/sdk/extension-agents.js +7 -0
- package/dist/agent-core/sdk/session.js +2 -2
- package/dist/agent-core/subagent/contracts.d.ts +12 -0
- package/dist/agent-core/subagent/scheduler.d.ts +3 -1
- package/dist/agent-core/subagent/scheduler.js +44 -0
- package/dist/agent-core/subagent/session-controller.d.ts +2 -0
- package/dist/agent-core/subagent/session-controller.js +116 -20
- package/dist/agent-core/subagent/wait.d.ts +19 -0
- package/dist/agent-core/subagent/wait.js +77 -0
- package/dist/agent-core/tools/agent/agent-wait.d.ts +1 -1
- package/dist/agent-core/tools/agent/agent-wait.js +23 -5
- package/dist/agent-core/tools/code-mode/quickjs-engine.js +8 -7
- package/dist/agent-core/tools/execution-limits.d.ts +1 -0
- package/dist/agent-core/tools/execution-limits.js +1 -0
- package/dist/agent-core/tools/output-projection.d.ts +2 -2
- package/dist/agent-core/tools/output-projection.js +12 -0
- package/dist/cli/codex-auth-file.js +3 -14
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { MarAgentReasoningEffort } from '../model/configuration.js';
|
|
2
|
-
import type { SubagentController, SubagentTaskSnapshot, SubagentHandledResult } from '../subagent/contracts.js';
|
|
2
|
+
import type { SubagentController, SubagentTaskSnapshot, SubagentManyOutput, SubagentHandledResult } from '../subagent/contracts.js';
|
|
3
3
|
import type { CoreOperationContext } from './operation.js';
|
|
4
4
|
import type { ExtensionResourceScope } from './resources.js';
|
|
5
5
|
import type { ExtensionHooks } from './hooks.js';
|
|
6
6
|
import type { CoreDomainOperations, CoreDomainFacts, CoreCapabilitySnapshot } from './contracts.js';
|
|
7
7
|
export type CoreAgentSnapshot = Readonly<SubagentTaskSnapshot>;
|
|
8
|
+
export type CoreAgentManySnapshot = Readonly<SubagentManyOutput>;
|
|
9
|
+
export type CoreAgentWaitResult = CoreAgentSnapshot | CoreAgentManySnapshot;
|
|
8
10
|
export type CoreAgentHandledResult = SubagentHandledResult;
|
|
9
11
|
export type CoreAgentSpawnResult = CoreAgentSnapshot | CoreAgentHandledResult;
|
|
10
|
-
export declare function isHandledAgentResult(value: CoreAgentSpawnResult): value is CoreAgentHandledResult;
|
|
12
|
+
export declare function isHandledAgentResult(value: CoreAgentSpawnResult | CoreAgentManySnapshot): value is CoreAgentHandledResult;
|
|
11
13
|
export interface CoreAgentSpawnInput {
|
|
12
14
|
readonly turnSignal?: AbortSignal;
|
|
13
15
|
readonly waitFor?: 'start' | 'completion';
|
|
@@ -26,6 +28,8 @@ export interface CoreAgentTarget {
|
|
|
26
28
|
export interface CoreAgentWaitInput {
|
|
27
29
|
readonly untilSettled?: true;
|
|
28
30
|
readonly agentId?: string;
|
|
31
|
+
readonly agentIds?: readonly string[];
|
|
32
|
+
readonly mode?: 'any' | 'all';
|
|
29
33
|
readonly signal?: AbortSignal;
|
|
30
34
|
readonly waitMs?: number;
|
|
31
35
|
readonly waitFor?: 'completion' | 'message';
|
|
@@ -39,7 +43,7 @@ export interface CoreAgentsBackend {
|
|
|
39
43
|
spawn(input: CoreAgentSpawnInput, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
|
|
40
44
|
list(call: CoreOperationContext): Promise<readonly CoreAgentSnapshot[]>;
|
|
41
45
|
inspect(input: CoreAgentTarget, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
|
|
42
|
-
wait(input: CoreAgentWaitInput, call: CoreOperationContext): Promise<
|
|
46
|
+
wait(input: CoreAgentWaitInput, call: CoreOperationContext): Promise<CoreAgentWaitResult>;
|
|
43
47
|
message(input: CoreAgentMessageInput, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
|
|
44
48
|
cancel(input: CoreAgentTarget, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
|
|
45
49
|
}
|
|
@@ -49,7 +53,13 @@ export interface CoreAgentsApi {
|
|
|
49
53
|
readonly signal?: AbortSignal;
|
|
50
54
|
}): Promise<readonly CoreAgentSnapshot[]>;
|
|
51
55
|
inspect(input: CoreAgentTarget): Promise<CoreAgentSnapshot>;
|
|
52
|
-
wait(input: CoreAgentWaitInput
|
|
56
|
+
wait(input: CoreAgentWaitInput & {
|
|
57
|
+
readonly agentIds: readonly string[];
|
|
58
|
+
}): Promise<CoreAgentManySnapshot>;
|
|
59
|
+
wait(input: CoreAgentWaitInput & {
|
|
60
|
+
readonly agentIds?: undefined;
|
|
61
|
+
}): Promise<CoreAgentSnapshot>;
|
|
62
|
+
wait(input: CoreAgentWaitInput): Promise<CoreAgentWaitResult>;
|
|
53
63
|
message(input: CoreAgentMessageInput): Promise<CoreAgentSnapshot>;
|
|
54
64
|
cancel(input: CoreAgentTarget): Promise<CoreAgentSnapshot>;
|
|
55
65
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MarAgentError } from '../error.js';
|
|
2
|
+
import { parseAgentWaitTargets } from '../subagent/wait.js';
|
|
2
3
|
export function isHandledAgentResult(value) {
|
|
3
|
-
return value.outcome === 'handled' && !Object.hasOwn(value, 'agentId');
|
|
4
|
+
return 'outcome' in value && value.outcome === 'handled' && !Object.hasOwn(value, 'agentId');
|
|
4
5
|
}
|
|
5
6
|
/** All callers share one interception and authoritative leaf-completion boundary. */
|
|
6
7
|
export function invokeCoreAgentOperation(hooks, key, input, call, leaf) {
|
|
@@ -8,12 +9,16 @@ export function invokeCoreAgentOperation(hooks, key, input, call, leaf) {
|
|
|
8
9
|
const result = structuredClone(await leaf(input, effective));
|
|
9
10
|
if (isHandledAgentResult(result))
|
|
10
11
|
return result;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
const agents = Array.isArray(result.agents)
|
|
13
|
+
? result.agents
|
|
14
|
+
: [result];
|
|
15
|
+
for (const agent of agents)
|
|
16
|
+
await hooks.emit('agent.completed', {
|
|
17
|
+
operation: key.slice('agent.'.length),
|
|
18
|
+
agentId: agent.agentId,
|
|
19
|
+
status: agent.status,
|
|
20
|
+
...(agent.deliveryStatus === undefined ? {} : { deliveryStatus: agent.deliveryStatus })
|
|
21
|
+
}, effective);
|
|
17
22
|
return result;
|
|
18
23
|
});
|
|
19
24
|
}
|
|
@@ -46,9 +51,18 @@ function resolveAgentSpawn(input) {
|
|
|
46
51
|
};
|
|
47
52
|
}
|
|
48
53
|
export function bindAgentController(controller, action) {
|
|
49
|
-
const wait = (input) => action('agent.wait', input, (target, call) =>
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
const wait = (input) => action('agent.wait', input, (target, call) => {
|
|
55
|
+
const selection = parseAgentWaitTargets(target);
|
|
56
|
+
return selection.agentIds !== undefined
|
|
57
|
+
? controller.outputMany(selection.agentIds, {
|
|
58
|
+
...target,
|
|
59
|
+
mode: selection.mode ?? 'any',
|
|
60
|
+
signal: call.signal
|
|
61
|
+
})
|
|
62
|
+
: target.agentId === undefined
|
|
63
|
+
? controller.outputAny({ ...target, signal: call.signal })
|
|
64
|
+
: controller.output(target.agentId, { ...target, signal: call.signal });
|
|
65
|
+
});
|
|
52
66
|
return {
|
|
53
67
|
run: (input, options) => {
|
|
54
68
|
return action('agent.spawn', {
|
|
@@ -83,6 +97,7 @@ export function bindAgentController(controller, action) {
|
|
|
83
97
|
},
|
|
84
98
|
output: (id, options) => wait({ ...options, agentId: id }),
|
|
85
99
|
outputAny: (options) => wait({ ...options }),
|
|
100
|
+
outputMany: (ids, options) => wait({ ...options, agentIds: ids }),
|
|
86
101
|
message: (id, message, options) => action('agent.message', { ...options, agentId: id, message }, (target, call) => controller.message(target.agentId, target.message, {
|
|
87
102
|
...(target.delivery === undefined ? {} : { delivery: target.delivery }),
|
|
88
103
|
signal: call.signal
|
|
@@ -146,11 +161,12 @@ export function bindCoreAgents(backend, scope, run, hooks) {
|
|
|
146
161
|
const copied = target(input);
|
|
147
162
|
return run(async (call) => structuredClone(await backend.inspect(copied, call)), copied.signal);
|
|
148
163
|
},
|
|
149
|
-
wait: async (input) => {
|
|
164
|
+
wait: (async (input) => {
|
|
150
165
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
151
166
|
throw invalid('Agent wait input is invalid.');
|
|
152
|
-
|
|
153
|
-
|
|
167
|
+
const selection = parseAgentWaitTargets(input);
|
|
168
|
+
return invoke('agent.wait', input.agentId === undefined ? { ...input, ...selection } : target(input));
|
|
169
|
+
}),
|
|
154
170
|
message: async (input) => invoke('agent.message', target(input)),
|
|
155
171
|
cancel: async (input) => invoke('agent.cancel', target(input))
|
|
156
172
|
};
|
|
@@ -4,7 +4,7 @@ import type { CorePromptInput, CorePromptResult } from './prompt-input.js';
|
|
|
4
4
|
import type { CoreModelEvent } from './model-stream.js';
|
|
5
5
|
import type { CorePromptComposition, CorePromptSectionInput } from './prompt-composition.js';
|
|
6
6
|
import type { CoreModelsApi } from './models.js';
|
|
7
|
-
import type { CoreAgentsApi, CoreAgentSpawnInput, CoreAgentWaitInput, CoreAgentMessageInput, CoreAgentTarget, CoreAgentSnapshot, CoreAgentSpawnResult, CoreAgentCompletion } from './agents.js';
|
|
7
|
+
import type { CoreAgentsApi, CoreAgentSpawnInput, CoreAgentWaitInput, CoreAgentWaitResult, CoreAgentMessageInput, CoreAgentTarget, CoreAgentSnapshot, CoreAgentSpawnResult, CoreAgentCompletion } from './agents.js';
|
|
8
8
|
import type { CoreDataApi } from './data.js';
|
|
9
9
|
import type { CoreMcpApi, CoreMcpConnectInput, CoreMcpConnectionInput, CoreMcpConnectionRef, CoreMcpCatalog, CoreMcpCompletion } from './mcp.js';
|
|
10
10
|
import type { CoreFilesApi, CoreFileOperations, CoreFileCompletion } from './files.js';
|
|
@@ -38,7 +38,7 @@ export interface CoreDomainOperations extends CoreFileOperations {
|
|
|
38
38
|
};
|
|
39
39
|
'agent.wait': {
|
|
40
40
|
input: CoreAgentWaitInput;
|
|
41
|
-
output:
|
|
41
|
+
output: CoreAgentWaitResult;
|
|
42
42
|
};
|
|
43
43
|
'agent.message': {
|
|
44
44
|
input: CoreAgentMessageInput;
|
|
@@ -10,6 +10,7 @@ import { fetchModelResponse, modelRetryMaxAttempts, isRetryableModelRequestError
|
|
|
10
10
|
import { anthropicThinkingBlock, assertAnthropicThinkingBudget } from './anthropic-thinking.js';
|
|
11
11
|
import { resolveModelTextBlocks } from './text-blocks.js';
|
|
12
12
|
import { reportModelAttempt } from './attempt-diagnostics.js';
|
|
13
|
+
import { productUserAgent } from './product-user-agent.js';
|
|
13
14
|
export class AnthropicMessagesAdapter {
|
|
14
15
|
configuration;
|
|
15
16
|
constructor(configuration) {
|
|
@@ -108,6 +109,7 @@ export class AnthropicMessagesAdapter {
|
|
|
108
109
|
'x-api-key': apiKey.reveal(),
|
|
109
110
|
'anthropic-version': '2023-06-01',
|
|
110
111
|
'content-type': 'application/json',
|
|
112
|
+
'user-agent': productUserAgent(),
|
|
111
113
|
accept: 'text/event-stream'
|
|
112
114
|
},
|
|
113
115
|
body: JSON.stringify({
|
|
@@ -12,6 +12,7 @@ import { acquireModelCredential, appendModelCredentialHeaders, modelCredentialSe
|
|
|
12
12
|
import { resolveModelTextBlocks } from './text-blocks.js';
|
|
13
13
|
import { jsonToolInput } from './tool-input.js';
|
|
14
14
|
import { reportModelAttempt } from './attempt-diagnostics.js';
|
|
15
|
+
import { productUserAgent } from './product-user-agent.js';
|
|
15
16
|
const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
|
|
16
17
|
const WEBSOCKET_SEND_FRAME_BYTES = 1024 * 1024;
|
|
17
18
|
export const OPENAI_RESPONSES_EARLY_EOF_REASON = 'OPENAI_RESPONSES_EARLY_EOF';
|
|
@@ -1554,6 +1555,7 @@ function responsesHeaders(credential, promptCacheKey, turnState) {
|
|
|
1554
1555
|
authorization: `Bearer ${credential.bearer.reveal()}`,
|
|
1555
1556
|
'content-type': 'application/json',
|
|
1556
1557
|
accept: 'text/event-stream',
|
|
1558
|
+
'user-agent': productUserAgent(),
|
|
1557
1559
|
'x-client-request-id': randomUUID(),
|
|
1558
1560
|
...(promptCacheKey
|
|
1559
1561
|
? {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
let cachedUserAgent;
|
|
3
|
+
/**
|
|
4
|
+
* Product identity sent to upstream model endpoints. Runtime credential headers may
|
|
5
|
+
* override this value per request.
|
|
6
|
+
*/
|
|
7
|
+
export function productUserAgent() {
|
|
8
|
+
cachedUserAgent ??= resolveProductUserAgent();
|
|
9
|
+
return cachedUserAgent;
|
|
10
|
+
}
|
|
11
|
+
function resolveProductUserAgent() {
|
|
12
|
+
try {
|
|
13
|
+
const raw = readFileSync(new URL('../../../package.json', import.meta.url), 'utf8');
|
|
14
|
+
const version = JSON.parse(raw).version;
|
|
15
|
+
if (typeof version === 'string' && version.length > 0 && version.length <= 64)
|
|
16
|
+
return 'MyAgentRoam/' + version + ' (' + process.platform + '; ' + process.arch + ')';
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Fall through to the identity-preserving fallback below.
|
|
20
|
+
}
|
|
21
|
+
return 'MyAgentRoam/unknown (' + process.platform + '; ' + process.arch + ')';
|
|
22
|
+
}
|
|
@@ -83,7 +83,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
|
|
|
83
83
|
summary: string;
|
|
84
84
|
truncated?: boolean;
|
|
85
85
|
artifacts?: readonly MarAgentToolArtifact[];
|
|
86
|
-
data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../subagent/contracts.js').SubagentTaskSnapshot;
|
|
86
|
+
data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../subagent/contracts.js').SubagentTaskSnapshot | import('../subagent/contracts.js').SubagentManyOutput;
|
|
87
87
|
} | {
|
|
88
88
|
type: 'tool.failed';
|
|
89
89
|
resultExtensions?: readonly import('../tools/result-extensions.js').ToolResultExtension[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MarAgentError } from '../error.js';
|
|
2
|
+
import { parseAgentWaitTargets } from '../subagent/wait.js';
|
|
2
3
|
/** Uses the Session's sole task controller, including its durable children and queue. */
|
|
3
4
|
export function createExtensionAgentsBackend(input) {
|
|
4
5
|
const withController = async (call, run) => {
|
|
@@ -30,12 +31,18 @@ export function createExtensionAgentsBackend(input) {
|
|
|
30
31
|
list: (call) => withController(call, async (controller) => controller.inspectAll()),
|
|
31
32
|
inspect: (target, call) => withController(call, (controller) => controller.inspect(target.agentId)),
|
|
32
33
|
wait: (target, call) => withController(call, (controller) => {
|
|
34
|
+
const selection = parseAgentWaitTargets(target);
|
|
33
35
|
const options = {
|
|
34
36
|
waitMs: target.waitMs ?? 30_000,
|
|
35
37
|
...(target.untilSettled === undefined ? {} : { untilSettled: target.untilSettled }),
|
|
36
38
|
...(target.waitFor === undefined ? {} : { waitFor: target.waitFor }),
|
|
37
39
|
signal: call.signal
|
|
38
40
|
};
|
|
41
|
+
if (selection.agentIds !== undefined)
|
|
42
|
+
return controller.outputMany(selection.agentIds, {
|
|
43
|
+
...options,
|
|
44
|
+
mode: selection.mode ?? 'any'
|
|
45
|
+
});
|
|
39
46
|
if (target.untilSettled && target.agentId === undefined)
|
|
40
47
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'An unbounded wait requires an agent id.');
|
|
41
48
|
return target.agentId === undefined
|
|
@@ -267,6 +267,7 @@ export function createSessionOpener(services) {
|
|
|
267
267
|
let sequence = 0;
|
|
268
268
|
let manualCompactionStarted = false;
|
|
269
269
|
let emitTail = Promise.resolve();
|
|
270
|
+
let releaseSession;
|
|
270
271
|
let exhaustedModelAttempt;
|
|
271
272
|
// Serialize durable events before either the local stream or Host observes them.
|
|
272
273
|
const emit = (event, assertLive) => {
|
|
@@ -283,7 +284,7 @@ export function createSessionOpener(services) {
|
|
|
283
284
|
const operation = emitTail.then(async () => {
|
|
284
285
|
assertLive?.();
|
|
285
286
|
const persistent = eventForPersistence(full);
|
|
286
|
-
if (persistent !== undefined)
|
|
287
|
+
if (persistent !== undefined && releaseSession !== undefined)
|
|
287
288
|
await store.append(sessionId, {
|
|
288
289
|
type: full.type,
|
|
289
290
|
payload: persistent,
|
|
@@ -337,7 +338,6 @@ export function createSessionOpener(services) {
|
|
|
337
338
|
let finalAnswer = '';
|
|
338
339
|
let executionLoop;
|
|
339
340
|
let manualCompaction;
|
|
340
|
-
let releaseSession;
|
|
341
341
|
let executionToolSources;
|
|
342
342
|
let selectedTurn;
|
|
343
343
|
const contextGc = new ContextGcRuntime();
|
|
@@ -81,6 +81,17 @@ export interface SubagentOutputOptions {
|
|
|
81
81
|
waitFor?: SubagentWaitFor;
|
|
82
82
|
signal?: AbortSignal;
|
|
83
83
|
}
|
|
84
|
+
export type SubagentWaitMode = 'any' | 'all';
|
|
85
|
+
export interface SubagentManyOutputOptions extends Omit<SubagentOutputOptions, 'untilSettled'> {
|
|
86
|
+
mode?: SubagentWaitMode;
|
|
87
|
+
}
|
|
88
|
+
export interface SubagentManyOutput {
|
|
89
|
+
mode: SubagentWaitMode;
|
|
90
|
+
waitFor: SubagentWaitFor;
|
|
91
|
+
waitOutcome: SubagentWaitOutcome;
|
|
92
|
+
agents: SubagentTaskSnapshot[];
|
|
93
|
+
pendingAgentIds: string[];
|
|
94
|
+
}
|
|
84
95
|
export interface SubagentHandledResult {
|
|
85
96
|
readonly outcome: 'handled';
|
|
86
97
|
readonly value?: unknown;
|
|
@@ -91,6 +102,7 @@ export interface SubagentController {
|
|
|
91
102
|
run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentRunResult>;
|
|
92
103
|
output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
93
104
|
outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
105
|
+
outputMany(agentIds: readonly string[], options?: SubagentManyOutputOptions): Promise<SubagentManyOutput>;
|
|
94
106
|
message(agentId: string, message: string, options?: SubagentMessageOptions): Promise<SubagentTaskSnapshot>;
|
|
95
107
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
96
108
|
listSessionResources(): readonly {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { SubagentManyOutput, SubagentManyOutputOptions } from './contracts.js';
|
|
1
2
|
import type { SubagentController, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskResult, SubagentTaskSnapshot } from './contracts.js';
|
|
2
|
-
export type { SubagentController, SubagentLatestActivity, SubagentLatestMessage, SubagentMessageOptions, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskResult, SubagentTaskSnapshot, SubagentWaitFor, SubagentWaitOutcome } from './contracts.js';
|
|
3
|
+
export type { SubagentController, SubagentLatestActivity, SubagentLatestMessage, SubagentMessageOptions, SubagentManyOutput, SubagentManyOutputOptions, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskResult, SubagentTaskSnapshot, SubagentWaitFor, SubagentWaitOutcome } from './contracts.js';
|
|
3
4
|
export declare class SubagentScheduler implements SubagentController {
|
|
4
5
|
#private;
|
|
5
6
|
readonly limits: {
|
|
@@ -118,6 +119,7 @@ export declare class SubagentScheduler implements SubagentController {
|
|
|
118
119
|
turnId?: string;
|
|
119
120
|
deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
|
|
120
121
|
}>;
|
|
122
|
+
outputMany(agentIds: readonly string[], options?: SubagentManyOutputOptions): Promise<SubagentManyOutput>;
|
|
121
123
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
122
124
|
message(_agentId: string, _message: string): Promise<SubagentTaskSnapshot>;
|
|
123
125
|
listSessionResources(): {
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { describeMarAgentError, MarAgentError } from '../error.js';
|
|
3
3
|
import { marAgentReasoningEffortSchema } from '../model/configuration.js';
|
|
4
4
|
import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
|
|
5
|
+
import { parseAgentWaitTargets, waitForAgentCondition } from './wait.js';
|
|
5
6
|
export class SubagentScheduler {
|
|
6
7
|
limits;
|
|
7
8
|
#tasks = new Map();
|
|
@@ -111,6 +112,49 @@ export class SubagentScheduler {
|
|
|
111
112
|
waitOutcome: completed ? 'completion' : 'timeout'
|
|
112
113
|
};
|
|
113
114
|
}
|
|
115
|
+
async outputMany(agentIds, options = {}) {
|
|
116
|
+
options.signal?.throwIfAborted();
|
|
117
|
+
const { agentIds: ids, mode = 'any' } = parseAgentWaitTargets({ agentIds, mode: options.mode });
|
|
118
|
+
validateOutputOptions(options);
|
|
119
|
+
rejectUnsupportedMessageWait(options);
|
|
120
|
+
if (!ids || 'untilSettled' in options)
|
|
121
|
+
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Group waits require explicit targets and a bounded deadline.');
|
|
122
|
+
const tasks = ids.map((id) => {
|
|
123
|
+
const task = this.#tasks.get(id);
|
|
124
|
+
if (!task)
|
|
125
|
+
throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent task was not found.');
|
|
126
|
+
return task;
|
|
127
|
+
});
|
|
128
|
+
const terminal = (task) => task.status !== 'queued' && task.status !== 'running';
|
|
129
|
+
const snapshot = options.waitMs === undefined || options.waitMs === 0;
|
|
130
|
+
const satisfied = !snapshot &&
|
|
131
|
+
(await waitForAgentCondition({
|
|
132
|
+
ready: () => (mode === 'all' ? tasks.every(terminal) : tasks.some(terminal)),
|
|
133
|
+
subscribe: (notify) => {
|
|
134
|
+
this.#completionWaiters.add(notify);
|
|
135
|
+
return () => this.#completionWaiters.delete(notify);
|
|
136
|
+
},
|
|
137
|
+
waitMs: Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs),
|
|
138
|
+
signal: options.signal
|
|
139
|
+
}));
|
|
140
|
+
options.signal?.throwIfAborted();
|
|
141
|
+
return {
|
|
142
|
+
mode,
|
|
143
|
+
waitFor: 'completion',
|
|
144
|
+
waitOutcome: snapshot ? 'snapshot' : satisfied ? 'completion' : 'timeout',
|
|
145
|
+
pendingAgentIds: tasks.filter((task) => !terminal(task)).map((task) => task.agentId),
|
|
146
|
+
agents: tasks.map((task) => ({
|
|
147
|
+
...this.#public(task),
|
|
148
|
+
waitOutcome: snapshot
|
|
149
|
+
? 'snapshot'
|
|
150
|
+
: terminal(task)
|
|
151
|
+
? 'completion'
|
|
152
|
+
: satisfied
|
|
153
|
+
? 'snapshot'
|
|
154
|
+
: 'timeout'
|
|
155
|
+
}))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
114
158
|
async cancel(agentId) {
|
|
115
159
|
const task = this.#tasks.get(agentId);
|
|
116
160
|
if (!task)
|
|
@@ -3,6 +3,7 @@ import type { MarAgentReasoningEffort } from '../model/configuration.js';
|
|
|
3
3
|
import type { ExecutionResult } from '../runtime/contracts.js';
|
|
4
4
|
import type { MarAgentEvent } from '../runtime/events.js';
|
|
5
5
|
import type { SessionRecord, SessionSource } from '../session/jsonl-store.js';
|
|
6
|
+
import type { SubagentManyOutput, SubagentManyOutputOptions } from './contracts.js';
|
|
6
7
|
import type { SubagentController, SubagentMessageOptions, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskSnapshot } from './scheduler.js';
|
|
7
8
|
export interface SubagentModelOption {
|
|
8
9
|
readonly id: string;
|
|
@@ -112,6 +113,7 @@ export declare class SubagentSessionController implements SubagentController {
|
|
|
112
113
|
run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
|
|
113
114
|
output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
114
115
|
outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
116
|
+
outputMany(agentIds: readonly string[], options?: SubagentManyOutputOptions): Promise<SubagentManyOutput>;
|
|
115
117
|
message(agentId: string, message: string, options?: SubagentMessageOptions): Promise<SubagentTaskSnapshot>;
|
|
116
118
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
117
119
|
listSessionResources(): {
|
|
@@ -2,6 +2,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { MarAgentError, describeMarAgentError } from '../error.js';
|
|
4
4
|
import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
|
|
5
|
+
import { parseAgentWaitTargets, waitForAgentCondition, waitDeadline } from './wait.js';
|
|
5
6
|
export class SubagentSessionController {
|
|
6
7
|
parentSessionId;
|
|
7
8
|
rootSessionId;
|
|
@@ -52,6 +53,7 @@ export class SubagentSessionController {
|
|
|
52
53
|
message: (id, message, options) => enter(() => this.message(id, message, options)),
|
|
53
54
|
output: (id, options) => this.output(id, options),
|
|
54
55
|
outputAny: (options) => this.outputAny(options),
|
|
56
|
+
outputMany: (ids, options) => this.outputMany(ids, options),
|
|
55
57
|
cancel: (id) => this.cancel(id),
|
|
56
58
|
listSessionResources: () => this.listSessionResources(),
|
|
57
59
|
dispose: () => this.dispose()
|
|
@@ -166,7 +168,7 @@ export class SubagentSessionController {
|
|
|
166
168
|
verification: [],
|
|
167
169
|
evidence: [],
|
|
168
170
|
terminalPending: false,
|
|
169
|
-
|
|
171
|
+
observedTerminalRevision: -1,
|
|
170
172
|
resourceRevision: ++this.#resourceRevision
|
|
171
173
|
};
|
|
172
174
|
if (!native)
|
|
@@ -356,6 +358,109 @@ export class SubagentSessionController {
|
|
|
356
358
|
waitOutcome: completed ? 'completion' : 'timeout'
|
|
357
359
|
};
|
|
358
360
|
}
|
|
361
|
+
async outputMany(agentIds, options = {}) {
|
|
362
|
+
options.signal?.throwIfAborted();
|
|
363
|
+
const { agentIds: ids, mode = 'any' } = parseAgentWaitTargets({ agentIds, mode: options.mode }, this.#retainedExecution.getStore()?.native
|
|
364
|
+
? Infinity
|
|
365
|
+
: TOOL_EXECUTION_LIMITS.agentWaitMaxAgents);
|
|
366
|
+
validateOutputOptions(options, this.#retainedExecution.getStore()?.native === true);
|
|
367
|
+
if (!ids || 'untilSettled' in options)
|
|
368
|
+
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Group waits require a bounded deadline.');
|
|
369
|
+
const states = await Promise.all(ids.map((id) => this.#find(id)));
|
|
370
|
+
options.signal?.throwIfAborted();
|
|
371
|
+
const waitFor = options.waitFor ?? 'completion';
|
|
372
|
+
const targets = states.map((state) => ({
|
|
373
|
+
state,
|
|
374
|
+
turn: state.turn,
|
|
375
|
+
view: copyWaitState(state),
|
|
376
|
+
observation: this.#nativeObservation(state)
|
|
377
|
+
}));
|
|
378
|
+
const refresh = () => {
|
|
379
|
+
for (const target of targets) {
|
|
380
|
+
if (!isTerminalState(target.view) &&
|
|
381
|
+
(target.state.turn === target.turn ||
|
|
382
|
+
(target.state.turn === undefined && isTerminalState(target.state))))
|
|
383
|
+
target.view = copyWaitState(target.state);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const unread = (target) => target.view.messageRevision >
|
|
387
|
+
(target.observation?.messageRevision ?? target.state.observedMessageRevision);
|
|
388
|
+
const ready = (target) => isTerminalState(target.view) || (waitFor === 'message' && unread(target));
|
|
389
|
+
const snapshot = options.waitMs === undefined || options.waitMs === 0;
|
|
390
|
+
let satisfied = false;
|
|
391
|
+
if (!snapshot) {
|
|
392
|
+
const waitMs = this.#retainedExecution.getStore()?.native
|
|
393
|
+
? options.waitMs
|
|
394
|
+
: Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
395
|
+
satisfied = await waitForAgentCondition({
|
|
396
|
+
waitMs,
|
|
397
|
+
signal: options.signal,
|
|
398
|
+
ready: () => {
|
|
399
|
+
refresh();
|
|
400
|
+
return mode === 'all' ? targets.every(ready) : targets.some(ready);
|
|
401
|
+
},
|
|
402
|
+
subscribe: (notify) => {
|
|
403
|
+
const listener = () => notify();
|
|
404
|
+
this.#activityWaiters.add(listener);
|
|
405
|
+
return () => this.#activityWaiters.delete(listener);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
options.signal?.throwIfAborted();
|
|
409
|
+
}
|
|
410
|
+
refresh();
|
|
411
|
+
const pendingAgentIds = targets
|
|
412
|
+
.filter((target) => !ready(target))
|
|
413
|
+
.map((target) => target.state.agentId);
|
|
414
|
+
const agents = targets.map((target) => {
|
|
415
|
+
const { state, view, observation } = target;
|
|
416
|
+
const terminal = isTerminalState(view);
|
|
417
|
+
const outcome = snapshot
|
|
418
|
+
? 'snapshot'
|
|
419
|
+
: terminal
|
|
420
|
+
? 'completion'
|
|
421
|
+
: ready(target)
|
|
422
|
+
? 'message'
|
|
423
|
+
: satisfied
|
|
424
|
+
? 'snapshot'
|
|
425
|
+
: 'timeout';
|
|
426
|
+
const includeLatestMessage = outcome !== 'snapshot' && unread(target);
|
|
427
|
+
const includeTerminalDetails = outcome === 'completion' &&
|
|
428
|
+
(observation
|
|
429
|
+
? observation.terminalRevision < view.resourceRevision
|
|
430
|
+
: state.observedTerminalRevision < view.resourceRevision);
|
|
431
|
+
const result = {
|
|
432
|
+
...this.#snapshot(view, { includeLatestMessage, includeTerminalDetails }),
|
|
433
|
+
...(target.turn === undefined ? {} : { turnId: target.turn.turnId }),
|
|
434
|
+
waitOutcome: outcome
|
|
435
|
+
};
|
|
436
|
+
if (includeLatestMessage) {
|
|
437
|
+
if (observation)
|
|
438
|
+
observation.messageRevision = Math.max(observation.messageRevision, view.messageRevision);
|
|
439
|
+
else
|
|
440
|
+
state.observedMessageRevision = Math.max(state.observedMessageRevision, view.messageRevision);
|
|
441
|
+
}
|
|
442
|
+
if (includeTerminalDetails) {
|
|
443
|
+
if (observation)
|
|
444
|
+
observation.terminalRevision = Math.max(observation.terminalRevision, view.resourceRevision);
|
|
445
|
+
else
|
|
446
|
+
state.observedTerminalRevision = Math.max(state.observedTerminalRevision, view.resourceRevision);
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
});
|
|
450
|
+
return {
|
|
451
|
+
mode,
|
|
452
|
+
waitFor,
|
|
453
|
+
agents,
|
|
454
|
+
pendingAgentIds,
|
|
455
|
+
waitOutcome: snapshot
|
|
456
|
+
? 'snapshot'
|
|
457
|
+
: !satisfied
|
|
458
|
+
? 'timeout'
|
|
459
|
+
: agents.some((agent) => agent.waitOutcome === 'message')
|
|
460
|
+
? 'message'
|
|
461
|
+
: 'completion'
|
|
462
|
+
};
|
|
463
|
+
}
|
|
359
464
|
async message(agentId, message, options = {}) {
|
|
360
465
|
const execution = this.#requireExecution();
|
|
361
466
|
if (typeof message !== 'string' ||
|
|
@@ -529,7 +634,6 @@ export class SubagentSessionController {
|
|
|
529
634
|
state.verification = [];
|
|
530
635
|
state.evidence = [];
|
|
531
636
|
state.terminalPending = false;
|
|
532
|
-
state.terminalObserved = false;
|
|
533
637
|
state.turn = {
|
|
534
638
|
turnId,
|
|
535
639
|
...(native?.ownerInstanceId === undefined ? {} : { ownerInstanceId: native.ownerInstanceId }),
|
|
@@ -730,9 +834,9 @@ export class SubagentSessionController {
|
|
|
730
834
|
native.terminalRevision = state.resourceRevision;
|
|
731
835
|
return true;
|
|
732
836
|
}
|
|
733
|
-
if (!isTerminalState(state) || state.
|
|
837
|
+
if (!isTerminalState(state) || state.observedTerminalRevision >= state.resourceRevision)
|
|
734
838
|
return false;
|
|
735
|
-
state.
|
|
839
|
+
state.observedTerminalRevision = state.resourceRevision;
|
|
736
840
|
return true;
|
|
737
841
|
}
|
|
738
842
|
#createDelivery(state, execution, receipt, signal) {
|
|
@@ -1022,7 +1126,7 @@ function restoreAgentState(session) {
|
|
|
1022
1126
|
verification: [],
|
|
1023
1127
|
evidence: [],
|
|
1024
1128
|
terminalPending: false,
|
|
1025
|
-
|
|
1129
|
+
observedTerminalRevision: -1,
|
|
1026
1130
|
resourceRevision: 0
|
|
1027
1131
|
};
|
|
1028
1132
|
if (terminalType === 'execution.completed' && terminal) {
|
|
@@ -1268,20 +1372,12 @@ function waitForPromise(promise, signal) {
|
|
|
1268
1372
|
abort();
|
|
1269
1373
|
});
|
|
1270
1374
|
}
|
|
1271
|
-
/**
|
|
1272
|
-
function
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
finish();
|
|
1279
|
-
else
|
|
1280
|
-
arm(remaining);
|
|
1281
|
-
};
|
|
1282
|
-
const arm = (remaining) => {
|
|
1283
|
-
timer = setTimeout(tick, Math.min(remaining, 2_147_483_647));
|
|
1375
|
+
/** Retain the waited Turn's evidence when the live Session moves on to another Turn. */
|
|
1376
|
+
function copyWaitState(state) {
|
|
1377
|
+
return {
|
|
1378
|
+
...state,
|
|
1379
|
+
changedFiles: [...state.changedFiles],
|
|
1380
|
+
verification: [...state.verification],
|
|
1381
|
+
evidence: state.evidence.map((item) => ({ ...item }))
|
|
1284
1382
|
};
|
|
1285
|
-
arm(waitMs);
|
|
1286
|
-
return () => clearTimeout(timer);
|
|
1287
1383
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SubagentWaitMode } from './contracts.js';
|
|
2
|
+
/** Validate the entire target set before any observation can be consumed. */
|
|
3
|
+
export declare function parseAgentWaitTargets(input: {
|
|
4
|
+
agentId?: unknown;
|
|
5
|
+
agentIds?: unknown;
|
|
6
|
+
mode?: unknown;
|
|
7
|
+
}, maxAgents?: number): {
|
|
8
|
+
agentIds?: string[];
|
|
9
|
+
mode?: SubagentWaitMode;
|
|
10
|
+
};
|
|
11
|
+
/** A group owns one listener and one deadline; readiness never consumes observations. */
|
|
12
|
+
export declare function waitForAgentCondition(input: {
|
|
13
|
+
ready(): boolean;
|
|
14
|
+
subscribe(notify: () => void): () => void;
|
|
15
|
+
waitMs: number;
|
|
16
|
+
signal?: AbortSignal | undefined;
|
|
17
|
+
}): Promise<boolean>;
|
|
18
|
+
/** Explicit waits retain their deadline without overflowing the platform timer. */
|
|
19
|
+
export declare function waitDeadline(waitMs: number, finish: () => void): () => void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { MarAgentError } from '../error.js';
|
|
2
|
+
/** Validate the entire target set before any observation can be consumed. */
|
|
3
|
+
export function parseAgentWaitTargets(input, maxAgents = Infinity) {
|
|
4
|
+
if (input.agentIds === undefined || input.agentIds === null) {
|
|
5
|
+
if (input.mode !== undefined && input.mode !== null)
|
|
6
|
+
invalid();
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
if ((input.agentId !== undefined && input.agentId !== null) ||
|
|
10
|
+
!Array.isArray(input.agentIds) ||
|
|
11
|
+
input.agentIds.length === 0 ||
|
|
12
|
+
input.agentIds.length > maxAgents ||
|
|
13
|
+
input.agentIds.some((id) => typeof id !== 'string' || !id.trim()) ||
|
|
14
|
+
(input.mode !== undefined &&
|
|
15
|
+
input.mode !== null &&
|
|
16
|
+
input.mode !== 'any' &&
|
|
17
|
+
input.mode !== 'all'))
|
|
18
|
+
invalid();
|
|
19
|
+
const agentIds = input.agentIds.map((id) => id.trim());
|
|
20
|
+
if (new Set(agentIds).size !== agentIds.length)
|
|
21
|
+
invalid();
|
|
22
|
+
return { agentIds, mode: input.mode === 'all' ? 'all' : 'any' };
|
|
23
|
+
}
|
|
24
|
+
function invalid() {
|
|
25
|
+
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Agent wait targets are invalid.');
|
|
26
|
+
}
|
|
27
|
+
/** A group owns one listener and one deadline; readiness never consumes observations. */
|
|
28
|
+
export function waitForAgentCondition(input) {
|
|
29
|
+
input.signal?.throwIfAborted();
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
let settled = false;
|
|
32
|
+
let unsubscribe = () => { };
|
|
33
|
+
let cancelTimer = () => { };
|
|
34
|
+
const finish = (ready, error) => {
|
|
35
|
+
if (settled)
|
|
36
|
+
return;
|
|
37
|
+
settled = true;
|
|
38
|
+
unsubscribe();
|
|
39
|
+
cancelTimer();
|
|
40
|
+
input.signal?.removeEventListener('abort', abort);
|
|
41
|
+
if (error === undefined)
|
|
42
|
+
resolve(ready);
|
|
43
|
+
else
|
|
44
|
+
reject(error);
|
|
45
|
+
};
|
|
46
|
+
const notify = () => {
|
|
47
|
+
if (input.ready())
|
|
48
|
+
finish(true);
|
|
49
|
+
};
|
|
50
|
+
const abort = () => finish(false, input.signal?.reason ??
|
|
51
|
+
new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
|
|
52
|
+
unsubscribe = input.subscribe(notify);
|
|
53
|
+
cancelTimer = waitDeadline(input.waitMs, () => finish(input.ready()));
|
|
54
|
+
input.signal?.addEventListener('abort', abort, { once: true });
|
|
55
|
+
if (input.signal?.aborted)
|
|
56
|
+
abort();
|
|
57
|
+
else
|
|
58
|
+
notify();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** Explicit waits retain their deadline without overflowing the platform timer. */
|
|
62
|
+
export function waitDeadline(waitMs, finish) {
|
|
63
|
+
const deadline = performance.now() + waitMs;
|
|
64
|
+
let timer;
|
|
65
|
+
const tick = () => {
|
|
66
|
+
const remaining = deadline - performance.now();
|
|
67
|
+
if (remaining <= 0)
|
|
68
|
+
finish();
|
|
69
|
+
else
|
|
70
|
+
arm(remaining);
|
|
71
|
+
};
|
|
72
|
+
const arm = (remaining) => {
|
|
73
|
+
timer = setTimeout(tick, Math.min(remaining, 2_147_483_647));
|
|
74
|
+
};
|
|
75
|
+
arm(waitMs);
|
|
76
|
+
return () => clearTimeout(timer);
|
|
77
|
+
}
|
|
@@ -3,5 +3,5 @@ import type { ToolRuntimeContext } from '../runtime-context.js';
|
|
|
3
3
|
export declare const agentWaitToolDefinition: ClientToolDefinition;
|
|
4
4
|
export declare function executeAgentWait(arguments_: unknown, context: ToolRuntimeContext): Promise<{
|
|
5
5
|
content: string;
|
|
6
|
-
data: import("../../subagent/contracts.js").SubagentTaskSnapshot;
|
|
6
|
+
data: import("../../subagent/contracts.js").SubagentTaskSnapshot | import("../../subagent/contracts.js").SubagentManyOutput;
|
|
7
7
|
}>;
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import { MarAgentError } from '../../error.js';
|
|
2
2
|
import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
|
|
3
|
+
import { parseAgentWaitTargets } from '../../subagent/wait.js';
|
|
3
4
|
export const agentWaitToolDefinition = {
|
|
4
5
|
name: 'agent_wait',
|
|
5
6
|
allowedCallers: ['direct'],
|
|
6
7
|
parallelSafety: 'serial',
|
|
7
|
-
description: 'Read or wait a bounded time for a child agent result. Wait only when the next local step depends on the child result; otherwise continue useful non-overlapping work. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec_wait instead (tools.exec_wait in Code Mode). Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits.
|
|
8
|
+
description: 'Read or wait a bounded time for a child agent result. Wait only when the next local step depends on the child result; otherwise continue useful non-overlapping work. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec_wait instead (tools.exec_wait in Code Mode). Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Use agentIds with mode any (default) or all to wait for an explicit non-empty set of up to 8 distinct child IDs; never combine agentId and agentIds or use mode without agentIds. mode and waitFor are independent. A group uses one deadline and returns mode, waitFor, waitOutcome, agents in input order, and pendingAgentIds. Failed, cancelled and interrupted turns also satisfy completion; terminal state satisfies message waits too. Group waitMs=0 returns only snapshots without consuming messages. Single-target and untargeted results include waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. A message, timeout with unread progress, or first terminal completion result includes the newly observed latest public commentary/final message; snapshots and repeated observations omit previously observed message text. latestActivity can still identify a public message or bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. First terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
|
|
8
9
|
inputSchema: {
|
|
9
10
|
type: 'object',
|
|
10
11
|
properties: {
|
|
11
12
|
agentId: { type: 'string' },
|
|
13
|
+
agentIds: {
|
|
14
|
+
type: 'array',
|
|
15
|
+
items: { type: 'string', minLength: 1 },
|
|
16
|
+
minItems: 1,
|
|
17
|
+
maxItems: TOOL_EXECUTION_LIMITS.agentWaitMaxAgents,
|
|
18
|
+
uniqueItems: true,
|
|
19
|
+
description: 'Explicit child-agent set. Mutually exclusive with agentId.'
|
|
20
|
+
},
|
|
21
|
+
mode: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
enum: ['any', 'all'],
|
|
24
|
+
default: 'any',
|
|
25
|
+
description: 'For agentIds only: return when any or all selected children satisfy waitFor.'
|
|
26
|
+
},
|
|
12
27
|
waitFor: {
|
|
13
28
|
type: 'string',
|
|
14
29
|
enum: ['completion', 'message'],
|
|
@@ -36,16 +51,18 @@ export async function executeAgentWait(arguments_, context) {
|
|
|
36
51
|
waitFor: value.waitFor ?? 'completion',
|
|
37
52
|
signal: context.signal
|
|
38
53
|
};
|
|
39
|
-
const task = value.
|
|
40
|
-
? await subagents.
|
|
41
|
-
:
|
|
54
|
+
const task = value.agentIds !== undefined
|
|
55
|
+
? await subagents.outputMany(value.agentIds, { ...options, mode: value.mode ?? 'any' })
|
|
56
|
+
: value.agentId === undefined
|
|
57
|
+
? await subagents.outputAny(options)
|
|
58
|
+
: await subagents.output(value.agentId, options);
|
|
42
59
|
return { content: JSON.stringify(task), data: task };
|
|
43
60
|
}
|
|
44
61
|
function parseAgentWaitInput(value) {
|
|
45
62
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
46
63
|
return invalidAgentWait();
|
|
47
64
|
const input = value;
|
|
48
|
-
if (Object.keys(input).some((key) => !['agentId', 'waitFor', 'waitMs'].includes(key)) ||
|
|
65
|
+
if (Object.keys(input).some((key) => !['agentId', 'agentIds', 'mode', 'waitFor', 'waitMs'].includes(key)) ||
|
|
49
66
|
(input.agentId !== undefined &&
|
|
50
67
|
input.agentId !== null &&
|
|
51
68
|
(typeof input.agentId !== 'string' || input.agentId.trim().length === 0)) ||
|
|
@@ -60,6 +77,7 @@ function parseAgentWaitInput(value) {
|
|
|
60
77
|
input.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs)))
|
|
61
78
|
return invalidAgentWait();
|
|
62
79
|
return {
|
|
80
|
+
...parseAgentWaitTargets(input, TOOL_EXECUTION_LIMITS.agentWaitMaxAgents),
|
|
63
81
|
...(typeof input.agentId === 'string' ? { agentId: input.agentId.trim() } : {}),
|
|
64
82
|
...(input.waitFor === 'completion' || input.waitFor === 'message'
|
|
65
83
|
? { waitFor: input.waitFor }
|
|
@@ -638,9 +638,8 @@ function streamText(value, emit) {
|
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
640
|
const ancestors = new Set();
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
item = item.toJSON(key);
|
|
641
|
+
const toJSONValue = (item, key) => item && typeof item === 'object' && typeof item.toJSON === 'function' ? item.toJSON(key) : item;
|
|
642
|
+
const write = (item, path) => {
|
|
644
643
|
if (['bigint', 'function', 'symbol', 'undefined'].includes(typeof item))
|
|
645
644
|
throw Object.assign(new TypeError(`Only JSON values may be emitted; ${path} has type ${typeof item}. Omit optional fields or use null explicitly.`), {
|
|
646
645
|
code: 'MAR_AGENT_TOOL_INPUT_INVALID'
|
|
@@ -670,7 +669,7 @@ function streamText(value, emit) {
|
|
|
670
669
|
for (let index = 0; index < length; index++) {
|
|
671
670
|
if (index)
|
|
672
671
|
emit(',');
|
|
673
|
-
write(item[index], String(index), `${path}[${index}]`.slice(0, 256));
|
|
672
|
+
write(toJSONValue(item[index], String(index)) ?? null, `${path}[${index}]`.slice(0, 256));
|
|
674
673
|
}
|
|
675
674
|
emit(']');
|
|
676
675
|
}
|
|
@@ -683,23 +682,25 @@ function streamText(value, emit) {
|
|
|
683
682
|
typeof item.mimeType === 'string' &&
|
|
684
683
|
item.mimeType.startsWith('image/');
|
|
685
684
|
for (const name of names) {
|
|
685
|
+
const field = toJSONValue(item[name], name);
|
|
686
|
+
if (field === undefined)
|
|
687
|
+
continue;
|
|
686
688
|
if (!first)
|
|
687
689
|
emit(',');
|
|
688
690
|
first = false;
|
|
689
691
|
chunks(name, true);
|
|
690
692
|
emit(':');
|
|
691
|
-
const field = item[name];
|
|
692
693
|
write(imagePayload &&
|
|
693
694
|
(name === 'dataBase64' || (name === 'data' && item.type === 'image')) &&
|
|
694
695
|
typeof field === 'string'
|
|
695
696
|
? '[image payload omitted; use observe.image]'
|
|
696
|
-
: field,
|
|
697
|
+
: field, `${path}[${JSON.stringify(name.slice(0, 64))}]`.slice(0, 256));
|
|
697
698
|
}
|
|
698
699
|
emit('}');
|
|
699
700
|
}
|
|
700
701
|
ancestors.delete(item);
|
|
701
702
|
};
|
|
702
|
-
write(value, '', '$');
|
|
703
|
+
write(toJSONValue(value, ''), '$');
|
|
703
704
|
}
|
|
704
705
|
function compilerSourceLocation(error, source) {
|
|
705
706
|
if (error?.name !== 'SyntaxError' ||
|
|
@@ -46,6 +46,7 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
|
|
|
46
46
|
agentWaitWaitFloorMs: 10000;
|
|
47
47
|
agentWaitDefaultWaitMs: 30000;
|
|
48
48
|
agentWaitMaxWaitMs: 300000;
|
|
49
|
+
agentWaitMaxAgents: 8;
|
|
49
50
|
subagentDescriptionMaxCharacters: 80;
|
|
50
51
|
subagentPromptMaxBytes: 131072;
|
|
51
52
|
subagentMessageMaxBytes: 131072;
|
|
@@ -46,6 +46,7 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
|
|
|
46
46
|
agentWaitWaitFloorMs: 10_000,
|
|
47
47
|
agentWaitDefaultWaitMs: 30_000,
|
|
48
48
|
agentWaitMaxWaitMs: 300_000,
|
|
49
|
+
agentWaitMaxAgents: 8,
|
|
49
50
|
subagentDescriptionMaxCharacters: 80,
|
|
50
51
|
subagentPromptMaxBytes: 131_072,
|
|
51
52
|
subagentMessageMaxBytes: 131_072,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SubagentTaskSnapshot } from '../subagent/contracts.js';
|
|
1
|
+
import type { SubagentTaskSnapshot, SubagentManyOutput } from '../subagent/contracts.js';
|
|
2
2
|
import type { ApplyPatchResult, ApplyPatchPartialResult, CodeCellEventData, ExecResult, MarAgentToolArtifact, ToolExecutionResult, ReadEventData, ViewImageResult } from './contracts.js';
|
|
3
3
|
import type { ToolRuntimeImage } from './runtime-context.js';
|
|
4
4
|
export declare function currentExecutionImages(modelInputImages: readonly {
|
|
@@ -10,7 +10,7 @@ export declare function boundedModelToolOutput(toolName: string, value: string,
|
|
|
10
10
|
truncated: boolean;
|
|
11
11
|
};
|
|
12
12
|
/** Project only known, validated tool result shapes into public structured events. */
|
|
13
|
-
export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SubagentTaskSnapshot | undefined;
|
|
13
|
+
export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SubagentTaskSnapshot | SubagentManyOutput | undefined;
|
|
14
14
|
/** Partial patch facts are allowlisted; arbitrary error details never become public event data. */
|
|
15
15
|
export declare function structuredToolFailureData(toolName: string, error: unknown): ApplyPatchPartialResult | undefined;
|
|
16
16
|
export declare function structuredToolArtifacts(value: readonly MarAgentToolArtifact[] | undefined): readonly MarAgentToolArtifact[] | undefined;
|
|
@@ -35,6 +35,18 @@ export function boundedModelToolOutput(toolName, value, data) {
|
|
|
35
35
|
export function structuredToolEventData(toolName, value) {
|
|
36
36
|
if (!isRecord(value))
|
|
37
37
|
return undefined;
|
|
38
|
+
if (toolName === 'agent_wait' &&
|
|
39
|
+
(value.mode === 'any' || value.mode === 'all') &&
|
|
40
|
+
(value.waitFor === 'completion' || value.waitFor === 'message') &&
|
|
41
|
+
['snapshot', 'message', 'completion', 'timeout'].includes(String(value.waitOutcome)) &&
|
|
42
|
+
Array.isArray(value.agents) &&
|
|
43
|
+
value.agents.length > 0 &&
|
|
44
|
+
value.agents.length <= TOOL_EXECUTION_LIMITS.agentWaitMaxAgents &&
|
|
45
|
+
value.agents.every((agent) => isRecord(agent) && isSubagentTaskSnapshot(agent)) &&
|
|
46
|
+
Array.isArray(value.pendingAgentIds) &&
|
|
47
|
+
value.pendingAgentIds.every((id) => typeof id === 'string' &&
|
|
48
|
+
value.agents.some((agent) => agent.agentId === id)))
|
|
49
|
+
return value;
|
|
38
50
|
if ((toolName === 'code_exec' || toolName === 'code_exec_wait') &&
|
|
39
51
|
typeof value.cellId === 'string' &&
|
|
40
52
|
['running', 'completed', 'failed', 'cancelled'].includes(String(value.status)))
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
|
-
import { lstat, open
|
|
3
|
+
import { lstat, open } from 'node:fs/promises';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { MarAgentError } from '../agent-core/error.js';
|
|
7
7
|
import { SecretValue } from '../agent-core/model/configuration.js';
|
|
8
|
+
import { productUserAgent } from '../agent-core/model/product-user-agent.js';
|
|
8
9
|
const MAX_AUTH_FILE_BYTES = 1024 * 1024;
|
|
9
10
|
const TOKEN_MAX_BYTES = 131_072;
|
|
10
11
|
const AUTH_FALLBACK_LIFETIME_MS = 8 * 24 * 60 * 60 * 1000;
|
|
@@ -52,7 +53,7 @@ export class CodexAuthFileCredentialProvider {
|
|
|
52
53
|
headers: {
|
|
53
54
|
'ChatGPT-Account-ID': new SecretValue(parsed.accountId),
|
|
54
55
|
originator: 'myagentroam',
|
|
55
|
-
'user-agent':
|
|
56
|
+
'user-agent': productUserAgent()
|
|
56
57
|
}
|
|
57
58
|
};
|
|
58
59
|
this.#cached = { fingerprint: file.fingerprint, credential };
|
|
@@ -263,18 +264,6 @@ function record(value) {
|
|
|
263
264
|
? value
|
|
264
265
|
: {};
|
|
265
266
|
}
|
|
266
|
-
let productUserAgentPromise;
|
|
267
|
-
async function productUserAgent() {
|
|
268
|
-
productUserAgentPromise ??= readFile(new URL('../../package.json', import.meta.url), 'utf8')
|
|
269
|
-
.then((content) => {
|
|
270
|
-
const value = JSON.parse(content);
|
|
271
|
-
if (typeof value.version !== 'string')
|
|
272
|
-
throw new Error('package version unavailable');
|
|
273
|
-
return `MyAgentRoam/${value.version} (${process.platform}; ${process.arch})`;
|
|
274
|
-
})
|
|
275
|
-
.catch(() => `MyAgentRoam/unknown (${process.platform}; ${process.arch})`);
|
|
276
|
-
return await productUserAgentPromise;
|
|
277
|
-
}
|
|
278
267
|
function unsafeFile(cause) {
|
|
279
268
|
return new MarAgentError('MAR_AGENT_CREDENTIAL_FILE_UNSAFE', 'Codex auth file permissions or file type are unsafe.', cause === undefined ? {} : { cause });
|
|
280
269
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type { CoreFilesApi, CoreFilesBackend, CoreFilePath, CoreFileWrite, CoreF
|
|
|
29
29
|
export type { CoreHostExtensionPorts, CoreHostToolSource, CoreHostToolsBackend } from './agent-core/host/contracts.js';
|
|
30
30
|
export type { CoreEnvironmentApi, CoreEnvironmentBackend, CoreEnvironmentKey, CoreEnvironmentSet, CoreEnvironmentChange, CoreEnvironmentCompletion } from './agent-core/extensions/environment.js';
|
|
31
31
|
export type { CoreMcpApi, CoreMcpBackend, CoreMcpConnectionBackend, CoreMcpToolCall, CoreMcpServerDefinition, CoreMcpServerRef, CoreMcpServerRegistration, CoreMcpConnectionRef, CoreMcpConnectInput, CoreMcpConnectionInput, CoreMcpCallInput, CoreMcpCatalog, CoreMcpCompletion } from './agent-core/extensions/mcp.js';
|
|
32
|
-
export type { CoreAgentsApi, CoreAgentsBackend, CoreAgentSpawnInput, CoreAgentTarget, CoreAgentWaitInput, CoreAgentMessageInput, CoreAgentSnapshot, CoreAgentHandledResult, CoreAgentSpawnResult, CoreAgentCompletion } from './agent-core/extensions/agents.js';
|
|
32
|
+
export type { CoreAgentsApi, CoreAgentsBackend, CoreAgentSpawnInput, CoreAgentTarget, CoreAgentWaitInput, CoreAgentWaitResult, CoreAgentManySnapshot, CoreAgentMessageInput, CoreAgentSnapshot, CoreAgentHandledResult, CoreAgentSpawnResult, CoreAgentCompletion } from './agent-core/extensions/agents.js';
|
|
33
33
|
export type { CoreSettingsApi, CoreSettingsBackend, CoreSettingsReadInput, CoreSettingsUpdate, CoreSettingsDescription, CoreSettingDefinition, CoreSettingsSnapshot, CoreSettingsResult, CoreSettingsCompletion } from './agent-core/extensions/settings.js';
|
|
34
34
|
export type { CoreNetworkApi, CoreNetworkBackend, CoreHttpRequest, CoreHttpResponse, CoreHttpCompletion } from './agent-core/extensions/network.js';
|
|
35
35
|
export type { CoreSessionApi, CoreSessionReceipt, CoreSessionInspection, CoreSessionDescription, CoreSessionMessages, CoreSessionMessagesInput, CoreSessionMessage, CoreSessionOperationStatus, CoreAppendInput, CoreSubmitInput, CoreInputSource, CoreCompactInput, CoreSessionActionResult } from './agent-core/extensions/session-input.js';
|