@myagentroam/marctl 0.10.5 → 0.10.7
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 +8 -1
- package/src/i18n/en-US.mjs +21 -4
- package/src/i18n/zh-CN.mjs +21 -4
- package/src/sessions.mjs +116 -32
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -106,11 +106,18 @@ export async function main(
|
|
|
106
106
|
? {}
|
|
107
107
|
: { upstreamRequestId: error.upstreamRequestId }),
|
|
108
108
|
...(error.details === undefined ? {} : { details: error.details }),
|
|
109
|
+
...(error.sessionId === undefined ? {} : { sessionId: error.sessionId }),
|
|
110
|
+
...(error.runId === undefined ? {} : { runId: error.runId }),
|
|
111
|
+
...(error.stage === undefined ? {} : { stage: error.stage }),
|
|
112
|
+
...(error.upstreamCode === undefined ? {} : { upstreamCode: error.upstreamCode }),
|
|
109
113
|
...(error.clientMessageId === undefined ? {} : { clientMessageId: error.clientMessageId })
|
|
110
114
|
};
|
|
111
115
|
if (args?.json === true || (args === undefined && argv.includes('--json')))
|
|
112
116
|
writeLine(stdout, JSON.stringify({ error: failure }));
|
|
113
|
-
else
|
|
117
|
+
else {
|
|
118
|
+
writeLine(stderr, program + ': ' + failure.message + ' [' + failure.code + ']');
|
|
119
|
+
if (failure.details !== undefined) writeLine(stderr, failure.details);
|
|
120
|
+
}
|
|
114
121
|
return sessionExitCode(error);
|
|
115
122
|
}
|
|
116
123
|
writeLine(
|
package/src/i18n/en-US.mjs
CHANGED
|
@@ -46,19 +46,22 @@ Notes:
|
|
|
46
46
|
|
|
47
47
|
Usage:
|
|
48
48
|
{program} current [--json]
|
|
49
|
-
{program} list [--
|
|
49
|
+
{program} list [--query <title>] [--parent <session-id>]
|
|
50
|
+
[--cursor <cursor>] [--limit <1..100>] [--json]
|
|
50
51
|
{program} show <session-id> [--json]
|
|
52
|
+
{program} config <session-id> [--model <id>] [--effort <value>] [--json]
|
|
53
|
+
{program} graph <session-id> [--limit <1..100>] [--json]
|
|
51
54
|
{program} history <session-id>
|
|
52
55
|
[--cursor <cursor>] [--limit <1..100>] [--json]
|
|
53
56
|
|
|
54
57
|
{program} start --runner <runner>
|
|
55
58
|
[-m <text> | --message-file <file|->] [--image <path> ...]
|
|
56
|
-
[--model <id>] [--title <name>]
|
|
59
|
+
[--model <id>] [--effort <value>] [--title <name>]
|
|
57
60
|
[--client-message-id <id>] [--json]
|
|
58
61
|
|
|
59
62
|
{program} send <session-id>
|
|
60
63
|
[-m <text> | --message-file <file|->] [--image <path> ...]
|
|
61
|
-
|
|
64
|
+
(--steer | --queue)
|
|
62
65
|
[--client-message-id <id>] [--json]
|
|
63
66
|
|
|
64
67
|
{program} stop <session-id> [--run <run-id>] [--json]
|
|
@@ -74,10 +77,18 @@ Input:
|
|
|
74
77
|
Files use UTF-8; - reads stdin. Repeat --image for PNG/JPEG/GIF/WebP files.
|
|
75
78
|
Start/send allow image-only input: up to 5 images, 5 MiB each, 10 MiB total.
|
|
76
79
|
Goal set accepts text only.
|
|
80
|
+
Config requires model or effort; other fields and permissions are unchanged.
|
|
81
|
+
Use --effort "" for the Runner default. Active execution is not restarted.
|
|
77
82
|
|
|
78
83
|
Delivery and waiting:
|
|
79
84
|
Start/send return a clientMessageId.
|
|
80
|
-
Send
|
|
85
|
+
Send requires exactly one of --steer or --queue, even when idle.
|
|
86
|
+
When useful, --steer adds gentle context to an in-progress task.
|
|
87
|
+
--queue places input in a subsequent turn.
|
|
88
|
+
Example:
|
|
89
|
+
marctl sessions send <session-id> --steer \\
|
|
90
|
+
-m "Additional context: focus on the resume path." --json
|
|
91
|
+
A queued runId may identify another
|
|
81
92
|
active input: correlate queueItemId/clientMessageId with visible history.
|
|
82
93
|
Wait observes the specified Run through Watch. Timeout does not stop it;
|
|
83
94
|
Run success is not proof of task success.
|
|
@@ -88,6 +99,11 @@ Credentials and scope:
|
|
|
88
99
|
Credentials expire 24 hours after issue without automatic renewal.
|
|
89
100
|
Scope is bound by MAR to the current Node/Workspace;
|
|
90
101
|
there are no scope override options.
|
|
102
|
+
List and ID lookup use Node-known Sessions without scanning Runners.
|
|
103
|
+
--query filters display titles; missing IDs may suggest nearby IDs, not retry.
|
|
104
|
+
Start records the current Session as parent; no parent override.
|
|
105
|
+
--parent lists direct children; graph includes the root and descendants.
|
|
106
|
+
Graph defaults to 100 nodes and reports truncation, not task dependencies.
|
|
91
107
|
|
|
92
108
|
Workers:
|
|
93
109
|
Create workers as needed, e.g. --title "fixer#42 Handle empty totals" or
|
|
@@ -261,6 +277,7 @@ or Server connection is required.`
|
|
|
261
277
|
terminalDirectUnsafeFallback:
|
|
262
278
|
'Direct terminal setup could not be safely canceled after OPEN; relay fallback was skipped.',
|
|
263
279
|
invalidArguments: 'Invalid command arguments.',
|
|
280
|
+
sessionDeliveryRequired: 'Send requires exactly one of --steer or --queue, even when idle.',
|
|
264
281
|
sshOptionsUnsupported: 'SSH options are not supported; use marssh <host>.',
|
|
265
282
|
scpOptionsUnsupported: 'This SCP option is not supported by the Node Terminal transport.',
|
|
266
283
|
commandFailed: 'The command failed.'
|
package/src/i18n/zh-CN.mjs
CHANGED
|
@@ -45,19 +45,22 @@ ${configurationHelp}
|
|
|
45
45
|
|
|
46
46
|
用法:
|
|
47
47
|
{program} current [--json]
|
|
48
|
-
{program} list [--
|
|
48
|
+
{program} list [--query <title>] [--parent <session-id>]
|
|
49
|
+
[--cursor <cursor>] [--limit <1..100>] [--json]
|
|
49
50
|
{program} show <session-id> [--json]
|
|
51
|
+
{program} config <session-id> [--model <id>] [--effort <value>] [--json]
|
|
52
|
+
{program} graph <session-id> [--limit <1..100>] [--json]
|
|
50
53
|
{program} history <session-id>
|
|
51
54
|
[--cursor <cursor>] [--limit <1..100>] [--json]
|
|
52
55
|
|
|
53
56
|
{program} start --runner <runner>
|
|
54
57
|
[-m <text> | --message-file <file|->] [--image <path> ...]
|
|
55
|
-
[--model <id>] [--title <name>]
|
|
58
|
+
[--model <id>] [--effort <value>] [--title <name>]
|
|
56
59
|
[--client-message-id <id>] [--json]
|
|
57
60
|
|
|
58
61
|
{program} send <session-id>
|
|
59
62
|
[-m <text> | --message-file <file|->] [--image <path> ...]
|
|
60
|
-
|
|
63
|
+
(--steer | --queue)
|
|
61
64
|
[--client-message-id <id>] [--json]
|
|
62
65
|
|
|
63
66
|
{program} stop <session-id> [--run <run-id>] [--json]
|
|
@@ -73,10 +76,18 @@ ${configurationHelp}
|
|
|
73
76
|
文件用 UTF-8,- 从 stdin 读取。
|
|
74
77
|
start/send 可重复 --image 传 PNG/JPEG/GIF/WebP 图片,也可只发送图片。
|
|
75
78
|
最多 5 张,单张 5 MiB、合计 10 MiB;goal set 只接受文本。
|
|
79
|
+
config 至少指定 model 或 effort;未指定字段和权限不变。
|
|
80
|
+
--effort "" 使用 Runner 默认值,不重启当前执行。
|
|
76
81
|
|
|
77
82
|
投递与等待:
|
|
78
83
|
start/send 返回 clientMessageId。
|
|
79
|
-
send
|
|
84
|
+
send 必须二选一:--steer 或 --queue,空闲时也需指定。
|
|
85
|
+
--steer 在必要时为正在进行的任务补充温和的新信息。
|
|
86
|
+
--queue 将输入排入后续轮次。
|
|
87
|
+
示例:
|
|
88
|
+
marctl sessions send <session-id> --steer \\
|
|
89
|
+
-m "Additional context: focus on the resume path." --json
|
|
90
|
+
排队回执的 runId 可能属于其他活动输入,
|
|
80
91
|
需按 queueItemId/clientMessageId 与可见历史核对。
|
|
81
92
|
wait 通过 Watch 等待指定 Run;超时不会停止任务,
|
|
82
93
|
Run 成功不等于业务完成。Worker 完成不自动唤醒 Coordinator。
|
|
@@ -85,6 +96,11 @@ ${configurationHelp}
|
|
|
85
96
|
凭据与范围:
|
|
86
97
|
凭据自签发起 24 小时到期,不自动续签。
|
|
87
98
|
MAR 绑定当前 Node/Workspace,不提供覆盖范围的参数。
|
|
99
|
+
列表及 ID 定位只查 Node 已知会话,不扫描 Runner。
|
|
100
|
+
--query 按展示标题筛选;ID 不存在时可提示近似候选,不自动重试。
|
|
101
|
+
start 自动记录可用的当前会话为 parent,不提供 parent 覆盖参数。
|
|
102
|
+
--parent 查直接子会话;graph 查包含根的后代,默认最多 100 个节点,
|
|
103
|
+
截断时明确标记;创建关系不表示任务依赖。
|
|
88
104
|
|
|
89
105
|
Worker:
|
|
90
106
|
按任务需要创建会话,例如 --title "fixer#42 Handle empty totals" 或
|
|
@@ -247,6 +263,7 @@ ${configurationHelp}`,
|
|
|
247
263
|
terminalSessionFailed: '终端会话失败。',
|
|
248
264
|
terminalDirectUnsafeFallback: 'OPEN 后无法安全取消直连终端,已跳过 relay 回退。',
|
|
249
265
|
invalidArguments: '命令参数无效。',
|
|
266
|
+
sessionDeliveryRequired: 'send 必须且只能选择 --steer 或 --queue,即使目标空闲也需指定。',
|
|
250
267
|
sshOptionsUnsupported: '不支持 SSH 选项,请使用 marssh <节点>。',
|
|
251
268
|
scpOptionsUnsupported: 'Node Terminal 传输不支持此 SCP 选项。',
|
|
252
269
|
terminalBinaryUnavailable: '二进制复制需要支持 pipe 终端的 Server 与 Node。',
|
package/src/sessions.mjs
CHANGED
|
@@ -7,11 +7,22 @@ import { createTranslator } from './i18n/index.mjs';
|
|
|
7
7
|
const terminal = new Set(['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED']);
|
|
8
8
|
const commands = {
|
|
9
9
|
current: [],
|
|
10
|
-
list: ['cursor', 'limit'],
|
|
10
|
+
list: ['cursor', 'limit', 'query', 'parent'],
|
|
11
|
+
graph: ['limit'],
|
|
11
12
|
show: [],
|
|
12
13
|
history: ['cursor', 'limit'],
|
|
13
|
-
start: [
|
|
14
|
-
|
|
14
|
+
start: [
|
|
15
|
+
'runner',
|
|
16
|
+
'model',
|
|
17
|
+
'effort',
|
|
18
|
+
'title',
|
|
19
|
+
'message',
|
|
20
|
+
'message-file',
|
|
21
|
+
'image',
|
|
22
|
+
'client-message-id'
|
|
23
|
+
],
|
|
24
|
+
config: ['model', 'effort'],
|
|
25
|
+
send: ['message', 'message-file', 'image', 'steer', 'queue', 'client-message-id'],
|
|
15
26
|
stop: ['run'],
|
|
16
27
|
'goal.get': [],
|
|
17
28
|
'goal.set': ['message', 'message-file'],
|
|
@@ -19,6 +30,7 @@ const commands = {
|
|
|
19
30
|
wait: ['run', 'timeout']
|
|
20
31
|
};
|
|
21
32
|
const fieldNames = {
|
|
33
|
+
parent: 'parentSessionId',
|
|
22
34
|
'message-file': 'messageFile',
|
|
23
35
|
'client-message-id': 'clientMessageId',
|
|
24
36
|
run: 'runId',
|
|
@@ -27,8 +39,8 @@ const fieldNames = {
|
|
|
27
39
|
};
|
|
28
40
|
|
|
29
41
|
export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
30
|
-
const invalid = () =>
|
|
31
|
-
new MarctlError(translate(
|
|
42
|
+
const invalid = (key = 'errors.invalidArguments') =>
|
|
43
|
+
new MarctlError(translate(key), { code: 'MARCTL_SESSION_INPUT_INVALID' });
|
|
32
44
|
if (
|
|
33
45
|
argv.length === 0 ||
|
|
34
46
|
argv.some(
|
|
@@ -54,12 +66,22 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
54
66
|
args.json = true;
|
|
55
67
|
continue;
|
|
56
68
|
}
|
|
69
|
+
if (action === 'send' && (flag === '--steer' || flag === '--queue')) {
|
|
70
|
+
const key = flag.slice(2);
|
|
71
|
+
if (Object.hasOwn(args, key)) throw invalid();
|
|
72
|
+
args[key] = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
57
75
|
const name = flag.startsWith('--') ? flag.slice(2) : '';
|
|
58
76
|
const key = fieldNames[name] ?? name;
|
|
59
77
|
if (!commands[action].includes(name) || (name !== 'image' && Object.hasOwn(args, key)))
|
|
60
78
|
throw invalid();
|
|
61
79
|
const value = input.shift();
|
|
62
|
-
if (
|
|
80
|
+
if (
|
|
81
|
+
value === undefined ||
|
|
82
|
+
(value === '' && name !== 'effort') ||
|
|
83
|
+
(name !== 'message' && value.startsWith('--'))
|
|
84
|
+
)
|
|
63
85
|
throw invalid();
|
|
64
86
|
if (name === 'image') {
|
|
65
87
|
(args.images ??= []).push(value);
|
|
@@ -74,6 +96,8 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
74
96
|
args[key] = ms;
|
|
75
97
|
} else args[key] = value;
|
|
76
98
|
}
|
|
99
|
+
if (action === 'send' && (args.steer === true) === (args.queue === true))
|
|
100
|
+
throw invalid('errors.sessionDeliveryRequired');
|
|
77
101
|
if (args.message !== undefined && args.messageFile !== undefined) throw invalid();
|
|
78
102
|
if (
|
|
79
103
|
['start', 'send', 'goal.set'].includes(action) &&
|
|
@@ -89,9 +113,13 @@ export function parseSessionArgs(argv, translate = createTranslator()) {
|
|
|
89
113
|
throw invalid();
|
|
90
114
|
if (action === 'wait' && (args.runId === undefined || args.timeoutMs === undefined))
|
|
91
115
|
throw invalid();
|
|
92
|
-
if (args.delivery !== undefined && !['send', 'queue', 'replace-current'].includes(args.delivery))
|
|
93
|
-
throw invalid();
|
|
94
116
|
if (args.title !== undefined && args.title.length > 160) throw invalid();
|
|
117
|
+
if (args.model !== undefined && (args.model.trim().length === 0 || args.model.length > 120))
|
|
118
|
+
throw invalid();
|
|
119
|
+
if (args.effort !== undefined && args.effort.length > 120) throw invalid();
|
|
120
|
+
if (action === 'config' && args.model === undefined && args.effort === undefined) throw invalid();
|
|
121
|
+
if (args.query !== undefined && args.query.length > 160) throw invalid();
|
|
122
|
+
if (args.parentSessionId !== undefined && args.parentSessionId.length > 512) throw invalid();
|
|
95
123
|
return args;
|
|
96
124
|
}
|
|
97
125
|
|
|
@@ -117,9 +145,22 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
117
145
|
}
|
|
118
146
|
}
|
|
119
147
|
const body = { action: args.action };
|
|
120
|
-
for (const name of [
|
|
148
|
+
for (const name of [
|
|
149
|
+
'sessionId',
|
|
150
|
+
'parentSessionId',
|
|
151
|
+
'runner',
|
|
152
|
+
'model',
|
|
153
|
+
'effort',
|
|
154
|
+
'title',
|
|
155
|
+
'cursor',
|
|
156
|
+
'limit',
|
|
157
|
+
'query',
|
|
158
|
+
'runId'
|
|
159
|
+
])
|
|
121
160
|
if (args[name] !== undefined) body[name] = args[name];
|
|
122
161
|
if (args.action === 'current' && env.MAR_CTL_SESSION_ID) body.sessionId = env.MAR_CTL_SESSION_ID;
|
|
162
|
+
if (args.action === 'start' && env.MAR_CTL_SESSION_ID)
|
|
163
|
+
body.parentSessionId = env.MAR_CTL_SESSION_ID;
|
|
123
164
|
if (args.messageFile !== undefined || args.message !== undefined)
|
|
124
165
|
body.content = await messageContent(
|
|
125
166
|
args,
|
|
@@ -134,9 +175,7 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
134
175
|
if (args.action === 'start' || args.action === 'send')
|
|
135
176
|
body.clientMessageId = args.clientMessageId ?? randomUUID();
|
|
136
177
|
if (args.action === 'send')
|
|
137
|
-
body.deliveryIntent =
|
|
138
|
-
args.delivery ?? 'send'
|
|
139
|
-
];
|
|
178
|
+
body.deliveryIntent = args.steer === true ? 'REPLACE_CURRENT' : 'QUEUE';
|
|
140
179
|
try {
|
|
141
180
|
const result = await client.sessionCommand(body);
|
|
142
181
|
return {
|
|
@@ -148,7 +187,7 @@ export async function runSessionCommand({ args, client, env, stdin, WebSocketCla
|
|
|
148
187
|
} catch (error) {
|
|
149
188
|
if (body.clientMessageId !== undefined) error.clientMessageId = body.clientMessageId;
|
|
150
189
|
if (
|
|
151
|
-
['start', 'send', 'stop', 'goal.set', 'goal.clear'].includes(args.action) &&
|
|
190
|
+
['start', 'send', 'config', 'stop', 'goal.set', 'goal.clear'].includes(args.action) &&
|
|
152
191
|
['MARCTL_REQUEST_TIMEOUT', 'MARCTL_SERVER_UNREACHABLE', 'MARCTL_RESPONSE_INVALID'].includes(
|
|
153
192
|
error.code
|
|
154
193
|
)
|
|
@@ -244,7 +283,7 @@ export async function waitForSessionRun({
|
|
|
244
283
|
translate = createTranslator(),
|
|
245
284
|
signal
|
|
246
285
|
}) {
|
|
247
|
-
|
|
286
|
+
let socket;
|
|
248
287
|
const watchId = randomUUID();
|
|
249
288
|
let connectionId;
|
|
250
289
|
let renewal;
|
|
@@ -256,13 +295,34 @@ export async function waitForSessionRun({
|
|
|
256
295
|
accept = resolve;
|
|
257
296
|
rejectResult = fail;
|
|
258
297
|
});
|
|
259
|
-
const reject = (error) => {
|
|
298
|
+
const reject = (error, stage) => {
|
|
260
299
|
if (settled) return;
|
|
261
300
|
settled = true;
|
|
301
|
+
Object.assign(error, { sessionId, runId, stage });
|
|
262
302
|
rejectResult(error);
|
|
263
303
|
};
|
|
264
|
-
const fail = (code, key, options = {}) =>
|
|
265
|
-
|
|
304
|
+
const fail = (code, key, { stage, upstreamCode, ...options } = {}) => {
|
|
305
|
+
const error = new MarctlError(translate(key), { ...options, code });
|
|
306
|
+
if (upstreamCode !== undefined) error.upstreamCode = upstreamCode;
|
|
307
|
+
reject(error, stage);
|
|
308
|
+
};
|
|
309
|
+
const unknown = (stage, detail, source) =>
|
|
310
|
+
fail('MARCTL_WAIT_UNKNOWN', 'errors.sessionWaitUnknown', {
|
|
311
|
+
stage,
|
|
312
|
+
upstreamCode:
|
|
313
|
+
typeof source?.code === 'string' && /^[A-Za-z][A-Za-z0-9_]{0,127}$/u.test(source.code)
|
|
314
|
+
? source.code
|
|
315
|
+
: undefined,
|
|
316
|
+
requestId: typeof source?.requestId === 'string' ? source.requestId.slice(0, 160) : undefined,
|
|
317
|
+
upstreamRequestId:
|
|
318
|
+
typeof source?.upstreamRequestId === 'string'
|
|
319
|
+
? source.upstreamRequestId.slice(0, 160)
|
|
320
|
+
: undefined,
|
|
321
|
+
details: [detail, source?.message, source?.details]
|
|
322
|
+
.filter((value) => typeof value === 'string')
|
|
323
|
+
.join(' ')
|
|
324
|
+
.slice(0, 4000)
|
|
325
|
+
});
|
|
266
326
|
const command = (mode) =>
|
|
267
327
|
client.sessionCommand({
|
|
268
328
|
action: 'watch',
|
|
@@ -275,7 +335,7 @@ export async function waitForSessionRun({
|
|
|
275
335
|
clearTimeout(renewal);
|
|
276
336
|
const remaining = expiresAt - Date.now();
|
|
277
337
|
if (!Number.isFinite(remaining) || remaining <= 0) {
|
|
278
|
-
|
|
338
|
+
unknown('lease', 'The Watch lease is missing or expired.');
|
|
279
339
|
return;
|
|
280
340
|
}
|
|
281
341
|
renewal = setTimeout(
|
|
@@ -285,8 +345,13 @@ export async function waitForSessionRun({
|
|
|
285
345
|
if (!settled) scheduleRenewal(ack?.expiresAt);
|
|
286
346
|
})
|
|
287
347
|
.catch((error) => {
|
|
288
|
-
if ([401, 403].includes(error.statusCode)) reject(error);
|
|
289
|
-
else
|
|
348
|
+
if ([401, 403].includes(error.statusCode)) reject(error, 'renew');
|
|
349
|
+
else
|
|
350
|
+
unknown(
|
|
351
|
+
'renew',
|
|
352
|
+
'The Watch lease could not be renewed.',
|
|
353
|
+
error instanceof MarctlError ? error : undefined
|
|
354
|
+
);
|
|
290
355
|
});
|
|
291
356
|
},
|
|
292
357
|
Math.max(50, Math.floor(remaining / 2))
|
|
@@ -315,22 +380,22 @@ export async function waitForSessionRun({
|
|
|
315
380
|
) {
|
|
316
381
|
connectionId = message.workbenchConnectionId;
|
|
317
382
|
subscribed = true;
|
|
318
|
-
command('initial').catch(reject);
|
|
383
|
+
command('initial').catch((error) => reject(error, 'subscribe'));
|
|
319
384
|
}
|
|
320
385
|
if (message.watchId !== watchId) return;
|
|
321
386
|
if (message.type === 'watch.failed') {
|
|
322
|
-
|
|
387
|
+
unknown('watch', 'The Session Watch failed.', message);
|
|
323
388
|
return;
|
|
324
389
|
}
|
|
325
390
|
if (message.type === 'watch.session.snapshot') {
|
|
326
391
|
const runs = message.payload?.runs;
|
|
327
392
|
if (!Array.isArray(runs)) {
|
|
328
|
-
|
|
393
|
+
unknown('snapshot', 'The Watch snapshot did not contain a Run list.');
|
|
329
394
|
return;
|
|
330
395
|
}
|
|
331
|
-
const run = runs.find((value) => value
|
|
396
|
+
const run = runs.find((value) => value?.id === runId && value.sessionId === sessionId);
|
|
332
397
|
if (run === undefined) {
|
|
333
|
-
|
|
398
|
+
unknown('snapshot', 'The requested Run was absent from the Watch snapshot.');
|
|
334
399
|
return;
|
|
335
400
|
}
|
|
336
401
|
check(run);
|
|
@@ -339,24 +404,43 @@ export async function waitForSessionRun({
|
|
|
339
404
|
};
|
|
340
405
|
const onClose = (code, reason) => {
|
|
341
406
|
if (code === 1008 && String(reason) === 'UNAUTHENTICATED')
|
|
342
|
-
fail('UNAUTHENTICATED', 'errors.userContextUnavailable', {
|
|
343
|
-
|
|
407
|
+
fail('UNAUTHENTICATED', 'errors.userContextUnavailable', {
|
|
408
|
+
statusCode: 401,
|
|
409
|
+
stage: 'connection'
|
|
410
|
+
});
|
|
411
|
+
else
|
|
412
|
+
unknown(
|
|
413
|
+
'connection',
|
|
414
|
+
Number.isInteger(code)
|
|
415
|
+
? `The Watch connection closed (code ${code}).`
|
|
416
|
+
: 'The Watch connection closed.'
|
|
417
|
+
);
|
|
344
418
|
};
|
|
419
|
+
const onError = (error) =>
|
|
420
|
+
unknown('connection', 'The Watch connection failed.', { code: error?.code });
|
|
345
421
|
const onOpen = () => {
|
|
346
422
|
try {
|
|
347
423
|
socket.send(JSON.stringify({ type: 'resume' }));
|
|
348
|
-
} catch {
|
|
349
|
-
|
|
424
|
+
} catch (error) {
|
|
425
|
+
onError(error);
|
|
350
426
|
}
|
|
351
427
|
};
|
|
352
|
-
const onAbort = () =>
|
|
428
|
+
const onAbort = () =>
|
|
429
|
+
fail('MARCTL_WAIT_CANCELLED', 'errors.sessionWaitCancelled', { stage: 'cancel' });
|
|
430
|
+
try {
|
|
431
|
+
socket = client.openWorkbenchWebSocket(WebSocketClass ?? (await import('ws')).default);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (error instanceof MarctlError) reject(error, 'connection');
|
|
434
|
+
else unknown('connection', 'The Watch connection could not be opened.');
|
|
435
|
+
return await result;
|
|
436
|
+
}
|
|
353
437
|
const timer = setTimeout(
|
|
354
|
-
() => fail('MARCTL_WAIT_TIMEOUT', 'errors.sessionWaitTimeout'),
|
|
438
|
+
() => fail('MARCTL_WAIT_TIMEOUT', 'errors.sessionWaitTimeout', { stage: 'timeout' }),
|
|
355
439
|
timeoutMs
|
|
356
440
|
);
|
|
357
441
|
socket.on('open', onOpen);
|
|
358
442
|
socket.on('message', onMessage);
|
|
359
|
-
socket.on('error',
|
|
443
|
+
socket.on('error', onError);
|
|
360
444
|
socket.on('close', onClose);
|
|
361
445
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
362
446
|
if (signal?.aborted) onAbort();
|