@anna-ai/cli 0.1.11 → 0.1.14

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.
Files changed (42) hide show
  1. package/dist/agent-DUmINbo4.js +372 -0
  2. package/dist/apps-BEJUn9Ws.js +44 -0
  3. package/dist/bridge-C0DWb5eQ.js +3 -0
  4. package/dist/cli.js +81 -10
  5. package/dist/{credentials-ggdaz_-7.js → credentials-BTv2IfUZ.js} +1 -1
  6. package/dist/credentials-DDqx6XMQ.js +3 -0
  7. package/dist/dashboard.html +8 -4
  8. package/dist/{dev-BPIUX2Nh.js → dev-BfLGxpiT.js} +52 -7
  9. package/dist/dev-C81H9c9_.js +3 -0
  10. package/dist/dev-account-DCyjamBa.js +44 -0
  11. package/dist/dev-app-cache-C3D1Sp_V.js +93 -0
  12. package/dist/dev-app-cache-CZ8lIKiw.js +4 -0
  13. package/dist/{doctor-R1pjmBDG.js → doctor-B3u0edUg.js} +1 -1
  14. package/dist/executa-dev-BhouP8jh.js +212 -0
  15. package/dist/executa-init-COEmKDOE.js +68 -0
  16. package/dist/executa-register-66WKIwQQ.js +47 -0
  17. package/dist/{login-D8cmvBb6.js → login-CsIVbrmf.js} +1 -1
  18. package/dist/{logout-P6L9VU4W.js → logout-gfmKQxMj.js} +1 -1
  19. package/dist/mascot-wlYTJqMs.js +218 -0
  20. package/dist/runner-Bral1LFW.js +279 -0
  21. package/dist/sampling-3EfSlDHM.js +155 -0
  22. package/dist/{server-Cd5Lo-2v.js → server-q6nKCeEV.js} +10 -4
  23. package/dist/storage-CnWTZqq_.js +316 -0
  24. package/dist/{whoami-jqlQwe7Z.js → whoami-BS5wy-Nh.js} +1 -1
  25. package/package.json +3 -3
  26. package/templates/executa/go/README.md +10 -0
  27. package/templates/executa/go/executa.json +4 -0
  28. package/templates/executa/go/go.mod +3 -0
  29. package/templates/executa/go/main.go +148 -0
  30. package/templates/executa/node/README.md +12 -0
  31. package/templates/executa/node/executa.json +4 -0
  32. package/templates/executa/node/package.json +12 -0
  33. package/templates/executa/node/plugin.mjs +126 -0
  34. package/templates/executa/node/sampling-fixture.jsonl +1 -0
  35. package/templates/executa/python/README.md +23 -0
  36. package/templates/executa/python/__SLUG_PY___plugin.py +146 -0
  37. package/templates/executa/python/executa.json +4 -0
  38. package/templates/executa/python/pyproject.toml +15 -0
  39. package/templates/executa/python/sampling-fixture.jsonl +4 -0
  40. package/dist/bridge-BIO7ilgO.js +0 -3
  41. /package/dist/{bridge-Cpm3D2Wk.js → bridge-D6YyP9DM.js} +0 -0
  42. /package/dist/{fixture-RceUUd84.js → fixture-CATHyLLI.js} +0 -0
