@myagentroam/agent 0.9.139 → 0.9.141
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/host/local-agent-host.d.ts +3 -1
- package/dist/host/local-agent-host.js +29 -3
- package/dist/model/anthropic-messages.js +24 -7
- package/dist/model/contracts.d.ts +4 -0
- package/dist/model/openai-responses.js +28 -10
- package/dist/model/sse.d.ts +2 -0
- package/dist/model/sse.js +11 -1
- package/dist/prompts/resources.js +4 -2
- package/dist/prompts/workflow.js +2 -0
- package/dist/runtime/context-gc.js +25 -8
- package/dist/sdk/agent.js +68 -42
- package/dist/session/context-gc-checkpoint.d.ts +1 -1
- package/dist/session/context-gc-checkpoint.js +8 -5
- package/dist/session/context-gc-replacements.d.ts +1 -1
- package/dist/session/context-gc-replacements.js +5 -3
- package/dist/tools/agent-wait.js +1 -1
- package/dist/tools/apply-patch.js +1 -1
- package/dist/tools/callers.js +1 -1
- package/dist/tools/code-mode/cells.d.ts +7 -1
- package/dist/tools/code-mode/cells.js +32 -8
- package/dist/tools/code-mode/engine.d.ts +5 -2
- package/dist/tools/code-mode/quickjs-engine.js +116 -18
- package/dist/tools/code-mode/text-output.d.ts +21 -0
- package/dist/tools/code-mode/text-output.js +75 -0
- package/dist/tools/code-mode/worker.js +1 -1
- package/dist/tools/code-mode.js +36 -18
- package/dist/tools/exec.d.ts +3 -0
- package/dist/tools/exec.js +56 -15
- package/dist/tools/execution-limits.d.ts +4 -1
- package/dist/tools/execution-limits.js +4 -1
- package/dist/tools/local-registry.js +5 -5
- package/dist/tools/registry.js +2 -1
- package/dist/tools/skill.js +1 -1
- package/dist/tools/tool-search.js +5 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CodeModeEngineFactory } from '../tools/code-mode/engine.js';
|
|
2
2
|
import type { HostDescription } from './contracts.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type LocalProcessExecutor } from './local-process-executor.js';
|
|
4
4
|
import type { WorkspacePathMount } from '../tools/shared/path-resolver.js';
|
|
5
5
|
import type { MarAgentEvent, MarAgentHost, HostExecutionExtensions, McpToolCatalogContext, McpToolCatalog, McpToolCallContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../sdk/types.js';
|
|
6
6
|
export interface LocalAgentMcpBinding {
|
|
@@ -13,6 +13,8 @@ export interface LocalAgentExecutionMcpBinding extends LocalAgentMcpBinding {
|
|
|
13
13
|
readonly extensions?: LocalAgentExecutionExtensions;
|
|
14
14
|
}
|
|
15
15
|
export interface LocalAgentExecutionExtensions extends HostExecutionExtensions {
|
|
16
|
+
/** Host-private process environment, snapshotted when bound to an execution. */
|
|
17
|
+
readonly environment?: Readonly<Record<string, string>>;
|
|
16
18
|
executeTool?(call: ToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
17
19
|
}
|
|
18
20
|
export declare class LocalAgentHost implements MarAgentHost {
|
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { basename, delimiter, isAbsolute, join, resolve, win32 } from 'node:path';
|
|
5
|
+
import { directLocalProcessExecutor } from './local-process-executor.js';
|
|
5
6
|
import { MAR_AGENT_HOST_API_VERSION } from './contracts.js';
|
|
6
7
|
import { MarAgentError } from '../error.js';
|
|
7
8
|
import { LocalToolRegistry } from '../tools/local-registry.js';
|
|
@@ -36,6 +37,7 @@ export class LocalAgentHost {
|
|
|
36
37
|
this.#hasWebSearch = input.webSearch !== undefined;
|
|
37
38
|
this.#hasWebFetch = input.webFetch !== false;
|
|
38
39
|
this.#resourceIdleTtlMs = input.resourceIdleTtlMs;
|
|
40
|
+
const processExecutor = input.processExecutor ?? directLocalProcessExecutor;
|
|
39
41
|
this.#tools = new LocalToolRegistry(this.#workspace, this.#environment, this.#shell.path, {
|
|
40
42
|
...(input.webSearch ? { webSearch: input.webSearch } : {}),
|
|
41
43
|
webFetch: this.#hasWebFetch,
|
|
@@ -44,7 +46,25 @@ export class LocalAgentHost {
|
|
|
44
46
|
...(input.codeModeEngineFactory === undefined
|
|
45
47
|
? {}
|
|
46
48
|
: { codeModeEngineFactory: input.codeModeEngineFactory }),
|
|
47
|
-
|
|
49
|
+
processExecutor: {
|
|
50
|
+
spawn: (request) => {
|
|
51
|
+
const binding = request.owner && this.#executionMcp.get(request.owner.executionId);
|
|
52
|
+
const environment = binding?.active ? binding.extensions?.environment : undefined;
|
|
53
|
+
return processExecutor.spawn(environment === undefined
|
|
54
|
+
? request
|
|
55
|
+
: {
|
|
56
|
+
...request,
|
|
57
|
+
environment: { ...request.environment, ...environment },
|
|
58
|
+
...(request.environmentKeys === undefined
|
|
59
|
+
? {}
|
|
60
|
+
: {
|
|
61
|
+
environmentKeys: [
|
|
62
|
+
...new Set([...request.environmentKeys, ...Object.keys(environment)])
|
|
63
|
+
]
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
48
68
|
...(input.restrictPaths === undefined ? {} : { restrictPaths: input.restrictPaths }),
|
|
49
69
|
...(input.workspaceWritable === undefined
|
|
50
70
|
? {}
|
|
@@ -108,7 +128,12 @@ export class LocalAgentHost {
|
|
|
108
128
|
(async () => {
|
|
109
129
|
throw new MarAgentError('MAR_AGENT_MCP_FAILED', 'MCP execution is unavailable.');
|
|
110
130
|
}),
|
|
111
|
-
extensions
|
|
131
|
+
extensions: {
|
|
132
|
+
...extensions,
|
|
133
|
+
...(extensions.environment === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: { environment: { ...extensions.environment } })
|
|
136
|
+
},
|
|
112
137
|
active: true
|
|
113
138
|
};
|
|
114
139
|
this.#currentExecutionMcp = binding;
|
|
@@ -116,7 +141,8 @@ export class LocalAgentHost {
|
|
|
116
141
|
}
|
|
117
142
|
async listExecutionExtensions(context) {
|
|
118
143
|
const binding = this.#executionBinding(context);
|
|
119
|
-
|
|
144
|
+
const { environment: _environment, ...extensions } = binding?.extensions ?? this.#extensions ?? {};
|
|
145
|
+
return extensions;
|
|
120
146
|
}
|
|
121
147
|
emit(event) {
|
|
122
148
|
this.events.push(event);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { MarAgentError } from '../error.js';
|
|
3
3
|
import { resolveModelDefaultReasoningEffort } from './configuration.js';
|
|
4
|
-
import { isRetryableUpstreamOverloadCode, number, parseSse, record, text } from './sse.js';
|
|
4
|
+
import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observeModelStreamEvent, modelStreamEventObservedAt } from './sse.js';
|
|
5
5
|
import { fetchModelResponse, modelRetryMaxAttempts, isRetryableModelRequestError, isRetryableModelTransportError, waitForModelRetry } from './http.js';
|
|
6
6
|
import { anthropicThinkingBlock, assertAnthropicThinkingBudget } from './anthropic-thinking.js';
|
|
7
7
|
export class AnthropicMessagesAdapter {
|
|
@@ -12,13 +12,22 @@ export class AnthropicMessagesAdapter {
|
|
|
12
12
|
async *start(request, signal) {
|
|
13
13
|
const retryState = request.retryState ?? { attempts: 0 };
|
|
14
14
|
const maxAttempts = modelRetryMaxAttempts(retryState);
|
|
15
|
+
let resetOutputTiming = false;
|
|
15
16
|
while (retryState.attempts < maxAttempts) {
|
|
17
|
+
if (resetOutputTiming)
|
|
18
|
+
yield { type: 'output.reset' };
|
|
19
|
+
resetOutputTiming = false;
|
|
16
20
|
const attempt = retryState.attempts++;
|
|
17
21
|
// Buffer non-semantic events until a semantic event makes this attempt non-retryable.
|
|
18
22
|
const prelude = [];
|
|
19
23
|
let sawSemanticEvent = false;
|
|
20
24
|
try {
|
|
21
25
|
for await (const event of this.startAttempt(request, signal)) {
|
|
26
|
+
if (event.type === 'output.delta') {
|
|
27
|
+
resetOutputTiming = true;
|
|
28
|
+
yield event;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
22
31
|
if (!sawSemanticEvent && !isSemanticEvent(event)) {
|
|
23
32
|
prelude.push(event);
|
|
24
33
|
continue;
|
|
@@ -193,17 +202,25 @@ export class AnthropicMessagesAdapter {
|
|
|
193
202
|
if (deltaType === 'text_delta') {
|
|
194
203
|
const value = text(delta.text) ?? '';
|
|
195
204
|
block.text += value;
|
|
196
|
-
yield { type: 'text.delta', itemId: block.id, phase: 'commentary', delta: value };
|
|
205
|
+
yield observeModelStreamEvent({ type: 'text.delta', itemId: block.id, phase: 'commentary', delta: value }, modelStreamEventObservedAt(raw));
|
|
206
|
+
}
|
|
207
|
+
else if (deltaType === 'thinking_delta') {
|
|
208
|
+
const value = text(delta.thinking) ?? '';
|
|
209
|
+
block.text += value;
|
|
210
|
+
if (value.length > 0)
|
|
211
|
+
yield observeModelStreamEvent({ type: 'output.delta' }, modelStreamEventObservedAt(raw));
|
|
197
212
|
}
|
|
198
|
-
else if (deltaType === 'thinking_delta')
|
|
199
|
-
block.text += text(delta.thinking) ?? '';
|
|
200
213
|
else if (deltaType === 'signature_delta') {
|
|
201
214
|
if (block.type !== 'thinking' || typeof delta.signature !== 'string')
|
|
202
215
|
throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Anthropic returned an invalid thinking signature delta.');
|
|
203
216
|
block.signature += delta.signature;
|
|
204
217
|
}
|
|
205
|
-
else if (deltaType === 'input_json_delta')
|
|
206
|
-
|
|
218
|
+
else if (deltaType === 'input_json_delta') {
|
|
219
|
+
const value = text(delta.partial_json) ?? '';
|
|
220
|
+
block.json += value;
|
|
221
|
+
if (value.length > 0)
|
|
222
|
+
yield observeModelStreamEvent({ type: 'output.delta' }, modelStreamEventObservedAt(raw));
|
|
223
|
+
}
|
|
207
224
|
else if (deltaType === 'citations_delta') {
|
|
208
225
|
const citation = record(delta.citation);
|
|
209
226
|
const url = text(citation.url);
|
|
@@ -307,7 +324,7 @@ export class AnthropicMessagesAdapter {
|
|
|
307
324
|
};
|
|
308
325
|
}
|
|
309
326
|
else if (type === 'message_stop') {
|
|
310
|
-
yield { type: 'completed', reason: terminal ?? 'completed' };
|
|
327
|
+
yield observeModelStreamEvent({ type: 'completed', reason: terminal ?? 'completed' }, modelStreamEventObservedAt(raw));
|
|
311
328
|
}
|
|
312
329
|
else if (type === 'error') {
|
|
313
330
|
const upstream = record(event.error);
|
|
@@ -95,6 +95,10 @@ export type ModelStopReason = 'completed' | 'tool_use' | 'pause' | 'max_output'
|
|
|
95
95
|
export type ModelEvent = {
|
|
96
96
|
type: 'response.started';
|
|
97
97
|
responseId: string;
|
|
98
|
+
} | {
|
|
99
|
+
type: 'output.delta';
|
|
100
|
+
} | {
|
|
101
|
+
type: 'output.reset';
|
|
98
102
|
} | {
|
|
99
103
|
type: 'response.item';
|
|
100
104
|
provider: 'OPENAI_RESPONSES' | 'ANTHROPIC_MESSAGES';
|
|
@@ -3,7 +3,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { describeMarAgentError, MarAgentError } from '../error.js';
|
|
4
4
|
import { marAgentReasoningEffortSchema, resolveModelDefaultReasoningEffort, usesResponsesConfigurationUpdates } from './configuration.js';
|
|
5
5
|
import WebSocket from 'ws';
|
|
6
|
-
import { isRetryableUpstreamOverloadCode, number, parseSse, record, text } from './sse.js';
|
|
6
|
+
import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observeModelStreamEvent, modelStreamEventObservedAt } from './sse.js';
|
|
7
7
|
import { connectSocks5Target, fetchModelResponse, isRetryableModelHttpStatus, isRetryableModelTransportError, modelRetryMaxAttempts, isRetryableModelRequestError, modelRetryDelay, waitForModelRetry } from './http.js';
|
|
8
8
|
import { acquireModelCredential, appendModelCredentialHeaders } from './runtime-credential.js';
|
|
9
9
|
const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
|
|
@@ -77,7 +77,11 @@ export class OpenAiResponsesAdapter {
|
|
|
77
77
|
let invalidContinuationFallbackUsed = false;
|
|
78
78
|
const retryState = request.retryState ?? { attempts: 0 };
|
|
79
79
|
const maxAttempts = modelRetryMaxAttempts(retryState);
|
|
80
|
+
let resetOutputTiming = false;
|
|
80
81
|
while (retryState.attempts < maxAttempts) {
|
|
82
|
+
if (resetOutputTiming)
|
|
83
|
+
yield { type: 'output.reset' };
|
|
84
|
+
resetOutputTiming = false;
|
|
81
85
|
const attempt = retryState.attempts++;
|
|
82
86
|
const attemptNumber = attempt + 1;
|
|
83
87
|
const attemptStartedAt = Date.now();
|
|
@@ -99,6 +103,11 @@ export class OpenAiResponsesAdapter {
|
|
|
99
103
|
});
|
|
100
104
|
try {
|
|
101
105
|
for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn)) {
|
|
106
|
+
if (event.type === 'output.delta') {
|
|
107
|
+
resetOutputTiming = true;
|
|
108
|
+
yield event;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
102
111
|
if (!sawSemanticEvent && !isSemanticEvent(event)) {
|
|
103
112
|
prelude.push(event);
|
|
104
113
|
continue;
|
|
@@ -341,8 +350,16 @@ export class OpenAiResponsesAdapter {
|
|
|
341
350
|
else if (type === 'response.custom_tool_call_input.delta') {
|
|
342
351
|
const itemId = text(event.item_id) ?? '';
|
|
343
352
|
const known = customCalls.get(itemId);
|
|
353
|
+
const delta = text(event.delta) ?? '';
|
|
344
354
|
if (known)
|
|
345
|
-
known.input +=
|
|
355
|
+
known.input += delta;
|
|
356
|
+
if (delta.length > 0)
|
|
357
|
+
yield observeModelStreamEvent({ type: 'output.delta' }, modelStreamEventObservedAt(raw));
|
|
358
|
+
}
|
|
359
|
+
else if (type === 'response.function_call_arguments.delta' ||
|
|
360
|
+
type === 'response.reasoning_summary_text.delta') {
|
|
361
|
+
if ((text(event.delta)?.length ?? 0) > 0)
|
|
362
|
+
yield observeModelStreamEvent({ type: 'output.delta' }, modelStreamEventObservedAt(raw));
|
|
346
363
|
}
|
|
347
364
|
else if (type === 'response.created')
|
|
348
365
|
yield {
|
|
@@ -350,14 +367,14 @@ export class OpenAiResponsesAdapter {
|
|
|
350
367
|
responseId: responseEventId(event) ?? 'unknown'
|
|
351
368
|
};
|
|
352
369
|
else if (type === 'response.output_text.delta')
|
|
353
|
-
yield {
|
|
370
|
+
yield observeModelStreamEvent({
|
|
354
371
|
type: 'text.delta',
|
|
355
372
|
itemId: text(event.item_id) ?? 'text',
|
|
356
373
|
phase: messagePhase(event.phase) ??
|
|
357
374
|
textPhases.get(text(event.item_id) ?? '') ??
|
|
358
375
|
'commentary',
|
|
359
376
|
delta: text(event.delta) ?? ''
|
|
360
|
-
};
|
|
377
|
+
}, modelStreamEventObservedAt(raw));
|
|
361
378
|
else if (type === 'response.output_text.done') {
|
|
362
379
|
const itemId = text(event.item_id) ?? 'text';
|
|
363
380
|
const completedText = text(event.text) ?? '';
|
|
@@ -519,10 +536,10 @@ export class OpenAiResponsesAdapter {
|
|
|
519
536
|
...(rolloutBudgetUnits === undefined ? {} : { rolloutBudgetUnits })
|
|
520
537
|
}
|
|
521
538
|
};
|
|
522
|
-
yield {
|
|
539
|
+
yield observeModelStreamEvent({
|
|
523
540
|
type: 'completed',
|
|
524
541
|
reason: type === 'response.incomplete' ? 'max_output' : 'completed'
|
|
525
|
-
};
|
|
542
|
+
}, modelStreamEventObservedAt(raw));
|
|
526
543
|
completed = true;
|
|
527
544
|
}
|
|
528
545
|
else if (type === 'error' || type === 'response.failed') {
|
|
@@ -634,6 +651,7 @@ class OpenAiResponsesTurnSession {
|
|
|
634
651
|
if (event.type === 'completed')
|
|
635
652
|
warmCompleted = event.reason === 'completed';
|
|
636
653
|
if (event.type === 'client_tool.call' ||
|
|
654
|
+
event.type === 'output.delta' ||
|
|
637
655
|
event.type === 'text.delta' ||
|
|
638
656
|
event.type === 'text.completed' ||
|
|
639
657
|
event.type === 'response.item' ||
|
|
@@ -660,6 +678,7 @@ class OpenAiResponsesTurnSession {
|
|
|
660
678
|
else if (event.type === 'response.item')
|
|
661
679
|
outputItems.push(event.item);
|
|
662
680
|
else if (event.type === 'text.delta' ||
|
|
681
|
+
event.type === 'output.delta' ||
|
|
663
682
|
event.type === 'text.completed' ||
|
|
664
683
|
event.type === 'reasoning.summary' ||
|
|
665
684
|
event.type === 'client_tool.call' ||
|
|
@@ -1179,7 +1198,8 @@ async function* webSocketMessages(socket, signal) {
|
|
|
1179
1198
|
};
|
|
1180
1199
|
const message = (value) => push({
|
|
1181
1200
|
type: 'message',
|
|
1182
|
-
value
|
|
1201
|
+
value,
|
|
1202
|
+
observedAt: performance.now()
|
|
1183
1203
|
});
|
|
1184
1204
|
const close = () => push({ type: 'closed' });
|
|
1185
1205
|
const error = (cause) => push({
|
|
@@ -1228,7 +1248,7 @@ async function* webSocketMessages(socket, signal) {
|
|
|
1228
1248
|
const event = JSON.parse(text);
|
|
1229
1249
|
if (!isWebSocketNonProgressEvent(event))
|
|
1230
1250
|
idleDeadline = Date.now() + WEBSOCKET_IDLE_TIMEOUT_MS;
|
|
1231
|
-
yield event;
|
|
1251
|
+
yield observeModelStreamEvent(event, item.observedAt);
|
|
1232
1252
|
}
|
|
1233
1253
|
catch (cause) {
|
|
1234
1254
|
throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Model WebSocket contained invalid JSON.', { cause });
|
|
@@ -1336,13 +1356,11 @@ const NON_SEMANTIC_OPENAI_RESPONSES_EVENTS = new Set([
|
|
|
1336
1356
|
'codex.response.metadata',
|
|
1337
1357
|
'response.content_part.added',
|
|
1338
1358
|
'response.content_part.done',
|
|
1339
|
-
'response.function_call_arguments.delta',
|
|
1340
1359
|
'response.in_progress',
|
|
1341
1360
|
'response.metadata',
|
|
1342
1361
|
'response.queued',
|
|
1343
1362
|
'response.reasoning_summary_part.added',
|
|
1344
1363
|
'response.reasoning_summary_part.done',
|
|
1345
|
-
'response.reasoning_summary_text.delta',
|
|
1346
1364
|
'responsesapi.websocket_timing'
|
|
1347
1365
|
]);
|
|
1348
1366
|
function isRetryableResponsesUpstreamError(code, message) {
|
package/dist/model/sse.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare function observeModelStreamEvent<const T>(value: T, observedAt: number | undefined): T;
|
|
2
|
+
export declare function modelStreamEventObservedAt(value: unknown): number | undefined;
|
|
1
3
|
export declare function parseSse(response: Response, signal: AbortSignal, maxBytes?: number, idleTimeoutMs?: number): AsyncIterable<unknown>;
|
|
2
4
|
export declare function isRetryableUpstreamOverloadCode(code: string | undefined): boolean;
|
|
3
5
|
export declare function record(value: unknown): Record<string, unknown>;
|
package/dist/model/sse.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { MarAgentError } from '../error.js';
|
|
2
2
|
import { isRetryableModelHttpStatus } from './http.js';
|
|
3
3
|
const DEFAULT_MODEL_STREAM_IDLE_TIMEOUT_MS = 300_000;
|
|
4
|
+
const streamEventObservedAt = new WeakMap();
|
|
5
|
+
export function observeModelStreamEvent(value, observedAt) {
|
|
6
|
+
if (typeof value === 'object' && value !== null && observedAt !== undefined)
|
|
7
|
+
streamEventObservedAt.set(value, observedAt);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
export function modelStreamEventObservedAt(value) {
|
|
11
|
+
return typeof value === 'object' && value !== null ? streamEventObservedAt.get(value) : undefined;
|
|
12
|
+
}
|
|
4
13
|
export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, idleTimeoutMs = DEFAULT_MODEL_STREAM_IDLE_TIMEOUT_MS) {
|
|
5
14
|
if (!response.ok) {
|
|
6
15
|
const body = await readResponseText(response, signal, maxBytes, idleTimeoutMs);
|
|
@@ -34,6 +43,7 @@ export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, i
|
|
|
34
43
|
if (signal.aborted)
|
|
35
44
|
throw signal.reason;
|
|
36
45
|
const { value, done } = await readStreamChunk(reader, signal, idleTimeoutMs);
|
|
46
|
+
const observedAt = performance.now();
|
|
37
47
|
if (done) {
|
|
38
48
|
streamCompleted = true;
|
|
39
49
|
break;
|
|
@@ -53,7 +63,7 @@ export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, i
|
|
|
53
63
|
if (data.length === 0 || data === '[DONE]')
|
|
54
64
|
continue;
|
|
55
65
|
try {
|
|
56
|
-
yield JSON.parse(data);
|
|
66
|
+
yield observeModelStreamEvent(JSON.parse(data), observedAt);
|
|
57
67
|
}
|
|
58
68
|
catch (cause) {
|
|
59
69
|
throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Model SSE contained invalid JSON.', { cause });
|
|
@@ -12,7 +12,9 @@ export function retainedSessionResourcesSnapshot(input) {
|
|
|
12
12
|
]
|
|
13
13
|
: []),
|
|
14
14
|
'These resources belong to this Session and survived the previous Execution. Reuse their stable IDs instead of starting duplicate processes or subagents.',
|
|
15
|
-
processes.length === 0
|
|
15
|
+
processes.length === 0
|
|
16
|
+
? ''
|
|
17
|
+
: 'Use exec_wait with processId to wait, write stdin or cancel; in Code Mode use tools.exec_wait. Cancelling a wait leaves the process running; explicit cancel releases the process resource.',
|
|
16
18
|
subagents.length === 0
|
|
17
19
|
? ''
|
|
18
20
|
: 'agent_cancel stops only the current child turn and retains its Session for later messages or explicit deletion.',
|
|
@@ -40,7 +42,7 @@ export function retainedSessionResourcesUpdate(snapshot, previousSnapshot, previ
|
|
|
40
42
|
return undefined;
|
|
41
43
|
if (!snapshot)
|
|
42
44
|
return previousSnapshot || previousMayContainResources
|
|
43
|
-
? 'The previously provided retained Session resources no longer apply. No retained processes, code cells or subagents remain.'
|
|
45
|
+
? 'The previously provided retained Session resources no longer apply. No retained processes, code cells or subagents remain. Historical resource IDs are not live handles; do not poll or wait on them. Use already recorded results or saved artifacts to recover completed work before starting anything again.'
|
|
44
46
|
: undefined;
|
|
45
47
|
return previousSnapshot || previousMayContainResources
|
|
46
48
|
? `This replaces all previously provided retained Session resources.\n\n${snapshot}`
|
package/dist/prompts/workflow.js
CHANGED
|
@@ -16,6 +16,8 @@ export function toolUsagePrompt(tools, directTools = tools) {
|
|
|
16
16
|
clauses.push('If the request names or clearly matches an available Skill description, call skill before generic tools and follow the loaded workflow; semantic matches do not require the user to know the Skill name. Do not load unrelated Skills or claim to use one without calling skill.');
|
|
17
17
|
if (names.has('exec'))
|
|
18
18
|
clauses.push('Use exec for platform-native commands, scripts, verification, and mechanical edits. Prefer rg or rg --files for search. Batch independent lookups and already-decided commands, preserving failure ordering. Use scripts for mechanical substitutions instead of reproducing long unchanged text in a patch; inspect actual effects afterward.');
|
|
19
|
+
if (names.has('exec_wait'))
|
|
20
|
+
clauses.push('Use exec_wait with processId for subsequent process waits, stdin writes or cancellation; in Code Mode use tools.exec_wait. exec only starts commands.');
|
|
19
21
|
if (names.has('read'))
|
|
20
22
|
clauses.push('Use read to batch known files and ranges. Batch only already-located, bounded ranges that are likely to fit together; for large files or documents, locate relevant sections before reading them. Read only what can affect the next decision, continue only relevant incomplete results using returned metadata, and avoid rereading unchanged content. Do not edit from an incomplete oversized-line preview. Obtain exact source with exec; if it still cannot fit, use a preconditioned deterministic replacement and verify the result.');
|
|
21
23
|
if (names.has('apply_patch'))
|
|
@@ -690,7 +690,7 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
|
|
|
690
690
|
let protectedTokens = 0;
|
|
691
691
|
let retryAfterRollouts = Infinity;
|
|
692
692
|
const execGroups = [...groups.values()]
|
|
693
|
-
.filter((group) => group.toolName === 'exec')
|
|
693
|
+
.filter((group) => group.toolName === 'exec' || group.toolName === 'exec_wait')
|
|
694
694
|
.sort((left, right) => (left.call?.record.sequence ?? Number.MAX_SAFE_INTEGER) -
|
|
695
695
|
(right.call?.record.sequence ?? Number.MAX_SAFE_INTEGER));
|
|
696
696
|
const invocations = new Map();
|
|
@@ -713,15 +713,32 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
|
|
|
713
713
|
}
|
|
714
714
|
for (const startGroup of execGroups) {
|
|
715
715
|
const start = invocations.get(startGroup.callId);
|
|
716
|
-
if (!start
|
|
717
|
-
(start.input.action !== 'start' &&
|
|
718
|
-
!(start.input.action == null && typeof start.input.command === 'string')))
|
|
716
|
+
if (!start)
|
|
719
717
|
continue;
|
|
718
|
+
const isStart = start.group.toolName === 'exec' &&
|
|
719
|
+
(start.input.action === 'start' ||
|
|
720
|
+
(start.input.action == null && typeof start.input.command === 'string'));
|
|
721
|
+
const processId = typeof start.input.processId === 'string'
|
|
722
|
+
? start.input.processId
|
|
723
|
+
: typeof start.result.processId === 'string'
|
|
724
|
+
? start.result.processId
|
|
725
|
+
: undefined;
|
|
726
|
+
if (!isStart) {
|
|
727
|
+
if (start.group.toolName !== 'exec_wait' || !processId)
|
|
728
|
+
continue;
|
|
729
|
+
// A process may start inside a cell or before the visible history boundary.
|
|
730
|
+
// Begin with its first visible wait, but never split an unfinished lifecycle.
|
|
731
|
+
const previous = execGroups.findLast((group) => (group.call?.record.sequence ?? Infinity) < start.group.call.record.sequence &&
|
|
732
|
+
((isRecord(group.call?.message.arguments) &&
|
|
733
|
+
group.call.message.arguments.processId === processId) ||
|
|
734
|
+
invocations.get(group.callId)?.result.processId === processId));
|
|
735
|
+
const previousStatus = previous && invocations.get(previous.callId)?.result.status;
|
|
736
|
+
if (previous && previousStatus !== 'completed' && previousStatus !== 'cancelled')
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
720
739
|
const lifecycle = [start];
|
|
721
|
-
let processId;
|
|
722
740
|
let terminal = start.result.status !== 'running';
|
|
723
741
|
if (!terminal) {
|
|
724
|
-
processId = typeof start.result.processId === 'string' ? start.result.processId : undefined;
|
|
725
742
|
if (!processId)
|
|
726
743
|
continue;
|
|
727
744
|
const relatedGroups = execGroups.filter((group) => (group.call?.record.sequence ?? 0) > start.group.call.record.sequence &&
|
|
@@ -765,10 +782,10 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
|
|
|
765
782
|
if (!existingTargets.has(contextGcTargetKey(callTarget)))
|
|
766
783
|
replacements.push({
|
|
767
784
|
kind: 'TOOL_CALL_INPUT',
|
|
768
|
-
toolName:
|
|
785
|
+
toolName: invocation.group.toolName,
|
|
769
786
|
callId: invocation.group.callId,
|
|
770
787
|
target: callTarget,
|
|
771
|
-
replacementArguments: contextGcExecInputReplacement(invocation.input),
|
|
788
|
+
replacementArguments: contextGcExecInputReplacement(invocation.input, 3, invocation.group.toolName),
|
|
772
789
|
providerAliasTargets: invocation.group.aliases
|
|
773
790
|
.map(({ record, message }) => targetFor(record, message))
|
|
774
791
|
.filter((target) => target !== undefined),
|