@anna-ai/cli 0.1.16 → 0.1.19
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/{apps-Bt9CT5Sl.js → apps-ClgEOdKD.js} +1 -1
- package/dist/bridge-B3Vwr4cg.js +3 -0
- package/dist/{bridge-nqQ3-j-t.js → bridge-mkb_EM-y.js} +80 -2
- package/dist/cli.js +7 -6
- package/dist/dev-D8o7xi0W.js +3 -0
- package/dist/{dev-app-cache-DAHcq46m.js → dev-app-cache-3Pfesngr.js} +16 -2
- package/dist/dev-app-cache-CZ1UjMz0.js +4 -0
- package/dist/{dev-C6v5yRV2.js → dev-b1j-dEM2.js} +37 -10
- package/dist/{doctor-C8MWfLt8.js → doctor-CgJYokiR.js} +1 -1
- package/dist/{executa-dev-4FZ7AJHR.js → executa-dev-BeC6a8S8.js} +2 -2
- package/dist/{server-D8R6ppOp.js → server-BgJGmEpv.js} +316 -3
- package/package.json +1 -1
- package/dist/bridge-AJilXBw2.js +0 -3
- package/dist/dev-BRlFgo2I.js +0 -3
- package/dist/dev-app-cache-DGF2Kuzd.js +0 -4
|
@@ -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-3Pfesngr.js";
|
|
3
3
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
4
4
|
|
|
5
5
|
//#region src/commands/apps.ts
|
|
@@ -9,17 +9,39 @@ 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.0a6";
|
|
13
|
+
/**
|
|
14
|
+
* Throwable from a {@link RequestHandler} to send a structured JSON-RPC
|
|
15
|
+
* error back to the python bridge with a stable string ``code`` (e.g.
|
|
16
|
+
* ``"llm_disabled"``, ``"provider_error"``). The python side decodes the
|
|
17
|
+
* code from ``error.data.errorCode``.
|
|
18
|
+
*/
|
|
19
|
+
var BridgeRequestError = class extends Error {
|
|
20
|
+
constructor(code, message, details) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.details = details;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
13
26
|
var PythonBridge = class {
|
|
14
27
|
proc = null;
|
|
15
28
|
nextId = 1;
|
|
16
29
|
pending = new Map();
|
|
30
|
+
requestHandlers = new Map();
|
|
17
31
|
readyPromise = null;
|
|
18
32
|
closed = false;
|
|
19
33
|
constructor(opts) {
|
|
20
34
|
this.opts = opts;
|
|
21
35
|
}
|
|
22
36
|
/**
|
|
37
|
+
* Register a handler for an outbound python→node RPC method (e.g.
|
|
38
|
+
* ``host.llm.complete``). Subsequent registrations for the same method
|
|
39
|
+
* replace the previous handler.
|
|
40
|
+
*/
|
|
41
|
+
onRequest(method, handler) {
|
|
42
|
+
this.requestHandlers.set(method, handler);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
23
45
|
* Build the default launch command from `opts.mode`. uvx by default; for
|
|
24
46
|
* `nexus-source` mode, `matrixNexusRoot` must be set.
|
|
25
47
|
*/
|
|
@@ -93,6 +115,10 @@ var PythonBridge = class {
|
|
|
93
115
|
resolve$1();
|
|
94
116
|
return;
|
|
95
117
|
}
|
|
118
|
+
if (typeof env$1.method === "string") {
|
|
119
|
+
this.handleIncomingRequest(env$1);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
96
122
|
this.handleResponse(env$1);
|
|
97
123
|
});
|
|
98
124
|
this.proc.on("error", (e) => reject(e));
|
|
@@ -102,6 +128,58 @@ var PythonBridge = class {
|
|
|
102
128
|
});
|
|
103
129
|
await this.readyPromise;
|
|
104
130
|
}
|
|
131
|
+
async handleIncomingRequest(env) {
|
|
132
|
+
const method = env.method;
|
|
133
|
+
const id = env.id;
|
|
134
|
+
if (id == null) return;
|
|
135
|
+
const params = env.params ?? {};
|
|
136
|
+
const handler = this.requestHandlers.get(method);
|
|
137
|
+
if (!handler) {
|
|
138
|
+
this.writeFrame({
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
id,
|
|
141
|
+
error: {
|
|
142
|
+
code: -32601,
|
|
143
|
+
message: `method not found: ${method}`
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const result = await handler(params);
|
|
150
|
+
this.writeFrame({
|
|
151
|
+
jsonrpc: "2.0",
|
|
152
|
+
id,
|
|
153
|
+
result: result ?? {}
|
|
154
|
+
});
|
|
155
|
+
} catch (e) {
|
|
156
|
+
if (e instanceof BridgeRequestError) this.writeFrame({
|
|
157
|
+
jsonrpc: "2.0",
|
|
158
|
+
id,
|
|
159
|
+
error: {
|
|
160
|
+
code: -32e3,
|
|
161
|
+
message: e.message,
|
|
162
|
+
data: {
|
|
163
|
+
errorCode: e.code,
|
|
164
|
+
...e.details ?? {}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
else this.writeFrame({
|
|
169
|
+
jsonrpc: "2.0",
|
|
170
|
+
id,
|
|
171
|
+
error: {
|
|
172
|
+
code: -32603,
|
|
173
|
+
message: e.message,
|
|
174
|
+
data: { errorCode: "internal_error" }
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
writeFrame(env) {
|
|
180
|
+
if (!this.proc) return;
|
|
181
|
+
this.proc.stdin.write(`${JSON.stringify(env)}\n`);
|
|
182
|
+
}
|
|
105
183
|
handleResponse(env) {
|
|
106
184
|
const id = env.id;
|
|
107
185
|
if (id == null) return;
|
|
@@ -143,4 +221,4 @@ var PythonBridge = class {
|
|
|
143
221
|
};
|
|
144
222
|
|
|
145
223
|
//#endregion
|
|
146
|
-
export { PINNED_RUNTIME_VERSION, PythonBridge };
|
|
224
|
+
export { BridgeRequestError, PINNED_RUNTIME_VERSION, PythonBridge };
|
package/dist/cli.js
CHANGED
|
@@ -450,8 +450,8 @@ program.command("validate").description("Run schema + ACL checks on a manifest+b
|
|
|
450
450
|
const code = printResult(result);
|
|
451
451
|
process.exit(code);
|
|
452
452
|
});
|
|
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) => {
|
|
454
|
-
const { runDev, parseExecutaSpec } = await import("./dev-
|
|
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)").option("--storage <mode>", "Storage backend: \"legacy\" (in-memory runtime_state, default) or \"aps\" (real nexus APS via /api/v1/storage/* — requires `anna-app login`).", "legacy").action(async (opts) => {
|
|
454
|
+
const { runDev, parseExecutaSpec } = await import("./dev-D8o7xi0W.js");
|
|
455
455
|
const cwd = opts.cwd ?? process.cwd();
|
|
456
456
|
let executas;
|
|
457
457
|
if (opts.executa && opts.executa.length > 0) {
|
|
@@ -479,7 +479,8 @@ program.command("dev").description("Run a local harness (in-process dispatcher +
|
|
|
479
479
|
noLlm: opts.llm === false,
|
|
480
480
|
mockLlm: opts.mockLlm,
|
|
481
481
|
llmAccount: opts.llmAccount,
|
|
482
|
-
llmAppSlug: opts.llmAppSlug
|
|
482
|
+
llmAppSlug: opts.llmAppSlug,
|
|
483
|
+
storageMode: opts.storage
|
|
483
484
|
});
|
|
484
485
|
process.exit(code);
|
|
485
486
|
});
|
|
@@ -509,7 +510,7 @@ fixture.command("replay <file>").description("Dry-run replay of a harness record
|
|
|
509
510
|
process.exit(code);
|
|
510
511
|
});
|
|
511
512
|
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) => {
|
|
512
|
-
const { runDoctor } = await import("./doctor-
|
|
513
|
+
const { runDoctor } = await import("./doctor-CgJYokiR.js");
|
|
513
514
|
const code = await runDoctor({ matrixNexusRoot: opts.matrixNexusRoot });
|
|
514
515
|
process.exit(code);
|
|
515
516
|
});
|
|
@@ -535,7 +536,7 @@ program.command("whoami").description("Show the current account (and any others)
|
|
|
535
536
|
process.exit(code);
|
|
536
537
|
});
|
|
537
538
|
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) => {
|
|
538
|
-
const { runAppsList } = await import("./apps-
|
|
539
|
+
const { runAppsList } = await import("./apps-ClgEOdKD.js");
|
|
539
540
|
const code = await runAppsList({
|
|
540
541
|
account: opts.account,
|
|
541
542
|
json: opts.json
|
|
@@ -570,7 +571,7 @@ executa.command("register").description("Register a standalone executa with nexu
|
|
|
570
571
|
process.exit(code);
|
|
571
572
|
});
|
|
572
573
|
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-
|
|
574
|
+
const { runExecutaDev } = await import("./executa-dev-BeC6a8S8.js");
|
|
574
575
|
const storageMode = opts.storage === void 0 ? void 0 : (() => {
|
|
575
576
|
const m = opts.storage;
|
|
576
577
|
if (m === "off" || m === "memory" || m === "mock" || m === "real") return m;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalHost } from "./credentials-BTv2IfUZ.js";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
|
|
5
5
|
//#region src/dev-app-cache.ts
|
|
6
6
|
const CACHE_DIR = ".anna";
|
|
@@ -28,6 +28,20 @@ function writeDevAppCache(cwd, entry) {
|
|
|
28
28
|
mkdirSync(dirname(p), { recursive: true });
|
|
29
29
|
writeFileSync(p, JSON.stringify(entry, null, 2) + "\n", "utf-8");
|
|
30
30
|
}
|
|
31
|
+
/** Delete the local dev-app cache file.
|
|
32
|
+
*
|
|
33
|
+
* Used by the LLM bridge's recovery flow: when `/dev/session/mint`
|
|
34
|
+
* returns 404 with "app slug ... not found", the cache is stale (the
|
|
35
|
+
* nexus DB no longer has the row this cache asserts is registered).
|
|
36
|
+
* Invalidating forces the next `ensureDevAppRegistered` call to
|
|
37
|
+
* re-POST `/dev/apps/register`. Idempotent: a missing file is a no-op.
|
|
38
|
+
*/
|
|
39
|
+
function invalidateDevAppCache(cwd) {
|
|
40
|
+
const p = cachePath(cwd);
|
|
41
|
+
if (existsSync(p)) try {
|
|
42
|
+
unlinkSync(p);
|
|
43
|
+
} catch {}
|
|
44
|
+
}
|
|
31
45
|
/** Call POST /api/v1/anna-apps/dev/apps/register. Idempotent server-side. */
|
|
32
46
|
async function registerDevApp(args) {
|
|
33
47
|
const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps/register`;
|
|
@@ -172,4 +186,4 @@ async function ensureDevExecutaRegistered(args) {
|
|
|
172
186
|
}
|
|
173
187
|
|
|
174
188
|
//#endregion
|
|
175
|
-
export { ensureDevAppRegistered, ensureDevExecutaRegistered, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache };
|
|
189
|
+
export { ensureDevAppRegistered, ensureDevExecutaRegistered, invalidateDevAppCache, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import { ensureDevAppRegistered, ensureDevExecutaRegistered, invalidateDevAppCache, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache } from "./dev-app-cache-3Pfesngr.js";
|
|
3
|
+
|
|
4
|
+
export { ensureDevAppRegistered, ensureDevExecutaRegistered, invalidateDevAppCache };
|
|
@@ -31,8 +31,20 @@ 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
|
|
35
|
-
|
|
34
|
+
const storageMode = opts.storageMode === "aps" ? "aps" : "legacy";
|
|
35
|
+
if (opts.storageMode && opts.storageMode !== "legacy" && opts.storageMode !== "aps") {
|
|
36
|
+
console.error(red(`✗ --storage must be "legacy" or "aps", got "${opts.storageMode}"`));
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
if (storageMode === "aps") {
|
|
40
|
+
if (opts.noLlm || opts.mockLlm) {
|
|
41
|
+
console.error(red("✗ --storage aps requires a real LLM bridge (PAT on disk); drop --no-llm / --mock-llm or switch back to --storage legacy."));
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
process.env.ANNA_APP_RUNTIME_STORAGE_MODE = "aps";
|
|
45
|
+
}
|
|
46
|
+
const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-B3Vwr4cg.js");
|
|
47
|
+
const { HarnessServer } = await import("./server-BgJGmEpv.js");
|
|
36
48
|
const bridge = new PythonBridge({
|
|
37
49
|
mode,
|
|
38
50
|
matrixNexusRoot: matrixNexusRoot ?? void 0,
|
|
@@ -75,7 +87,9 @@ async function runDev(opts) {
|
|
|
75
87
|
manifest
|
|
76
88
|
});
|
|
77
89
|
if (llm === null) return 2;
|
|
90
|
+
llm.storageMode = storageMode;
|
|
78
91
|
console.log(` llm bridge ${dim(llm.mode === "off" ? "disabled (--no-llm)" : llm.mode === "mock" ? `mock (${opts.mockLlm})` : `real${opts.llmAccount ? ` [${opts.llmAccount}]` : ""} → app_slug=${llm.appSlug}`)}`);
|
|
92
|
+
console.log(` storage backend ${dim(storageMode === "aps" ? "aps (real nexus APS via /api/v1/storage/*)" : "legacy (in-memory runtime_state)")}`);
|
|
79
93
|
const server = new HarnessServer({
|
|
80
94
|
slug,
|
|
81
95
|
manifest,
|
|
@@ -123,7 +137,7 @@ async function runDev(opts) {
|
|
|
123
137
|
*/
|
|
124
138
|
async function resolveRealLlm(args) {
|
|
125
139
|
const { getAccount } = await import("./credentials-DDqx6XMQ.js");
|
|
126
|
-
const { ensureDevAppRegistered } = await import("./dev-app-cache-
|
|
140
|
+
const { ensureDevAppRegistered } = await import("./dev-app-cache-CZ1UjMz0.js");
|
|
127
141
|
const acc = getAccount(args.account);
|
|
128
142
|
if (!acc) {
|
|
129
143
|
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.)"));
|
|
@@ -134,22 +148,35 @@ async function resolveRealLlm(args) {
|
|
|
134
148
|
return null;
|
|
135
149
|
}
|
|
136
150
|
const manifest = args.manifest;
|
|
151
|
+
const registerInput = {
|
|
152
|
+
slug: args.appSlug,
|
|
153
|
+
name: manifest.name,
|
|
154
|
+
category: manifest.category,
|
|
155
|
+
tagline: manifest.tagline
|
|
156
|
+
};
|
|
137
157
|
try {
|
|
138
158
|
const entry = await ensureDevAppRegistered({
|
|
139
159
|
cwd: args.cwd,
|
|
140
160
|
host: acc.host,
|
|
141
161
|
pat: acc.pat,
|
|
142
|
-
input:
|
|
143
|
-
slug: args.appSlug,
|
|
144
|
-
name: manifest.name,
|
|
145
|
-
category: manifest.category,
|
|
146
|
-
tagline: manifest.tagline
|
|
147
|
-
}
|
|
162
|
+
input: registerInput
|
|
148
163
|
});
|
|
149
164
|
return {
|
|
150
165
|
mode: "real",
|
|
151
166
|
account: args.account,
|
|
152
|
-
appSlug: entry.slug
|
|
167
|
+
appSlug: entry.slug,
|
|
168
|
+
onAppSlugNotFound: async () => {
|
|
169
|
+
const { invalidateDevAppCache } = await import("./dev-app-cache-CZ1UjMz0.js");
|
|
170
|
+
invalidateDevAppCache(args.cwd);
|
|
171
|
+
const fresh = getAccount(args.account);
|
|
172
|
+
if (!fresh) throw new Error("PAT not found while re-registering dev app — run `anna-app login` again");
|
|
173
|
+
await ensureDevAppRegistered({
|
|
174
|
+
cwd: args.cwd,
|
|
175
|
+
host: fresh.host,
|
|
176
|
+
pat: fresh.pat,
|
|
177
|
+
input: registerInput
|
|
178
|
+
});
|
|
179
|
+
}
|
|
153
180
|
};
|
|
154
181
|
} catch (e) {
|
|
155
182
|
console.error(red(`✗ failed to register dev app on nexus: ${e.message}`));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PINNED_RUNTIME_VERSION } from "./bridge-
|
|
1
|
+
import { PINNED_RUNTIME_VERSION } from "./bridge-mkb_EM-y.js";
|
|
2
2
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseExecutaSpec } from "./dev-
|
|
1
|
+
import { parseExecutaSpec } from "./dev-b1j-dEM2.js";
|
|
2
2
|
import { isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
@@ -34,7 +34,7 @@ async function runExecutaDev(opts) {
|
|
|
34
34
|
const oneShot = !!(opts.describe || opts.health || opts.invoke);
|
|
35
35
|
const quiet = oneShot && (opts.json ?? false);
|
|
36
36
|
const { getAccount } = await import("./credentials-DDqx6XMQ.js");
|
|
37
|
-
const { ensureDevExecutaRegistered } = await import("./dev-app-cache-
|
|
37
|
+
const { ensureDevExecutaRegistered } = await import("./dev-app-cache-CZ1UjMz0.js");
|
|
38
38
|
const needsRealMint = !opts.noSampling && !opts.mockSampling || !opts.noAgent && !opts.mockAgent || !opts.noImage && !opts.mockImage || !opts.noUpload && !opts.mockUpload || opts.storage === "real";
|
|
39
39
|
let effectiveAppSlug = opts.appSlug;
|
|
40
40
|
let autoRegistered = false;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import { BridgeRequestError } from "./bridge-mkb_EM-y.js";
|
|
2
3
|
import { dirname, join, normalize, resolve } from "node:path";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { createReadStream, existsSync, readFileSync, statSync, watch } from "node:fs";
|
|
@@ -12,6 +13,10 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
|
12
13
|
var LlmBridge = class {
|
|
13
14
|
mintedAuto = new Map();
|
|
14
15
|
mintedAgent = new Map();
|
|
16
|
+
/** Single shared storage_token per LlmBridge instance — scope is per
|
|
17
|
+
* ``(user, app)`` server-side, not per-window, so caching one is
|
|
18
|
+
* correct. Re-minted automatically before expiry. */
|
|
19
|
+
mintedStorage = null;
|
|
15
20
|
mocks = [];
|
|
16
21
|
streamCounter = 0;
|
|
17
22
|
constructor(opts) {
|
|
@@ -30,10 +35,30 @@ var LlmBridge = class {
|
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
/** Returns true iff this bridge handles `(ns, method)` (i.e. the harness
|
|
33
|
-
* should NOT forward it to the in-process Python dispatcher).
|
|
38
|
+
* should NOT forward it to the in-process Python dispatcher).
|
|
39
|
+
*
|
|
40
|
+
* Storage handling depends on ``storageMode``:
|
|
41
|
+
* - ``legacy`` (default): the bridge does NOT claim storage; the Python
|
|
42
|
+
* dispatcher implements ``anna.storage.*`` against the in-memory
|
|
43
|
+
* ``runtime_state`` dict. Pre-APS parity, works fully offline.
|
|
44
|
+
* - ``aps``: the bridge claims storage and forwards to real nexus APS
|
|
45
|
+
* via ``/api/v1/storage/*`` with a Bearer ``storage_token``. */
|
|
46
|
+
handles(ns, method) {
|
|
47
|
+
if (ns === "llm" && (method === "complete" || method === "embed")) return true;
|
|
48
|
+
if (ns === "agent" && method.startsWith("session.")) return true;
|
|
49
|
+
if (ns === "image" && (method === "generate" || method === "edit")) return true;
|
|
50
|
+
if (ns === "upload" && (method === "inline" || method === "negotiate" || method === "confirm")) return true;
|
|
51
|
+
if (ns === "storage" && this.opts.storageMode === "aps" && this.opts.mode === "real" && (method === "get" || method === "set" || method === "delete" || method === "list")) return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/** Back-compat static alias — older code paths still call
|
|
55
|
+
* ``LlmBridge.handles(ns, method)`` without an instance. Returns the
|
|
56
|
+
* legacy (no-storage) decision since options aren't available here. */
|
|
34
57
|
static handles(ns, method) {
|
|
35
|
-
if (ns === "llm" && method === "complete") return true;
|
|
58
|
+
if (ns === "llm" && (method === "complete" || method === "embed")) return true;
|
|
36
59
|
if (ns === "agent" && method.startsWith("session.")) return true;
|
|
60
|
+
if (ns === "image" && (method === "generate" || method === "edit")) return true;
|
|
61
|
+
if (ns === "upload" && (method === "inline" || method === "negotiate" || method === "confirm")) return true;
|
|
37
62
|
return false;
|
|
38
63
|
}
|
|
39
64
|
/** Resolve the active account or throw with a friendly message. */
|
|
@@ -90,12 +115,65 @@ var LlmBridge = class {
|
|
|
90
115
|
this.mintedAgent.set(ms.appSessionUuid, ms);
|
|
91
116
|
return ms;
|
|
92
117
|
}
|
|
118
|
+
/** Mint (or reuse) a storage_token for APS forwarding.
|
|
119
|
+
*
|
|
120
|
+
* Server-side ``/dev/storage/mint`` issues a JWT scoped to
|
|
121
|
+
* ``(user, app)`` with ``allowed_scopes ⊆ {user, app, tool}`` and a
|
|
122
|
+
* configurable TTL (default 600s) + ``max_calls`` budget (default 200).
|
|
123
|
+
* We re-mint when within 30s of expiry; budget exhaustion currently
|
|
124
|
+
* surfaces as an HTTP 401/403 which propagates back to the iframe.
|
|
125
|
+
*
|
|
126
|
+
* Per-app, not per-window: the token doesn't carry a window or
|
|
127
|
+
* session uuid — storage scopes are ``user``/``app``/``tool``, not
|
|
128
|
+
* ``window``. Caching one per LlmBridge instance is correct. */
|
|
129
|
+
async mintStorage() {
|
|
130
|
+
const cached = this.mintedStorage;
|
|
131
|
+
if (cached && cached.expiresAt - 30 > Math.floor(Date.now() / 1e3)) return cached;
|
|
132
|
+
const acc = this.account();
|
|
133
|
+
const slug = this.requireAppSlug();
|
|
134
|
+
const body = {
|
|
135
|
+
pat: acc.pat,
|
|
136
|
+
app_slug: slug,
|
|
137
|
+
ttl_seconds: 600,
|
|
138
|
+
max_calls: 200
|
|
139
|
+
};
|
|
140
|
+
const url = `${canonicalHost(acc.host)}/api/v1/anna-apps/dev/storage/mint`;
|
|
141
|
+
const res = await fetch(url, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "content-type": "application/json" },
|
|
144
|
+
body: JSON.stringify(body)
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
const text = await res.text().catch(() => "");
|
|
148
|
+
throw new Error(`storage.mint failed: HTTP ${res.status} ${text}`);
|
|
149
|
+
}
|
|
150
|
+
const payload = await res.json();
|
|
151
|
+
const ms = {
|
|
152
|
+
storageToken: payload.storage_token,
|
|
153
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (payload.expires_in || 600),
|
|
154
|
+
allowedScopes: payload.allowed_scopes ?? [
|
|
155
|
+
"user",
|
|
156
|
+
"app",
|
|
157
|
+
"tool"
|
|
158
|
+
]
|
|
159
|
+
};
|
|
160
|
+
this.mintedStorage = ms;
|
|
161
|
+
return ms;
|
|
162
|
+
}
|
|
93
163
|
/** Throws a friendly error if no appSlug was wired into the bridge. */
|
|
94
164
|
requireAppSlug() {
|
|
95
165
|
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
166
|
return this.opts.appSlug;
|
|
97
167
|
}
|
|
98
168
|
async callMint(host, body) {
|
|
169
|
+
return this._callMintOnce(
|
|
170
|
+
host,
|
|
171
|
+
body,
|
|
172
|
+
/*retryOnAppNotFound=*/
|
|
173
|
+
true
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
async _callMintOnce(host, body, retryOnAppNotFound) {
|
|
99
177
|
const url = `${canonicalHost(host)}/api/v1/anna-apps/dev/session/mint`;
|
|
100
178
|
const res = await fetch(url, {
|
|
101
179
|
method: "POST",
|
|
@@ -104,6 +182,19 @@ var LlmBridge = class {
|
|
|
104
182
|
});
|
|
105
183
|
if (!res.ok) {
|
|
106
184
|
const text = await res.text().catch(() => "");
|
|
185
|
+
if (retryOnAppNotFound && res.status === 404 && /app slug .* not found/.test(text) && typeof this.opts.onAppSlugNotFound === "function") {
|
|
186
|
+
try {
|
|
187
|
+
await this.opts.onAppSlugNotFound();
|
|
188
|
+
} catch (hookErr) {
|
|
189
|
+
throw new Error(`session.mint failed: HTTP 404 ${text}\n recovery hook failed: ${hookErr.message}`);
|
|
190
|
+
}
|
|
191
|
+
return this._callMintOnce(
|
|
192
|
+
host,
|
|
193
|
+
body,
|
|
194
|
+
/*retryOnAppNotFound=*/
|
|
195
|
+
false
|
|
196
|
+
);
|
|
197
|
+
}
|
|
107
198
|
throw new Error(`session.mint failed: HTTP ${res.status} ${text}`);
|
|
108
199
|
}
|
|
109
200
|
return await res.json();
|
|
@@ -194,6 +285,14 @@ var LlmBridge = class {
|
|
|
194
285
|
result
|
|
195
286
|
};
|
|
196
287
|
}
|
|
288
|
+
if (args.ns === "llm" && args.method === "embed") {
|
|
289
|
+
const ms = await this.mintComplete(args.windowUuid);
|
|
290
|
+
const result = await this.postJson(`${canonicalHost(acc.host)}/api/v1/copilot/app/embed`, ms.appSessionToken, args.args);
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
result
|
|
294
|
+
};
|
|
295
|
+
}
|
|
197
296
|
if (args.ns === "agent") switch (args.method) {
|
|
198
297
|
case "session.create": {
|
|
199
298
|
const ms = await this.mintAgent(args.args);
|
|
@@ -290,6 +389,121 @@ var LlmBridge = class {
|
|
|
290
389
|
};
|
|
291
390
|
}
|
|
292
391
|
}
|
|
392
|
+
if (args.ns === "image" && (args.method === "generate" || args.method === "edit")) {
|
|
393
|
+
const ms = await this.mintComplete(args.windowUuid);
|
|
394
|
+
const result = await this.postJson(`${canonicalHost(acc.host)}/api/v1/copilot/app/image/${args.method}`, ms.appSessionToken, args.args);
|
|
395
|
+
return {
|
|
396
|
+
ok: true,
|
|
397
|
+
result
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (args.ns === "upload") {
|
|
401
|
+
const ms = await this.mintComplete(args.windowUuid);
|
|
402
|
+
const path = args.method === "inline" ? "/api/v1/copilot/app/upload" : args.method === "negotiate" ? "/api/v1/copilot/app/upload/negotiate" : args.method === "confirm" ? "/api/v1/copilot/app/upload/confirm" : null;
|
|
403
|
+
if (path != null) {
|
|
404
|
+
const result = await this.postJson(`${canonicalHost(acc.host)}${path}`, ms.appSessionToken, args.args);
|
|
405
|
+
return {
|
|
406
|
+
ok: true,
|
|
407
|
+
result
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (args.ns === "storage") {
|
|
412
|
+
const sm = await this.mintStorage();
|
|
413
|
+
const a = args.args;
|
|
414
|
+
const base = `${canonicalHost(acc.host)}/api/v1/storage`;
|
|
415
|
+
if (args.method === "get") {
|
|
416
|
+
const qs = new URLSearchParams();
|
|
417
|
+
qs.set("key", String(a.key ?? ""));
|
|
418
|
+
if (a.scope) qs.set("scope", String(a.scope));
|
|
419
|
+
const res = await fetch(`${base}/kv?${qs.toString()}`, {
|
|
420
|
+
method: "GET",
|
|
421
|
+
headers: { authorization: `Bearer ${sm.storageToken}` }
|
|
422
|
+
});
|
|
423
|
+
if (res.status === 404) return {
|
|
424
|
+
ok: true,
|
|
425
|
+
result: {
|
|
426
|
+
value: null,
|
|
427
|
+
exists: false
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
if (!res.ok) {
|
|
431
|
+
const text = await res.text().catch(() => "");
|
|
432
|
+
throw new Error(`storage.get HTTP ${res.status}: ${text}`);
|
|
433
|
+
}
|
|
434
|
+
const entry = await res.json();
|
|
435
|
+
if (entry.exists === void 0) entry.exists = true;
|
|
436
|
+
return {
|
|
437
|
+
ok: true,
|
|
438
|
+
result: entry
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (args.method === "set") {
|
|
442
|
+
const qs = new URLSearchParams();
|
|
443
|
+
if (a.scope) qs.set("scope", String(a.scope));
|
|
444
|
+
const body = {
|
|
445
|
+
key: a.key,
|
|
446
|
+
value: a.value
|
|
447
|
+
};
|
|
448
|
+
if (a.if_match !== void 0) body.if_match = a.if_match;
|
|
449
|
+
if (a.metadata !== void 0) body.metadata = a.metadata;
|
|
450
|
+
if (a.tags !== void 0) body.tags = a.tags;
|
|
451
|
+
if (a.ttl_seconds !== void 0) body.ttl_seconds = a.ttl_seconds;
|
|
452
|
+
const res = await fetch(`${base}/kv?${qs.toString()}`, {
|
|
453
|
+
method: "PUT",
|
|
454
|
+
headers: {
|
|
455
|
+
"content-type": "application/json",
|
|
456
|
+
authorization: `Bearer ${sm.storageToken}`
|
|
457
|
+
},
|
|
458
|
+
body: JSON.stringify(body)
|
|
459
|
+
});
|
|
460
|
+
if (!res.ok) {
|
|
461
|
+
const text = await res.text().catch(() => "");
|
|
462
|
+
throw new Error(`storage.set HTTP ${res.status}: ${text}`);
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
ok: true,
|
|
466
|
+
result: await res.json()
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
if (args.method === "delete") {
|
|
470
|
+
const qs = new URLSearchParams();
|
|
471
|
+
qs.set("key", String(a.key ?? ""));
|
|
472
|
+
if (a.if_match !== void 0) qs.set("if_match", String(a.if_match));
|
|
473
|
+
if (a.scope) qs.set("scope", String(a.scope));
|
|
474
|
+
const res = await fetch(`${base}/kv?${qs.toString()}`, {
|
|
475
|
+
method: "DELETE",
|
|
476
|
+
headers: { authorization: `Bearer ${sm.storageToken}` }
|
|
477
|
+
});
|
|
478
|
+
if (!res.ok) {
|
|
479
|
+
const text = await res.text().catch(() => "");
|
|
480
|
+
throw new Error(`storage.delete HTTP ${res.status}: ${text}`);
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
ok: true,
|
|
484
|
+
result: await res.json()
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (args.method === "list") {
|
|
488
|
+
const qs = new URLSearchParams();
|
|
489
|
+
if (a.prefix !== void 0) qs.set("prefix", String(a.prefix));
|
|
490
|
+
if (a.cursor !== void 0) qs.set("cursor", String(a.cursor));
|
|
491
|
+
if (a.limit !== void 0) qs.set("limit", String(a.limit));
|
|
492
|
+
if (a.scope) qs.set("scope", String(a.scope));
|
|
493
|
+
const res = await fetch(`${base}/list?${qs.toString()}`, {
|
|
494
|
+
method: "GET",
|
|
495
|
+
headers: { authorization: `Bearer ${sm.storageToken}` }
|
|
496
|
+
});
|
|
497
|
+
if (!res.ok) {
|
|
498
|
+
const text = await res.text().catch(() => "");
|
|
499
|
+
throw new Error(`storage.list HTTP ${res.status}: ${text}`);
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
ok: true,
|
|
503
|
+
result: await res.json()
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
293
507
|
return {
|
|
294
508
|
ok: false,
|
|
295
509
|
error: {
|
|
@@ -434,6 +648,105 @@ var HarnessServer = class {
|
|
|
434
648
|
this.cfg = cfg;
|
|
435
649
|
this.bridge = bridge;
|
|
436
650
|
this.llmBridge = cfg.llm ? new LlmBridge(cfg.llm) : null;
|
|
651
|
+
const HOST_OUTBOUND_ROUTES = [
|
|
652
|
+
[
|
|
653
|
+
"host.llm.complete",
|
|
654
|
+
"llm",
|
|
655
|
+
"complete"
|
|
656
|
+
],
|
|
657
|
+
[
|
|
658
|
+
"host.llm.embed",
|
|
659
|
+
"llm",
|
|
660
|
+
"embed"
|
|
661
|
+
],
|
|
662
|
+
[
|
|
663
|
+
"host.agent.session.create",
|
|
664
|
+
"agent",
|
|
665
|
+
"session.create"
|
|
666
|
+
],
|
|
667
|
+
[
|
|
668
|
+
"host.agent.session.run",
|
|
669
|
+
"agent",
|
|
670
|
+
"session.run"
|
|
671
|
+
],
|
|
672
|
+
[
|
|
673
|
+
"host.agent.session.cancel",
|
|
674
|
+
"agent",
|
|
675
|
+
"session.cancel"
|
|
676
|
+
],
|
|
677
|
+
[
|
|
678
|
+
"host.agent.session.history",
|
|
679
|
+
"agent",
|
|
680
|
+
"session.history"
|
|
681
|
+
],
|
|
682
|
+
[
|
|
683
|
+
"host.agent.session.delete",
|
|
684
|
+
"agent",
|
|
685
|
+
"session.delete"
|
|
686
|
+
],
|
|
687
|
+
[
|
|
688
|
+
"host.image.generate",
|
|
689
|
+
"image",
|
|
690
|
+
"generate"
|
|
691
|
+
],
|
|
692
|
+
[
|
|
693
|
+
"host.image.edit",
|
|
694
|
+
"image",
|
|
695
|
+
"edit"
|
|
696
|
+
],
|
|
697
|
+
[
|
|
698
|
+
"host.upload.inline",
|
|
699
|
+
"upload",
|
|
700
|
+
"inline"
|
|
701
|
+
],
|
|
702
|
+
[
|
|
703
|
+
"host.upload.negotiate",
|
|
704
|
+
"upload",
|
|
705
|
+
"negotiate"
|
|
706
|
+
],
|
|
707
|
+
[
|
|
708
|
+
"host.upload.confirm",
|
|
709
|
+
"upload",
|
|
710
|
+
"confirm"
|
|
711
|
+
],
|
|
712
|
+
[
|
|
713
|
+
"host.storage.get",
|
|
714
|
+
"storage",
|
|
715
|
+
"get"
|
|
716
|
+
],
|
|
717
|
+
[
|
|
718
|
+
"host.storage.set",
|
|
719
|
+
"storage",
|
|
720
|
+
"set"
|
|
721
|
+
],
|
|
722
|
+
[
|
|
723
|
+
"host.storage.delete",
|
|
724
|
+
"storage",
|
|
725
|
+
"delete"
|
|
726
|
+
],
|
|
727
|
+
[
|
|
728
|
+
"host.storage.list",
|
|
729
|
+
"storage",
|
|
730
|
+
"list"
|
|
731
|
+
]
|
|
732
|
+
];
|
|
733
|
+
for (const [hostMethod, ns, dispatchMethod] of HOST_OUTBOUND_ROUTES) this.bridge.onRequest(hostMethod, async (params) => {
|
|
734
|
+
if (this.llmBridge == null) throw new BridgeRequestError("llm_disabled", "harness started without an LlmBridge (use --no-llm to suppress this path or `anna-app login` for a real bridge)");
|
|
735
|
+
const out = await this.llmBridge.dispatch({
|
|
736
|
+
windowUuid: this.sessionId ?? "harness",
|
|
737
|
+
ns,
|
|
738
|
+
method: dispatchMethod,
|
|
739
|
+
args: params,
|
|
740
|
+
onEvent: (kind, payload) => {
|
|
741
|
+
this.llmEventQueue.push({
|
|
742
|
+
event: kind,
|
|
743
|
+
payload
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
if (out.ok) return out.result;
|
|
748
|
+
throw new BridgeRequestError(out.error.code, out.error.message);
|
|
749
|
+
});
|
|
437
750
|
}
|
|
438
751
|
async listen() {
|
|
439
752
|
if (this.cfg.executas && this.cfg.executas.length > 0) await this.bridge.call("executas.register", { executas: this.cfg.executas.map((e) => ({
|
|
@@ -577,7 +890,7 @@ var HarnessServer = class {
|
|
|
577
890
|
}
|
|
578
891
|
});
|
|
579
892
|
}
|
|
580
|
-
if (this.llmBridge != null &&
|
|
893
|
+
if (this.llmBridge != null && this.llmBridge.handles(parsed.ns, parsed.method)) {
|
|
581
894
|
const out = await this.llmBridge.dispatch({
|
|
582
895
|
windowUuid: this.sessionId ?? "harness",
|
|
583
896
|
ns: parsed.ns,
|
package/package.json
CHANGED
package/dist/bridge-AJilXBw2.js
DELETED
package/dist/dev-BRlFgo2I.js
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import "./credentials-BTv2IfUZ.js";
|
|
2
|
-
import { ensureDevAppRegistered, ensureDevExecutaRegistered, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache } from "./dev-app-cache-DAHcq46m.js";
|
|
3
|
-
|
|
4
|
-
export { ensureDevAppRegistered, ensureDevExecutaRegistered };
|