@@ -0,0 +1,218 @@
1
+ import kleur from "kleur";
2
+
3
+ //#region src/mascot.ts
4
+ const FACE = [
5
+ 255,
6
+ 227,
7
+ 234
8
+ ];
9
+ const EYE = [
10
+ 122,
11
+ 90,
12
+ 101
13
+ ];
14
+ const GEO = (() => {
15
+ const VX0 = 252, VY0 = 280, VW = 520;
16
+ const n = (x, y) => [(x - VX0) / VW, (y - VY0) / VW];
17
+ const r = (v) => v / VW;
18
+ const [fcx, fcy] = n(512, 540);
19
+ const [lex, ley] = n(462, 520);
20
+ const [rex, rey] = n(562, 520);
21
+ return {
22
+ face: {
23
+ cx: fcx,
24
+ cy: fcy,
25
+ r: r(260)
26
+ },
27
+ eyeL: {
28
+ cx: lex,
29
+ cy: ley,
30
+ r: r(34)
31
+ },
32
+ eyeR: {
33
+ cx: rex,
34
+ cy: rey,
35
+ r: r(34)
36
+ }
37
+ };
38
+ })();
39
+ /** Sample the SVG at normalized (x, y) ∈ [0, 1]². Returns null for empty. */
40
+ function sampleSvg(x, y) {
41
+ const inCircle = (cx, cy, r) => {
42
+ const dx = x - cx, dy = y - cy;
43
+ return dx * dx + dy * dy <= r * r;
44
+ };
45
+ let c = null;
46
+ if (inCircle(GEO.face.cx, GEO.face.cy, GEO.face.r)) c = FACE;
47
+ return c;
48
+ }
49
+ /**
50
+ * 2×2 supersampled pixel using **majority vote** (not averaging).
51
+ * Anti-aliases silhouette edges (filled vs empty) while keeping every
52
+ * pixel a pure palette color — no muddy blends between the pink face and
53
+ * the dark eyes. Ties resolve in favor of the topmost layer encountered.
54
+ */
55
+ function pixel(px, py, w, h) {
56
+ const counts = new Map();
57
+ let nulls = 0;
58
+ for (const oy of [.25, .75]) for (const ox of [.25, .75]) {
59
+ const s = sampleSvg((px + ox) / w, (py + oy) / h);
60
+ if (!s) {
61
+ nulls++;
62
+ continue;
63
+ }
64
+ const key = `${s[0]},${s[1]},${s[2]}`;
65
+ const entry = counts.get(key);
66
+ if (entry) entry.n++;
67
+ else counts.set(key, {
68
+ color: s,
69
+ n: 1
70
+ });
71
+ }
72
+ if (counts.size === 0) return null;
73
+ let best = null;
74
+ for (const e of counts.values()) if (!best || e.n > best.n) best = e;
75
+ if (nulls > (best?.n ?? 0)) return null;
76
+ return best.color;
77
+ }
78
+ const FG = (c) => `\x1b[38;2;${c[0]};${c[1]};${c[2]}m`;
79
+ const BG = (c) => `\x1b[48;2;${c[0]};${c[1]};${c[2]}m`;
80
+ const BG_DEFAULT = "\x1B[49m";
81
+ const RESET = "\x1B[0m";
82
+ function eq(a, b) {
83
+ if (a === b) return true;
84
+ if (!a || !b) return false;
85
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
86
+ }
87
+ /**
88
+ * Rasterize the face into a list of half-block lines.
89
+ * `cols` is character width; pixel rows are derived from the cell aspect
90
+ * so each pixel covers an (approximately) square area on screen.
91
+ */
92
+ function renderFace(cols, cellAspect) {
93
+ const W = cols;
94
+ const charRows = Math.max(1, Math.round(cols / cellAspect));
95
+ const H = charRows * 2;
96
+ const eyeRow = Math.round(GEO.eyeL.cy * charRows - .5);
97
+ const eyeColL = Math.max(0, Math.round(GEO.eyeL.cx * W) - 1);
98
+ const eyeColR = Math.max(0, Math.round(GEO.eyeR.cx * W) - 1);
99
+ const isEyeCell = (cx, cy) => cy === eyeRow && (cx >= eyeColL && cx < eyeColL + 2 || cx >= eyeColR && cx < eyeColR + 2);
100
+ const rows = [];
101
+ for (let cy = 0; cy < charRows; cy++) {
102
+ let line = "";
103
+ let lastFg = null;
104
+ let lastBg = null;
105
+ let lastBgDefault = true;
106
+ for (let cx = 0; cx < W; cx++) {
107
+ if (isEyeCell(cx, cy)) {
108
+ if (!eq(lastFg, EYE)) {
109
+ line += FG(EYE);
110
+ lastFg = EYE;
111
+ }
112
+ if (lastBgDefault || !eq(lastBg, FACE)) {
113
+ line += BG(FACE);
114
+ lastBg = FACE;
115
+ lastBgDefault = false;
116
+ }
117
+ line += "█";
118
+ continue;
119
+ }
120
+ const top = pixel(cx, cy * 2, W, H);
121
+ const bot = pixel(cx, cy * 2 + 1, W, H);
122
+ if (!top && !bot) {
123
+ if (!lastBgDefault) {
124
+ line += BG_DEFAULT;
125
+ lastBgDefault = true;
126
+ lastBg = null;
127
+ }
128
+ line += " ";
129
+ continue;
130
+ }
131
+ if (top && bot) {
132
+ if (!eq(lastFg, top)) {
133
+ line += FG(top);
134
+ lastFg = top;
135
+ }
136
+ if (lastBgDefault || !eq(lastBg, bot)) {
137
+ line += BG(bot);
138
+ lastBg = bot;
139
+ lastBgDefault = false;
140
+ }
141
+ line += "▀";
142
+ } else if (top) {
143
+ if (!eq(lastFg, top)) {
144
+ line += FG(top);
145
+ lastFg = top;
146
+ }
147
+ if (!lastBgDefault) {
148
+ line += BG_DEFAULT;
149
+ lastBgDefault = true;
150
+ lastBg = null;
151
+ }
152
+ line += "▀";
153
+ } else {
154
+ if (!eq(lastFg, bot)) {
155
+ line += FG(bot);
156
+ lastFg = bot;
157
+ }
158
+ if (!lastBgDefault) {
159
+ line += BG_DEFAULT;
160
+ lastBgDefault = true;
161
+ lastBg = null;
162
+ }
163
+ line += "▄";
164
+ }
165
+ }
166
+ rows.push(line + RESET);
167
+ }
168
+ return rows;
169
+ }
170
+ function mascotEnabled() {
171
+ if (process.env.ANNA_CLI_NO_MASCOT) return false;
172
+ if (process.env.NO_COLOR) return false;
173
+ if (process.env.ANNA_CLI_FORCE_MASCOT) return true;
174
+ return Boolean(process.stdout.isTTY);
175
+ }
176
+ function pickWidth() {
177
+ const override = Number.parseInt(process.env.ANNA_CLI_MASCOT_WIDTH ?? "", 10);
178
+ if (Number.isFinite(override) && override >= 10 && override <= 120) return override;
179
+ const cols = process.stdout.columns || 80;
180
+ if (cols < 60) return 10;
181
+ if (cols >= 120) return 16;
182
+ return 13;
183
+ }
184
+ function pickAspect() {
185
+ const v = Number.parseFloat(process.env.ANNA_CLI_MASCOT_ASPECT ?? "");
186
+ if (Number.isFinite(v) && v >= 1.2 && v <= 3) return v;
187
+ return 2.1;
188
+ }
189
+ /**
190
+ * Render the mascot as a multi-line string. Always returns the colored art
191
+ * (callers decide whether to print based on `mascotEnabled`).
192
+ */
193
+ function renderMascot(message) {
194
+ const cols = pickWidth();
195
+ const aspect = pickAspect();
196
+ const face = renderFace(cols, aspect);
197
+ const pad = " ".repeat(2);
198
+ const label = kleur.magenta().bold("Anna");
199
+ const tagline = kleur.gray("Anna App developer CLI");
200
+ const textLines = [`${label} ${tagline}`];
201
+ if (message) textLines.push(`${kleur.gray("›")} ${message}`);
202
+ const start = Math.max(0, Math.floor((face.length - textLines.length) / 2));
203
+ const lines = [];
204
+ for (let i = 0; i < face.length; i++) {
205
+ const tIdx = i - start;
206
+ const text = tIdx >= 0 && tIdx < textLines.length ? " " + textLines[tIdx] : "";
207
+ lines.push(pad + face[i] + text);
208
+ }
209
+ return lines.join("\n");
210
+ }
211
+ /** Print the mascot to stderr if enabled. Stderr keeps stdout clean for piping. */
212
+ function printMascot(message) {
213
+ if (!mascotEnabled()) return;
214
+ process.stderr.write(renderMascot(message) + "\n\n");
215
+ }
216
+
217
+ //#endregion
218
+ export { printMascot };
@@ -0,0 +1,279 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+
4
+ //#region src/executa/runner.ts
5
+ /** Spec describing where the host advertises its capabilities. */
6
+ const HOST_INITIALIZE_PARAMS = {
7
+ protocolVersion: "2.0",
8
+ client_capabilities: {
9
+ sampling: {},
10
+ agent: {},
11
+ storage: {}
12
+ },
13
+ client_info: {
14
+ name: "anna-app-cli",
15
+ version: "executa-dev"
16
+ }
17
+ };
18
+ var ExecutaRunner = class {
19
+ proc = null;
20
+ nextId = 1;
21
+ pending = new Map();
22
+ exitWaiters = [];
23
+ closed = false;
24
+ /** Resolved by start() after the v2 handshake (or v1 fallback) succeeds. */
25
+ initialized = null;
26
+ constructor(opts) {
27
+ this.opts = opts;
28
+ }
29
+ /** Spawn the subprocess and perform the initialize handshake. */
30
+ async start() {
31
+ if (this.proc) {
32
+ if (!this.initialized) throw new Error("executa already starting");
33
+ return this.initialized;
34
+ }
35
+ const [bin, ...args] = this.opts.command;
36
+ if (!bin) throw new Error("ExecutaRunner: empty command");
37
+ const env = {
38
+ ...process.env,
39
+ ...this.opts.env
40
+ };
41
+ env.ANNA_EXECUTA_DEV = "1";
42
+ this.proc = spawn(bin, args, {
43
+ cwd: this.opts.cwd,
44
+ stdio: [
45
+ "pipe",
46
+ "pipe",
47
+ "pipe"
48
+ ],
49
+ env
50
+ });
51
+ const stderrSink = this.opts.onStderr ?? ((l) => process.stderr.write(`[executa] ${l}\n`));
52
+ createInterface({ input: this.proc.stderr }).on("line", stderrSink);
53
+ this.proc.on("exit", (code) => {
54
+ this.closed = true;
55
+ const waiters = this.exitWaiters.splice(0);
56
+ for (const w of waiters) w(code);
57
+ const err = new Error(`executa exited (code=${code ?? "null"})`);
58
+ for (const p of this.pending.values()) p.reject(err);
59
+ this.pending.clear();
60
+ });
61
+ this.proc.on("error", (e) => {
62
+ this.closed = true;
63
+ for (const p of this.pending.values()) p.reject(e);
64
+ this.pending.clear();
65
+ });
66
+ createInterface({ input: this.proc.stdout }).on("line", (line) => {
67
+ this.handleLine(line, stderrSink);
68
+ });
69
+ const initTimeout = this.opts.initTimeoutMs ?? 8e3;
70
+ try {
71
+ const raw = await this.callWithTimeout("initialize", HOST_INITIALIZE_PARAMS, initTimeout);
72
+ const ver = String(raw.protocolVersion ?? "1.0");
73
+ this.initialized = {
74
+ protocolVersion: ver === "2.0" ? "2.0" : "1.0",
75
+ raw
76
+ };
77
+ } catch (e) {
78
+ const rpc = e.rpc;
79
+ if (rpc && rpc.code === -32601) this.initialized = {
80
+ protocolVersion: "1.0",
81
+ raw: {}
82
+ };
83
+ else {
84
+ await this.stop();
85
+ throw e;
86
+ }
87
+ }
88
+ return this.initialized;
89
+ }
90
+ /** Send a JSON-RPC request and await the response. */
91
+ call(method, params = {}) {
92
+ if (this.closed || !this.proc) return Promise.reject(new Error("executa not running"));
93
+ const id = this.nextId++;
94
+ const env = {
95
+ jsonrpc: "2.0",
96
+ id,
97
+ method,
98
+ params
99
+ };
100
+ return new Promise((resolve, reject) => {
101
+ this.pending.set(id, {
102
+ resolve: (v) => resolve(v),
103
+ reject
104
+ });
105
+ this.proc.stdin.write(`${JSON.stringify(env)}\n`);
106
+ });
107
+ }
108
+ /** Convenience: `describe → MANIFEST`. */
109
+ describe() {
110
+ return this.call("describe");
111
+ }
112
+ /** Convenience: `invoke {tool, arguments}`. */
113
+ invoke(tool, args) {
114
+ return this.call("invoke", {
115
+ tool,
116
+ arguments: args
117
+ });
118
+ }
119
+ /** Convenience: `health → {status}`. */
120
+ health() {
121
+ return this.call("health");
122
+ }
123
+ /** Close stdin (graceful EOF shutdown) then SIGTERM after a grace period. */
124
+ async stop(graceMs = 1500) {
125
+ if (!this.proc) return;
126
+ this.closed = true;
127
+ const p = this.proc;
128
+ try {
129
+ p.stdin.end();
130
+ } catch {}
131
+ const waitExit = new Promise((resolve) => {
132
+ this.exitWaiters.push(resolve);
133
+ if (p.exitCode != null) resolve(p.exitCode);
134
+ });
135
+ const timer = new Promise((resolve) => {
136
+ setTimeout(() => resolve("timeout"), graceMs);
137
+ });
138
+ const winner = await Promise.race([waitExit, timer]);
139
+ if (winner === "timeout") try {
140
+ p.kill("SIGTERM");
141
+ } catch {}
142
+ this.proc = null;
143
+ }
144
+ async callWithTimeout(method, params, ms) {
145
+ const id = this.nextId++;
146
+ const env = {
147
+ jsonrpc: "2.0",
148
+ id,
149
+ method,
150
+ params
151
+ };
152
+ return new Promise((resolve, reject) => {
153
+ const to = setTimeout(() => {
154
+ this.pending.delete(id);
155
+ reject(new Error(`executa ${method} timed out after ${ms}ms`));
156
+ }, ms);
157
+ this.pending.set(id, {
158
+ resolve: (v) => {
159
+ clearTimeout(to);
160
+ resolve(v);
161
+ },
162
+ reject: (e) => {
163
+ clearTimeout(to);
164
+ reject(e);
165
+ }
166
+ });
167
+ this.proc.stdin.write(`${JSON.stringify(env)}\n`);
168
+ });
169
+ }
170
+ handleLine(line, stderrSink) {
171
+ if (!line.trim()) return;
172
+ let env;
173
+ try {
174
+ env = JSON.parse(line);
175
+ } catch {
176
+ stderrSink(`non-json from executa stdout: ${line}`);
177
+ return;
178
+ }
179
+ if (typeof env.method === "string") {
180
+ this.handleReverse(env);
181
+ return;
182
+ }
183
+ const id = env.id;
184
+ if (typeof id !== "number") return;
185
+ const pending = this.pending.get(id);
186
+ if (!pending) return;
187
+ this.pending.delete(id);
188
+ if (env.error) {
189
+ const e = env.error;
190
+ const err = new Error(`[${e.code}] ${e.message}`);
191
+ err.rpc = e;
192
+ pending.reject(err);
193
+ } else pending.resolve(env.result);
194
+ }
195
+ async handleReverse(env) {
196
+ const id = env.id;
197
+ const method = env.method;
198
+ const params = env.params ?? {};
199
+ const respond = (body) => {
200
+ if (id === void 0 || id === null) return;
201
+ if (!this.proc) return;
202
+ const out = {
203
+ jsonrpc: "2.0",
204
+ id,
205
+ ...body
206
+ };
207
+ try {
208
+ this.proc.stdin.write(`${JSON.stringify(out)}\n`);
209
+ } catch {}
210
+ };
211
+ if (method === "sampling/createMessage") {
212
+ if (!this.opts.sampling) {
213
+ respond({ error: {
214
+ code: -32008,
215
+ message: "sampling not configured (use `anna-app login` and rerun without `--no-sampling`, or pass `--mock-sampling`)"
216
+ } });
217
+ return;
218
+ }
219
+ try {
220
+ const result = await this.opts.sampling.createMessage(params);
221
+ respond({ result });
222
+ } catch (e) {
223
+ const code = e.rpcCode ?? -32e3;
224
+ respond({ error: {
225
+ code,
226
+ message: e.message
227
+ } });
228
+ }
229
+ return;
230
+ }
231
+ if (method.startsWith("agent/")) {
232
+ if (!this.opts.agent) {
233
+ respond({ error: {
234
+ code: -32041,
235
+ message: "agent reverse-RPC not configured (rerun without `--no-agent`, or pass `--mock-agent <fixture>`)"
236
+ } });
237
+ return;
238
+ }
239
+ try {
240
+ const result = await this.opts.agent.call(method, params);
241
+ respond({ result });
242
+ } catch (e) {
243
+ const code = e.rpcCode ?? -32e3;
244
+ respond({ error: {
245
+ code,
246
+ message: e.message
247
+ } });
248
+ }
249
+ return;
250
+ }
251
+ if (method.startsWith("storage/") || method.startsWith("files/")) {
252
+ if (!this.opts.storage) {
253
+ respond({ error: {
254
+ code: -32021,
255
+ message: "storage not configured (rerun with `--storage memory|mock|real`)"
256
+ } });
257
+ return;
258
+ }
259
+ try {
260
+ const result = await this.opts.storage.call(method, params);
261
+ respond({ result });
262
+ } catch (e) {
263
+ const code = e.rpcCode ?? -32e3;
264
+ respond({ error: {
265
+ code,
266
+ message: e.message
267
+ } });
268
+ }
269
+ return;
270
+ }
271
+ respond({ error: {
272
+ code: -32601,
273
+ message: `unknown reverse method: ${method}`
274
+ } });
275
+ }
276
+ };
277
+
278
+ //#endregion
279
+ export { ExecutaRunner };
@@ -0,0 +1,155 @@
1
+ import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
2
+ import { resolve } from "node:path";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+
5
+ //#region src/executa/sampling.ts
6
+ var SamplingBridge = class {
7
+ mocks = [];
8
+ cachedSession = null;
9
+ constructor(opts) {
10
+ this.opts = opts;
11
+ if (opts.mode === "mock" && opts.mockFile) {
12
+ const path = resolve(opts.mockFile);
13
+ if (existsSync(path)) for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
14
+ if (!line.trim() || line.startsWith("#")) continue;
15
+ try {
16
+ this.mocks.push(JSON.parse(line));
17
+ } catch {}
18
+ }
19
+ }
20
+ }
21
+ async createMessage(req) {
22
+ if (this.opts.mode === "off") throw withCode(new Error("sampling disabled — rerun without `--no-sampling`"), -32008);
23
+ if (this.opts.mode === "mock") return this.mockMessage(req);
24
+ return this.realMessage(req);
25
+ }
26
+ mockMessage(req) {
27
+ const content = collectContent(req);
28
+ const entry = this.mocks.find((m) => m.ns === "sampling" && m.method === "createMessage" || m.ns === "llm" && m.method === "complete") ?? null;
29
+ const matched = entry && (!entry.match?.contentIncludes || content.includes(entry.match.contentIncludes)) ? entry : this.mocks.find((m) => m.ns === "sampling" && m.method === "createMessage" || m.ns === "llm" && m.method === "complete");
30
+ if (matched && matched.result) return normaliseSamplingResult(matched.result, "mock-model");
31
+ return {
32
+ role: "assistant",
33
+ content: {
34
+ type: "text",
35
+ text: "(mock) no fixture matched"
36
+ },
37
+ model: "mock-model",
38
+ stopReason: "endTurn"
39
+ };
40
+ }
41
+ account() {
42
+ const acc = getAccount(this.opts.account);
43
+ if (!acc) throw withCode(new Error("no PAT on disk — run `anna-app login --host <nexus-url>` first (or pass `--mock-sampling <fixture>` / `--no-sampling`)"), -32001);
44
+ if (acc.expires_at && acc.expires_at < Math.floor(Date.now() / 1e3)) throw withCode(new Error("PAT expired — run `anna-app login` again"), -32001);
45
+ return acc;
46
+ }
47
+ async mint() {
48
+ if (this.cachedSession && this.cachedSession.expiresAt - 30 > Math.floor(Date.now() / 1e3)) return this.cachedSession;
49
+ const acc = this.account();
50
+ if (!this.opts.appSlug) throw withCode(new Error("sampling bridge has no app_slug — pass `--app-slug <slug>` to `anna-app executa dev` so nexus can attribute the spend"), -32602);
51
+ const url = `${canonicalHost(acc.host)}/api/v1/anna-apps/dev/session/mint`;
52
+ const res = await fetch(url, {
53
+ method: "POST",
54
+ headers: { "content-type": "application/json" },
55
+ body: JSON.stringify({
56
+ pat: acc.pat,
57
+ kind: "complete",
58
+ app_slug: this.opts.appSlug
59
+ })
60
+ });
61
+ if (!res.ok) {
62
+ const text = await res.text().catch(() => "");
63
+ throw withCode(new Error(`session.mint failed: HTTP ${res.status} ${text}`), -32e3);
64
+ }
65
+ const body = await res.json();
66
+ this.cachedSession = {
67
+ appSessionToken: body.app_session_token,
68
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.expires_in || 600)
69
+ };
70
+ return this.cachedSession;
71
+ }
72
+ async realMessage(req) {
73
+ const acc = this.account();
74
+ const session = await this.mint();
75
+ const body = mcpToCompleteBody(req);
76
+ const res = await fetch(`${canonicalHost(acc.host)}/api/v1/copilot/app/complete`, {
77
+ method: "POST",
78
+ headers: {
79
+ "content-type": "application/json",
80
+ authorization: `Bearer ${session.appSessionToken}`
81
+ },
82
+ body: JSON.stringify(body)
83
+ });
84
+ if (!res.ok) {
85
+ const text = await res.text().catch(() => "");
86
+ throw withCode(new Error(`HTTP ${res.status}: ${text}`), -32e3);
87
+ }
88
+ const out = await res.json();
89
+ return normaliseSamplingResult(out, out.model ?? "unknown");
90
+ }
91
+ };
92
+ function withCode(err, code) {
93
+ err.rpcCode = code;
94
+ return err;
95
+ }
96
+ /** Collect a single string view of a sampling request — used for mock matching. */
97
+ function collectContent(req) {
98
+ const parts = [];
99
+ if (req.systemPrompt) parts.push(req.systemPrompt);
100
+ for (const m of req.messages ?? []) {
101
+ const c = m.content;
102
+ if (typeof c === "string") parts.push(c);
103
+ else if (c && typeof c === "object" && "text" in c) parts.push(c.text);
104
+ }
105
+ return parts.join("\n");
106
+ }
107
+ /**
108
+ * Translate an MCP `sampling/createMessage` request to the nexus
109
+ * `/api/v1/copilot/app/complete` body. Mapping is intentionally lossy —
110
+ * we forward only the fields nexus understands.
111
+ */
112
+ function mcpToCompleteBody(req) {
113
+ const messages = (req.messages ?? []).map((m) => ({
114
+ role: m.role,
115
+ content: typeof m.content === "string" ? m.content : m.content.text
116
+ }));
117
+ const out = { messages };
118
+ if (req.systemPrompt) out.system = req.systemPrompt;
119
+ if (req.maxTokens != null) out.max_tokens = req.maxTokens;
120
+ if (req.temperature != null) out.temperature = req.temperature;
121
+ if (req.stopSequences) out.stop = req.stopSequences;
122
+ const hint = req.modelPreferences?.hints?.[0]?.name;
123
+ if (hint) out.model_hint = hint;
124
+ if (req.metadata) out.metadata = req.metadata;
125
+ return out;
126
+ }
127
+ /** Coerce arbitrary nexus / fixture output into a `SamplingResult`. */
128
+ function normaliseSamplingResult(raw, fallbackModel) {
129
+ if (!raw || typeof raw !== "object") return {
130
+ role: "assistant",
131
+ content: {
132
+ type: "text",
133
+ text: String(raw ?? "")
134
+ },
135
+ model: fallbackModel,
136
+ stopReason: "endTurn"
137
+ };
138
+ const r = raw;
139
+ if (r.role === "assistant" && r.content && typeof r.content === "object") return r;
140
+ const text = typeof r.text === "string" && r.text || (typeof r.content === "string" ? r.content : "") || (typeof r.message === "string" ? r.message : "") || "";
141
+ return {
142
+ role: "assistant",
143
+ content: {
144
+ type: "text",
145
+ text
146
+ },
147
+ model: r.model ?? fallbackModel,
148
+ stopReason: r.stop_reason ?? r.stopReason ?? "endTurn",
149
+ usage: r.usage ?? void 0,
150
+ _meta: { source: "nexus" }
151
+ };
152
+ }
153
+
154
+ //#endregion
155
+ export { SamplingBridge };
@@ -1,4 +1,4 @@
1
- import { canonicalHost, getAccount } from "./credentials-ggdaz_-7.js";
1
+ import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
2
2
  import { dirname, join, normalize, resolve } from "node:path";
