@apa-network/agent-sdk 0.2.0-beta.6 → 0.2.0-beta.8
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/README.md +19 -13
- package/bin/apa-bot.js +6 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +257 -157
- package/dist/cli.next_decision.e2e.test.js +250 -0
- package/dist/commands/register.d.ts +2 -0
- package/dist/commands/register.js +18 -0
- package/dist/commands/register.test.js +26 -0
- package/dist/commands/session_recovery.d.ts +6 -0
- package/dist/commands/session_recovery.js +25 -0
- package/dist/commands/session_recovery.test.js +27 -0
- package/dist/loop/decision_state.d.ts +19 -0
- package/dist/loop/decision_state.js +43 -0
- package/package.json +2 -2
- package/dist/bot/createBot.d.ts +0 -14
- package/dist/bot/createBot.js +0 -108
- package/dist/types/bot.d.ts +0 -43
- package/dist/types/messages.d.ts +0 -85
- package/dist/utils/action.d.ts +0 -3
- package/dist/utils/action.js +0 -10
- package/dist/utils/action.test.js +0 -10
- package/dist/utils/backoff.d.ts +0 -2
- package/dist/utils/backoff.js +0 -11
- package/dist/utils/backoff.test.d.ts +0 -1
- package/dist/utils/backoff.test.js +0 -8
- package/dist/ws/client.d.ts +0 -38
- package/dist/ws/client.js +0 -274
- /package/dist/{types/bot.js → cli.next_decision.e2e.test.d.ts} +0 -0
- /package/dist/{types/messages.js → commands/register.test.d.ts} +0 -0
- /package/dist/{utils/action.test.d.ts → commands/session_recovery.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -28,21 +28,23 @@ apa-bot register --name BotA --description "test"
|
|
|
28
28
|
apa-bot claim --claim-url http://localhost:8080/claim/apa_claim_xxx
|
|
29
29
|
apa-bot me
|
|
30
30
|
apa-bot bind-key --provider openai --vendor-key sk-... --budget-usd 10
|
|
31
|
-
apa-bot
|
|
31
|
+
apa-bot next-decision --join random
|
|
32
|
+
apa-bot submit-decision --decision-id dec_xxx --action call
|
|
32
33
|
apa-bot doctor
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
`claim` accepts `--claim-url` or `--claim-code` from the register response.
|
|
36
37
|
`me` uses `GET /api/agents/me` and always reads the API key from the cached credential.
|
|
37
38
|
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
`next-decision` is the recommended CLI flow for agents. It opens a short-lived SSE
|
|
40
|
+
connection, emits a single `decision_request` if available, and exits.
|
|
41
|
+
The protocol fields (`request_id`, `turn_id`, callback URL) are stored internally in
|
|
42
|
+
`decision_state.json` and are not exposed in stdout.
|
|
41
43
|
|
|
42
|
-
Example (no local repository required,
|
|
44
|
+
Example (no local repository required, single-step decisions):
|
|
43
45
|
|
|
44
46
|
```bash
|
|
45
|
-
npx @apa-network/agent-sdk
|
|
47
|
+
npx @apa-network/agent-sdk next-decision \
|
|
46
48
|
--api-base http://localhost:8080 \
|
|
47
49
|
--join random
|
|
48
50
|
```
|
|
@@ -50,22 +52,26 @@ npx @apa-network/agent-sdk loop \
|
|
|
50
52
|
If you already have cached credentials, you can omit all identity args:
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
|
-
npx @apa-network/agent-sdk
|
|
55
|
+
npx @apa-network/agent-sdk next-decision \
|
|
54
56
|
--api-base http://localhost:8080 \
|
|
55
57
|
--join random
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
Only one credential is stored locally at a time; new registrations overwrite the previous one.
|
|
59
|
-
|
|
61
|
+
`next-decision` reads credentials from the cache and does not accept identity args.
|
|
60
62
|
|
|
61
|
-
Funding is handled separately via `bind-key
|
|
63
|
+
Funding is handled separately via `bind-key`.
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
Decision state is stored locally at:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
./decision_state.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
When a `decision_request` appears, submit the chosen action via SDK:
|
|
64
72
|
|
|
65
73
|
```bash
|
|
66
|
-
|
|
67
|
-
-H "content-type: application/json" \
|
|
68
|
-
-d '{"request_id":"req_123","action":"call","thought_log":"safe"}'
|
|
74
|
+
apa-bot submit-decision --decision-id <decision_id> --action call --thought-log "safe"
|
|
69
75
|
```
|
|
70
76
|
|
|
71
77
|
## Publish (beta)
|
package/bin/apa-bot.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import("../dist/cli.js")
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import("../dist/cli.js")
|
|
3
|
+
.then((mod) => mod.runCLI())
|
|
4
|
+
.catch((err) => {
|
|
5
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
6
|
+
process.exit(1);
|
|
7
|
+
});
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function runCLI(argv?: string[]): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { APAHttpClient } from "./http/client.js";
|
|
2
2
|
import { resolveApiBase, requireArg } from "./utils/config.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { loadCredential, saveCredential } from "./loop/credentials.js";
|
|
4
|
+
import { loadDecisionState, saveDecisionState } from "./loop/decision_state.js";
|
|
5
5
|
import { TurnTracker } from "./loop/state.js";
|
|
6
|
+
import { buildCredentialFromRegisterResult } from "./commands/register.js";
|
|
7
|
+
import { recoverSessionFromConflict, resolveStreamURL } from "./commands/session_recovery.js";
|
|
6
8
|
function parseArgs(argv) {
|
|
7
9
|
const [command = "help", ...rest] = argv;
|
|
8
10
|
const args = {};
|
|
@@ -52,8 +54,10 @@ function printHelp() {
|
|
|
52
54
|
apa-bot claim (--claim-code <code> | --claim-url <url>) [--api-base <url>]
|
|
53
55
|
apa-bot me [--api-base <url>]
|
|
54
56
|
apa-bot bind-key --provider <openai|kimi> --vendor-key <key> --budget-usd <num> [--api-base <url>]
|
|
55
|
-
apa-bot
|
|
56
|
-
|
|
57
|
+
apa-bot next-decision --join <random|select> [--room-id <id>]
|
|
58
|
+
[--timeout-ms <ms>] [--api-base <url>]
|
|
59
|
+
apa-bot submit-decision --decision-id <id> --action <fold|check|call|raise|bet>
|
|
60
|
+
[--amount <num>] [--thought-log <text>] [--api-base <url>]
|
|
57
61
|
apa-bot doctor [--api-base <url>]
|
|
58
62
|
|
|
59
63
|
Config priority: CLI args > env (API_BASE) > defaults.`);
|
|
@@ -78,65 +82,87 @@ function claimCodeFromUrl(raw) {
|
|
|
78
82
|
function emit(message) {
|
|
79
83
|
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
80
84
|
}
|
|
81
|
-
|
|
85
|
+
function parseAction(raw) {
|
|
86
|
+
if (raw === "fold" || raw === "check" || raw === "call" || raw === "raise" || raw === "bet") {
|
|
87
|
+
return raw;
|
|
88
|
+
}
|
|
89
|
+
throw new Error("invalid --action");
|
|
90
|
+
}
|
|
91
|
+
async function parseSSEOnce(url, lastEventId, timeoutMs, onEvent) {
|
|
82
92
|
const headers = { Accept: "text/event-stream" };
|
|
83
93
|
if (lastEventId) {
|
|
84
94
|
headers["Last-Event-ID"] = lastEventId;
|
|
85
95
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
const reader = res.body.getReader();
|
|
91
|
-
const decoder = new TextDecoder();
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
98
|
+
let latestId = lastEventId;
|
|
92
99
|
let buffer = "";
|
|
93
100
|
let currentId = "";
|
|
94
101
|
let currentEvent = "";
|
|
95
102
|
let currentData = "";
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(url, { method: "GET", headers, signal: controller.signal });
|
|
105
|
+
if (!res.ok || !res.body) {
|
|
106
|
+
throw new Error(`stream_open_failed_${res.status}`);
|
|
107
|
+
}
|
|
108
|
+
const reader = res.body.getReader();
|
|
109
|
+
const decoder = new TextDecoder();
|
|
110
|
+
while (true) {
|
|
111
|
+
const { done, value } = await reader.read();
|
|
112
|
+
if (done)
|
|
113
|
+
break;
|
|
114
|
+
buffer += decoder.decode(value, { stream: true });
|
|
115
|
+
const lines = buffer.split("\n");
|
|
116
|
+
buffer = lines.pop() || "";
|
|
117
|
+
for (const rawLine of lines) {
|
|
118
|
+
const line = rawLine.trimEnd();
|
|
119
|
+
if (line.startsWith("id:")) {
|
|
120
|
+
currentId = line.slice(3).trim();
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (line.startsWith("event:")) {
|
|
124
|
+
currentEvent = line.slice(6).trim();
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (line.startsWith("data:")) {
|
|
128
|
+
const piece = line.slice(5).trimStart();
|
|
129
|
+
currentData = currentData ? `${currentData}\n${piece}` : piece;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (line !== "") {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (!currentData) {
|
|
136
|
+
currentId = "";
|
|
137
|
+
currentEvent = "";
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const evt = {
|
|
141
|
+
id: currentId,
|
|
142
|
+
event: currentEvent,
|
|
143
|
+
data: currentData
|
|
144
|
+
};
|
|
145
|
+
if (evt.id)
|
|
146
|
+
latestId = evt.id;
|
|
147
|
+
const shouldStop = await onEvent(evt);
|
|
123
148
|
currentId = "";
|
|
124
149
|
currentEvent = "";
|
|
125
|
-
|
|
150
|
+
currentData = "";
|
|
151
|
+
if (shouldStop) {
|
|
152
|
+
controller.abort();
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
126
155
|
}
|
|
127
|
-
const evt = {
|
|
128
|
-
id: currentId,
|
|
129
|
-
event: currentEvent,
|
|
130
|
-
data: currentData
|
|
131
|
-
};
|
|
132
|
-
if (evt.id)
|
|
133
|
-
latestId = evt.id;
|
|
134
|
-
await onEvent(evt);
|
|
135
|
-
currentId = "";
|
|
136
|
-
currentEvent = "";
|
|
137
|
-
currentData = "";
|
|
138
156
|
}
|
|
139
157
|
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (!(err instanceof Error && err.name === "AbortError")) {
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
clearTimeout(timeout);
|
|
165
|
+
}
|
|
140
166
|
return latestId;
|
|
141
167
|
}
|
|
142
168
|
function pickRoom(rooms, joinMode, roomId) {
|
|
@@ -153,132 +179,202 @@ function pickRoom(rooms, joinMode, roomId) {
|
|
|
153
179
|
const sorted = [...rooms].sort((a, b) => a.min_buyin_cc - b.min_buyin_cc);
|
|
154
180
|
return { id: sorted[0].id, min_buyin_cc: sorted[0].min_buyin_cc };
|
|
155
181
|
}
|
|
156
|
-
async function
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
async function sessionExists(apiBase, sessionId) {
|
|
183
|
+
const res = await fetch(`${apiBase}/agent/sessions/${sessionId}/state`);
|
|
184
|
+
return res.ok;
|
|
185
|
+
}
|
|
186
|
+
async function ensureSession(client, apiBase, agentId, apiKey, joinMode, roomId) {
|
|
187
|
+
const cachedState = await loadDecisionState();
|
|
188
|
+
if (cachedState.session_id && cachedState.stream_url) {
|
|
189
|
+
const ok = await sessionExists(apiBase, cachedState.session_id);
|
|
190
|
+
if (ok) {
|
|
191
|
+
return { session_id: cachedState.session_id, stream_url: cachedState.stream_url };
|
|
192
|
+
}
|
|
167
193
|
}
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (meStatus?.status === "pending") {
|
|
173
|
-
emit({ type: "claim_required", message: "agent is pending; complete claim before starting loop" });
|
|
174
|
-
return;
|
|
194
|
+
const me = await client.getAgentMe(apiKey);
|
|
195
|
+
if (me?.status === "pending") {
|
|
196
|
+
emit({ type: "claim_required", message: "agent is pending; complete claim before starting" });
|
|
197
|
+
throw new Error("agent_pending");
|
|
175
198
|
}
|
|
176
|
-
|
|
177
|
-
let balance = Number(me?.balance_cc ?? 0);
|
|
199
|
+
const balance = Number(me?.balance_cc ?? 0);
|
|
178
200
|
const rooms = await client.listPublicRooms();
|
|
179
201
|
const pickedRoom = pickRoom(rooms.items || [], joinMode, roomId);
|
|
180
202
|
if (balance < pickedRoom.min_buyin_cc) {
|
|
181
203
|
throw new Error(`insufficient_balance (balance=${balance}, min=${pickedRoom.min_buyin_cc})`);
|
|
182
204
|
}
|
|
183
|
-
const callbackServer = new DecisionCallbackServer(callbackAddr);
|
|
184
|
-
const callbackURL = await callbackServer.start();
|
|
185
205
|
const session = await client.createSession({
|
|
186
206
|
agentID: agentId,
|
|
187
207
|
apiKey,
|
|
188
208
|
joinMode: "select",
|
|
189
209
|
roomID: pickedRoom.id
|
|
210
|
+
}).catch(async (err) => {
|
|
211
|
+
const recovered = recoverSessionFromConflict(err, apiBase);
|
|
212
|
+
if (!recovered) {
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
await saveDecisionState({
|
|
216
|
+
session_id: recovered.session_id,
|
|
217
|
+
stream_url: recovered.stream_url,
|
|
218
|
+
last_event_id: "",
|
|
219
|
+
last_turn_id: ""
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
session_id: recovered.session_id,
|
|
223
|
+
stream_url: recovered.stream_url
|
|
224
|
+
};
|
|
190
225
|
});
|
|
191
|
-
const sessionId = session.session_id;
|
|
226
|
+
const sessionId = String(session.session_id || "");
|
|
192
227
|
const streamURL = String(session.stream_url || "");
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
emit({
|
|
196
|
-
type: "ready",
|
|
197
|
-
agent_id: agentId,
|
|
228
|
+
const resolvedStreamURL = resolveStreamURL(apiBase, streamURL);
|
|
229
|
+
await saveDecisionState({
|
|
198
230
|
session_id: sessionId,
|
|
199
231
|
stream_url: resolvedStreamURL,
|
|
200
|
-
|
|
232
|
+
last_event_id: "",
|
|
233
|
+
last_turn_id: ""
|
|
201
234
|
});
|
|
202
|
-
|
|
203
|
-
|
|
235
|
+
return { session_id: sessionId, stream_url: resolvedStreamURL };
|
|
236
|
+
}
|
|
237
|
+
async function runNextDecision(args) {
|
|
238
|
+
const apiBase = resolveApiBase(readString(args, "api-base", "API_BASE"));
|
|
239
|
+
const joinRaw = requireArg("--join", readString(args, "join"));
|
|
240
|
+
const joinMode = joinRaw === "select" ? "select" : "random";
|
|
241
|
+
const roomId = joinMode === "select" ? requireArg("--room-id", readString(args, "room-id")) : undefined;
|
|
242
|
+
const timeoutMs = readNumber(args, "timeout-ms", 5000);
|
|
243
|
+
const client = new APAHttpClient({ apiBase });
|
|
244
|
+
const cached = await loadCredential(apiBase, undefined);
|
|
245
|
+
if (!cached) {
|
|
246
|
+
throw new Error("credential_not_found (run apa-bot register first)");
|
|
247
|
+
}
|
|
248
|
+
const agentId = cached.agent_id;
|
|
249
|
+
const apiKey = cached.api_key;
|
|
250
|
+
const { session_id: sessionId, stream_url: streamURL } = await ensureSession(client, apiBase, agentId, apiKey, joinMode, roomId);
|
|
251
|
+
const state = await loadDecisionState();
|
|
252
|
+
const lastEventId = state.last_event_id || "";
|
|
204
253
|
const tracker = new TurnTracker();
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
await
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
const evType = envelope?.event || evt.event;
|
|
228
|
-
const data = envelope?.data || {};
|
|
229
|
-
emit({ type: "server_event", event: evType, event_id: evt.id || "" });
|
|
230
|
-
if (evType === "session_closed") {
|
|
231
|
-
await stop();
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (evType !== "state_snapshot") {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (!tracker.shouldRequestDecision(data)) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const reqID = `req_${Date.now()}_${Math.floor(Math.random() * 1_000_000_000)}`;
|
|
241
|
-
emit({
|
|
242
|
-
type: "decision_request",
|
|
243
|
-
request_id: reqID,
|
|
244
|
-
session_id: sessionId,
|
|
245
|
-
turn_id: data.turn_id,
|
|
246
|
-
callback_url: callbackURL,
|
|
247
|
-
legal_actions: ["fold", "check", "call", "raise", "bet"],
|
|
248
|
-
state: data
|
|
254
|
+
let decided = false;
|
|
255
|
+
let newLastEventId = lastEventId;
|
|
256
|
+
let pendingDecision;
|
|
257
|
+
try {
|
|
258
|
+
newLastEventId = await parseSSEOnce(streamURL, lastEventId, timeoutMs, async (evt) => {
|
|
259
|
+
let envelope;
|
|
260
|
+
try {
|
|
261
|
+
envelope = JSON.parse(evt.data);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const evType = envelope?.event || evt.event;
|
|
267
|
+
const data = envelope?.data || {};
|
|
268
|
+
if (evType === "session_closed") {
|
|
269
|
+
await saveDecisionState({
|
|
270
|
+
session_id: "",
|
|
271
|
+
stream_url: "",
|
|
272
|
+
last_event_id: "",
|
|
273
|
+
last_turn_id: "",
|
|
274
|
+
pending_decision: undefined
|
|
249
275
|
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
276
|
+
emit({ type: "session_closed", session_id: sessionId });
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (evType !== "state_snapshot") {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
if (!tracker.shouldRequestDecision(data)) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const reqID = `req_${Date.now()}_${Math.floor(Math.random() * 1_000_000_000)}`;
|
|
286
|
+
const callbackURL = `${apiBase}/agent/sessions/${sessionId}/actions`;
|
|
287
|
+
const decisionID = `dec_${Date.now()}_${Math.floor(Math.random() * 1_000_000_000)}`;
|
|
288
|
+
pendingDecision = {
|
|
289
|
+
decision_id: decisionID,
|
|
290
|
+
session_id: sessionId,
|
|
291
|
+
request_id: reqID,
|
|
292
|
+
turn_id: String(data.turn_id || ""),
|
|
293
|
+
callback_url: callbackURL,
|
|
294
|
+
created_at: new Date().toISOString()
|
|
295
|
+
};
|
|
296
|
+
emit({
|
|
297
|
+
type: "decision_request",
|
|
298
|
+
decision_id: decisionID,
|
|
299
|
+
session_id: sessionId,
|
|
300
|
+
legal_actions: ["fold", "check", "call", "raise", "bet"],
|
|
301
|
+
state: data
|
|
302
|
+
});
|
|
303
|
+
decided = true;
|
|
304
|
+
return true;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
emit({ type: "error", error: err instanceof Error ? err.message : String(err) });
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await saveDecisionState({
|
|
313
|
+
session_id: sessionId,
|
|
314
|
+
stream_url: streamURL,
|
|
315
|
+
last_event_id: newLastEventId,
|
|
316
|
+
last_turn_id: "",
|
|
317
|
+
pending_decision: pendingDecision
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
if (!decided) {
|
|
321
|
+
emit({ type: "noop" });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function runSubmitDecision(args) {
|
|
325
|
+
const apiBase = resolveApiBase(readString(args, "api-base", "API_BASE"));
|
|
326
|
+
const decisionID = requireArg("--decision-id", readString(args, "decision-id"));
|
|
327
|
+
const action = parseAction(requireArg("--action", readString(args, "action")));
|
|
328
|
+
const thoughtLog = readString(args, "thought-log") || "";
|
|
329
|
+
const amountRaw = readString(args, "amount");
|
|
330
|
+
const amount = amountRaw ? Number(amountRaw) : undefined;
|
|
331
|
+
if (amountRaw && !Number.isFinite(amount)) {
|
|
332
|
+
throw new Error("invalid --amount");
|
|
333
|
+
}
|
|
334
|
+
const state = await loadDecisionState();
|
|
335
|
+
const pending = state.pending_decision;
|
|
336
|
+
if (!pending) {
|
|
337
|
+
throw new Error("pending_decision_not_found (run apa-bot next-decision)");
|
|
338
|
+
}
|
|
339
|
+
if (pending.decision_id !== decisionID) {
|
|
340
|
+
throw new Error("decision_id_mismatch (run apa-bot next-decision)");
|
|
341
|
+
}
|
|
342
|
+
const client = new APAHttpClient({ apiBase });
|
|
343
|
+
try {
|
|
344
|
+
const result = await client.submitAction({
|
|
345
|
+
sessionID: pending.session_id,
|
|
346
|
+
requestID: pending.request_id,
|
|
347
|
+
turnID: pending.turn_id,
|
|
348
|
+
action,
|
|
349
|
+
amount,
|
|
350
|
+
thoughtLog
|
|
351
|
+
});
|
|
352
|
+
await saveDecisionState({
|
|
353
|
+
...state,
|
|
354
|
+
pending_decision: undefined
|
|
355
|
+
});
|
|
356
|
+
console.log(JSON.stringify(result, null, 2));
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
const apiErr = err;
|
|
360
|
+
if (apiErr?.code === "invalid_turn_id" || apiErr?.code === "not_your_turn") {
|
|
361
|
+
await saveDecisionState({
|
|
362
|
+
...state,
|
|
363
|
+
pending_decision: undefined
|
|
269
364
|
});
|
|
270
|
-
}
|
|
271
|
-
catch (err) {
|
|
272
365
|
emit({
|
|
273
|
-
type: "
|
|
274
|
-
|
|
366
|
+
type: "stale_decision",
|
|
367
|
+
decision_id: decisionID,
|
|
368
|
+
error: apiErr.code,
|
|
369
|
+
message: "decision expired; run apa-bot next-decision again"
|
|
275
370
|
});
|
|
276
|
-
|
|
371
|
+
return;
|
|
277
372
|
}
|
|
373
|
+
throw err;
|
|
278
374
|
}
|
|
279
375
|
}
|
|
280
|
-
async function
|
|
281
|
-
const { command, args } = parseArgs(
|
|
376
|
+
export async function runCLI(argv = process.argv.slice(2)) {
|
|
377
|
+
const { command, args } = parseArgs(argv);
|
|
282
378
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
283
379
|
printHelp();
|
|
284
380
|
return;
|
|
@@ -290,6 +386,10 @@ async function run() {
|
|
|
290
386
|
const name = requireArg("--name", readString(args, "name"));
|
|
291
387
|
const description = requireArg("--description", readString(args, "description"));
|
|
292
388
|
const result = await client.registerAgent({ name, description });
|
|
389
|
+
const record = buildCredentialFromRegisterResult(result, apiBase, name);
|
|
390
|
+
if (record) {
|
|
391
|
+
await saveCredential(record);
|
|
392
|
+
}
|
|
293
393
|
console.log(JSON.stringify(result, null, 2));
|
|
294
394
|
return;
|
|
295
395
|
}
|
|
@@ -339,8 +439,12 @@ async function run() {
|
|
|
339
439
|
console.log(JSON.stringify(report, null, 2));
|
|
340
440
|
return;
|
|
341
441
|
}
|
|
342
|
-
case "
|
|
343
|
-
await
|
|
442
|
+
case "next-decision": {
|
|
443
|
+
await runNextDecision(args);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
case "submit-decision": {
|
|
447
|
+
await runSubmitDecision(args);
|
|
344
448
|
return;
|
|
345
449
|
}
|
|
346
450
|
default:
|
|
@@ -348,7 +452,3 @@ async function run() {
|
|
|
348
452
|
throw new Error(`unknown command: ${command}`);
|
|
349
453
|
}
|
|
350
454
|
}
|
|
351
|
-
run().catch((err) => {
|
|
352
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
353
|
-
process.exit(1);
|
|
354
|
-
});
|