@myagentroam/agent 0.11.61 → 0.11.62
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/cli/main.js +25 -15
- package/dist/cli/subagent/control.js +2 -2
- package/dist/cli/subagent/start.js +1 -1
- package/dist/cli/terminal-input.d.ts +12 -0
- package/dist/cli/terminal-input.js +86 -0
- package/dist/host/local-tool-registry.js +2 -2
- package/dist/tools/agent/question.js +1 -1
- package/dist/tools/agent/sleep.js +1 -1
- package/dist/tools/agent/todo-write.js +1 -1
- package/dist/tools/code-mode/index.js +1 -1
- package/dist/tools/contracts.d.ts +1 -0
- package/dist/tools/local/apply-patch.d.ts +1 -1
- package/dist/tools/local/apply-patch.js +11 -2
- package/dist/tools/mcp/tool-search.js +1 -1
- package/dist/tools/scheduler.js +4 -4
- package/package.json +1 -1
package/dist/cli/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import { readModelCatalog, writeModelCatalog } from './catalog-file.js';
|
|
|
9
9
|
import { AgentError, createAgent, agentReasoningEffortSchema, resolveModelDefaultReasoningEffort, SecretValue, testAgentModelTransport } from '../index.js';
|
|
10
10
|
import { CodexAuthFileCredentialProvider } from './codex-auth-file.js';
|
|
11
11
|
import { generateSessionTitle } from '../shared/auto-title.js';
|
|
12
|
+
import { TerminalInput, TerminalInputClosed } from './terminal-input.js';
|
|
12
13
|
const HELP = `maragent - MAR coding agent
|
|
13
14
|
|
|
14
15
|
Usage:
|
|
@@ -261,21 +262,22 @@ async function main() {
|
|
|
261
262
|
// `run` does not need stdin until a question occurs. Create
|
|
262
263
|
// readline lazily so a closed or pseudo-TTY stream in an external harness
|
|
263
264
|
// cannot fail the execution before its first model request.
|
|
264
|
-
|
|
265
|
-
const requireTerminal = () => (terminal ??= createInterface({ input: stdin, output: stdout }));
|
|
265
|
+
const terminal = new TerminalInput(() => createInterface({ input: stdin, output: stdout }));
|
|
266
266
|
const host = new LocalAgentHost({
|
|
267
267
|
homeDirectory: home,
|
|
268
268
|
workspace,
|
|
269
|
-
question: async (request) => {
|
|
269
|
+
question: async (request, signal) => {
|
|
270
270
|
if (!stdin.isTTY)
|
|
271
271
|
throw new AgentError('MAR_AGENT_USER_INPUT_UNAVAILABLE', 'Interactive input is unavailable.');
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
272
|
+
return terminal.run(signal, async (ask) => {
|
|
273
|
+
const answers = {};
|
|
274
|
+
for (const question of request.questions) {
|
|
275
|
+
const value = question;
|
|
276
|
+
const id = typeof value.id === 'string' ? value.id : String(Object.keys(answers).length);
|
|
277
|
+
answers[id] = await ask(`${typeof value.question === 'string' ? value.question : JSON.stringify(question)} `);
|
|
278
|
+
}
|
|
279
|
+
return answers;
|
|
280
|
+
});
|
|
279
281
|
}
|
|
280
282
|
});
|
|
281
283
|
const runtime = runtimeModels(catalog.models, codexAuthFile);
|
|
@@ -311,7 +313,7 @@ async function main() {
|
|
|
311
313
|
const listed = (await agent.listSessions());
|
|
312
314
|
for (const item of listed.sessions ?? [])
|
|
313
315
|
stdout.write(`${item.id}\t${item.title}\t${item.lastActivityAt}\n`);
|
|
314
|
-
resumeId = (await
|
|
316
|
+
resumeId = (await terminal.run(undefined, (ask) => ask('Session ID: '))).trim();
|
|
315
317
|
if (!resumeId)
|
|
316
318
|
throw new AgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', 'Session id is required.');
|
|
317
319
|
}
|
|
@@ -329,7 +331,7 @@ async function main() {
|
|
|
329
331
|
if (activeHandle)
|
|
330
332
|
activeHandle.cancel();
|
|
331
333
|
else
|
|
332
|
-
terminal
|
|
334
|
+
terminal.close();
|
|
333
335
|
titleController?.abort();
|
|
334
336
|
process.exitCode = 130;
|
|
335
337
|
if (interrupts > 1)
|
|
@@ -338,8 +340,16 @@ async function main() {
|
|
|
338
340
|
process.on('SIGINT', interrupt);
|
|
339
341
|
try {
|
|
340
342
|
do {
|
|
341
|
-
if (interactive)
|
|
342
|
-
|
|
343
|
+
if (interactive) {
|
|
344
|
+
try {
|
|
345
|
+
prompt = await terminal.run(undefined, (ask) => ask('> '));
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
if (error instanceof TerminalInputClosed)
|
|
349
|
+
break;
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
343
353
|
if (!prompt.trim())
|
|
344
354
|
break;
|
|
345
355
|
const invocation = parseExecutionInvocation(prompt);
|
|
@@ -409,7 +419,7 @@ async function main() {
|
|
|
409
419
|
}
|
|
410
420
|
}
|
|
411
421
|
finally {
|
|
412
|
-
terminal
|
|
422
|
+
terminal.close();
|
|
413
423
|
await agent.dispose();
|
|
414
424
|
}
|
|
415
425
|
}
|
|
@@ -49,7 +49,7 @@ export function registerControlTools(context, agent, observations, warmObservati
|
|
|
49
49
|
name: 'agent_send',
|
|
50
50
|
description: 'Send a message to a direct child or report to the direct parent.',
|
|
51
51
|
allowedCallers: ['direct', 'programmatic'],
|
|
52
|
-
parallelSafety: '
|
|
52
|
+
parallelSafety: 'safe',
|
|
53
53
|
inputSchema: {
|
|
54
54
|
type: 'object',
|
|
55
55
|
properties: {
|
|
@@ -70,7 +70,7 @@ export function registerControlTools(context, agent, observations, warmObservati
|
|
|
70
70
|
name: 'agent_stop',
|
|
71
71
|
description: 'Stop a direct child execution and wait for its cleanup.',
|
|
72
72
|
allowedCallers: ['direct', 'programmatic'],
|
|
73
|
-
parallelSafety: '
|
|
73
|
+
parallelSafety: 'safe',
|
|
74
74
|
inputSchema: {
|
|
75
75
|
type: 'object',
|
|
76
76
|
properties: {
|
|
@@ -44,7 +44,7 @@ export function registerStart(context, agent, admission) {
|
|
|
44
44
|
name: 'agent_start',
|
|
45
45
|
description: 'Start a child Session and return its identity without waiting for completion.',
|
|
46
46
|
allowedCallers: ['direct', 'programmatic'],
|
|
47
|
-
parallelSafety: '
|
|
47
|
+
parallelSafety: 'safe',
|
|
48
48
|
inputSchema: {
|
|
49
49
|
type: 'object',
|
|
50
50
|
properties: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
export declare class TerminalInputClosed extends Error {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
/** One interactive transaction at a time per terminal, including multi-question requests. */
|
|
6
|
+
export declare class TerminalInput {
|
|
7
|
+
#private;
|
|
8
|
+
private readonly create;
|
|
9
|
+
constructor(create: () => ReturnType<typeof createInterface>);
|
|
10
|
+
run<T>(signal: AbortSignal | undefined, work: (ask: (prompt: string) => Promise<string>) => Promise<T>): Promise<T>;
|
|
11
|
+
close(): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export class TerminalInputClosed extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super('Terminal input is closed.');
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
/** One interactive transaction at a time per terminal, including multi-question requests. */
|
|
7
|
+
export class TerminalInput {
|
|
8
|
+
create;
|
|
9
|
+
#terminal;
|
|
10
|
+
#tail = Promise.resolve();
|
|
11
|
+
#lines = [];
|
|
12
|
+
#waiter;
|
|
13
|
+
#closed = false;
|
|
14
|
+
constructor(create) {
|
|
15
|
+
this.create = create;
|
|
16
|
+
}
|
|
17
|
+
run(signal, work) {
|
|
18
|
+
const operation = this.#tail.then(async () => {
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
return work((prompt) => this.#ask(prompt, signal));
|
|
21
|
+
});
|
|
22
|
+
this.#tail = operation.then(() => undefined, () => undefined);
|
|
23
|
+
if (!signal)
|
|
24
|
+
return operation;
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const aborted = () => reject(signal.reason);
|
|
27
|
+
if (signal.aborted)
|
|
28
|
+
aborted();
|
|
29
|
+
else
|
|
30
|
+
signal.addEventListener('abort', aborted, { once: true });
|
|
31
|
+
operation.then(resolve, reject).finally(() => signal.removeEventListener('abort', aborted));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
#ask(prompt, signal) {
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
if (this.#closed) {
|
|
37
|
+
const line = this.#lines.shift();
|
|
38
|
+
return line === undefined ? Promise.reject(new TerminalInputClosed()) : Promise.resolve(line);
|
|
39
|
+
}
|
|
40
|
+
if (!this.#terminal) {
|
|
41
|
+
this.#terminal = this.create();
|
|
42
|
+
this.#terminal.on('line', (line) => {
|
|
43
|
+
const waiter = this.#waiter;
|
|
44
|
+
if (waiter) {
|
|
45
|
+
this.#waiter = undefined;
|
|
46
|
+
waiter.resolve(line);
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
this.#lines.push(line);
|
|
50
|
+
});
|
|
51
|
+
this.#terminal.on('close', () => {
|
|
52
|
+
this.#closed = true;
|
|
53
|
+
const waiter = this.#waiter;
|
|
54
|
+
this.#waiter = undefined;
|
|
55
|
+
waiter?.reject(new TerminalInputClosed());
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
this.#terminal.setPrompt(prompt);
|
|
59
|
+
this.#terminal.prompt();
|
|
60
|
+
const line = this.#lines.shift();
|
|
61
|
+
if (line !== undefined)
|
|
62
|
+
return Promise.resolve(line);
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const finish = (callback) => {
|
|
65
|
+
signal?.removeEventListener('abort', abort);
|
|
66
|
+
callback();
|
|
67
|
+
};
|
|
68
|
+
const abort = () => {
|
|
69
|
+
this.#waiter = undefined;
|
|
70
|
+
finish(() => reject(signal.reason));
|
|
71
|
+
};
|
|
72
|
+
this.#waiter = {
|
|
73
|
+
resolve: (value) => finish(() => resolve(value)),
|
|
74
|
+
reject: (error) => finish(() => reject(error))
|
|
75
|
+
};
|
|
76
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
77
|
+
if (signal?.aborted)
|
|
78
|
+
abort();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
close() {
|
|
82
|
+
this.#closed = true;
|
|
83
|
+
this.#lines = [];
|
|
84
|
+
this.#terminal?.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -71,8 +71,8 @@ export class LocalToolRegistry {
|
|
|
71
71
|
],
|
|
72
72
|
[
|
|
73
73
|
'apply_patch',
|
|
74
|
-
async (arguments_) => {
|
|
75
|
-
const result = await patch.execute(applyPatchToolInput(arguments_));
|
|
74
|
+
async (arguments_, context) => {
|
|
75
|
+
const result = await patch.execute(applyPatchToolInput(arguments_), context.signal);
|
|
76
76
|
return { content: JSON.stringify(result), data: result };
|
|
77
77
|
}
|
|
78
78
|
],
|
|
@@ -4,7 +4,7 @@ import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
|
|
|
4
4
|
export const questionToolDefinition = {
|
|
5
5
|
name: 'question',
|
|
6
6
|
allowedCallers: ['direct'],
|
|
7
|
-
parallelSafety: '
|
|
7
|
+
parallelSafety: 'safe',
|
|
8
8
|
description: 'Pause for one to three tightly related user-owned decisions that materially change the result and cannot be inferred from the request, conversation, or relevant safe discovery. Prefer asking over guessing in Plan mode; do not avoid clarification by inventing configuration, branches, abstractions, or support for multiple outcomes. Provide 2-3 mutually exclusive options with consequences, put the recommended option first and identify it in its label or description, and use another question round if key ambiguity remains. This is not a permission/approval tool and should not interrupt routine in-scope work.',
|
|
9
9
|
inputSchema: {
|
|
10
10
|
type: 'object',
|
|
@@ -4,7 +4,7 @@ import { waitForToolCondition } from '../wait.js';
|
|
|
4
4
|
export const sleepToolDefinition = {
|
|
5
5
|
name: 'sleep',
|
|
6
6
|
allowedCallers: ['direct'],
|
|
7
|
-
parallelSafety: '
|
|
7
|
+
parallelSafety: 'safe',
|
|
8
8
|
description: 'Pause for up to durationMs milliseconds. Returns early when user input or a new public subagent message is available. Returns elapsed time and the wake reason.',
|
|
9
9
|
inputSchema: {
|
|
10
10
|
type: 'object',
|
|
@@ -3,7 +3,7 @@ import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
|
|
|
3
3
|
export const todoWriteToolDefinition = {
|
|
4
4
|
name: 'todo_write',
|
|
5
5
|
allowedCallers: ['direct', 'programmatic'],
|
|
6
|
-
parallelSafety: '
|
|
6
|
+
parallelSafety: 'safe',
|
|
7
7
|
description: 'Replace this session’s entire Todo list with the supplied items; no prior read is required. Preserve stable IDs and statuses only when extending the same objective. For a materially different objective, include only the new objective’s items. Do not retain completed items from an earlier objective; when all current items are completed, default a substantive new request to replacement unless the user explicitly asks to continue, extend, verify, or follow up. Use only after Todo has been chosen for coordination-heavy work; straightforward bounded work does not need it. Keep at most one in_progress item and update statuses after meaningful stage changes. When another substantive tool call is already ready and does not depend on the Todo result, include both in the same model response instead of spending a response only for Todo bookkeeping.',
|
|
8
8
|
inputSchema: {
|
|
9
9
|
type: 'object',
|
|
@@ -13,6 +13,7 @@ export interface ClientToolDefinition extends ModelToolDefinition {
|
|
|
13
13
|
eventInputFields?: readonly string[];
|
|
14
14
|
/** Runtime-only exposure override; retains both permitted entry points with Code Mode enabled. */
|
|
15
15
|
codeModeExposure?: 'both';
|
|
16
|
+
/** Runtime-only scheduling policy; defaults to safe. Serial calls are exclusive barriers. */
|
|
16
17
|
parallelSafety?: 'safe' | 'serial';
|
|
17
18
|
/** Preserved business-schema eligibility after framework metadata projection. */
|
|
18
19
|
parameterless?: boolean;
|
|
@@ -15,5 +15,5 @@ export declare class ApplyPatchTool {
|
|
|
15
15
|
#private;
|
|
16
16
|
private readonly paths;
|
|
17
17
|
constructor(paths: WorkspacePathResolver, operations?: Partial<PatchFileOperations>);
|
|
18
|
-
execute(patch: string): Promise<ApplyPatchResult>;
|
|
18
|
+
execute(patch: string, signal?: AbortSignal): Promise<ApplyPatchResult>;
|
|
19
19
|
}
|
|
@@ -10,7 +10,7 @@ export const applyPatchToolDefinition = {
|
|
|
10
10
|
name: 'apply_patch',
|
|
11
11
|
allowedCallers: ['direct', 'programmatic'],
|
|
12
12
|
codeModeExposure: 'both',
|
|
13
|
-
parallelSafety: '
|
|
13
|
+
parallelSafety: 'safe',
|
|
14
14
|
description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch, each marker on its own line. Surrounding marker whitespace and an optional trailing *** are accepted; incomplete markers or extra text are rejected. Add File content lines, including blank lines, must start with +. Syntax errors include line/column and a bounded excerpt of the submitted patch with a caret at the error. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>. Context-only hunks, including trailing hunks, and unchanged file sections are validated in order but are not written or reported as edits. The patch must include a change or move. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
|
|
15
15
|
inputMode: 'freeform',
|
|
16
16
|
outputSchema: {
|
|
@@ -67,11 +67,20 @@ const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
|
|
67
67
|
export class ApplyPatchTool {
|
|
68
68
|
paths;
|
|
69
69
|
#fileOperations;
|
|
70
|
+
#tail = Promise.resolve();
|
|
70
71
|
constructor(paths, operations = {}) {
|
|
71
72
|
this.paths = paths;
|
|
72
73
|
this.#fileOperations = { ...defaultFileOperations, ...operations };
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
execute(patch, signal) {
|
|
76
|
+
const result = this.#tail.then(() => {
|
|
77
|
+
signal?.throwIfAborted();
|
|
78
|
+
return this.#apply(patch);
|
|
79
|
+
});
|
|
80
|
+
this.#tail = result.then(() => undefined, () => undefined);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
async #apply(patch) {
|
|
75
84
|
const changes = parsePatch(patch);
|
|
76
85
|
reserveFileChangePaths(changes.flatMap((change) => change.kind === 'update' && change.targetPath
|
|
77
86
|
? [change.path, change.targetPath]
|
package/dist/tools/scheduler.js
CHANGED
|
@@ -35,7 +35,7 @@ export function createSessionToolScheduler() {
|
|
|
35
35
|
const inherited = leases.getStore();
|
|
36
36
|
const parent = inherited?.ingress === sessionToolIngress.getStore() ? inherited : undefined;
|
|
37
37
|
const result = (parent?.active ? parent.schedule : root)({
|
|
38
|
-
name: definition?.parallelSafety === '
|
|
38
|
+
name: definition?.parallelSafety === 'serial' ? 'serial' : 'safe',
|
|
39
39
|
run
|
|
40
40
|
});
|
|
41
41
|
if (parent?.active) {
|
|
@@ -52,7 +52,7 @@ export function createToolCallScheduler(definitions, execute) {
|
|
|
52
52
|
let barrier = Promise.resolve();
|
|
53
53
|
const readers = new Set();
|
|
54
54
|
return (call) => {
|
|
55
|
-
const safe = safety.get(call.name)
|
|
55
|
+
const safe = safety.get(call.name) !== 'serial';
|
|
56
56
|
const gate = safe ? barrier : Promise.all([barrier, ...readers]).then(() => undefined);
|
|
57
57
|
const result = gate.then(() => execute(call));
|
|
58
58
|
const settled = result.then(() => undefined, () => undefined);
|
|
@@ -72,13 +72,13 @@ export async function scheduleToolCallBatch(calls, definitions, execute) {
|
|
|
72
72
|
const results = new Array(calls.length);
|
|
73
73
|
let index = 0;
|
|
74
74
|
while (index < calls.length) {
|
|
75
|
-
if (safety.get(calls[index].name)
|
|
75
|
+
if (safety.get(calls[index].name) === 'serial') {
|
|
76
76
|
results[index] = await execute(calls[index]);
|
|
77
77
|
index++;
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
80
|
let end = index + 1;
|
|
81
|
-
while (end < calls.length && safety.get(calls[end].name)
|
|
81
|
+
while (end < calls.length && safety.get(calls[end].name) !== 'serial')
|
|
82
82
|
end++;
|
|
83
83
|
const segment = await Promise.all(calls.slice(index, end).map((call) => execute(call)));
|
|
84
84
|
for (let segmentIndex = 0; segmentIndex < segment.length; segmentIndex++)
|