@myagentroam/marctl 0.11.4 → 0.11.5
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/package.json +1 -1
- package/src/cli.mjs +4 -4
- package/src/client.mjs +25 -5
- package/src/i18n/en-US.mjs +62 -119
- package/src/i18n/zh-CN.mjs +57 -111
- package/src/session-args.mjs +87 -16
- package/src/session-prepare.mjs +48 -0
- package/src/session-target.mjs +2 -1
- package/src/sessions.mjs +21 -9
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -75,7 +75,7 @@ export async function main(
|
|
|
75
75
|
}
|
|
76
76
|
return 0;
|
|
77
77
|
}
|
|
78
|
-
if (args.command === '
|
|
78
|
+
if (args.command === 'agents') {
|
|
79
79
|
const execute = (signal) =>
|
|
80
80
|
runSessionCommand({
|
|
81
81
|
args,
|
|
@@ -122,7 +122,7 @@ export async function main(
|
|
|
122
122
|
})
|
|
123
123
|
);
|
|
124
124
|
} catch (error) {
|
|
125
|
-
if (argv[0] === '
|
|
125
|
+
if (argv[0] === 'agents') {
|
|
126
126
|
const failure = {
|
|
127
127
|
code: error.code ?? 'MARCTL_COMMAND_FAILED',
|
|
128
128
|
message: error instanceof MarctlError ? error.message : translate('errors.commandFailed'),
|
|
@@ -176,7 +176,7 @@ export function executableArgs(executable, args) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
export function parseArgs(argv, translate = createTranslator()) {
|
|
179
|
-
if (argv[0] === '
|
|
179
|
+
if (argv[0] === 'agents') return parseSessionArgs(argv.slice(1), translate);
|
|
180
180
|
let command;
|
|
181
181
|
let nodeId;
|
|
182
182
|
const remoteArgs = [];
|
|
@@ -208,7 +208,7 @@ export function parseArgs(argv, translate = createTranslator()) {
|
|
|
208
208
|
argument !== 'nodes' &&
|
|
209
209
|
argument !== 'ssh' &&
|
|
210
210
|
argument !== 'scp' &&
|
|
211
|
-
argument !== '
|
|
211
|
+
argument !== 'agents' &&
|
|
212
212
|
argument !== 'version'
|
|
213
213
|
)
|
|
214
214
|
throw unsupportedSshOption(argument, translate);
|
package/src/client.mjs
CHANGED
|
@@ -49,11 +49,13 @@ export class MarctlClient {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async sessionCommand(body) {
|
|
52
|
+
async sessionCommand(body, { timeoutMs, signal } = {}) {
|
|
53
53
|
const result = await this.#requestJson('/api/marctl/sessions', {
|
|
54
54
|
method: 'POST',
|
|
55
55
|
body,
|
|
56
|
-
preserveError: true
|
|
56
|
+
preserveError: true,
|
|
57
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
58
|
+
...(signal === undefined ? {} : { signal })
|
|
57
59
|
});
|
|
58
60
|
if (!isRecord(result))
|
|
59
61
|
throw new MarctlError(this.#translate('errors.serverInvalidJson'), {
|
|
@@ -171,19 +173,33 @@ export class MarctlClient {
|
|
|
171
173
|
return this.#request(pathname, { ...options, json: true });
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
async #request(
|
|
176
|
+
async #request(
|
|
177
|
+
pathname,
|
|
178
|
+
{
|
|
179
|
+
method = 'GET',
|
|
180
|
+
body,
|
|
181
|
+
json = false,
|
|
182
|
+
preserveError = false,
|
|
183
|
+
timeoutMs = this.#requestTimeoutMs,
|
|
184
|
+
signal
|
|
185
|
+
} = {}
|
|
186
|
+
) {
|
|
175
187
|
const headers = {
|
|
176
188
|
Accept: 'application/json',
|
|
177
189
|
Authorization: `Bearer ${this.#key}`
|
|
178
190
|
};
|
|
179
191
|
const controller = new AbortController();
|
|
180
|
-
const request = {
|
|
192
|
+
const request = {
|
|
193
|
+
method,
|
|
194
|
+
headers,
|
|
195
|
+
signal: signal ? AbortSignal.any([controller.signal, signal]) : controller.signal
|
|
196
|
+
};
|
|
181
197
|
if (body !== undefined) {
|
|
182
198
|
headers['Content-Type'] = 'application/json';
|
|
183
199
|
request.body = JSON.stringify(body);
|
|
184
200
|
}
|
|
185
201
|
|
|
186
|
-
const timer = setTimeout(() => controller.abort(),
|
|
202
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
187
203
|
try {
|
|
188
204
|
const response = await this.#fetch(new URL(pathname, `${this.#serverUrl}/`), request);
|
|
189
205
|
if (!response?.ok) {
|
|
@@ -227,6 +243,10 @@ export class MarctlClient {
|
|
|
227
243
|
);
|
|
228
244
|
}
|
|
229
245
|
} catch (error) {
|
|
246
|
+
if (signal?.aborted)
|
|
247
|
+
throw new MarctlError(this.#translate('errors.sessionWaitCancelled'), {
|
|
248
|
+
code: 'MARCTL_WAIT_CANCELLED'
|
|
249
|
+
});
|
|
230
250
|
if (error instanceof MarctlError) throw error;
|
|
231
251
|
throw new MarctlError(
|
|
232
252
|
this.#translate(
|
package/src/i18n/en-US.mjs
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
marctl help [command]
|
|
12
12
|
|
|
13
13
|
Commands:
|
|
14
|
-
|
|
14
|
+
agents Inspect and coordinate Sessions in the current Workspace.
|
|
15
15
|
nodes List accessible Nodes and their connection status.
|
|
16
16
|
ssh Open a Node terminal or execute a remote command.
|
|
17
17
|
scp Copy a regular file between this machine and Nodes, or between Nodes.
|
|
@@ -42,130 +42,73 @@ Notes:
|
|
|
42
42
|
Each alias shows only its own command help. Use marctl help <command> for details.
|
|
43
43
|
Remote access uses authorized Node Terminal transport, not system SSH or SCP.`,
|
|
44
44
|
commandHelp: Object.freeze({
|
|
45
|
-
|
|
45
|
+
agents: `{program} — Coordinate Agents in the current MAR Workspace
|
|
46
46
|
|
|
47
47
|
Usage:
|
|
48
|
-
{program}
|
|
49
|
-
|
|
48
|
+
{program} start [-m <text> | --message-file <file|->] [--image <path> ...]
|
|
49
|
+
[--runner <runner>] [--parent <id>] [--model <id>] [--effort <value>]
|
|
50
|
+
[--title <name>] [--goal <objective> | --clear-goal]
|
|
51
|
+
[--client-message-id <id>] [--json]
|
|
52
|
+
{program} send <id> [-m <text> | --message-file <file|->]
|
|
53
|
+
[--image <path> ...]
|
|
54
|
+
[--steer | --queue] [--goal <objective> | --clear-goal]
|
|
55
|
+
[--approval <id> --decision <approve|reject>]
|
|
56
|
+
[--client-message-id <id>] [--json]
|
|
57
|
+
{program} wait [<id>] [--agent <id> ...] [--mode any|all]
|
|
58
|
+
[--wait-for message|completion] [--timeout <duration>] [--json]
|
|
59
|
+
{program} stop <id> --execution <execution-id> [--json]
|
|
60
|
+
{program} list [--scope children|descendants|workspace|profile]
|
|
61
|
+
[--agent <id>]
|
|
62
|
+
[--runner <runner>] [--query <title>] [--parent <id>]
|
|
50
63
|
[--cursor <cursor>] [--limit <1..100>] [--json]
|
|
51
|
-
{program}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{program}
|
|
56
|
-
{program}
|
|
57
|
-
|
|
64
|
+
{program} view [<id>] [--history] [--cursor <cursor>]
|
|
65
|
+
[--limit <1..100>] [--json]
|
|
66
|
+
|
|
67
|
+
Management:
|
|
68
|
+
{program} config [<id>] [--model <id>] [--effort <value>] [--json]
|
|
69
|
+
{program} rename [<id>] --title <name> [--json]
|
|
70
|
+
{program} prompt get|clear [<id>] [--json]
|
|
71
|
+
{program} prompt set [<id>] (-m <text> | --message-file <file|->) [--json]
|
|
58
72
|
{program} schedule list [--cursor <cursor>] [--limit <1..100>] [--json]
|
|
59
|
-
{program} schedule get [<
|
|
60
|
-
{program} schedule create [<
|
|
73
|
+
{program} schedule get [<id>] [--json]
|
|
74
|
+
{program} schedule create [<id>] --cron <cron>
|
|
61
75
|
(-m <text> | --message-file <file|->) [--json]
|
|
62
|
-
{program} schedule update [<
|
|
63
|
-
--expected-revision <revision>
|
|
76
|
+
{program} schedule update [<id>] --expected-revision <revision>
|
|
64
77
|
[--cron <cron>] [-m <text> | --message-file <file|->] [--json]
|
|
65
|
-
{program}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{program}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Use --effort "" for the Runner default. Active execution is not restarted.
|
|
100
|
-
Prompt reads, sets or clears persistent instructions for future executions.
|
|
101
|
-
|
|
102
|
-
Titles and scheduled tasks:
|
|
103
|
-
rename uses the existing title update; no parent-tree restriction.
|
|
104
|
-
It does not restart execution. schedule list reads local Workspace tasks.
|
|
105
|
-
schedule get/create/update share MCP task data.
|
|
106
|
-
Schedule text is limited to 20,000 characters; update requires cron or text.
|
|
107
|
-
Other-owner edits create PENDING proposals.
|
|
108
|
-
Read the current revision before updating; a version conflict is not retried.
|
|
109
|
-
|
|
110
|
-
Reading JSON:
|
|
111
|
-
list: id/title/runner/status/runId and one latestActivity per Session.
|
|
112
|
-
show: session.status, session.title, session.runId,
|
|
113
|
-
session.model, session.effort, session.queuedCount,
|
|
114
|
-
session.latestMessage, session.recentTools (up to three summaries).
|
|
115
|
-
session.pendingApprovals shows current requests; pendingApprovalsTruncated
|
|
116
|
-
marks an incomplete summary. Full evidence remains in history.
|
|
117
|
-
Status is RUNNING/IDLE/SLEEPING; tools keep their original states.
|
|
118
|
-
history: segments[].items[]; select kind === "assistant_message",
|
|
119
|
-
read payload.text.
|
|
120
|
-
|
|
121
|
-
Delivery and waiting:
|
|
122
|
-
Start/send return a clientMessageId.
|
|
123
|
-
Send requires exactly one of --steer or --queue, even when idle.
|
|
124
|
-
Prefer --steer for timely coordination: blockers, decisions, corrections,
|
|
125
|
-
and results the recipient is waiting for. It uses native delivery when
|
|
126
|
-
available, but may switch the current execution.
|
|
127
|
-
Node decides from actual state.
|
|
128
|
-
Use --queue only for deliberately deferred follow-up work.
|
|
129
|
-
It does not interrupt active execution; during normal active work it
|
|
130
|
-
waits for that execution and earlier queued work. An active Goal can delay
|
|
131
|
-
it until the Goal exits.
|
|
132
|
-
Use --steer instead when the update is needed during current work.
|
|
133
|
-
New input resumes a paused FIFO without interrupting
|
|
134
|
-
active execution; cancellation settles before resuming.
|
|
135
|
-
Example:
|
|
136
|
-
marctl sessions send <session-id> --steer \\
|
|
137
|
-
-m "Additional context: focus on the resume path." --json
|
|
138
|
-
A queued runId may identify another
|
|
139
|
-
active input: correlate queueItemId/clientMessageId with visible history.
|
|
140
|
-
Wait observes the specified Run through Watch. Timeout does not stop it;
|
|
141
|
-
timeout errors may include progress, the last observed bounded snapshot.
|
|
142
|
-
--until approval also returns when the Run needs an approval decision.
|
|
143
|
-
Approval decide explicitly approves or rejects one request, subject to
|
|
144
|
-
existing user authorization. Accepted decisions do not prove completion.
|
|
145
|
-
Run success is not proof of task success.
|
|
146
|
-
Completion never wakes a Coordinator.
|
|
147
|
-
On an unknown delivery outcome, inspect state/history; do not blindly resend.
|
|
148
|
-
|
|
149
|
-
Credentials and scope:
|
|
150
|
-
Credentials expire 24 hours after issue without automatic renewal.
|
|
151
|
-
Scope is bound by MAR to the current Node/Workspace;
|
|
152
|
-
there are no scope override options.
|
|
153
|
-
List and ID lookup use Node-known Sessions without scanning Runners.
|
|
154
|
-
--query filters display titles; missing IDs may suggest nearby IDs, not retry.
|
|
155
|
-
Start records the current Session as parent; no parent override.
|
|
156
|
-
--parent lists direct children; graph includes the root and descendants.
|
|
157
|
-
Graph defaults to 100 nodes and reports truncation, not task dependencies.
|
|
158
|
-
|
|
159
|
-
Workers:
|
|
160
|
-
Create workers as needed, e.g. --title "fixer#42 Handle empty totals" or
|
|
161
|
-
--title "reviewer#42 Review empty totals". Titles are not routing identities;
|
|
162
|
-
record returned Session IDs. Put task/role instructions in the first message.
|
|
163
|
-
Sessions share files and the Git index; no automatic worktree or worker pool.
|
|
164
|
-
|
|
165
|
-
Exit codes:
|
|
166
|
-
1 failure, 2 input, 3 authorization, 4 unsupported, 5 timeout,
|
|
167
|
-
6 unknown outcome, 130 cancelled wait.
|
|
168
|
-
--json prints one result/error object.`,
|
|
78
|
+
{program} approval decide <id> --run <run-id> --approval <approval-id>
|
|
79
|
+
--decision <approve|reject> [--json]
|
|
80
|
+
{program} wait <id> --run <run-id> [--until approval] --timeout <duration>
|
|
81
|
+
{program} goal get|clear <id> [--json]
|
|
82
|
+
{program} goal set <id> (-m <text> | --message-file <file|->) [--json]
|
|
83
|
+
{program} current | show [<id>] | graph [<id>] | history [<id>]
|
|
84
|
+
|
|
85
|
+
Input and results:
|
|
86
|
+
Start defaults to the current Session Runner; a terminal without a current
|
|
87
|
+
Session must specify --runner or --parent. Profile lists configured models,
|
|
88
|
+
not guaranteed account availability. Start returns without waiting.
|
|
89
|
+
Send appends by default; --steer replaces and --queue defers.
|
|
90
|
+
--goal sets or replaces a Goal; --clear-goal clears it. Send can update a Goal
|
|
91
|
+
without text; omitted Goal is unchanged. Stop clears the target Session Goal.
|
|
92
|
+
Wait defaults to direct-child completion and 30s; 0ms is a snapshot.
|
|
93
|
+
Positive timeouts below 10s become 10s; maximum is 5m. Waiting does not stop.
|
|
94
|
+
Images may be supplied without text: at most 5, 5 MiB each, 10 MiB total.
|
|
95
|
+
-m and --message-file are exclusive; files use UTF-8 and - reads stdin.
|
|
96
|
+
List exposes current identity and bounded pages; nextCursor marks more.
|
|
97
|
+
Configuration and prompt changes apply to future executions.
|
|
98
|
+
Prefer --steer for timely coordination and for
|
|
99
|
+
results the recipient is waiting for; Node may switch the current execution.
|
|
100
|
+
Use --queue only for deliberately deferred follow-up work;
|
|
101
|
+
it waits for that execution and earlier queued work. Use --steer instead when
|
|
102
|
+
a result cannot wait until the current Goal exits.
|
|
103
|
+
|
|
104
|
+
show --json includes session.status, session.model, session.effort,
|
|
105
|
+
session.title, session.runId, session.recentTools, session.queuedCount and
|
|
106
|
+
session.latestMessage. list reports latestActivity; wait may include progress.
|
|
107
|
+
Status is RUNNING/IDLE/SLEEPING. In history, segments[].items[] with
|
|
108
|
+
kind === "assistant_message" expose payload.text.
|
|
109
|
+
|
|
110
|
+
Exit codes: 1 failure, 2 input, 3 authorization, 4 unsupported, 5 timeout,
|
|
111
|
+
6 unknown outcome, 130 cancelled wait. --json prints one result or error.`,
|
|
169
112
|
nodes: `{program} — List accessible Nodes
|
|
170
113
|
|
|
171
114
|
Usage:
|
package/src/i18n/zh-CN.mjs
CHANGED
|
@@ -10,7 +10,7 @@ export default Object.freeze({
|
|
|
10
10
|
marctl help [子命令]
|
|
11
11
|
|
|
12
12
|
子命令:
|
|
13
|
-
|
|
13
|
+
agents 查看和协调当前工作区中的会话。
|
|
14
14
|
nodes 列出有权限的节点及连接状态。
|
|
15
15
|
ssh 打开节点终端或执行远程命令。
|
|
16
16
|
scp 在当前机器与节点之间、节点之间复制普通文件。
|
|
@@ -41,122 +41,68 @@ ${configurationHelp}
|
|
|
41
41
|
快捷命令仅显示对应操作的帮助。用 marctl help <子命令> 查看详细说明。
|
|
42
42
|
远程访问使用授权的 Node Terminal 传输,不是系统 SSH 或 SCP 协议。`,
|
|
43
43
|
commandHelp: Object.freeze({
|
|
44
|
-
|
|
44
|
+
agents: `{program} — 协调当前 MAR 工作区中的 Agent
|
|
45
45
|
|
|
46
46
|
用法:
|
|
47
|
-
{program}
|
|
48
|
-
|
|
47
|
+
{program} start [-m <text> | --message-file <file|->] [--image <path> ...]
|
|
48
|
+
[--runner <runner>] [--parent <id>] [--model <id>] [--effort <value>]
|
|
49
|
+
[--title <name>] [--goal <objective> | --clear-goal]
|
|
50
|
+
[--client-message-id <id>] [--json]
|
|
51
|
+
{program} send <id> [-m <text> | --message-file <file|->]
|
|
52
|
+
[--image <path> ...]
|
|
53
|
+
[--steer | --queue] [--goal <objective> | --clear-goal]
|
|
54
|
+
[--approval <id> --decision <approve|reject>]
|
|
55
|
+
[--client-message-id <id>] [--json]
|
|
56
|
+
{program} wait [<id>] [--agent <id> ...] [--mode any|all]
|
|
57
|
+
[--wait-for message|completion] [--timeout <duration>] [--json]
|
|
58
|
+
{program} stop <id> --execution <execution-id> [--json]
|
|
59
|
+
{program} list [--scope children|descendants|workspace|profile]
|
|
60
|
+
[--agent <id>]
|
|
61
|
+
[--runner <runner>] [--query <title>] [--parent <id>]
|
|
49
62
|
[--cursor <cursor>] [--limit <1..100>] [--json]
|
|
50
|
-
{program}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{program}
|
|
55
|
-
{program}
|
|
56
|
-
|
|
63
|
+
{program} view [<id>] [--history] [--cursor <cursor>]
|
|
64
|
+
[--limit <1..100>] [--json]
|
|
65
|
+
|
|
66
|
+
管理:
|
|
67
|
+
{program} config [<id>] [--model <id>] [--effort <value>] [--json]
|
|
68
|
+
{program} rename [<id>] --title <name> [--json]
|
|
69
|
+
{program} prompt get|clear [<id>] [--json]
|
|
70
|
+
{program} prompt set [<id>] (-m <text> | --message-file <file|->) [--json]
|
|
57
71
|
{program} schedule list [--cursor <cursor>] [--limit <1..100>] [--json]
|
|
58
|
-
{program} schedule get [<
|
|
59
|
-
{program} schedule create [<
|
|
72
|
+
{program} schedule get [<id>] [--json]
|
|
73
|
+
{program} schedule create [<id>] --cron <cron>
|
|
60
74
|
(-m <text> | --message-file <file|->) [--json]
|
|
61
|
-
{program} schedule update [<
|
|
62
|
-
--expected-revision <revision>
|
|
75
|
+
{program} schedule update [<id>] --expected-revision <revision>
|
|
63
76
|
[--cron <cron>] [-m <text> | --message-file <file|->] [--json]
|
|
64
|
-
{program}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{program}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
文件用 UTF-8,- 从 stdin 读取。
|
|
94
|
-
start/send 可重复 --image 传 PNG/JPEG/GIF/WebP 图片,也可只发送图片。
|
|
95
|
-
最多 5 张,单张 5 MiB、合计 10 MiB;goal set 只接受文本。
|
|
96
|
-
config 至少指定 model 或 effort;未指定字段和权限不变。
|
|
97
|
-
--effort "" 使用 Runner 默认值,不重启当前执行。
|
|
98
|
-
prompt 读取、设置或清除持久指令,从后续执行生效。
|
|
99
|
-
|
|
100
|
-
标题与定时任务:
|
|
101
|
-
rename 复用现有标题更新,不限制 parent 树、不重启执行。
|
|
102
|
-
schedule list 读取本地 Workspace 任务,get/create/update 与 MCP 共用任务数据。
|
|
103
|
-
任务正文最多 20,000 字符;update 至少指定 cron 或正文。
|
|
104
|
-
修改其他用户的任务会生成 PENDING 提议。
|
|
105
|
-
更新前读取当前 revision;版本冲突不会自动重试。
|
|
106
|
-
|
|
107
|
-
读取 JSON:
|
|
108
|
-
list:id/title/runner/status/runId,以及一条 latestActivity。
|
|
109
|
-
show:session.status、session.title、session.runId,
|
|
110
|
-
session.model、session.effort、session.queuedCount,
|
|
111
|
-
session.latestMessage、session.recentTools(最多三条摘要)。
|
|
112
|
-
session.pendingApprovals 展示当前审批;pendingApprovalsTruncated 标记
|
|
113
|
-
摘要未完整列出,完整证据仍查 history。
|
|
114
|
-
状态为 RUNNING/IDLE/SLEEPING;工具保留原有生命周期。
|
|
115
|
-
history:segments[].items[] 中按 kind === "assistant_message"
|
|
116
|
-
筛选,读取 payload.text。
|
|
117
|
-
|
|
118
|
-
投递与等待:
|
|
119
|
-
start/send 返回 clientMessageId。
|
|
120
|
-
send 必须二选一:--steer 或 --queue,空闲时也需指定。
|
|
121
|
-
及时协作优先使用 --steer:阻塞、决策请求、纠偏
|
|
122
|
-
和对方正在等待的结果。可用时优先原生投递,
|
|
123
|
-
否则可能切换当前执行;Node 按实际状态裁决。
|
|
124
|
-
仅明确要延后执行的后续工作使用 --queue。
|
|
125
|
-
它不打断活动执行;正常运行时等待当前执行和前面的队列项;
|
|
126
|
-
有活动 Goal 时可能等待 Goal 退出才处理。
|
|
127
|
-
当前工作需要及时获知的信息应使用 --steer。
|
|
128
|
-
新输入自动恢复暂停的 FIFO,不打断活动执行;取消收敛后才恢复。
|
|
129
|
-
示例:
|
|
130
|
-
marctl sessions send <session-id> --steer \\
|
|
131
|
-
-m "Additional context: focus on the resume path." --json
|
|
132
|
-
排队回执的 runId 可能属于其他活动输入,
|
|
133
|
-
需按 queueItemId/clientMessageId 与可见历史核对。
|
|
134
|
-
wait 通过 Watch 等待指定 Run;超时不会停止任务,
|
|
135
|
-
超时错误可附 progress,表示最后观察到的有界快照。
|
|
136
|
-
--until approval 在需要审批决定时也提前返回。
|
|
137
|
-
approval decide 显式批准或拒绝单个请求,沿用实际用户授权;
|
|
138
|
-
决定被接受不代表任务完成。
|
|
139
|
-
Run 成功不等于业务完成。Worker 完成不自动唤醒 Coordinator。
|
|
140
|
-
投递结果未知时先查状态/历史,不盲目重发。
|
|
141
|
-
|
|
142
|
-
凭据与范围:
|
|
143
|
-
凭据自签发起 24 小时到期,不自动续签。
|
|
144
|
-
MAR 绑定当前 Node/Workspace,不提供覆盖范围的参数。
|
|
145
|
-
列表及 ID 定位只查 Node 已知会话,不扫描 Runner。
|
|
146
|
-
--query 按展示标题筛选;ID 不存在时可提示近似候选,不自动重试。
|
|
147
|
-
start 自动记录可用的当前会话为 parent,不提供 parent 覆盖参数。
|
|
148
|
-
--parent 查直接子会话;graph 查包含根的后代,默认最多 100 个节点,
|
|
149
|
-
截断时明确标记;创建关系不表示任务依赖。
|
|
150
|
-
|
|
151
|
-
Worker:
|
|
152
|
-
按任务需要创建会话,例如 --title "fixer#42 Handle empty totals" 或
|
|
153
|
-
--title "reviewer#42 Review empty totals";标题不是寻址身份,
|
|
154
|
-
保存返回的会话 ID。角色和任务要求放入首条普通消息。
|
|
155
|
-
会话共享文件与 Git 索引,不自动隔离工作目录或维护池。
|
|
156
|
-
|
|
157
|
-
退出码:
|
|
158
|
-
1 失败,2 参数,3 授权,4 不支持,5 超时,6 结果未知,130 取消等待。
|
|
159
|
-
--json 只输出一个结果或错误对象。`,
|
|
77
|
+
{program} approval decide <id> --run <run-id> --approval <approval-id>
|
|
78
|
+
--decision <approve|reject> [--json]
|
|
79
|
+
{program} wait <id> --run <run-id> [--until approval] --timeout <duration>
|
|
80
|
+
{program} goal get|clear <id> [--json]
|
|
81
|
+
{program} goal set <id> (-m <text> | --message-file <file|->) [--json]
|
|
82
|
+
{program} current | show [<id>] | graph [<id>] | history [<id>]
|
|
83
|
+
|
|
84
|
+
输入与结果:
|
|
85
|
+
start 默认当前会话的 Runner;没有当前会话的终端须指定 --runner 或
|
|
86
|
+
--parent。profile 显示已配置模型,不代表账号实时可用。start 不等待。
|
|
87
|
+
send 默认追加;--steer 替换,--queue 排队。--goal 设置或替换目标,
|
|
88
|
+
--clear-goal 清除目标;send 可仅更新目标,省略则不修改。
|
|
89
|
+
stop 清除目标会话的 Goal。wait 默认等待直接子会话终态 30 秒,
|
|
90
|
+
0ms 为快照;正数小于 10 秒按 10 秒等待,最多 5 分钟。等待不会停止任务。
|
|
91
|
+
可只传图片:最多 5 张,单张 5 MiB、总计 10 MiB。-m 与 --message-file
|
|
92
|
+
互斥;文件使用 UTF-8,- 从 stdin 读取。list 返回当前身份和分页结果,
|
|
93
|
+
nextCursor 表示还有后续页面。配置和持久提示从后续执行生效。
|
|
94
|
+
及时协作优先使用 --steer,包括对方正在等待的结果;Node 可能切换当前执行。
|
|
95
|
+
仅明确要延后执行的后续工作使用 --queue,它会等待当前执行和前面的队列项;
|
|
96
|
+
无法等待 Goal 退出的结果应使用 --steer。
|
|
97
|
+
|
|
98
|
+
show --json 包含 session.status、session.model、session.effort、
|
|
99
|
+
session.title、session.runId、session.recentTools、session.queuedCount 和
|
|
100
|
+
session.latestMessage。list 返回 latestActivity;wait 可能包含 progress;
|
|
101
|
+
状态为 RUNNING/IDLE/SLEEPING。历史中 segments[].items[] 的
|
|
102
|
+
kind === "assistant_message" 包含 payload.text。
|
|
103
|
+
|
|
104
|
+
退出码:1 失败,2 参数,3 授权,4 不支持,5 超时,6 结果未知,
|
|
105
|
+
130 取消等待。--json 输出单个结果或错误对象。`,
|
|
160
106
|
nodes: `{program} — 列出有权限的节点
|
|
161
107
|
|
|
162
108
|
用法:
|
package/src/session-args.mjs
CHANGED
|
@@ -4,9 +4,10 @@ import { sessionTarget } from './session-target.mjs';
|
|
|
4
4
|
|
|
5
5
|
const commands = {
|
|
6
6
|
current: [],
|
|
7
|
-
list: ['cursor', 'limit', 'query', 'parent'],
|
|
7
|
+
list: ['cursor', 'limit', 'query', 'parent', 'scope', 'agent', 'runner'],
|
|
8
8
|
graph: ['limit'],
|
|
9
9
|
show: [],
|
|
10
|
+
view: ['cursor', 'limit', 'history'],
|
|
10
11
|
history: ['cursor', 'limit'],
|
|
11
12
|
start: [
|
|
12
13
|
'runner',
|
|
@@ -16,7 +17,10 @@ const commands = {
|
|
|
16
17
|
'message',
|
|
17
18
|
'message-file',
|
|
18
19
|
'image',
|
|
19
|
-
'client-message-id'
|
|
20
|
+
'client-message-id',
|
|
21
|
+
'parent',
|
|
22
|
+
'goal',
|
|
23
|
+
'clear-goal'
|
|
20
24
|
],
|
|
21
25
|
config: ['model', 'effort'],
|
|
22
26
|
rename: ['title'],
|
|
@@ -28,12 +32,23 @@ const commands = {
|
|
|
28
32
|
'schedule.get': [],
|
|
29
33
|
'schedule.create': ['cron', 'message', 'message-file'],
|
|
30
34
|
'schedule.update': ['cron', 'message', 'message-file', 'expected-revision'],
|
|
31
|
-
send: [
|
|
32
|
-
|
|
35
|
+
send: [
|
|
36
|
+
'message',
|
|
37
|
+
'message-file',
|
|
38
|
+
'image',
|
|
39
|
+
'steer',
|
|
40
|
+
'queue',
|
|
41
|
+
'client-message-id',
|
|
42
|
+
'goal',
|
|
43
|
+
'clear-goal',
|
|
44
|
+
'approval',
|
|
45
|
+
'decision'
|
|
46
|
+
],
|
|
47
|
+
stop: ['execution'],
|
|
33
48
|
'goal.get': [],
|
|
34
49
|
'goal.set': ['message', 'message-file'],
|
|
35
50
|
'goal.clear': [],
|
|
36
|
-
wait: ['
|
|
51
|
+
wait: ['timeout', 'wait-for', 'mode', 'agent', 'run', 'until']
|
|
37
52
|
};
|
|
38
53
|
const fieldNames = {
|
|
39
54
|
parent: 'parentSessionId',
|
|
@@ -43,6 +58,9 @@ const fieldNames = {
|
|
|
43
58
|
run: 'runId',
|
|
44
59
|
approval: 'approvalId',
|
|
45
60
|
timeout: 'timeoutMs',
|
|
61
|
+
execution: 'executionId',
|
|
62
|
+
'wait-for': 'waitFor',
|
|
63
|
+
'clear-goal': 'clearGoal',
|
|
46
64
|
image: 'images'
|
|
47
65
|
};
|
|
48
66
|
|
|
@@ -65,12 +83,12 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
65
83
|
(value === '--help' || value === '-h') && !['-m', '--message'].includes(argv[index - 1])
|
|
66
84
|
)
|
|
67
85
|
)
|
|
68
|
-
return { command: '
|
|
86
|
+
return { command: 'agents', help: true };
|
|
69
87
|
const input = [...argv];
|
|
70
88
|
let action = input.shift();
|
|
71
89
|
if (['goal', 'schedule', 'prompt', 'approval'].includes(action)) action += '.' + input.shift();
|
|
72
90
|
if (!Object.hasOwn(commands, action)) throw invalid();
|
|
73
|
-
const args = { command: '
|
|
91
|
+
const args = { command: 'agents', action };
|
|
74
92
|
if (sessionTarget[action] !== 'workspace') {
|
|
75
93
|
if (
|
|
76
94
|
(input[0] === undefined || input[0].startsWith('-')) &&
|
|
@@ -96,9 +114,21 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
96
114
|
args[key] = true;
|
|
97
115
|
continue;
|
|
98
116
|
}
|
|
117
|
+
if (
|
|
118
|
+
(flag === '--clear-goal' && ['start', 'send'].includes(action)) ||
|
|
119
|
+
(flag === '--history' && action === 'view')
|
|
120
|
+
) {
|
|
121
|
+
const key = flag === '--history' ? 'history' : 'clearGoal';
|
|
122
|
+
if (Object.hasOwn(args, key)) throw invalid();
|
|
123
|
+
args[key] = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
99
126
|
const name = flag.startsWith('--') ? flag.slice(2) : '';
|
|
100
127
|
const key = fieldNames[name] ?? name;
|
|
101
|
-
if (
|
|
128
|
+
if (
|
|
129
|
+
!commands[action].includes(name) ||
|
|
130
|
+
(name !== 'image' && !(name === 'agent' && action === 'wait') && Object.hasOwn(args, key))
|
|
131
|
+
)
|
|
102
132
|
throw invalid();
|
|
103
133
|
const value = input.shift();
|
|
104
134
|
if (
|
|
@@ -107,7 +137,10 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
107
137
|
(name !== 'message' && value.startsWith('--'))
|
|
108
138
|
)
|
|
109
139
|
throw invalid();
|
|
110
|
-
if (name === '
|
|
140
|
+
if (name === 'agent' && action === 'wait') {
|
|
141
|
+
(args.agentIds ??= []).push(value);
|
|
142
|
+
if (args.agentIds.length > 8) throw invalid();
|
|
143
|
+
} else if (name === 'image') {
|
|
111
144
|
(args.images ??= []).push(value);
|
|
112
145
|
if (args.images.length > 5) throw invalid();
|
|
113
146
|
} else if (name === 'limit' || name === 'expected-revision') {
|
|
@@ -119,31 +152,69 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
119
152
|
throw invalid();
|
|
120
153
|
args[key] = Number(value);
|
|
121
154
|
} else if (name === 'timeout') {
|
|
122
|
-
const match = /^([1-9][0-9]*)(ms|s|m)$/u.exec(value);
|
|
155
|
+
const match = /^(0|[1-9][0-9]*)(ms|s|m)$/u.exec(value);
|
|
123
156
|
const ms = match === null ? 0 : Number(match[1]) * { ms: 1, s: 1000, m: 60000 }[match[2]];
|
|
124
|
-
if (!Number.isSafeInteger(ms) || ms
|
|
157
|
+
if (match === null || !Number.isSafeInteger(ms) || ms > 300000) throw invalid();
|
|
125
158
|
args[key] = ms;
|
|
126
159
|
} else args[key] = value;
|
|
127
160
|
}
|
|
128
|
-
if (action === 'send' &&
|
|
161
|
+
if (action === 'send' && args.steer === true && args.queue === true)
|
|
129
162
|
throw invalid('errors.sessionDeliveryRequired');
|
|
163
|
+
if (
|
|
164
|
+
action === 'send' &&
|
|
165
|
+
((args.approvalId === undefined) !== (args.decision === undefined) ||
|
|
166
|
+
(args.decision !== undefined && !['approve', 'reject'].includes(args.decision)))
|
|
167
|
+
)
|
|
168
|
+
throw invalid();
|
|
169
|
+
if (args.goal !== undefined && (args.clearGoal || !args.goal.trim() || args.goal.length > 20000))
|
|
170
|
+
throw invalid();
|
|
130
171
|
if (args.message !== undefined && args.messageFile !== undefined) throw invalid();
|
|
131
172
|
if (
|
|
132
173
|
['start', 'send', 'goal.set', 'schedule.create', 'prompt.set'].includes(action) &&
|
|
133
174
|
args.messageFile === undefined &&
|
|
134
175
|
args.message === undefined &&
|
|
135
|
-
!args.images?.length
|
|
176
|
+
!args.images?.length &&
|
|
177
|
+
!(
|
|
178
|
+
action === 'send' &&
|
|
179
|
+
(args.goal !== undefined || args.clearGoal || args.approvalId !== undefined)
|
|
180
|
+
)
|
|
136
181
|
)
|
|
137
182
|
throw invalid();
|
|
138
183
|
if (
|
|
139
184
|
action === 'start' &&
|
|
185
|
+
args.runner !== undefined &&
|
|
140
186
|
!['mar-agent', 'codex', 'claude-code', 'opencode'].includes(args.runner)
|
|
141
187
|
)
|
|
142
188
|
throw invalid();
|
|
143
|
-
if (action === '
|
|
189
|
+
if (action === 'stop' && !args.executionId) throw invalid();
|
|
190
|
+
if (action === 'wait' && args.sessionId && args.agentIds?.length) throw invalid();
|
|
191
|
+
if (
|
|
192
|
+
args.until !== undefined &&
|
|
193
|
+
(args.until !== 'approval' ||
|
|
194
|
+
!args.sessionId ||
|
|
195
|
+
!args.runId ||
|
|
196
|
+
!args.timeoutMs ||
|
|
197
|
+
args.agentIds?.length)
|
|
198
|
+
)
|
|
199
|
+
throw invalid();
|
|
200
|
+
if (
|
|
201
|
+
action === 'wait' &&
|
|
202
|
+
args.runId !== undefined &&
|
|
203
|
+
(!args.sessionId ||
|
|
204
|
+
args.timeoutMs === 0 ||
|
|
205
|
+
args.agentIds?.length ||
|
|
206
|
+
args.mode !== undefined ||
|
|
207
|
+
args.waitFor !== undefined)
|
|
208
|
+
)
|
|
209
|
+
throw invalid();
|
|
210
|
+
if (args.waitFor !== undefined && !['message', 'completion'].includes(args.waitFor))
|
|
211
|
+
throw invalid();
|
|
212
|
+
if (args.mode !== undefined && !['any', 'all'].includes(args.mode)) throw invalid();
|
|
213
|
+
if (
|
|
214
|
+
args.scope !== undefined &&
|
|
215
|
+
!['children', 'descendants', 'workspace', 'profile'].includes(args.scope)
|
|
216
|
+
)
|
|
144
217
|
throw invalid();
|
|
145
|
-
if (args.until !== undefined && args.until !== 'approval')
|
|
146
|
-
throw invalid(undefined, '--until: expected approval.');
|
|
147
218
|
if (
|
|
148
219
|
action === 'approval.decide' &&
|
|
149
220
|
(!args.runId || !args.approvalId || !['approve', 'reject'].includes(args.decision))
|
package/src/session-prepare.mjs
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { messageContent, imageAttachments } from './session-input.mjs';
|
|
3
3
|
|
|
4
|
+
const networkActions = new Set(['start', 'send', 'wait', 'stop', 'list', 'view']);
|
|
5
|
+
|
|
6
|
+
/** Adapt CLI syntax to the same Node Agent Network command used by native tools. */
|
|
7
|
+
export async function prepareAgentNetworkRequest({ args, stdin, translate, sessionId }) {
|
|
8
|
+
const input = {};
|
|
9
|
+
for (const name of [
|
|
10
|
+
'runner',
|
|
11
|
+
'model',
|
|
12
|
+
'effort',
|
|
13
|
+
'title',
|
|
14
|
+
'cursor',
|
|
15
|
+
'limit',
|
|
16
|
+
'query',
|
|
17
|
+
'parentSessionId',
|
|
18
|
+
'scope',
|
|
19
|
+
'waitFor',
|
|
20
|
+
'mode',
|
|
21
|
+
'executionId'
|
|
22
|
+
])
|
|
23
|
+
if (args[name] !== undefined) input[name] = args[name];
|
|
24
|
+
if (args.action === 'list' && input.parentSessionId !== undefined) {
|
|
25
|
+
input.parent = input.parentSessionId;
|
|
26
|
+
delete input.parentSessionId;
|
|
27
|
+
}
|
|
28
|
+
if (args.action === 'list' && args.agent !== undefined) input.agentId = args.agent;
|
|
29
|
+
if (['send', 'stop', 'view', 'wait'].includes(args.action) && sessionId)
|
|
30
|
+
input.agentId = sessionId;
|
|
31
|
+
if (args.action === 'wait' && args.agentIds?.length) input.agentIds = args.agentIds;
|
|
32
|
+
if (args.action === 'wait' && args.timeoutMs !== undefined) input.waitMs = args.timeoutMs;
|
|
33
|
+
if (args.action === 'view' && args.history) input.includeHistory = true;
|
|
34
|
+
if (args.message !== undefined || args.messageFile !== undefined)
|
|
35
|
+
input.content = await messageContent(args, stdin, 100000, translate);
|
|
36
|
+
if (args.images?.length) input.attachments = await imageAttachments(args.images, translate);
|
|
37
|
+
if (args.goal !== undefined) input.goal = { objective: args.goal.trim() };
|
|
38
|
+
if (args.clearGoal) input.goal = { clear: true };
|
|
39
|
+
if (args.action === 'send' && args.approvalId !== undefined)
|
|
40
|
+
input.approval = { approvalId: args.approvalId, decision: args.decision };
|
|
41
|
+
if (args.action === 'start' || args.action === 'send')
|
|
42
|
+
input.clientMessageId = args.clientMessageId ?? randomUUID();
|
|
43
|
+
if (args.action === 'send')
|
|
44
|
+
input.deliveryIntent = args.steer ? 'REPLACE_CURRENT' : args.queue ? 'QUEUE' : 'SEND';
|
|
45
|
+
return { action: 'network', command: { operation: args.action, input } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isAgentNetworkAction(action) {
|
|
49
|
+
return networkActions.has(action);
|
|
50
|
+
}
|
|
51
|
+
|
|
4
52
|
export async function prepareSessionRequest({ args, env, stdin, translate, sessionId, targeted }) {
|
|
5
53
|
const body = { action: args.action };
|
|
6
54
|
for (const name of [
|
package/src/session-target.mjs
CHANGED
|
@@ -5,6 +5,7 @@ export const sessionTarget = Object.freeze({
|
|
|
5
5
|
start: 'workspace',
|
|
6
6
|
'schedule.list': 'workspace',
|
|
7
7
|
show: 'current',
|
|
8
|
+
view: 'current',
|
|
8
9
|
config: 'current',
|
|
9
10
|
history: 'current',
|
|
10
11
|
graph: 'current',
|
|
@@ -17,7 +18,7 @@ export const sessionTarget = Object.freeze({
|
|
|
17
18
|
'schedule.update': 'current',
|
|
18
19
|
send: 'explicit',
|
|
19
20
|
stop: 'explicit',
|
|
20
|
-
wait: '
|
|
21
|
+
wait: 'optional',
|
|
21
22
|
'approval.decide': 'explicit',
|
|
22
23
|
'goal.get': 'explicit',
|
|
23
24
|
'goal.set': 'explicit',
|
package/src/sessions.mjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { MarctlError } from './errors.mjs';
|
|
2
2
|
import { sessionTarget } from './session-target.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
isAgentNetworkAction,
|
|
5
|
+
prepareAgentNetworkRequest,
|
|
6
|
+
prepareSessionRequest
|
|
7
|
+
} from './session-prepare.mjs';
|
|
4
8
|
import { waitForSessionRun } from './session-wait.mjs';
|
|
5
9
|
|
|
6
10
|
export async function runSessionCommand({
|
|
@@ -17,33 +21,41 @@ export async function runSessionCommand({
|
|
|
17
21
|
const sessionId =
|
|
18
22
|
args.sessionId ??
|
|
19
23
|
(sessionTarget[args.action] === 'current' ? env.MAR_CTL_SESSION_ID : undefined);
|
|
20
|
-
if (targeted && !sessionId)
|
|
24
|
+
if (targeted && !sessionId && sessionTarget[args.action] !== 'optional')
|
|
21
25
|
throw new MarctlError(translate('errors.sessionContextRequired'), {
|
|
22
26
|
code: 'MARCTL_SESSION_CONTEXT_REQUIRED'
|
|
23
27
|
});
|
|
24
|
-
if (args.action === 'wait')
|
|
28
|
+
if (args.action === 'wait' && args.runId !== undefined)
|
|
25
29
|
return waitForSessionRun({
|
|
26
30
|
client,
|
|
27
31
|
sessionId,
|
|
28
32
|
runId: args.runId,
|
|
29
|
-
timeoutMs: args.timeoutMs,
|
|
33
|
+
timeoutMs: args.timeoutMs ?? 30000,
|
|
30
34
|
until: args.until,
|
|
31
35
|
WebSocketClass,
|
|
32
36
|
translate,
|
|
33
37
|
signal
|
|
34
38
|
});
|
|
35
|
-
|
|
36
|
-
const body =
|
|
39
|
+
const network = isAgentNetworkAction(args.action);
|
|
40
|
+
const body = network
|
|
41
|
+
? await prepareAgentNetworkRequest({ args, stdin, translate, sessionId })
|
|
42
|
+
: await prepareSessionRequest({ args, env, stdin, translate, sessionId, targeted });
|
|
43
|
+
const clientMessageId = network ? body.command.input.clientMessageId : body.clientMessageId;
|
|
37
44
|
try {
|
|
38
|
-
const
|
|
45
|
+
const requestedWait = args.timeoutMs ?? 30000;
|
|
46
|
+
const effectiveWait = requestedWait === 0 ? 0 : Math.max(10000, requestedWait);
|
|
47
|
+
const result = await client.sessionCommand(
|
|
48
|
+
body,
|
|
49
|
+
args.action === 'wait' ? { timeoutMs: effectiveWait + 10000, signal } : undefined
|
|
50
|
+
);
|
|
39
51
|
return {
|
|
40
52
|
...result,
|
|
41
53
|
...(typeof result?.session?.id === 'string' ? { sessionId: result.session.id } : {}),
|
|
42
54
|
...(typeof result?.run?.id === 'string' ? { runId: result.run.id } : {}),
|
|
43
|
-
...(
|
|
55
|
+
...(clientMessageId === undefined ? {} : { clientMessageId })
|
|
44
56
|
};
|
|
45
57
|
} catch (error) {
|
|
46
|
-
if (
|
|
58
|
+
if (clientMessageId !== undefined) error.clientMessageId = clientMessageId;
|
|
47
59
|
if (
|
|
48
60
|
[
|
|
49
61
|
'start',
|