runsignal 0.1.1 → 0.1.2
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/LICENSE +22 -22
- package/README.md +30 -30
- package/bin/runsignal.js +28 -26
- package/package.json +48 -48
- package/src/api-client.js +107 -107
package/LICENSE
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 RunSignal
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
22
|
-
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RunSignal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# RunSignal CLI
|
|
2
|
-
|
|
3
|
-
RunSignal CLI lets you initialize a machine, wrap agent commands, and inspect event decisions.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm i -g runsignal
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or run directly:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx runsignal --help
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Commands
|
|
18
|
-
|
|
19
|
-
- `runsignal login`
|
|
20
|
-
- `runsignal init
|
|
21
|
-
- `runsignal wrap -- <command>`
|
|
22
|
-
- `runsignal send --message "..."`
|
|
23
|
-
- `runsignal status <eventId>`
|
|
24
|
-
- `runsignal logout`
|
|
25
|
-
|
|
1
|
+
# RunSignal CLI
|
|
2
|
+
|
|
3
|
+
RunSignal CLI lets you initialize a machine, wrap agent commands, and inspect event decisions.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g runsignal
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx runsignal --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
- `runsignal login`
|
|
20
|
+
- `runsignal init`
|
|
21
|
+
- `runsignal wrap -- <command>`
|
|
22
|
+
- `runsignal send --message "..."`
|
|
23
|
+
- `runsignal status <eventId>`
|
|
24
|
+
- `runsignal logout`
|
|
25
|
+
|
|
26
26
|
## Quickstart
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
runsignal login
|
|
30
|
-
runsignal init
|
|
31
|
-
runsignal wrap -- claude "refactor auth flow"
|
|
29
|
+
npx runsignal@latest login
|
|
30
|
+
npx runsignal@latest init
|
|
31
|
+
npx runsignal@latest wrap -- claude "refactor auth flow"
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Environment
|
|
35
35
|
|
|
36
|
-
- `RUNSIGNAL_APP_URL` to override app URL (default: `
|
|
37
|
-
|
|
36
|
+
- `RUNSIGNAL_APP_URL` to override app URL (default: `https://runsignal.dev`)
|
|
37
|
+
|
package/bin/runsignal.js
CHANGED
|
@@ -5,8 +5,6 @@ import {spawn, execSync} from 'node:child_process';
|
|
|
5
5
|
import {promises as fs} from 'node:fs';
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import readline from 'node:readline/promises';
|
|
9
|
-
import {stdin as input, stdout as output} from 'node:process';
|
|
10
8
|
import {
|
|
11
9
|
getEventAnswer,
|
|
12
10
|
getEventDecision,
|
|
@@ -15,7 +13,7 @@ import {
|
|
|
15
13
|
sendEvent
|
|
16
14
|
} from '../src/api-client.js';
|
|
17
15
|
|
|
18
|
-
const DEFAULT_APP_URL = process.env.RUNSIGNAL_APP_URL || '
|
|
16
|
+
const DEFAULT_APP_URL = process.env.RUNSIGNAL_APP_URL || 'https://runsignal.dev';
|
|
19
17
|
const CONFIG_DIR = path.join(os.homedir(), '.runsignal');
|
|
20
18
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
21
19
|
|
|
@@ -100,16 +98,6 @@ function sleep(ms) {
|
|
|
100
98
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
async function prompt(question) {
|
|
104
|
-
const rl = readline.createInterface({input, output});
|
|
105
|
-
try {
|
|
106
|
-
const value = await rl.question(question);
|
|
107
|
-
return value.trim();
|
|
108
|
-
} finally {
|
|
109
|
-
rl.close();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
101
|
function getMachineName() {
|
|
114
102
|
return process.env.COMPUTERNAME || os.hostname() || 'machine';
|
|
115
103
|
}
|
|
@@ -239,10 +227,20 @@ async function runInit(options) {
|
|
|
239
227
|
throw new Error('not_logged_in');
|
|
240
228
|
}
|
|
241
229
|
|
|
230
|
+
const hasSlackWebhook = typeof options.webhookUrl === 'string' && options.webhookUrl.length > 0;
|
|
231
|
+
const hasTelegramConfig =
|
|
232
|
+
typeof options.botToken === 'string' ||
|
|
233
|
+
typeof options.chatId === 'string' ||
|
|
234
|
+
typeof config.provider === 'string';
|
|
242
235
|
let provider = String(options.provider || '').toLowerCase();
|
|
243
236
|
if (provider !== 'telegram' && provider !== 'slack') {
|
|
244
|
-
|
|
245
|
-
|
|
237
|
+
if (hasSlackWebhook) {
|
|
238
|
+
provider = 'slack';
|
|
239
|
+
} else if (hasTelegramConfig) {
|
|
240
|
+
provider = 'telegram';
|
|
241
|
+
} else {
|
|
242
|
+
provider = 'telegram';
|
|
243
|
+
}
|
|
246
244
|
}
|
|
247
245
|
|
|
248
246
|
if (provider !== 'telegram' && provider !== 'slack') {
|
|
@@ -253,21 +251,25 @@ async function runInit(options) {
|
|
|
253
251
|
const providerConfig = {};
|
|
254
252
|
|
|
255
253
|
if (provider === 'telegram') {
|
|
256
|
-
const botToken = String(options.botToken ||
|
|
257
|
-
const chatId = String(options.chatId ||
|
|
258
|
-
if (
|
|
254
|
+
const botToken = String(options.botToken || '').trim();
|
|
255
|
+
const chatId = String(options.chatId || '').trim();
|
|
256
|
+
if ((botToken && !chatId) || (!botToken && chatId)) {
|
|
259
257
|
throw new Error('telegram_config_invalid');
|
|
260
258
|
}
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
if (botToken && chatId) {
|
|
260
|
+
providerConfig.bot_token = botToken;
|
|
261
|
+
providerConfig.chat_id = chatId;
|
|
262
|
+
}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
if (provider === 'slack') {
|
|
266
|
-
const webhookUrl = String(options.webhookUrl ||
|
|
267
|
-
if (
|
|
268
|
-
|
|
266
|
+
const webhookUrl = String(options.webhookUrl || '').trim();
|
|
267
|
+
if (webhookUrl) {
|
|
268
|
+
if (!webhookUrl.startsWith('https://')) {
|
|
269
|
+
throw new Error('slack_webhook_invalid');
|
|
270
|
+
}
|
|
271
|
+
providerConfig.webhook_url = webhookUrl;
|
|
269
272
|
}
|
|
270
|
-
providerConfig.webhook_url = webhookUrl;
|
|
271
273
|
}
|
|
272
274
|
|
|
273
275
|
const register = await apiFetch(baseUrl, '/api/cli/machines/register', {
|
|
@@ -607,7 +609,7 @@ async function runLogout() {
|
|
|
607
609
|
|
|
608
610
|
const program = new Command();
|
|
609
611
|
|
|
610
|
-
program.name('runsignal').description('RunSignal CLI').version('0.1.
|
|
612
|
+
program.name('runsignal').description('RunSignal CLI').version('0.1.2');
|
|
611
613
|
|
|
612
614
|
program
|
|
613
615
|
.command('login')
|
|
@@ -618,7 +620,7 @@ program
|
|
|
618
620
|
|
|
619
621
|
program
|
|
620
622
|
.command('init')
|
|
621
|
-
.option('--provider <provider>', 'telegram or slack')
|
|
623
|
+
.option('--provider <provider>', 'telegram or slack (optional)')
|
|
622
624
|
.option('--app-url <url>', 'RunSignal app base URL')
|
|
623
625
|
.option('--machine-name <name>', 'Machine name override')
|
|
624
626
|
.option('--bot-token <token>', 'Telegram bot token')
|
package/package.json
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "runsignal",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "RunSignal CLI for wrapping AI agent runs and handling approvals",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"homepage": "https://runsignal.dev",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/gdamien33/runsignal.git",
|
|
11
|
-
"directory": "apps/cli"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/gdamien33/runsignal/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"runsignal",
|
|
18
|
-
"cli",
|
|
19
|
-
"ai",
|
|
20
|
-
"agent",
|
|
21
|
-
"devtools",
|
|
22
|
-
"monitoring"
|
|
23
|
-
],
|
|
24
|
-
"engines": {
|
|
25
|
-
"node": ">=18.18.0"
|
|
26
|
-
},
|
|
27
|
-
"publishConfig": {
|
|
28
|
-
"access": "public"
|
|
29
|
-
},
|
|
30
|
-
"files": [
|
|
31
|
-
"bin",
|
|
32
|
-
"src",
|
|
33
|
-
"README.md",
|
|
34
|
-
"LICENSE"
|
|
35
|
-
],
|
|
36
|
-
"bin": {
|
|
37
|
-
"runsignal": "bin/runsignal.js"
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"start": "node bin/runsignal.js --help",
|
|
41
|
-
"test:smoke": "node test/e2e-smoke.mjs",
|
|
42
|
-
"pack:check": "npm pack --dry-run",
|
|
43
|
-
"prepublishOnly": "npm run test:smoke"
|
|
44
|
-
},
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"commander": "^14.0.1"
|
|
47
|
-
}
|
|
48
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "runsignal",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "RunSignal CLI for wrapping AI agent runs and handling approvals",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://runsignal.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/gdamien33/runsignal.git",
|
|
11
|
+
"directory": "apps/cli"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/gdamien33/runsignal/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"runsignal",
|
|
18
|
+
"cli",
|
|
19
|
+
"ai",
|
|
20
|
+
"agent",
|
|
21
|
+
"devtools",
|
|
22
|
+
"monitoring"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.18.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"bin",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"bin": {
|
|
37
|
+
"runsignal": "bin/runsignal.js"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"start": "node bin/runsignal.js --help",
|
|
41
|
+
"test:smoke": "node test/e2e-smoke.mjs",
|
|
42
|
+
"pack:check": "npm pack --dry-run",
|
|
43
|
+
"prepublishOnly": "npm run test:smoke"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"commander": "^14.0.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/api-client.js
CHANGED
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
function normalizeEventResponse(payload) {
|
|
2
|
-
if (!payload || typeof payload !== 'object') return { eventId: null, decision: 'pending' };
|
|
3
|
-
|
|
4
|
-
if (payload.data && typeof payload.data === 'object') {
|
|
5
|
-
return {
|
|
6
|
-
eventId: payload.data.event_id || null,
|
|
7
|
-
decision: payload.data.decision || 'pending'
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
eventId: payload.event_id || null,
|
|
13
|
-
decision: payload.decision || 'pending'
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function sendEvent({ apiFetch, baseUrl, machineApiKey, event }) {
|
|
18
|
-
const { ok, status, payload } = await apiFetch(baseUrl, '/api/events', {
|
|
19
|
-
method: 'POST',
|
|
20
|
-
headers: { authorization: `Bearer ${machineApiKey}` },
|
|
21
|
-
body: JSON.stringify(event)
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
if (!ok) {
|
|
25
|
-
const errorMessage = payload?.error?.message || payload?.error || `http_${status}`;
|
|
26
|
-
throw new Error(`send_event_failed:${errorMessage}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return normalizeEventResponse(payload);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function getEventDecision({ apiFetch, baseUrl, machineApiKey, eventId }) {
|
|
33
|
-
const response = await apiFetch(
|
|
34
|
-
baseUrl,
|
|
35
|
-
`/api/cli/events/${encodeURIComponent(eventId)}/decision`,
|
|
36
|
-
{
|
|
37
|
-
method: 'GET',
|
|
38
|
-
headers: { authorization: `Bearer ${machineApiKey}` }
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`status_failed:${response.payload?.error || response.status}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return response.payload?.decision || 'pending';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function getEventAnswer({ apiFetch, baseUrl, machineApiKey, eventId }) {
|
|
50
|
-
const response = await apiFetch(
|
|
51
|
-
baseUrl,
|
|
52
|
-
`/api/cli/events/${encodeURIComponent(eventId)}/answer`,
|
|
53
|
-
{
|
|
54
|
-
method: 'GET',
|
|
55
|
-
headers: { authorization: `Bearer ${machineApiKey}` }
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (!response.ok) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
return response.payload?.answer ?? null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function pollDecision({
|
|
66
|
-
apiFetch,
|
|
67
|
-
baseUrl,
|
|
68
|
-
machineApiKey,
|
|
69
|
-
eventId,
|
|
70
|
-
sleep,
|
|
71
|
-
timeoutMs = 5 * 60 * 1000
|
|
72
|
-
}) {
|
|
73
|
-
const startedAt = Date.now();
|
|
74
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
75
|
-
const decision = await getEventDecision({ apiFetch, baseUrl, machineApiKey, eventId }).catch(
|
|
76
|
-
() => 'pending'
|
|
77
|
-
);
|
|
78
|
-
if (decision !== 'pending') {
|
|
79
|
-
return decision;
|
|
80
|
-
}
|
|
81
|
-
await sleep(2000);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return 'pending';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export async function pollAnswer({
|
|
88
|
-
apiFetch,
|
|
89
|
-
baseUrl,
|
|
90
|
-
machineApiKey,
|
|
91
|
-
eventId,
|
|
92
|
-
sleep,
|
|
93
|
-
timeoutMs = 5 * 60 * 1000
|
|
94
|
-
}) {
|
|
95
|
-
const startedAt = Date.now();
|
|
96
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
97
|
-
const answer = await getEventAnswer({ apiFetch, baseUrl, machineApiKey, eventId }).catch(
|
|
98
|
-
() => null
|
|
99
|
-
);
|
|
100
|
-
if (typeof answer === 'string' && answer.trim()) {
|
|
101
|
-
return answer.trim();
|
|
102
|
-
}
|
|
103
|
-
await sleep(2000);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
1
|
+
function normalizeEventResponse(payload) {
|
|
2
|
+
if (!payload || typeof payload !== 'object') return { eventId: null, decision: 'pending' };
|
|
3
|
+
|
|
4
|
+
if (payload.data && typeof payload.data === 'object') {
|
|
5
|
+
return {
|
|
6
|
+
eventId: payload.data.event_id || null,
|
|
7
|
+
decision: payload.data.decision || 'pending'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
eventId: payload.event_id || null,
|
|
13
|
+
decision: payload.decision || 'pending'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function sendEvent({ apiFetch, baseUrl, machineApiKey, event }) {
|
|
18
|
+
const { ok, status, payload } = await apiFetch(baseUrl, '/api/events', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { authorization: `Bearer ${machineApiKey}` },
|
|
21
|
+
body: JSON.stringify(event)
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!ok) {
|
|
25
|
+
const errorMessage = payload?.error?.message || payload?.error || `http_${status}`;
|
|
26
|
+
throw new Error(`send_event_failed:${errorMessage}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return normalizeEventResponse(payload);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getEventDecision({ apiFetch, baseUrl, machineApiKey, eventId }) {
|
|
33
|
+
const response = await apiFetch(
|
|
34
|
+
baseUrl,
|
|
35
|
+
`/api/cli/events/${encodeURIComponent(eventId)}/decision`,
|
|
36
|
+
{
|
|
37
|
+
method: 'GET',
|
|
38
|
+
headers: { authorization: `Bearer ${machineApiKey}` }
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`status_failed:${response.payload?.error || response.status}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return response.payload?.decision || 'pending';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function getEventAnswer({ apiFetch, baseUrl, machineApiKey, eventId }) {
|
|
50
|
+
const response = await apiFetch(
|
|
51
|
+
baseUrl,
|
|
52
|
+
`/api/cli/events/${encodeURIComponent(eventId)}/answer`,
|
|
53
|
+
{
|
|
54
|
+
method: 'GET',
|
|
55
|
+
headers: { authorization: `Bearer ${machineApiKey}` }
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return response.payload?.answer ?? null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function pollDecision({
|
|
66
|
+
apiFetch,
|
|
67
|
+
baseUrl,
|
|
68
|
+
machineApiKey,
|
|
69
|
+
eventId,
|
|
70
|
+
sleep,
|
|
71
|
+
timeoutMs = 5 * 60 * 1000
|
|
72
|
+
}) {
|
|
73
|
+
const startedAt = Date.now();
|
|
74
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
75
|
+
const decision = await getEventDecision({ apiFetch, baseUrl, machineApiKey, eventId }).catch(
|
|
76
|
+
() => 'pending'
|
|
77
|
+
);
|
|
78
|
+
if (decision !== 'pending') {
|
|
79
|
+
return decision;
|
|
80
|
+
}
|
|
81
|
+
await sleep(2000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return 'pending';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function pollAnswer({
|
|
88
|
+
apiFetch,
|
|
89
|
+
baseUrl,
|
|
90
|
+
machineApiKey,
|
|
91
|
+
eventId,
|
|
92
|
+
sleep,
|
|
93
|
+
timeoutMs = 5 * 60 * 1000
|
|
94
|
+
}) {
|
|
95
|
+
const startedAt = Date.now();
|
|
96
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
97
|
+
const answer = await getEventAnswer({ apiFetch, baseUrl, machineApiKey, eventId }).catch(
|
|
98
|
+
() => null
|
|
99
|
+
);
|
|
100
|
+
if (typeof answer === 'string' && answer.trim()) {
|
|
101
|
+
return answer.trim();
|
|
102
|
+
}
|
|
103
|
+
await sleep(2000);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|