@anna-ai/cli 0.1.12 → 0.1.16
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/dist/agent-DUmINbo4.js +372 -0
- package/dist/{apps-CDe6Fjq2.js → apps-Bt9CT5Sl.js} +1 -1
- package/dist/bridge-AJilXBw2.js +3 -0
- package/dist/{bridge-BQUo6ehX.js → bridge-nqQ3-j-t.js} +1 -1
- package/dist/cli.js +78 -9
- package/dist/dev-BRlFgo2I.js +3 -0
- package/dist/{dev-DoY58pBM.js → dev-C6v5yRV2.js} +4 -4
- package/dist/dev-account-DCyjamBa.js +44 -0
- package/dist/dev-app-cache-DAHcq46m.js +175 -0
- package/dist/dev-app-cache-DGF2Kuzd.js +4 -0
- package/dist/{doctor-DP2UB10l.js → doctor-C8MWfLt8.js} +1 -1
- package/dist/executa-dev-4FZ7AJHR.js +259 -0
- package/dist/executa-init-COEmKDOE.js +68 -0
- package/dist/executa-register-66WKIwQQ.js +47 -0
- package/dist/host_upload-C_pGOS6p.js +136 -0
- package/dist/image-bwolX7pa.js +131 -0
- package/dist/mascot-wlYTJqMs.js +218 -0
- package/dist/runner-DmGLdat0.js +322 -0
- package/dist/sampling-CJUDG-mf.js +155 -0
- package/dist/storage-EQJA_0UW.js +316 -0
- package/package.json +1 -1
- package/templates/executa/go/README.md +10 -0
- package/templates/executa/go/executa.json +4 -0
- package/templates/executa/go/go.mod +3 -0
- package/templates/executa/go/main.go +148 -0
- package/templates/executa/node/README.md +12 -0
- package/templates/executa/node/executa.json +4 -0
- package/templates/executa/node/package.json +12 -0
- package/templates/executa/node/plugin.mjs +126 -0
- package/templates/executa/node/sampling-fixture.jsonl +1 -0
- package/templates/executa/python/README.md +23 -0
- package/templates/executa/python/__SLUG_PY___plugin.py +146 -0
- package/templates/executa/python/executa.json +4 -0
- package/templates/executa/python/pyproject.toml +15 -0
- package/templates/executa/python/sampling-fixture.jsonl +4 -0
- package/templates/minimal/bundle/app.js +2 -0
- package/templates/minimal/bundle/index.html +1 -2
- package/dist/bridge-BEHyfpPI.js +0 -3
- package/dist/dev-app-cache-BMfOlTHd.js +0 -93
- package/dist/dev-app-cache-cXvO2XwQ.js +0 -4
- /package/dist/{credentials-CIOYq2Lm.js → credentials-DDqx6XMQ.js} +0 -0
- /package/dist/{fixture-BEu4LXLG.js → fixture-CATHyLLI.js} +0 -0
- /package/dist/{login-dl1Zfny8.js → login-CsIVbrmf.js} +0 -0
- /package/dist/{logout-DablvlFs.js → logout-gfmKQxMj.js} +0 -0
- /package/dist/{server-NXmiWJjX.js → server-D8R6ppOp.js} +0 -0
- /package/dist/{whoami-giXOY415.js → whoami-BS5wy-Nh.js} +0 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { canonicalHost } from "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import { hostOf, mintAppSession, requireAccount, withCode } from "./dev-account-DCyjamBa.js";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
//#region src/executa/agent.ts
|
|
7
|
+
/** JSON-RPC error codes from matrix/src/executa/protocol.py. */
|
|
8
|
+
const AGENT_ERR_NOT_GRANTED = -32041;
|
|
9
|
+
const AGENT_ERR_SESSION_NOT_FOUND = -32042;
|
|
10
|
+
const AGENT_ERR_INVALID_REQUEST = -32043;
|
|
11
|
+
const AGENT_ERR_PROVIDER_ERROR = -32046;
|
|
12
|
+
const AGENT_ERR_RUN_TOO_LARGE = -32047;
|
|
13
|
+
const MAX_FRAMES_PER_RUN = 4096;
|
|
14
|
+
var AgentBridge = class {
|
|
15
|
+
mocks = [];
|
|
16
|
+
/** uuid → minted session token (one entry per `session.create`). */
|
|
17
|
+
sessions = new Map();
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.opts = opts;
|
|
20
|
+
if (opts.mode === "mock" && opts.mockFile) {
|
|
21
|
+
const path = resolve(opts.mockFile);
|
|
22
|
+
if (existsSync(path)) for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
|
|
23
|
+
if (!line.trim() || line.startsWith("#")) continue;
|
|
24
|
+
try {
|
|
25
|
+
this.mocks.push(JSON.parse(line));
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async call(method, params) {
|
|
31
|
+
if (this.opts.mode === "off") throw withCode(new Error("agent reverse-RPC disabled — rerun without `--no-agent`, or pass `--mock-agent <fixture>` / `--agent <real>`"), AGENT_ERR_NOT_GRANTED);
|
|
32
|
+
if (this.opts.mode === "mock") return this.mockCall(method, params);
|
|
33
|
+
return this.realCall(method, params);
|
|
34
|
+
}
|
|
35
|
+
mockCall(method, params) {
|
|
36
|
+
const shortName = method.startsWith("agent/") ? method.slice(6) : method;
|
|
37
|
+
const content = collectContent(params);
|
|
38
|
+
const matched = this.mocks.find((m) => m.ns === "agent" && m.method === shortName && (!m.match?.contentIncludes || content.includes(m.match.contentIncludes))) ?? this.mocks.find((m) => m.ns === "agent" && m.method === shortName);
|
|
39
|
+
if (matched && matched.result !== void 0) return matched.result;
|
|
40
|
+
if (shortName === "session.create") return {
|
|
41
|
+
app_session_uuid: `aps_mock_${Math.random().toString(16).slice(2, 10)}`,
|
|
42
|
+
expires_in: 600,
|
|
43
|
+
agent_submode: "auto",
|
|
44
|
+
granted_tools: []
|
|
45
|
+
};
|
|
46
|
+
if (shortName === "session.run") {
|
|
47
|
+
const text = String(params.content ?? "");
|
|
48
|
+
return {
|
|
49
|
+
run_id: "mock_run",
|
|
50
|
+
stream_id: "strm_mock",
|
|
51
|
+
frames: [{
|
|
52
|
+
event: "final",
|
|
53
|
+
text: `(mock) echoing: ${text}`,
|
|
54
|
+
seq: 0,
|
|
55
|
+
stream_id: "strm_mock"
|
|
56
|
+
}],
|
|
57
|
+
final: {
|
|
58
|
+
event: "final",
|
|
59
|
+
text: `(mock) echoing: ${text}`
|
|
60
|
+
},
|
|
61
|
+
frame_count: 1
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (shortName === "session.cancel") return { cancelled: true };
|
|
65
|
+
if (shortName === "session.history") return {
|
|
66
|
+
messages: [],
|
|
67
|
+
cursor: null
|
|
68
|
+
};
|
|
69
|
+
if (shortName === "session.delete") return { deleted: true };
|
|
70
|
+
if (shortName === "complete") return {
|
|
71
|
+
role: "assistant",
|
|
72
|
+
content: {
|
|
73
|
+
type: "text",
|
|
74
|
+
text: "(mock) no fixture matched"
|
|
75
|
+
},
|
|
76
|
+
model: "mock-model",
|
|
77
|
+
stopReason: "endTurn"
|
|
78
|
+
};
|
|
79
|
+
throw withCode(new Error(`unknown agent method: ${method}`), AGENT_ERR_INVALID_REQUEST);
|
|
80
|
+
}
|
|
81
|
+
requireAppSlug() {
|
|
82
|
+
if (!this.opts.appSlug) throw withCode(new Error("agent bridge has no app_slug — pass `--app-slug <slug>` so nexus can attribute the spend"), -32602);
|
|
83
|
+
return this.opts.appSlug;
|
|
84
|
+
}
|
|
85
|
+
async realCall(method, params) {
|
|
86
|
+
const shortName = method.startsWith("agent/") ? method.slice(6) : method;
|
|
87
|
+
switch (shortName) {
|
|
88
|
+
case "session.create": return this.sessionCreate(params);
|
|
89
|
+
case "session.run": return this.sessionRun(params);
|
|
90
|
+
case "session.cancel": return this.sessionCancel(params);
|
|
91
|
+
case "session.history": return this.sessionHistory(params);
|
|
92
|
+
case "session.delete": return this.sessionDelete(params);
|
|
93
|
+
case "complete": return this.complete(params);
|
|
94
|
+
default: throw withCode(new Error(`unknown agent method: ${method}`), AGENT_ERR_INVALID_REQUEST);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async sessionCreate(params) {
|
|
98
|
+
const acc = requireAccount(this.opts.account);
|
|
99
|
+
const submode = params.agent_submode ?? "auto";
|
|
100
|
+
if (submode !== "auto" && submode !== "fixed") throw withCode(new Error("agent_submode must be 'auto' or 'fixed'"), AGENT_ERR_INVALID_REQUEST);
|
|
101
|
+
const fixedClientId = params.fixed_client_id ?? params.fixedClientId ?? null;
|
|
102
|
+
if (submode === "fixed" && !fixedClientId) throw withCode(new Error("fixed_client_id required when agent_submode='fixed'"), AGENT_ERR_INVALID_REQUEST);
|
|
103
|
+
const minted = await mintAppSession(acc.host, {
|
|
104
|
+
pat: acc.pat,
|
|
105
|
+
kind: "agent",
|
|
106
|
+
app_slug: this.requireAppSlug(),
|
|
107
|
+
submode,
|
|
108
|
+
fixed_client_id: fixedClientId,
|
|
109
|
+
label: params.label ?? "anna-app executa dev",
|
|
110
|
+
quota_caps: params.quota_caps ?? null,
|
|
111
|
+
ttl_seconds: params.ttl_seconds ?? 600
|
|
112
|
+
});
|
|
113
|
+
this.sessions.set(minted.app_session_uuid, {
|
|
114
|
+
appSessionToken: minted.app_session_token,
|
|
115
|
+
appSessionUuid: minted.app_session_uuid,
|
|
116
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (minted.expires_in || 600) - 30,
|
|
117
|
+
submode
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
app_session_uuid: minted.app_session_uuid,
|
|
121
|
+
thread_id: minted.app_session_uuid,
|
|
122
|
+
agent_submode: submode,
|
|
123
|
+
fixed_client_id: fixedClientId,
|
|
124
|
+
expires_in: minted.expires_in,
|
|
125
|
+
granted_tools: []
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
getSession(params) {
|
|
129
|
+
const uuid = String(params.app_session_uuid ?? "");
|
|
130
|
+
if (!uuid.startsWith("aps_")) throw withCode(new Error("'app_session_uuid' must start with 'aps_'"), AGENT_ERR_INVALID_REQUEST);
|
|
131
|
+
const ms = this.sessions.get(uuid);
|
|
132
|
+
if (!ms) throw withCode(new Error(`app_session_uuid=${uuid} not in dev cache (expired or never created)`), AGENT_ERR_SESSION_NOT_FOUND);
|
|
133
|
+
if (ms.expiresAt < Math.floor(Date.now() / 1e3)) {
|
|
134
|
+
this.sessions.delete(uuid);
|
|
135
|
+
throw withCode(new Error(`app_session_uuid=${uuid} expired — call session.create again`), AGENT_ERR_SESSION_NOT_FOUND);
|
|
136
|
+
}
|
|
137
|
+
return ms;
|
|
138
|
+
}
|
|
139
|
+
async sessionRun(params) {
|
|
140
|
+
const acc = requireAccount(this.opts.account);
|
|
141
|
+
const ms = this.getSession(params);
|
|
142
|
+
const content = params.content;
|
|
143
|
+
if (typeof content !== "string" || !content.trim()) throw withCode(new Error("'content' must be a non-empty string"), AGENT_ERR_INVALID_REQUEST);
|
|
144
|
+
const runId = params.run_id ?? `run_${Math.random().toString(16).slice(2, 18)}`;
|
|
145
|
+
const streamId = `strm_${Math.random().toString(16).slice(2, 18)}`;
|
|
146
|
+
const body = {
|
|
147
|
+
app_session_uuid: ms.appSessionUuid,
|
|
148
|
+
content,
|
|
149
|
+
run_id: runId,
|
|
150
|
+
stream: true
|
|
151
|
+
};
|
|
152
|
+
if (params.attachments !== void 0) body.attachments = params.attachments;
|
|
153
|
+
if (params.recursion_limit !== void 0) body.recursion_limit = params.recursion_limit;
|
|
154
|
+
if (params.allowed_tools !== void 0) body.allowed_tools = params.allowed_tools;
|
|
155
|
+
if (params.system !== void 0) body.system = params.system;
|
|
156
|
+
const mp = params.modelPreferences ?? params.model_preferences;
|
|
157
|
+
if (mp !== void 0) body.model_preferences = mp;
|
|
158
|
+
const url = `${hostOf(acc)}/api/v1/copilot/app/agent`;
|
|
159
|
+
const res = await fetch(url, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: {
|
|
162
|
+
"content-type": "application/json",
|
|
163
|
+
authorization: `Bearer ${ms.appSessionToken}`,
|
|
164
|
+
accept: "text/event-stream"
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify(body)
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok || !res.body) {
|
|
169
|
+
const text = await res.text().catch(() => "");
|
|
170
|
+
throw withCode(new Error(`agent/session.run failed: HTTP ${res.status}: ${text}`), AGENT_ERR_PROVIDER_ERROR);
|
|
171
|
+
}
|
|
172
|
+
const frames = [];
|
|
173
|
+
let final = null;
|
|
174
|
+
const textAcc = [];
|
|
175
|
+
const reader = res.body.getReader();
|
|
176
|
+
const decoder = new TextDecoder();
|
|
177
|
+
let buf = "";
|
|
178
|
+
while (true) {
|
|
179
|
+
const { value, done } = await reader.read();
|
|
180
|
+
if (done) break;
|
|
181
|
+
buf += decoder.decode(value, { stream: true });
|
|
182
|
+
let idx;
|
|
183
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
184
|
+
const frameRaw = buf.slice(0, idx);
|
|
185
|
+
buf = buf.slice(idx + 2);
|
|
186
|
+
const payload = parseSseFrame(frameRaw);
|
|
187
|
+
if (payload && typeof payload === "object") {
|
|
188
|
+
const frame = payload;
|
|
189
|
+
if (frame.seq === void 0) frame.seq = frames.length;
|
|
190
|
+
if (frame.stream_id === void 0) frame.stream_id = streamId;
|
|
191
|
+
frames.push(frame);
|
|
192
|
+
if (frame.event === "delta" && typeof frame.text === "string") textAcc.push(frame.text);
|
|
193
|
+
else if (frame.event === "final") final = frame;
|
|
194
|
+
if (frames.length > MAX_FRAMES_PER_RUN) throw withCode(new Error(`agent run exceeded ${MAX_FRAMES_PER_RUN} frames`), AGENT_ERR_RUN_TOO_LARGE);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (buf.trim().length > 0) {
|
|
199
|
+
const payload = parseSseFrame(buf);
|
|
200
|
+
if (payload && typeof payload === "object") {
|
|
201
|
+
const frame = payload;
|
|
202
|
+
if (frame.seq === void 0) frame.seq = frames.length;
|
|
203
|
+
if (frame.stream_id === void 0) frame.stream_id = streamId;
|
|
204
|
+
frames.push(frame);
|
|
205
|
+
if (frame.event === "final") final = frame;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (final === null && textAcc.length > 0) {
|
|
209
|
+
final = {
|
|
210
|
+
event: "final",
|
|
211
|
+
text: textAcc.join(""),
|
|
212
|
+
stream_id: streamId,
|
|
213
|
+
seq: frames.length,
|
|
214
|
+
synthesized: true
|
|
215
|
+
};
|
|
216
|
+
frames.push(final);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
run_id: runId,
|
|
220
|
+
stream_id: streamId,
|
|
221
|
+
frames,
|
|
222
|
+
final,
|
|
223
|
+
frame_count: frames.length
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async sessionCancel(params) {
|
|
227
|
+
const acc = requireAccount(this.opts.account);
|
|
228
|
+
const ms = this.getSession(params);
|
|
229
|
+
const runId = params.run_id;
|
|
230
|
+
if (typeof runId !== "string" || !runId.trim()) throw withCode(new Error("'run_id' required"), AGENT_ERR_INVALID_REQUEST);
|
|
231
|
+
const url = `${hostOf(acc)}/api/v1/copilot/app/agent/cancel`;
|
|
232
|
+
const res = await fetch(url, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: {
|
|
235
|
+
"content-type": "application/json",
|
|
236
|
+
authorization: `Bearer ${ms.appSessionToken}`
|
|
237
|
+
},
|
|
238
|
+
body: JSON.stringify({
|
|
239
|
+
app_session_uuid: ms.appSessionUuid,
|
|
240
|
+
run_id: runId
|
|
241
|
+
})
|
|
242
|
+
});
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
const text = await res.text().catch(() => "");
|
|
245
|
+
throw withCode(new Error(`agent/session.cancel failed: HTTP ${res.status}: ${text}`), AGENT_ERR_PROVIDER_ERROR);
|
|
246
|
+
}
|
|
247
|
+
return await res.json().catch(() => ({}));
|
|
248
|
+
}
|
|
249
|
+
async sessionHistory(params) {
|
|
250
|
+
const acc = requireAccount(this.opts.account);
|
|
251
|
+
const ms = this.getSession(params);
|
|
252
|
+
const limit = Math.min(200, Math.max(1, Number(params.limit ?? 50)));
|
|
253
|
+
const cursor = params.cursor ?? null;
|
|
254
|
+
const url = new URL(`${hostOf(acc)}/api/v1/copilot/app/sessions/${encodeURIComponent(ms.appSessionUuid)}/history`);
|
|
255
|
+
url.searchParams.set("limit", String(limit));
|
|
256
|
+
if (cursor) url.searchParams.set("cursor", String(cursor));
|
|
257
|
+
const res = await fetch(url, {
|
|
258
|
+
method: "GET",
|
|
259
|
+
headers: { authorization: `Bearer ${ms.appSessionToken}` }
|
|
260
|
+
});
|
|
261
|
+
if (res.status === 404) return {
|
|
262
|
+
messages: [],
|
|
263
|
+
cursor: null
|
|
264
|
+
};
|
|
265
|
+
if (!res.ok) {
|
|
266
|
+
const text = await res.text().catch(() => "");
|
|
267
|
+
throw withCode(new Error(`agent/session.history failed: HTTP ${res.status}: ${text}`), AGENT_ERR_PROVIDER_ERROR);
|
|
268
|
+
}
|
|
269
|
+
return await res.json();
|
|
270
|
+
}
|
|
271
|
+
async sessionDelete(params) {
|
|
272
|
+
const acc = requireAccount(this.opts.account);
|
|
273
|
+
const uuid = String(params.app_session_uuid ?? "");
|
|
274
|
+
if (!uuid.startsWith("aps_")) throw withCode(new Error("'app_session_uuid' must start with 'aps_'"), AGENT_ERR_INVALID_REQUEST);
|
|
275
|
+
const ms = this.sessions.get(uuid);
|
|
276
|
+
this.sessions.delete(uuid);
|
|
277
|
+
if (!ms) return {
|
|
278
|
+
deleted: true,
|
|
279
|
+
app_session_uuid: uuid,
|
|
280
|
+
already_gone: true
|
|
281
|
+
};
|
|
282
|
+
const url = `${hostOf(acc)}/api/v1/copilot/app/sessions/${encodeURIComponent(uuid)}`;
|
|
283
|
+
const res = await fetch(url, {
|
|
284
|
+
method: "DELETE",
|
|
285
|
+
headers: { authorization: `Bearer ${ms.appSessionToken}` }
|
|
286
|
+
});
|
|
287
|
+
if (!res.ok && res.status !== 404) {
|
|
288
|
+
const text = await res.text().catch(() => "");
|
|
289
|
+
throw withCode(new Error(`agent/session.delete failed: HTTP ${res.status}: ${text}`), AGENT_ERR_PROVIDER_ERROR);
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
deleted: true,
|
|
293
|
+
app_session_uuid: uuid
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* L1 `agent/complete` — single-turn completion convenience. Mints a
|
|
298
|
+
* transient kind=complete session, forwards to `/copilot/app/complete`,
|
|
299
|
+
* returns the assistant message.
|
|
300
|
+
*/
|
|
301
|
+
async complete(params) {
|
|
302
|
+
const acc = requireAccount(this.opts.account);
|
|
303
|
+
const messages = params.messages;
|
|
304
|
+
if (!Array.isArray(messages) || messages.length === 0) throw withCode(new Error("'messages' must be a non-empty array"), AGENT_ERR_INVALID_REQUEST);
|
|
305
|
+
const minted = await mintAppSession(acc.host, {
|
|
306
|
+
pat: acc.pat,
|
|
307
|
+
kind: "complete",
|
|
308
|
+
app_slug: this.requireAppSlug()
|
|
309
|
+
});
|
|
310
|
+
const body = { messages };
|
|
311
|
+
if (params.system) body.system = params.system;
|
|
312
|
+
if (params.max_tokens != null) body.max_tokens = params.max_tokens;
|
|
313
|
+
if (params.maxTokens != null) body.max_tokens = params.maxTokens;
|
|
314
|
+
if (params.temperature != null) body.temperature = params.temperature;
|
|
315
|
+
if (params.stop) body.stop = params.stop;
|
|
316
|
+
if (params.model_hint) body.model_hint = params.model_hint;
|
|
317
|
+
if (params.metadata) body.metadata = params.metadata;
|
|
318
|
+
const res = await fetch(`${hostOf(acc)}/api/v1/copilot/app/complete`, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: {
|
|
321
|
+
"content-type": "application/json",
|
|
322
|
+
authorization: `Bearer ${minted.app_session_token}`
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify(body)
|
|
325
|
+
});
|
|
326
|
+
if (!res.ok) {
|
|
327
|
+
const text = await res.text().catch(() => "");
|
|
328
|
+
throw withCode(new Error(`agent/complete failed: HTTP ${res.status}: ${text}`), AGENT_ERR_PROVIDER_ERROR);
|
|
329
|
+
}
|
|
330
|
+
return await res.json();
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
function collectContent(params) {
|
|
334
|
+
const parts = [];
|
|
335
|
+
if (typeof params.content === "string") parts.push(params.content);
|
|
336
|
+
if (typeof params.system === "string") parts.push(params.system);
|
|
337
|
+
const msgs = params.messages;
|
|
338
|
+
if (Array.isArray(msgs)) for (const m of msgs) {
|
|
339
|
+
const c = m.content;
|
|
340
|
+
if (typeof c === "string") parts.push(c);
|
|
341
|
+
else if (c && typeof c === "object" && "text" in c) parts.push(String(c.text ?? ""));
|
|
342
|
+
}
|
|
343
|
+
return parts.join("\n");
|
|
344
|
+
}
|
|
345
|
+
function parseSseFrame(frame) {
|
|
346
|
+
const lines = frame.split(/\r?\n/);
|
|
347
|
+
const dataLines = [];
|
|
348
|
+
let eventName = null;
|
|
349
|
+
for (const raw of lines) {
|
|
350
|
+
if (!raw || raw.startsWith(":")) continue;
|
|
351
|
+
if (raw.startsWith("data:")) dataLines.push(raw.slice(5).trimStart());
|
|
352
|
+
else if (raw.startsWith("event:")) eventName = raw.slice(6).trim();
|
|
353
|
+
}
|
|
354
|
+
if (dataLines.length === 0) return null;
|
|
355
|
+
const data = dataLines.join("\n");
|
|
356
|
+
try {
|
|
357
|
+
const obj = JSON.parse(data);
|
|
358
|
+
if (eventName && obj && typeof obj === "object" && !("event" in obj)) return {
|
|
359
|
+
...obj,
|
|
360
|
+
event: eventName
|
|
361
|
+
};
|
|
362
|
+
return obj;
|
|
363
|
+
} catch {
|
|
364
|
+
return {
|
|
365
|
+
event: eventName ?? "raw",
|
|
366
|
+
text: data
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
//#endregion
|
|
372
|
+
export { AgentBridge };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getAccount } from "./credentials-BTv2IfUZ.js";
|
|
2
|
-
import { listDevApps } from "./dev-app-cache-
|
|
2
|
+
import { listDevApps } from "./dev-app-cache-DAHcq46m.js";
|
|
3
3
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
4
4
|
|
|
5
5
|
//#region src/commands/apps.ts
|
|
@@ -9,7 +9,7 @@ import { createInterface } from "node:readline";
|
|
|
9
9
|
* `uvx <pkg>@<version>` so end users always run the dispatcher version
|
|
10
10
|
* the CLI was tested against.
|
|
11
11
|
*/
|
|
12
|
-
const PINNED_RUNTIME_VERSION = "0.2.
|
|
12
|
+
const PINNED_RUNTIME_VERSION = "0.2.0a3";
|
|
13
13
|
var PythonBridge = class {
|
|
14
14
|
proc = null;
|
|
15
15
|
nextId = 1;
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { printMascot } from "./mascot-wlYTJqMs.js";
|
|
2
3
|
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { Command } from "commander";
|
|
@@ -53,6 +54,7 @@ function runInit(opts) {
|
|
|
53
54
|
}
|
|
54
55
|
const tplRoot = templateRoot(opts.template);
|
|
55
56
|
copyDirWithSubst(tplRoot, target, opts.slug);
|
|
57
|
+
printMascot(`scaffolded a fresh app — happy hacking!`);
|
|
56
58
|
console.log(kleur.green(`✓ scaffolded "${opts.slug}" at ${target}`));
|
|
57
59
|
console.log(kleur.gray(` next: cd ${opts.targetDir} && anna-app validate`));
|
|
58
60
|
return 0;
|
|
@@ -423,6 +425,10 @@ function printResult(result) {
|
|
|
423
425
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
424
426
|
const program = new Command();
|
|
425
427
|
program.name("anna-app").description("Anna App developer CLI (scaffold, validate, harness)").version(pkg.version);
|
|
428
|
+
const argv = process.argv.slice(2);
|
|
429
|
+
const wantsTopLevelHelp = argv.length === 0 || argv[0] === "-h" || argv[0] === "--help" || argv[0] === "help";
|
|
430
|
+
const tagline = wantsTopLevelHelp ? `v${pkg.version} — run \`anna-app help <command>\` for details` : `v${pkg.version} — ${argv.join(" ")}`;
|
|
431
|
+
printMascot(tagline);
|
|
426
432
|
program.command("init <dir>").description("Scaffold a new Anna App project").option("--slug <slug>", "App slug (lowercase, hyphens)", "").option("--template <name>", "Template to use", "minimal").option("--force", "Overwrite existing dir if non-empty", false).action((dir, opts) => {
|
|
427
433
|
const slug = opts.slug || dir.split(/[\\/]/).pop() || "my-anna-app";
|
|
428
434
|
const code = runInit({
|
|
@@ -445,7 +451,7 @@ program.command("validate").description("Run schema + ACL checks on a manifest+b
|
|
|
445
451
|
process.exit(code);
|
|
446
452
|
});
|
|
447
453
|
program.command("dev").description("Run a local harness (in-process dispatcher + iframe + SSE relay)").option("--manifest <path>", "manifest.json path", "manifest.json").option("--bundle <dir>", "bundle directory (default: ./bundle)").option("--slug <slug>", "App slug (overrides manifest.slug/name)").option("--view <name>", "View name to open (default: manifest default)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (auto-detected if omitted; can also use $ANNA_NEXUS_ROOT)").option("--port <number>", "HTTP port", "5180").option("--user-id <id>", "Harness user_id", "1").option("--cwd <dir>", "Project root (default: cwd)").option("--no-watch", "Disable bundle file watcher (default: enabled)").option("--executa <spec>", "Explicit executa registration; repeatable. Spec: comma-separated key=value (dir=<path>[,tool_id=<id>][,type=python|node|go|binary][,command=\"<argv>\"]). When only `dir=` is given, the executa is auto-detected from executa.json / pyproject.toml / package.json / go.mod. Overrides directory auto-discovery under <manifest-dir>/executas/.", (val, prev) => prev ? [...prev, val] : [val]).option("--no-llm", "Disable LLM bridge (anna.llm/agent return llm_disabled)").option("--mock-llm <fixture>", "Serve canned LLM responses from a JSONL fixture").option("--llm-account <host>", "Saved account host to use (default: current)").option("--llm-app-slug <slug>", "Override the manifest slug used to register / look up the dev AnnaApp (default: manifest.slug)").action(async (opts) => {
|
|
448
|
-
const { runDev, parseExecutaSpec } = await import("./dev-
|
|
454
|
+
const { runDev, parseExecutaSpec } = await import("./dev-BRlFgo2I.js");
|
|
449
455
|
const cwd = opts.cwd ?? process.cwd();
|
|
450
456
|
let executas;
|
|
451
457
|
if (opts.executa && opts.executa.length > 0) {
|
|
@@ -479,7 +485,7 @@ program.command("dev").description("Run a local harness (in-process dispatcher +
|
|
|
479
485
|
});
|
|
480
486
|
const fixture = program.command("fixture").description("Inspect / replay harness recordings (Phase 6)");
|
|
481
487
|
fixture.command("verify <file>").description("Schema + invariant checks on a harness JSONL recording").option("--json", "Emit machine-readable JSON", false).action(async (file, opts) => {
|
|
482
|
-
const { runFixtureVerify } = await import("./fixture-
|
|
488
|
+
const { runFixtureVerify } = await import("./fixture-CATHyLLI.js");
|
|
483
489
|
const code = await runFixtureVerify({
|
|
484
490
|
file,
|
|
485
491
|
json: opts.json
|
|
@@ -487,7 +493,7 @@ fixture.command("verify <file>").description("Schema + invariant checks on a har
|
|
|
487
493
|
process.exit(code);
|
|
488
494
|
});
|
|
489
495
|
fixture.command("summarize <file>").description("Print a human-readable digest of a harness recording").option("--json", "Emit machine-readable JSON", false).action(async (file, opts) => {
|
|
490
|
-
const { runFixtureSummarize } = await import("./fixture-
|
|
496
|
+
const { runFixtureSummarize } = await import("./fixture-CATHyLLI.js");
|
|
491
497
|
const code = await runFixtureSummarize({
|
|
492
498
|
file,
|
|
493
499
|
json: opts.json
|
|
@@ -495,7 +501,7 @@ fixture.command("summarize <file>").description("Print a human-readable digest o
|
|
|
495
501
|
process.exit(code);
|
|
496
502
|
});
|
|
497
503
|
fixture.command("replay <file>").description("Dry-run replay of a harness recording (Phase 6 MVP)").option("--manifest <path>", "manifest.json path", "manifest.json").action(async (file, opts) => {
|
|
498
|
-
const { runFixtureReplay } = await import("./fixture-
|
|
504
|
+
const { runFixtureReplay } = await import("./fixture-CATHyLLI.js");
|
|
499
505
|
const code = await runFixtureReplay({
|
|
500
506
|
file,
|
|
501
507
|
manifest: opts.manifest
|
|
@@ -503,12 +509,12 @@ fixture.command("replay <file>").description("Dry-run replay of a harness record
|
|
|
503
509
|
process.exit(code);
|
|
504
510
|
});
|
|
505
511
|
program.command("doctor").description("Check environment for `anna-app dev` (uv, matrix-nexus, dev key)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (optional)").action(async (opts) => {
|
|
506
|
-
const { runDoctor } = await import("./doctor-
|
|
512
|
+
const { runDoctor } = await import("./doctor-C8MWfLt8.js");
|
|
507
513
|
const code = await runDoctor({ matrixNexusRoot: opts.matrixNexusRoot });
|
|
508
514
|
process.exit(code);
|
|
509
515
|
});
|
|
510
516
|
program.command("login").description("Device-flow login against a nexus host; saves a PAT to ~/.config/anna/credentials.json").requiredOption("--host <url>", "nexus base URL, e.g. https://nexus.example.com").option("--no-browser", "Do not open a browser window automatically", false).action(async (opts) => {
|
|
511
|
-
const { runLogin } = await import("./login-
|
|
517
|
+
const { runLogin } = await import("./login-CsIVbrmf.js");
|
|
512
518
|
const code = await runLogin({
|
|
513
519
|
host: opts.host,
|
|
514
520
|
noBrowser: opts.browser === false
|
|
@@ -516,7 +522,7 @@ program.command("login").description("Device-flow login against a nexus host; sa
|
|
|
516
522
|
process.exit(code);
|
|
517
523
|
});
|
|
518
524
|
program.command("logout").description("Remove a saved PAT entry").option("--host <url>", "Account to remove (default: current)").option("--all", "Remove every saved account", false).action(async (opts) => {
|
|
519
|
-
const { runLogout } = await import("./logout-
|
|
525
|
+
const { runLogout } = await import("./logout-gfmKQxMj.js");
|
|
520
526
|
const code = await runLogout({
|
|
521
527
|
host: opts.host,
|
|
522
528
|
all: opts.all
|
|
@@ -524,18 +530,81 @@ program.command("logout").description("Remove a saved PAT entry").option("--host
|
|
|
524
530
|
process.exit(code);
|
|
525
531
|
});
|
|
526
532
|
program.command("whoami").description("Show the current account (and any others)").option("--json", "Emit machine-readable JSON", false).action(async (opts) => {
|
|
527
|
-
const { runWhoami } = await import("./whoami-
|
|
533
|
+
const { runWhoami } = await import("./whoami-BS5wy-Nh.js");
|
|
528
534
|
const code = await runWhoami({ json: opts.json });
|
|
529
535
|
process.exit(code);
|
|
530
536
|
});
|
|
531
537
|
program.command("apps:list").description("List dev apps installed for the current PAT").option("--account <host>", "Saved account host (default: current)").option("--json", "Emit machine-readable JSON", false).action(async (opts) => {
|
|
532
|
-
const { runAppsList } = await import("./apps-
|
|
538
|
+
const { runAppsList } = await import("./apps-Bt9CT5Sl.js");
|
|
533
539
|
const code = await runAppsList({
|
|
534
540
|
account: opts.account,
|
|
535
541
|
json: opts.json
|
|
536
542
|
});
|
|
537
543
|
process.exit(code);
|
|
538
544
|
});
|
|
545
|
+
const executa = program.command("executa").description("Standalone Executa plugin scaffolding + local runner");
|
|
546
|
+
executa.command("init <dir>").description("Scaffold a standalone Executa plugin (python | node | go)").option("--slug <slug>", "Plugin slug (lowercase, hyphens)", "").option("--template <name>", "Language template: python | node | go", "python").option("--tool-id <id>", "Override the minted tool_id (default: tool-dev-<slug>)").option("--force", "Overwrite existing dir if non-empty", false).action(async (dir, opts) => {
|
|
547
|
+
const slug = opts.slug || dir.split(/[\\/]/).pop() || "my-executa";
|
|
548
|
+
const tpl = opts.template;
|
|
549
|
+
if (![
|
|
550
|
+
"python",
|
|
551
|
+
"node",
|
|
552
|
+
"go"
|
|
553
|
+
].includes(tpl)) {
|
|
554
|
+
console.error(`✗ unknown template "${opts.template}" — expected python | node | go`);
|
|
555
|
+
process.exit(2);
|
|
556
|
+
}
|
|
557
|
+
const { runExecutaInit } = await import("./executa-init-COEmKDOE.js");
|
|
558
|
+
const code = runExecutaInit({
|
|
559
|
+
targetDir: dir,
|
|
560
|
+
slug,
|
|
561
|
+
template: tpl,
|
|
562
|
+
toolId: opts.toolId,
|
|
563
|
+
force: opts.force
|
|
564
|
+
});
|
|
565
|
+
process.exit(code);
|
|
566
|
+
});
|
|
567
|
+
executa.command("register").description("Register a standalone executa with nexus so `executa dev --storage real` (and future agent / sampling real-mode flows) can mint tokens for it").requiredOption("--tool-id <id>", "Executa tool_id (matches manifest.name)").option("--slug <slug>", "App slug (default: executa-<tool_id>; must be lowercase + hyphens)").option("--name <name>", "Human-readable display name (default: <tool_id>)").option("--account <host>", "Saved account host (default: current)").action(async (opts) => {
|
|
568
|
+
const { runExecutaRegister } = await import("./executa-register-66WKIwQQ.js");
|
|
569
|
+
const code = await runExecutaRegister(opts);
|
|
570
|
+
process.exit(code);
|
|
571
|
+
});
|
|
572
|
+
executa.command("dev").description("Run one Executa plugin in isolation (REPL or one-shot describe/invoke)").option("--dir <path>", "Executa project dir (default: CWD)").option("--spec <spec>", "Override discovery: comma-separated key=value (tool_id=...,type=...,command=\"...\")").option("--describe", "Print MANIFEST and exit", false).option("--health", "Print health and exit", false).option("--invoke <tool>", "Invoke one tool and exit").option("--args <json>", "JSON object passed as tool arguments", "{}").option("--json", "One-shot: emit compact JSON (no banners)", false).option("--no-sampling", "Hard-disable sampling reverse RPC (returns sampling_disabled)").option("--mock-sampling <fixture>", "Serve canned sampling responses from a JSONL fixture (offline)").option("--app-slug <slug>", "Forward sampling to nexus on behalf of this dev AnnaApp slug").option("--sampling-account <host>", "Saved account host for nexus sampling (default: current)").option("--no-agent", "Hard-disable agent reverse RPC (returns agent_not_granted)").option("--mock-agent <fixture>", "Serve canned agent/* responses from a JSONL fixture (offline)").option("--agent-account <host>", "Saved account host for nexus agent (default: --sampling-account or current)").option("--storage <mode>", "Storage backend: off | memory | mock | real (default: memory)").option("--mock-storage <fixture>", "Serve canned storage/* + files/* responses from a JSONL fixture").option("--storage-account <host>", "Saved account host for nexus storage (default: --sampling-account or current)").option("--storage-scopes <list>", "Comma-separated scopes for real storage tokens (default: user,app,tool)").option("--no-image", "Hard-disable image reverse RPC (returns image_not_granted)").option("--mock-image <fixture>", "Serve canned image/generate + image/edit responses from a JSONL fixture").option("--image-account <host>", "Saved account host for nexus image (default: --sampling-account or current)").option("--no-upload", "Hard-disable host/uploadFile reverse RPC (returns upload_not_granted)").option("--mock-upload <fixture>", "Serve canned host/uploadFile responses from a JSONL fixture").option("--upload-account <host>", "Saved account host for nexus uploads (default: --sampling-account or current)").action(async (opts) => {
|
|
573
|
+
const { runExecutaDev } = await import("./executa-dev-4FZ7AJHR.js");
|
|
574
|
+
const storageMode = opts.storage === void 0 ? void 0 : (() => {
|
|
575
|
+
const m = opts.storage;
|
|
576
|
+
if (m === "off" || m === "memory" || m === "mock" || m === "real") return m;
|
|
577
|
+
console.error(`✗ --storage must be one of off | memory | mock | real (got: ${m})`);
|
|
578
|
+
process.exit(2);
|
|
579
|
+
})();
|
|
580
|
+
const code = await runExecutaDev({
|
|
581
|
+
dir: opts.dir,
|
|
582
|
+
spec: opts.spec,
|
|
583
|
+
describe: opts.describe,
|
|
584
|
+
health: opts.health,
|
|
585
|
+
invoke: opts.invoke,
|
|
586
|
+
args: opts.args,
|
|
587
|
+
json: opts.json,
|
|
588
|
+
noSampling: opts.sampling === false,
|
|
589
|
+
mockSampling: opts.mockSampling,
|
|
590
|
+
appSlug: opts.appSlug,
|
|
591
|
+
samplingAccount: opts.samplingAccount,
|
|
592
|
+
noAgent: opts.agent === false,
|
|
593
|
+
mockAgent: opts.mockAgent,
|
|
594
|
+
agentAccount: opts.agentAccount,
|
|
595
|
+
storage: storageMode,
|
|
596
|
+
mockStorage: opts.mockStorage,
|
|
597
|
+
storageAccount: opts.storageAccount,
|
|
598
|
+
storageScopes: opts.storageScopes,
|
|
599
|
+
noImage: opts.image === false,
|
|
600
|
+
mockImage: opts.mockImage,
|
|
601
|
+
imageAccount: opts.imageAccount,
|
|
602
|
+
noUpload: opts.upload === false,
|
|
603
|
+
mockUpload: opts.mockUpload,
|
|
604
|
+
uploadAccount: opts.uploadAccount
|
|
605
|
+
});
|
|
606
|
+
process.exit(code);
|
|
607
|
+
});
|
|
539
608
|
program.parseAsync(process.argv).catch((e) => {
|
|
540
609
|
console.error(e);
|
|
541
610
|
process.exit(2);
|
|
@@ -31,8 +31,8 @@ async function runDev(opts) {
|
|
|
31
31
|
}
|
|
32
32
|
const matrixNexusRoot = await resolveMatrixNexusRoot(opts.matrixNexusRoot, cwd);
|
|
33
33
|
const mode = matrixNexusRoot ? "nexus-source" : "uvx";
|
|
34
|
-
const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-
|
|
35
|
-
const { HarnessServer } = await import("./server-
|
|
34
|
+
const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-AJilXBw2.js");
|
|
35
|
+
const { HarnessServer } = await import("./server-D8R6ppOp.js");
|
|
36
36
|
const bridge = new PythonBridge({
|
|
37
37
|
mode,
|
|
38
38
|
matrixNexusRoot: matrixNexusRoot ?? void 0,
|
|
@@ -122,8 +122,8 @@ async function runDev(opts) {
|
|
|
122
122
|
* registered `appSlug`.
|
|
123
123
|
*/
|
|
124
124
|
async function resolveRealLlm(args) {
|
|
125
|
-
const { getAccount } = await import("./credentials-
|
|
126
|
-
const { ensureDevAppRegistered } = await import("./dev-app-cache-
|
|
125
|
+
const { getAccount } = await import("./credentials-DDqx6XMQ.js");
|
|
126
|
+
const { ensureDevAppRegistered } = await import("./dev-app-cache-DGF2Kuzd.js");
|
|
127
127
|
const acc = getAccount(args.account);
|
|
128
128
|
if (!acc) {
|
|
129
129
|
console.error(red("✗ no developer PAT on disk — run `anna-app login --host <nexus-url>` first.\n (or use `--no-llm` / `--mock-llm <fixture>` to develop offline.)"));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
|
|
2
|
+
|
|
3
|
+
//#region src/executa/dev-account.ts
|
|
4
|
+
/** Embed a JSON-RPC error code on a thrown Error so the runner can forward it. */
|
|
5
|
+
function withCode(err, code) {
|
|
6
|
+
err.rpcCode = code;
|
|
7
|
+
return err;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a saved developer PAT for the given account host (or the
|
|
11
|
+
* "current" one when omitted).
|
|
12
|
+
*
|
|
13
|
+
* @throws JSON-RPC `-32001` when no PAT is on disk or it has expired.
|
|
14
|
+
*/
|
|
15
|
+
function requireAccount(account) {
|
|
16
|
+
const acc = getAccount(account);
|
|
17
|
+
if (!acc) throw withCode(new Error("no PAT on disk — run `anna-app login --host <nexus-url>` first (or use a `mock`/`off`/`memory` mode for the affected bridge)"), -32001);
|
|
18
|
+
if (acc.expires_at && acc.expires_at < Math.floor(Date.now() / 1e3)) throw withCode(new Error("PAT expired — run `anna-app login` again"), -32001);
|
|
19
|
+
return acc;
|
|
20
|
+
}
|
|
21
|
+
/** Return the canonical `https://host[:port]` form of a saved account. */
|
|
22
|
+
function hostOf(acc) {
|
|
23
|
+
return canonicalHost(acc.host);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Call `POST /api/v1/anna-apps/dev/session/mint` and parse the response.
|
|
27
|
+
* Thin wrapper used by both sampling and agent bridges.
|
|
28
|
+
*/
|
|
29
|
+
async function mintAppSession(host, body) {
|
|
30
|
+
const url = `${canonicalHost(host)}/api/v1/anna-apps/dev/session/mint`;
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "content-type": "application/json" },
|
|
34
|
+
body: JSON.stringify(body)
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const text = await res.text().catch(() => "");
|
|
38
|
+
throw withCode(new Error(`session.mint failed: HTTP ${res.status} ${text}`), -32e3);
|
|
39
|
+
}
|
|
40
|
+
return await res.json();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { hostOf, mintAppSession, requireAccount, withCode };
|