@myagentroam/marctl 0.11.0 → 0.11.1
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/i18n/en-US.mjs +17 -4
- package/src/i18n/zh-CN.mjs +17 -4
- package/src/sessions.mjs +89 -10
package/package.json
CHANGED
package/src/i18n/en-US.mjs
CHANGED
|
@@ -52,6 +52,9 @@ Usage:
|
|
|
52
52
|
{program} config [<session-id>]
|
|
53
53
|
[--model <id>] [--effort <value>] [--json]
|
|
54
54
|
{program} rename [<session-id>] --title <name> [--json]
|
|
55
|
+
{program} prompt get|clear [<session-id>] [--json]
|
|
56
|
+
{program} prompt set [<session-id>]
|
|
57
|
+
(-m <text> | --message-file <file|->) [--json]
|
|
55
58
|
{program} schedule list [--cursor <cursor>] [--limit <1..100>] [--json]
|
|
56
59
|
{program} schedule get [<session-id>] [--json]
|
|
57
60
|
{program} schedule create [<session-id>] --cron <cron>
|
|
@@ -78,13 +81,15 @@ Usage:
|
|
|
78
81
|
{program} goal set <session-id>
|
|
79
82
|
(-m <text> | --message-file <file|->) [--json]
|
|
80
83
|
{program} wait <session-id>
|
|
81
|
-
--run <run-id> --timeout <30s|5m> [--json]
|
|
84
|
+
--run <run-id> --timeout <30s|5m> [--until approval] [--json]
|
|
85
|
+
{program} approval decide <session-id> --run <run-id>
|
|
86
|
+
--approval <approval-id> --decision <approve|reject> [--json]
|
|
82
87
|
|
|
83
88
|
Input:
|
|
84
|
-
show/config/history/graph/rename may omit the ID.
|
|
89
|
+
show/config/history/graph/rename and prompt may omit the ID.
|
|
85
90
|
schedule get/create/update follow the same rule.
|
|
86
91
|
They use the current Session; without one, specify an ID explicitly.
|
|
87
|
-
send/goal/stop/wait always require an explicit Session ID.
|
|
92
|
+
send/goal/stop/wait/approval always require an explicit Session ID.
|
|
88
93
|
Runners: mar-agent, codex, claude-code, opencode; actual capabilities apply.
|
|
89
94
|
-m / --message supplies text; it is mutually exclusive with --message-file.
|
|
90
95
|
Files use UTF-8; - reads stdin. Repeat --image for PNG/JPEG/GIF/WebP files.
|
|
@@ -92,6 +97,7 @@ Input:
|
|
|
92
97
|
Goal set accepts text only.
|
|
93
98
|
Config requires model or effort; other fields and permissions are unchanged.
|
|
94
99
|
Use --effort "" for the Runner default. Active execution is not restarted.
|
|
100
|
+
Prompt reads, sets or clears persistent instructions for future executions.
|
|
95
101
|
|
|
96
102
|
Titles and scheduled tasks:
|
|
97
103
|
rename uses the existing title update; no parent-tree restriction.
|
|
@@ -106,6 +112,8 @@ Reading JSON:
|
|
|
106
112
|
show: session.status, session.title, session.runId,
|
|
107
113
|
session.model, session.effort, session.queuedCount,
|
|
108
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.
|
|
109
117
|
Status is RUNNING/IDLE/SLEEPING; tools keep their original states.
|
|
110
118
|
history: segments[].items[]; select kind === "assistant_message",
|
|
111
119
|
read payload.text.
|
|
@@ -117,7 +125,8 @@ Delivery and waiting:
|
|
|
117
125
|
Node decides from actual state; reserve it for updates
|
|
118
126
|
affecting current work.
|
|
119
127
|
--queue schedules a subsequent turn; an active Goal can delay it
|
|
120
|
-
until the Goal exits.
|
|
128
|
+
until the Goal exits. New input resumes a paused FIFO without interrupting
|
|
129
|
+
active execution; cancellation settles before resuming.
|
|
121
130
|
Example:
|
|
122
131
|
marctl sessions send <session-id> --steer \\
|
|
123
132
|
-m "Additional context: focus on the resume path." --json
|
|
@@ -125,6 +134,9 @@ Delivery and waiting:
|
|
|
125
134
|
active input: correlate queueItemId/clientMessageId with visible history.
|
|
126
135
|
Wait observes the specified Run through Watch. Timeout does not stop it;
|
|
127
136
|
timeout errors may include progress, the last observed bounded snapshot.
|
|
137
|
+
--until approval also returns when the Run needs an approval decision.
|
|
138
|
+
Approval decide explicitly approves or rejects one request, subject to
|
|
139
|
+
existing user authorization. Accepted decisions do not prove completion.
|
|
128
140
|
Run success is not proof of task success.
|
|
129
141
|
Completion never wakes a Coordinator.
|
|
130
142
|
On an unknown delivery outcome, inspect state/history; do not blindly resend.
|
|
@@ -265,6 +277,7 @@ or Server connection is required.`
|
|
|
265
277
|
sessionWaitUnknown: 'The specified Run could not be confirmed. Inspect Session state/history.',
|
|
266
278
|
sessionWaitCancelled: 'Waiting was cancelled; the target Run was not stopped.',
|
|
267
279
|
sessionWaitTimeout: 'Waiting timed out; the target Run was not stopped.',
|
|
280
|
+
sessionApprovalUnsupported: 'This Node does not provide pending approval observations.',
|
|
268
281
|
|
|
269
282
|
userContextUnavailable: 'This Node session has no valid marctl user authorization.',
|
|
270
283
|
sessionContextRequired: 'No current Session is available; specify a Session ID explicitly.',
|
package/src/i18n/zh-CN.mjs
CHANGED
|
@@ -51,6 +51,9 @@ ${configurationHelp}
|
|
|
51
51
|
{program} config [<session-id>]
|
|
52
52
|
[--model <id>] [--effort <value>] [--json]
|
|
53
53
|
{program} rename [<session-id>] --title <name> [--json]
|
|
54
|
+
{program} prompt get|clear [<session-id>] [--json]
|
|
55
|
+
{program} prompt set [<session-id>]
|
|
56
|
+
(-m <text> | --message-file <file|->) [--json]
|
|
54
57
|
{program} schedule list [--cursor <cursor>] [--limit <1..100>] [--json]
|
|
55
58
|
{program} schedule get [<session-id>] [--json]
|
|
56
59
|
{program} schedule create [<session-id>] --cron <cron>
|
|
@@ -77,12 +80,14 @@ ${configurationHelp}
|
|
|
77
80
|
{program} goal set <session-id>
|
|
78
81
|
(-m <text> | --message-file <file|->) [--json]
|
|
79
82
|
{program} wait <session-id>
|
|
80
|
-
--run <run-id> --timeout <30s|5m> [--json]
|
|
83
|
+
--run <run-id> --timeout <30s|5m> [--until approval] [--json]
|
|
84
|
+
{program} approval decide <session-id> --run <run-id>
|
|
85
|
+
--approval <approval-id> --decision <approve|reject> [--json]
|
|
81
86
|
|
|
82
87
|
输入:
|
|
83
|
-
show/config/history/graph/rename 和 schedule get/create/update
|
|
84
|
-
|
|
85
|
-
send/goal/stop/wait 始终要求显式会话 ID。
|
|
88
|
+
show/config/history/graph/rename、prompt 和 schedule get/create/update
|
|
89
|
+
可省略 ID,使用当前会话;没有当前会话时请显式指定 ID。
|
|
90
|
+
send/goal/stop/wait/approval 始终要求显式会话 ID。
|
|
86
91
|
Runner:mar-agent、codex、claude-code、opencode,操作以实际能力为准。
|
|
87
92
|
-m / --message 直接传文本,与 --message-file 互斥;
|
|
88
93
|
文件用 UTF-8,- 从 stdin 读取。
|
|
@@ -90,6 +95,7 @@ ${configurationHelp}
|
|
|
90
95
|
最多 5 张,单张 5 MiB、合计 10 MiB;goal set 只接受文本。
|
|
91
96
|
config 至少指定 model 或 effort;未指定字段和权限不变。
|
|
92
97
|
--effort "" 使用 Runner 默认值,不重启当前执行。
|
|
98
|
+
prompt 读取、设置或清除持久指令,从后续执行生效。
|
|
93
99
|
|
|
94
100
|
标题与定时任务:
|
|
95
101
|
rename 复用现有标题更新,不限制 parent 树、不重启执行。
|
|
@@ -103,6 +109,8 @@ ${configurationHelp}
|
|
|
103
109
|
show:session.status、session.title、session.runId,
|
|
104
110
|
session.model、session.effort、session.queuedCount,
|
|
105
111
|
session.latestMessage、session.recentTools(最多三条摘要)。
|
|
112
|
+
session.pendingApprovals 展示当前审批;pendingApprovalsTruncated 标记
|
|
113
|
+
摘要未完整列出,完整证据仍查 history。
|
|
106
114
|
状态为 RUNNING/IDLE/SLEEPING;工具保留原有生命周期。
|
|
107
115
|
history:segments[].items[] 中按 kind === "assistant_message"
|
|
108
116
|
筛选,读取 payload.text。
|
|
@@ -113,6 +121,7 @@ ${configurationHelp}
|
|
|
113
121
|
--steer 优先补充新信息,可能切换当前执行;
|
|
114
122
|
Node 按实际状态裁决,仅用于影响当前工作的必要更新。
|
|
115
123
|
--queue 排入后续轮次;有活动 Goal 时可能等待 Goal 退出才处理。
|
|
124
|
+
新输入自动恢复暂停的 FIFO,不打断活动执行;取消收敛后才恢复。
|
|
116
125
|
示例:
|
|
117
126
|
marctl sessions send <session-id> --steer \\
|
|
118
127
|
-m "Additional context: focus on the resume path." --json
|
|
@@ -120,6 +129,9 @@ ${configurationHelp}
|
|
|
120
129
|
需按 queueItemId/clientMessageId 与可见历史核对。
|
|
121
130
|
wait 通过 Watch 等待指定 Run;超时不会停止任务,
|
|
122
131
|
超时错误可附 progress,表示最后观察到的有界快照。
|
|
132
|
+
--until approval 在需要审批决定时也提前返回。
|
|
133
|
+
approval decide 显式批准或拒绝单个请求,沿用实际用户授权;
|
|
134
|
+
决定被接受不代表任务完成。
|
|
123
135
|
Run 成功不等于业务完成。Worker 完成不自动唤醒 Coordinator。
|
|
124
136
|
投递结果未知时先查状态/历史,不盲目重发。
|
|
125
137
|
|
|
@@ -250,6 +262,7 @@ ${configurationHelp}`,
|
|
|
250
262
|
sessionWaitUnknown: '无法确认指定 Run,请查看会话状态和历史。',
|
|
251
263
|
sessionWaitCancelled: '已取消等待,未停止目标 Run。',
|
|
252
264
|
sessionWaitTimeout: '等待超时,未停止目标 Run。',
|
|
265
|
+
sessionApprovalUnsupported: '此 Node 不支持待审批状态观察。',
|
|
253
266
|
|
|
254
267
|
userContextUnavailable: '此 Node 会话没有有效的 marctl 用户授权。',
|
|
255
268
|
sessionContextRequired: '当前上下文没有会话,请显式指定会话 ID。',
|
package/src/sessions.mjs
CHANGED
|
@@ -11,6 +11,9 @@ const currentSessionActions = new Set([
|
|
|
11
11
|
'history',
|
|
12
12
|
'graph',
|
|
13
13
|
'rename',
|
|
14
|
+
'prompt.get',
|
|
15
|
+
'prompt.set',
|
|
16
|
+
'prompt.clear',
|
|
14
17
|
'schedule.get',
|
|
15
18
|
'schedule.create',
|
|
16
19
|
'schedule.update'
|
|
@@ -33,6 +36,10 @@ const commands = {
|
|
|
33
36
|
],
|
|
34
37
|
config: ['model', 'effort'],
|
|
35
38
|
rename: ['title'],
|
|
39
|
+
'prompt.get': [],
|
|
40
|
+
'prompt.set': ['message', 'message-file'],
|
|
41
|
+
'prompt.clear': [],
|
|
42
|
+
'approval.decide': ['run', 'approval', 'decision'],
|
|
36
43
|
'schedule.list': ['cursor', 'limit'],
|
|
37
44
|
'schedule.get': [],
|
|
38
45
|
'schedule.create': ['cron', 'message', 'message-file'],
|
|
@@ -42,7 +49,7 @@ const commands = {
|
|
|
42
49
|
'goal.get': [],
|
|
43
50
|
'goal.set': ['message', 'message-file'],
|
|
44
51
|
'goal.clear': [],
|
|
45
|
-
wait: ['run', 'timeout']
|
|
52
|
+
wait: ['run', 'timeout', 'until']
|
|
46
53
|
};
|
|
47
54
|
const fieldNames = {
|
|
48
55
|
parent: 'parentSessionId',
|
|
@@ -50,13 +57,23 @@ const fieldNames = {
|
|
|
50
57
|
'message-file': 'messageFile',
|
|
51
58
|
'client-message-id': 'clientMessageId',
|
|
52
59
|
run: 'runId',
|
|
60
|
+
approval: 'approvalId',
|
|
53
61
|
timeout: 'timeoutMs',
|
|
54
62
|
image: 'images'
|
|
55
63
|
};
|
|
56
64
|
|
|
57
65
|
export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
58
|
-
const invalid = (key = 'errors.invalidArguments') =>
|
|
59
|
-
new MarctlError(translate(key), {
|
|
66
|
+
const invalid = (key = 'errors.invalidArguments', details) =>
|
|
67
|
+
new MarctlError(translate(key), {
|
|
68
|
+
code: 'MARCTL_SESSION_INPUT_INVALID',
|
|
69
|
+
details:
|
|
70
|
+
details ??
|
|
71
|
+
(argv[0] === 'prompt'
|
|
72
|
+
? 'Use prompt get|clear [session-id], or prompt set [session-id] with exactly one of -m/--message or --message-file.'
|
|
73
|
+
: argv[0] === 'approval'
|
|
74
|
+
? 'Use approval decide <session-id> --run <run-id> --approval <approval-id> --decision approve|reject.'
|
|
75
|
+
: undefined)
|
|
76
|
+
});
|
|
60
77
|
if (
|
|
61
78
|
argv.length === 0 ||
|
|
62
79
|
argv.some(
|
|
@@ -67,7 +84,7 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
67
84
|
return { command: 'sessions', help: true };
|
|
68
85
|
const input = [...argv];
|
|
69
86
|
let action = input.shift();
|
|
70
|
-
if (
|
|
87
|
+
if (['goal', 'schedule', 'prompt', 'approval'].includes(action)) action += '.' + input.shift();
|
|
71
88
|
if (!Object.hasOwn(commands, action)) throw invalid();
|
|
72
89
|
const args = { command: 'sessions', action };
|
|
73
90
|
if (!['current', 'list', 'start', 'schedule.list'].includes(action)) {
|
|
@@ -125,7 +142,7 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
125
142
|
throw invalid('errors.sessionDeliveryRequired');
|
|
126
143
|
if (args.message !== undefined && args.messageFile !== undefined) throw invalid();
|
|
127
144
|
if (
|
|
128
|
-
['start', 'send', 'goal.set', 'schedule.create'].includes(action) &&
|
|
145
|
+
['start', 'send', 'goal.set', 'schedule.create', 'prompt.set'].includes(action) &&
|
|
129
146
|
args.messageFile === undefined &&
|
|
130
147
|
args.message === undefined &&
|
|
131
148
|
!args.images?.length
|
|
@@ -138,6 +155,16 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
138
155
|
throw invalid();
|
|
139
156
|
if (action === 'wait' && (args.runId === undefined || args.timeoutMs === undefined))
|
|
140
157
|
throw invalid();
|
|
158
|
+
if (args.until !== undefined && args.until !== 'approval')
|
|
159
|
+
throw invalid(undefined, '--until: expected approval.');
|
|
160
|
+
if (
|
|
161
|
+
action === 'approval.decide' &&
|
|
162
|
+
(!args.runId || !args.approvalId || !['approve', 'reject'].includes(args.decision))
|
|
163
|
+
)
|
|
164
|
+
throw invalid(
|
|
165
|
+
undefined,
|
|
166
|
+
'Approval decisions require --run, --approval and --decision approve|reject.'
|
|
167
|
+
);
|
|
141
168
|
if (action === 'rename' && args.title !== undefined) args.title = args.title.trim();
|
|
142
169
|
if (args.title !== undefined && args.title.length > 160) throw invalid();
|
|
143
170
|
if (args.model !== undefined && (args.model.trim().length === 0 || args.model.length > 120))
|
|
@@ -180,6 +207,7 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
180
207
|
sessionId,
|
|
181
208
|
runId: args.runId,
|
|
182
209
|
timeoutMs: args.timeoutMs,
|
|
210
|
+
until: args.until,
|
|
183
211
|
WebSocketClass,
|
|
184
212
|
translate,
|
|
185
213
|
signal: controller.signal
|
|
@@ -201,6 +229,7 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
201
229
|
'limit',
|
|
202
230
|
'query',
|
|
203
231
|
'runId',
|
|
232
|
+
'approvalId',
|
|
204
233
|
'cron',
|
|
205
234
|
'expectedRevision'
|
|
206
235
|
])
|
|
@@ -215,9 +244,16 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
215
244
|
body.content = await messageContent(
|
|
216
245
|
args,
|
|
217
246
|
stdin,
|
|
218
|
-
args.action === 'goal.set' ||
|
|
219
|
-
|
|
247
|
+
args.action === 'goal.set' ||
|
|
248
|
+
args.action.startsWith('schedule.') ||
|
|
249
|
+
args.action === 'prompt.set'
|
|
250
|
+
? 20000
|
|
251
|
+
: 100000,
|
|
252
|
+
translate,
|
|
253
|
+
args.action === 'prompt.set'
|
|
220
254
|
);
|
|
255
|
+
if (args.action === 'approval.decide')
|
|
256
|
+
body.decision = args.decision === 'approve' ? 'APPROVED' : 'REJECTED';
|
|
221
257
|
if (args.images !== undefined) {
|
|
222
258
|
body.attachments = await imageAttachments(args.images, translate);
|
|
223
259
|
body.content ??= '';
|
|
@@ -242,6 +278,9 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
242
278
|
'send',
|
|
243
279
|
'config',
|
|
244
280
|
'rename',
|
|
281
|
+
'prompt.set',
|
|
282
|
+
'prompt.clear',
|
|
283
|
+
'approval.decide',
|
|
245
284
|
'schedule.create',
|
|
246
285
|
'schedule.update',
|
|
247
286
|
'stop',
|
|
@@ -259,7 +298,13 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
259
298
|
}
|
|
260
299
|
}
|
|
261
300
|
|
|
262
|
-
async function messageContent(
|
|
301
|
+
async function messageContent(
|
|
302
|
+
{ message, messageFile: path },
|
|
303
|
+
stdin,
|
|
304
|
+
limit,
|
|
305
|
+
translate,
|
|
306
|
+
diagnostic = false
|
|
307
|
+
) {
|
|
263
308
|
let file;
|
|
264
309
|
try {
|
|
265
310
|
let content = message;
|
|
@@ -280,9 +325,14 @@ async function messageContent({ message, messageFile: path }, stdin, limit, tran
|
|
|
280
325
|
}
|
|
281
326
|
if (content.trim().length === 0 || content.length > limit) throw new Error('MESSAGE_INVALID');
|
|
282
327
|
return content;
|
|
283
|
-
} catch {
|
|
328
|
+
} catch (error) {
|
|
284
329
|
throw new MarctlError(translate('errors.sessionMessageInvalid'), {
|
|
285
|
-
code: 'MARCTL_SESSION_INPUT_INVALID'
|
|
330
|
+
code: 'MARCTL_SESSION_INPUT_INVALID',
|
|
331
|
+
...(diagnostic
|
|
332
|
+
? {
|
|
333
|
+
details: `${path === undefined ? 'message' : path === '-' ? 'stdin' : 'message-file'}: provide non-blank UTF-8 text of at most ${limit} characters.${typeof error.code === 'string' && /^[A-Z_]+$/u.test(error.code) ? ' File error: ' + error.code + '.' : ''}`
|
|
334
|
+
}
|
|
335
|
+
: {})
|
|
286
336
|
});
|
|
287
337
|
} finally {
|
|
288
338
|
await file?.close();
|
|
@@ -339,6 +389,7 @@ export async function waitForSessionRun({
|
|
|
339
389
|
sessionId,
|
|
340
390
|
runId,
|
|
341
391
|
timeoutMs,
|
|
392
|
+
until,
|
|
342
393
|
WebSocketClass,
|
|
343
394
|
translate = createTranslator(),
|
|
344
395
|
signal
|
|
@@ -425,6 +476,27 @@ export async function waitForSessionRun({
|
|
|
425
476
|
accept({ run });
|
|
426
477
|
}
|
|
427
478
|
};
|
|
479
|
+
const checkApproval = () => {
|
|
480
|
+
if (settled || until !== 'approval' || progress === undefined) return;
|
|
481
|
+
if (!Array.isArray(progress.pendingApprovals)) {
|
|
482
|
+
fail('NODE_OPERATION_UNSUPPORTED', 'errors.sessionApprovalUnsupported', {
|
|
483
|
+
stage: 'approval'
|
|
484
|
+
});
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const pendingApprovals = progress.pendingApprovals.filter(
|
|
488
|
+
(approval) => approval.runId === runId
|
|
489
|
+
);
|
|
490
|
+
if (pendingApprovals.length === 0) return;
|
|
491
|
+
settled = true;
|
|
492
|
+
accept({
|
|
493
|
+
sessionId,
|
|
494
|
+
runId,
|
|
495
|
+
stopReason: 'APPROVAL_REQUIRED',
|
|
496
|
+
pendingApprovals,
|
|
497
|
+
...(progress.pendingApprovalsTruncated ? { pendingApprovalsTruncated: true } : {})
|
|
498
|
+
});
|
|
499
|
+
};
|
|
428
500
|
const onMessage = (raw) => {
|
|
429
501
|
if (settled) return;
|
|
430
502
|
let message;
|
|
@@ -463,6 +535,12 @@ export async function waitForSessionRun({
|
|
|
463
535
|
if (message.payload?.session?.id === sessionId && message.payload?.progress?.runId === runId)
|
|
464
536
|
progress = message.payload.progress;
|
|
465
537
|
check(run);
|
|
538
|
+
if (!settled && until === 'approval' && progress === undefined) {
|
|
539
|
+
fail('NODE_OPERATION_UNSUPPORTED', 'errors.sessionApprovalUnsupported', {
|
|
540
|
+
stage: 'approval'
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
checkApproval();
|
|
466
544
|
} else if (message.type === 'watch.session.event') {
|
|
467
545
|
const payload = message.event?.payload;
|
|
468
546
|
if (
|
|
@@ -471,6 +549,7 @@ export async function waitForSessionRun({
|
|
|
471
549
|
)
|
|
472
550
|
progress = payload.progress;
|
|
473
551
|
check(payload?.run);
|
|
552
|
+
checkApproval();
|
|
474
553
|
}
|
|
475
554
|
if (!settled && message.expiresAt !== undefined) scheduleRenewal(message.expiresAt);
|
|
476
555
|
};
|