3
3
  import { createRequire } from "node:module";
4
4
  import { createReadStream, existsSync, readFileSync, statSync, watch } from "node:fs";
@@ -48,12 +48,12 @@ var LlmBridge = class {
48
48
  const cached = this.mintedAuto.get(windowUuid);
49
49
  if (cached && cached.expiresAt - 30 > Math.floor(Date.now() / 1e3)) return cached;
50
50
  const acc = this.account();
51
+ const slug = this.requireAppSlug();
51
52
  const body = {
52
53
  pat: acc.pat,
53
54
  kind: "complete",
54
- app_id: this.opts.appId ?? null
55
+ app_slug: slug
55
56
  };
56
- if (!body.app_id) body.app_id = 1;
57
57
  const minted = await this.callMint(acc.host, body);
58
58
  const ms = {
59
59
  appSessionToken: minted.app_session_token,
@@ -68,13 +68,14 @@ var LlmBridge = class {
68
68
  /** Mint a kind=agent session — called from `agent.session.create`. */
69
69
  async mintAgent(args) {
70
70
  const acc = this.account();
71
+ const slug = this.requireAppSlug();
71
72
  const submode = args.submode ?? "auto";
72
73
  const body = {
73
74
  pat: acc.pat,
74
75
  kind: "agent",
75
76
  submode,
76
77
  fixed_client_id: args.fixed_client_id ?? args.fixedClientId ?? null,
77
- app_id: this.opts.appId ?? 1,
78
+ app_slug: slug,
78
79
  label: args.label ?? "anna-app dev",
79
80
  quota_caps: args.quotaCaps ?? null
80
81
  };
@@ -89,6 +90,11 @@ var LlmBridge = class {
89
90
  this.mintedAgent.set(ms.appSessionUuid, ms);
90
91
  return ms;
91
92
  }
93
+ /** Throws a friendly error if no appSlug was wired into the bridge. */
94
+ requireAppSlug() {
95
+ if (!this.opts.appSlug) throw new Error("llm bridge has no app_slug — the harness must register the dev app via POST /api/v1/anna-apps/dev/apps/register before minting sessions. (Run `anna-app login` once, then `anna-app dev` will auto-register the manifest slug.)");
96
+ return this.opts.appSlug;
97
+ }
92
98
  async callMint(host, body) {
93
99
  const url = `${canonicalHost(host)}/api/v1/anna-apps/dev/session/mint`;
94
100
  const res = await fetch(url, {