@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/marctl",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "MyAgentRoam command-line interface for authorized Node access.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -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.',
@@ -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 可省略 ID,
84
- 使用当前会话;没有当前会话时请显式指定 ID。
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), { code: 'MARCTL_SESSION_INPUT_INVALID' });
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 (action === 'goal' || action === 'schedule') action += '.' + input.shift();
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' || args.action.startsWith('schedule.') ? 20000 : 100000,
219
- translate
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({ message, messageFile: path }, stdin, limit, translate) {
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
  };