@wrenai/genbi 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -2
- package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
- package/dist/assets/index-A_Wrq7Ey.css +1 -0
- package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
- package/dist/index.html +2 -2
- package/dist-server/harness/bundle/schema.d.ts +38 -0
- package/dist-server/harness/bundle/schema.js +18 -10
- package/dist-server/harness/bundle/version.js +1 -1
- package/dist-server/harness/cli-args.d.ts +10 -2
- package/dist-server/harness/cli-args.js +24 -8
- package/dist-server/harness/cli.js +75 -9
- package/dist-server/harness/compile/cache.js +4 -4
- package/dist-server/harness/compile/compose-profile.d.ts +51 -14
- package/dist-server/harness/compile/compose-profile.js +85 -19
- package/dist-server/harness/compile/context-loader.d.ts +56 -0
- package/dist-server/harness/compile/context-loader.js +237 -0
- package/dist-server/harness/compile/errors.d.ts +19 -0
- package/dist-server/harness/compile/errors.js +35 -0
- package/dist-server/harness/compile/index.d.ts +4 -3
- package/dist-server/harness/compile/index.js +4 -3
- package/dist-server/harness/compile/pipeline.d.ts +15 -7
- package/dist-server/harness/compile/pipeline.js +62 -14
- package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
- package/dist-server/harness/compile/resolve-binary.js +3 -3
- package/dist-server/harness/compile/types.d.ts +29 -0
- package/dist-server/harness/compile/warble-identity.d.ts +11 -0
- package/dist-server/harness/compile/warble-identity.js +13 -0
- package/dist-server/harness/components/ai-step.d.ts +4 -0
- package/dist-server/harness/components/ai-step.js +40 -0
- package/dist-server/harness/components/broker.d.ts +60 -0
- package/dist-server/harness/components/broker.js +117 -0
- package/dist-server/harness/components/capability-card.d.ts +98 -0
- package/dist-server/harness/components/capability-card.js +169 -0
- package/dist-server/harness/components/context.d.ts +2 -0
- package/dist-server/harness/components/context.js +30 -0
- package/dist-server/harness/components/display.d.ts +6 -0
- package/dist-server/harness/components/display.js +29 -0
- package/dist-server/harness/components/egress-judge.d.ts +12 -0
- package/dist-server/harness/components/egress-judge.js +27 -0
- package/dist-server/harness/components/egress.d.ts +142 -0
- package/dist-server/harness/components/egress.js +469 -0
- package/dist-server/harness/components/in-process.d.ts +7 -0
- package/dist-server/harness/components/in-process.js +245 -0
- package/dist-server/harness/components/normalize.d.ts +35 -0
- package/dist-server/harness/components/normalize.js +245 -0
- package/dist-server/harness/components/plan.d.ts +31 -0
- package/dist-server/harness/components/plan.js +184 -0
- package/dist-server/harness/components/report.d.ts +207 -0
- package/dist-server/harness/components/report.js +309 -0
- package/dist-server/harness/components/runner.d.ts +193 -0
- package/dist-server/harness/components/runner.js +492 -0
- package/dist-server/harness/components/semantic-guards.d.ts +20 -0
- package/dist-server/harness/components/semantic-guards.js +42 -0
- package/dist-server/harness/components/terminal-json.d.ts +18 -0
- package/dist-server/harness/components/terminal-json.js +82 -0
- package/dist-server/harness/components/usage.d.ts +14 -0
- package/dist-server/harness/components/usage.js +19 -0
- package/dist-server/harness/components/wren-access.d.ts +29 -0
- package/dist-server/harness/components/wren-access.js +233 -0
- package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
- package/dist-server/harness/components/zone-dry-run.js +107 -0
- package/dist-server/harness/components/zone-gate.d.ts +76 -0
- package/dist-server/harness/components/zone-gate.js +144 -0
- package/dist-server/harness/events/index.d.ts +1 -1
- package/dist-server/harness/events/types.d.ts +52 -0
- package/dist-server/harness/index.d.ts +9 -2
- package/dist-server/harness/index.js +5 -1
- package/dist-server/harness/loop/executor.d.ts +1 -0
- package/dist-server/harness/loop/executor.js +37 -15
- package/dist-server/harness/npm-package-resolve.d.ts +2 -0
- package/dist-server/harness/npm-package-resolve.js +16 -4
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
- package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
- package/dist-server/harness/providers/adapters/openai.js +6 -0
- package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
- package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
- package/dist-server/harness/providers/binding.d.ts +45 -4
- package/dist-server/harness/providers/binding.js +44 -8
- package/dist-server/harness/providers/index.d.ts +9 -3
- package/dist-server/harness/providers/index.js +10 -3
- package/dist-server/harness/providers/zone.d.ts +96 -0
- package/dist-server/harness/providers/zone.js +112 -0
- package/dist-server/harness/render/answering-query.d.ts +29 -0
- package/dist-server/harness/render/answering-query.js +62 -0
- package/dist-server/harness/render/envelope.d.ts +6 -0
- package/dist-server/harness/render/envelope.js +10 -3
- package/dist-server/harness/route/adapter-spec.d.ts +8 -9
- package/dist-server/harness/route/adapter-spec.js +13 -14
- package/dist-server/harness/route/codex-ask-events.js +2 -1
- package/dist-server/harness/route/codex-ask.d.ts +4 -0
- package/dist-server/harness/route/codex-ask.js +69 -30
- package/dist-server/harness/route/codex-host-query.d.ts +52 -0
- package/dist-server/harness/route/codex-host-query.js +191 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
- package/dist-server/harness/route/codex-local-manifest.js +96 -15
- package/dist-server/harness/route/codex-wren-proxy.d.ts +33 -0
- package/dist-server/harness/route/codex-wren-proxy.js +227 -0
- package/dist-server/harness/route/describe.js +45 -6
- package/dist-server/harness/route/in-process.d.ts +22 -1
- package/dist-server/harness/route/in-process.js +54 -4
- package/dist-server/harness/route/route.js +5 -2
- package/dist-server/harness/route/types.d.ts +30 -1
- package/dist-server/harness/session/run.js +30 -10
- package/dist-server/harness/setup/runner.d.ts +2 -0
- package/dist-server/harness/setup/runner.js +38 -17
- package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
- package/dist-server/harness/setup/yaml-scalar.js +62 -0
- package/dist-server/server/adopt.js +1 -17
- package/dist-server/server/app.d.ts +1 -0
- package/dist-server/server/app.js +163 -14
- package/dist-server/server/auth-choice.d.ts +13 -1
- package/dist-server/server/auth-choice.js +66 -1
- package/dist-server/server/bin.js +177 -25
- package/dist-server/server/codex-local-grounding.d.ts +18 -0
- package/dist-server/server/codex-local-grounding.js +92 -0
- package/dist-server/server/codex-native-session.d.ts +25 -0
- package/dist-server/server/codex-native-session.js +1 -0
- package/dist-server/server/codex-runtime-composition.d.ts +34 -0
- package/dist-server/server/codex-runtime-composition.js +250 -0
- package/dist-server/server/codex-structured-ask.d.ts +23 -0
- package/dist-server/server/codex-structured-ask.js +102 -0
- package/dist-server/server/conn-config.js +4 -16
- package/dist-server/server/conversation-socket.d.ts +16 -0
- package/dist-server/server/conversation-socket.js +65 -0
- package/dist-server/server/db.d.ts +46 -0
- package/dist-server/server/db.js +88 -9
- package/dist-server/server/env-detect.d.ts +6 -0
- package/dist-server/server/env-detect.js +7 -0
- package/dist-server/server/fold.d.ts +22 -4
- package/dist-server/server/fold.js +37 -5
- package/dist-server/server/harness.d.ts +2 -2
- package/dist-server/server/harness.js +33 -7
- package/dist-server/server/interactive-terminal.d.ts +10 -6
- package/dist-server/server/interactive-terminal.js +33 -8
- package/dist-server/server/managed-wren-runtime.d.ts +116 -0
- package/dist-server/server/managed-wren-runtime.js +670 -0
- package/dist-server/server/native-artifacts.d.ts +23 -7
- package/dist-server/server/native-artifacts.js +43 -12
- package/dist-server/server/native-component-context.d.ts +19 -0
- package/dist-server/server/native-component-context.js +76 -0
- package/dist-server/server/native-component-preparation.d.ts +20 -0
- package/dist-server/server/native-component-preparation.js +129 -0
- package/dist-server/server/native-component-runtime.d.ts +36 -0
- package/dist-server/server/native-component-runtime.js +92 -0
- package/dist-server/server/native-components.d.ts +84 -0
- package/dist-server/server/native-components.js +160 -0
- package/dist-server/server/native-dispatch-process.d.ts +8 -0
- package/dist-server/server/native-dispatch-process.js +65 -0
- package/dist-server/server/native-runtime-spec.d.ts +89 -0
- package/dist-server/server/native-runtime-spec.js +169 -0
- package/dist-server/server/native-sessions.d.ts +172 -19
- package/dist-server/server/native-sessions.js +839 -244
- package/dist-server/server/native-shutdown.d.ts +6 -0
- package/dist-server/server/native-shutdown.js +28 -0
- package/dist-server/server/native-wren-home.d.ts +18 -0
- package/dist-server/server/native-wren-home.js +262 -0
- package/dist-server/server/node-pty-host.d.ts +2 -1
- package/dist-server/server/node-pty-host.js +3 -2
- package/dist-server/server/profile-registry.d.ts +139 -0
- package/dist-server/server/profile-registry.js +620 -0
- package/dist-server/server/runtime-binding.d.ts +4 -1
- package/dist-server/server/runtime-binding.js +50 -2
- package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
- package/dist-server/server/runtime-host/claude-component-step.js +186 -0
- package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
- package/dist-server/server/runtime-host/codex-app-server.js +239 -0
- package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
- package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
- package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
- package/dist-server/server/runtime-host/codex-component-step.js +308 -0
- package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
- package/dist-server/server/runtime-host/codex-conversation.js +295 -0
- package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
- package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
- package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
- package/dist-server/server/runtime-host/codex-events.js +66 -0
- package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
- package/dist-server/server/runtime-host/codex-policy.js +151 -0
- package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
- package/dist-server/server/runtime-host/codex-process.js +96 -0
- package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
- package/dist-server/server/runtime-host/codex-rpc.js +182 -0
- package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
- package/dist-server/server/runtime-host/codex-session.js +492 -0
- package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
- package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
- package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
- package/dist-server/server/runtime-host/codex-terminal.js +120 -0
- package/dist-server/server/runtime-host/index.d.ts +5 -0
- package/dist-server/server/runtime-host/index.js +3 -0
- package/dist-server/server/runtime-host/local.d.ts +29 -0
- package/dist-server/server/runtime-host/local.js +90 -0
- package/dist-server/server/runtime-host/policy.d.ts +8 -0
- package/dist-server/server/runtime-host/policy.js +78 -0
- package/dist-server/server/runtime-host/types.d.ts +52 -0
- package/dist-server/server/runtime-host/types.js +62 -0
- package/dist-server/server/structured-query-evidence.d.ts +7 -0
- package/dist-server/server/structured-query-evidence.js +47 -0
- package/dist-server/server/structured-runtime.d.ts +52 -0
- package/dist-server/server/structured-runtime.js +130 -0
- package/dist-server/server/structured-scope.d.ts +4 -0
- package/dist-server/server/structured-scope.js +12 -0
- package/dist-server/server/turn.d.ts +6 -1
- package/dist-server/server/turn.js +82 -25
- package/dist-server/server/wire-types.d.ts +67 -3
- package/dist-server/server/wren-profiles.js +1 -1
- package/managed-wren/README.md +60 -0
- package/managed-wren/dependency-wheels.json +282 -0
- package/managed-wren/manifest.json +373 -0
- package/managed-wren/release-inputs.json +45 -0
- package/managed-wren/runtime-tree.cjs +115 -0
- package/managed-wren/source-inputs.json +40 -0
- package/package.json +15 -6
- package/profiles/genbi-default/context/binding.yml +17 -7
- package/profiles/genbi-default/context/context.json +146 -0
- package/profiles/genbi-default/ir.golden.json +39 -43
- package/profiles/genbi-default/profile.yml +31 -0
- package/profiles/genbi-enrich-context/context/binding.yml +14 -3
- package/profiles/genbi-enrich-context/context/context.json +146 -0
- package/profiles/genbi-enrich-context/ir.golden.json +4 -5
- package/profiles/genbi-monitor/context/binding.yml +13 -5
- package/profiles/genbi-monitor/context/context.json +146 -0
- package/profiles/genbi-monitor/ir.golden.json +13 -7
- package/profiles/genbi-report/context/binding.yml +19 -0
- package/profiles/genbi-report/context/context.json +223 -0
- package/profiles/genbi-report/ir.golden.json +328 -0
- package/profiles/genbi-report/profile.yml +38 -0
- package/profiles/genbi-setup/ir.golden.json +3 -1
- package/dist/assets/index-Xs4q7pNo.css +0 -1
|
@@ -61,6 +61,7 @@ import { buildUniformTierBinding } from "../route/tier-binding.js";
|
|
|
61
61
|
import { createNativeToolRegistry, createSetupExecutionTool, SETUP_EXECUTION_TOOL_NAME } from "../tools/index.js";
|
|
62
62
|
import { withResolvedTools } from "../tools/wiring.js";
|
|
63
63
|
import { CodexSetupEventMapper } from "./codex-events.js";
|
|
64
|
+
import { readYamlScalarField } from "./yaml-scalar.js";
|
|
64
65
|
/** The warble component the setup wizard's connect step dispatches. */
|
|
65
66
|
export const CONNECT_SOURCE_AGENT_ID = "connect_source";
|
|
66
67
|
/** The Warble component the setup wizard's context step dispatches (`profiles/genbi-setup/components/build_context`). */
|
|
@@ -142,6 +143,7 @@ export class DispatchedSetupRunner {
|
|
|
142
143
|
return resolveEffectiveMaxTurns(this.options.maxTurns, agentId);
|
|
143
144
|
}
|
|
144
145
|
async run(runOptions) {
|
|
146
|
+
runOptions.signal?.throwIfAborted();
|
|
145
147
|
const { authChoice } = runOptions;
|
|
146
148
|
const agentId = runOptions.agentId ?? CONNECT_SOURCE_AGENT_ID;
|
|
147
149
|
// Only build_context needs the raised turn budget — generating an MDL is
|
|
@@ -176,6 +178,7 @@ export class DispatchedSetupRunner {
|
|
|
176
178
|
...(modelsConfig !== undefined ? { modelsConfig } : {}),
|
|
177
179
|
...(maxTurns !== undefined ? { maxTurns } : {}),
|
|
178
180
|
...(runOptions.onEvent !== undefined ? { onEvent: runOptions.onEvent } : {}),
|
|
181
|
+
...(runOptions.signal !== undefined ? { signal: runOptions.signal } : {}),
|
|
179
182
|
...(runOptions.resumeSessionId !== undefined ? { resumeSessionId: runOptions.resumeSessionId } : {}),
|
|
180
183
|
});
|
|
181
184
|
return { finalText: result.finalText, ...(result.sessionId !== undefined ? { sessionId: result.sessionId } : {}) };
|
|
@@ -248,6 +251,7 @@ export class CodexSetupRunner {
|
|
|
248
251
|
this.options = options;
|
|
249
252
|
}
|
|
250
253
|
async run(runOptions) {
|
|
254
|
+
runOptions.signal?.throwIfAborted();
|
|
251
255
|
if (runOptions.authChoice.mode !== "subscription" || runOptions.authChoice.provider !== "codex") {
|
|
252
256
|
throw new Error("Codex setup runner requires a Codex subscription auth choice");
|
|
253
257
|
}
|
|
@@ -286,10 +290,9 @@ export class CodexSetupRunner {
|
|
|
286
290
|
"--server-arg=--workspace-root",
|
|
287
291
|
"--server-arg",
|
|
288
292
|
turnRoot,
|
|
289
|
-
"--
|
|
290
|
-
SETUP_EXECUTION_TOOL_NAME
|
|
291
|
-
"--
|
|
292
|
-
SETUP_EXECUTION_TOOL_NAME,
|
|
293
|
+
"--transport", "exec",
|
|
294
|
+
"--step-tool", `${agentId === CONNECT_SOURCE_AGENT_ID ? "connect" : "build"}=${SETUP_EXECUTION_TOOL_NAME}`,
|
|
295
|
+
"--require-tool", agentId === CONNECT_SOURCE_AGENT_ID ? "connect" : "build",
|
|
293
296
|
"--timeout",
|
|
294
297
|
String(timeoutMs),
|
|
295
298
|
...(this.options.codexBin ? ["--codex-bin", this.options.codexBin] : []),
|
|
@@ -299,7 +302,7 @@ export class CodexSetupRunner {
|
|
|
299
302
|
emitter.emit({ kind: "run.start", mode: "B", agentId });
|
|
300
303
|
const mapper = new CodexSetupEventMapper(tracePath);
|
|
301
304
|
try {
|
|
302
|
-
const rawFinalText = await spawnCodexSetup(cli.command, args, mapper, emitter.emit, timeoutMs + 5_000);
|
|
305
|
+
const rawFinalText = await spawnCodexSetup(cli.command, args, mapper, emitter.emit, timeoutMs + 5_000, runOptions.signal);
|
|
303
306
|
const finalText = unwrapCodexSetupEnvelope(rawFinalText, expectedProducedField);
|
|
304
307
|
emitter.emit({ kind: "run.finish", status: "answer" });
|
|
305
308
|
return { finalText };
|
|
@@ -325,8 +328,12 @@ function defaultCodexSetupMcpInvocation(tracePath) {
|
|
|
325
328
|
function sanitizedCodexSetupEnvironment() {
|
|
326
329
|
return Object.fromEntries(Object.entries(process.env).filter(([key, value]) => value !== undefined && !CODEX_BILLING_ENV_KEYS.has(key.toUpperCase())));
|
|
327
330
|
}
|
|
328
|
-
function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
|
|
331
|
+
function spawnCodexSetup(command, args, mapper, emit, timeoutMs, abortSignal) {
|
|
329
332
|
return new Promise((resolve, reject) => {
|
|
333
|
+
if (abortSignal?.aborted) {
|
|
334
|
+
reject(new Error("Codex setup cancelled"));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
330
337
|
const child = spawn(command, [...args], {
|
|
331
338
|
env: sanitizedCodexSetupEnvironment(),
|
|
332
339
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -335,6 +342,7 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
|
|
|
335
342
|
let stderr = "";
|
|
336
343
|
let settled = false;
|
|
337
344
|
let timedOut = false;
|
|
345
|
+
let cancelled = false;
|
|
338
346
|
let killTimer;
|
|
339
347
|
const terminate = (signal) => {
|
|
340
348
|
if (child.pid !== undefined && process.platform !== "win32") {
|
|
@@ -348,7 +356,17 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
|
|
|
348
356
|
}
|
|
349
357
|
child.kill(signal);
|
|
350
358
|
};
|
|
359
|
+
const cancel = () => {
|
|
360
|
+
if (settled || cancelled || timedOut)
|
|
361
|
+
return;
|
|
362
|
+
cancelled = true;
|
|
363
|
+
terminate("SIGTERM");
|
|
364
|
+
killTimer = setTimeout(() => terminate("SIGKILL"), 1_000);
|
|
365
|
+
};
|
|
366
|
+
abortSignal?.addEventListener("abort", cancel, { once: true });
|
|
351
367
|
const timer = setTimeout(() => {
|
|
368
|
+
if (cancelled)
|
|
369
|
+
return;
|
|
352
370
|
timedOut = true;
|
|
353
371
|
terminate("SIGTERM");
|
|
354
372
|
killTimer = setTimeout(() => terminate("SIGKILL"), 1_000);
|
|
@@ -365,6 +383,7 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
|
|
|
365
383
|
catch (error) {
|
|
366
384
|
settled = true;
|
|
367
385
|
clearTimeout(timer);
|
|
386
|
+
abortSignal?.removeEventListener("abort", cancel);
|
|
368
387
|
terminate("SIGTERM");
|
|
369
388
|
killTimer = setTimeout(() => terminate("SIGKILL"), 1_000);
|
|
370
389
|
reject(error);
|
|
@@ -379,12 +398,14 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
|
|
|
379
398
|
return;
|
|
380
399
|
settled = true;
|
|
381
400
|
clearTimeout(timer);
|
|
401
|
+
abortSignal?.removeEventListener("abort", cancel);
|
|
382
402
|
if (killTimer)
|
|
383
403
|
clearTimeout(killTimer);
|
|
384
404
|
reject(new Error(`warble-codex-local failed to start: ${error.message}`));
|
|
385
405
|
});
|
|
386
406
|
child.on("close", (code, signal) => {
|
|
387
407
|
clearTimeout(timer);
|
|
408
|
+
abortSignal?.removeEventListener("abort", cancel);
|
|
388
409
|
lines.close();
|
|
389
410
|
// Do not cancel an already-scheduled process-group SIGKILL here. The
|
|
390
411
|
// direct dispatcher can exit on SIGTERM while one of its descendants
|
|
@@ -392,7 +413,10 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
|
|
|
392
413
|
if (settled)
|
|
393
414
|
return;
|
|
394
415
|
settled = true;
|
|
395
|
-
if (
|
|
416
|
+
if (cancelled) {
|
|
417
|
+
reject(new Error("Codex setup cancelled"));
|
|
418
|
+
}
|
|
419
|
+
else if (timedOut) {
|
|
396
420
|
reject(new Error(`warble-codex-local timed out after ${timeoutMs}ms`));
|
|
397
421
|
}
|
|
398
422
|
else if (code !== 0) {
|
|
@@ -483,6 +507,7 @@ export class InProcessSetupRunner {
|
|
|
483
507
|
return resolveEffectiveMaxTurns(this.options.maxTurns, agentId);
|
|
484
508
|
}
|
|
485
509
|
async run(runOptions) {
|
|
510
|
+
runOptions.signal?.throwIfAborted();
|
|
486
511
|
const { authChoice } = runOptions;
|
|
487
512
|
const agentId = runOptions.agentId ?? CONNECT_SOURCE_AGENT_ID;
|
|
488
513
|
if (authChoice.mode === "subscription") {
|
|
@@ -548,6 +573,7 @@ export class InProcessSetupRunner {
|
|
|
548
573
|
registry,
|
|
549
574
|
tools,
|
|
550
575
|
userInput: runOptions.prompt,
|
|
576
|
+
...(runOptions.signal ? { signal: runOptions.signal } : {}),
|
|
551
577
|
onEvent: emitter.emit,
|
|
552
578
|
...(maxSteps !== undefined ? { maxSteps } : {}),
|
|
553
579
|
}));
|
|
@@ -620,7 +646,9 @@ export class InProcessSetupRunner {
|
|
|
620
646
|
* rather than pulling in a YAML parser dependency. Matches only an unindented
|
|
621
647
|
* `field:` key (so it can't be fooled by a same-named key nested under
|
|
622
648
|
* another mapping), strips a single layer of surrounding quotes, and returns
|
|
623
|
-
* `undefined` if the file is missing/unreadable or the field isn't present.
|
|
649
|
+
* `undefined` if the file is missing/unreadable or the field isn't present. Values
|
|
650
|
+
* are read as YAML scalars (trailing comments dropped; blank or comment-only is `""`),
|
|
651
|
+
* via `./yaml-scalar.ts`.
|
|
624
652
|
*/
|
|
625
653
|
function readProjectYamlField(yamlPath, field) {
|
|
626
654
|
let content;
|
|
@@ -630,14 +658,7 @@ function readProjectYamlField(yamlPath, field) {
|
|
|
630
658
|
catch {
|
|
631
659
|
return undefined;
|
|
632
660
|
}
|
|
633
|
-
|
|
634
|
-
const match = pattern.exec(content);
|
|
635
|
-
if (!match) {
|
|
636
|
-
return undefined;
|
|
637
|
-
}
|
|
638
|
-
const raw = match[1];
|
|
639
|
-
const unquoted = /^"(.*)"$/.exec(raw) ?? /^'(.*)'$/.exec(raw);
|
|
640
|
-
return (unquoted ? unquoted[1] : raw).trim();
|
|
661
|
+
return readYamlScalarField(content, field);
|
|
641
662
|
}
|
|
642
663
|
/** Parses the small KEY=value subset of a project .env file used by Wren profiles. */
|
|
643
664
|
function parseDotEnv(text) {
|
|
@@ -809,7 +830,7 @@ export function contextLifecycleIdentityFingerprint(root, name, selectedSourceTy
|
|
|
809
830
|
const declaredSourceType = readProjectYamlField(projectYml, "data_source");
|
|
810
831
|
const normalizedSelectedSourceType = selectedSourceType.trim().toLowerCase();
|
|
811
832
|
const normalizedDeclaredSourceType = declaredSourceType?.trim().toLowerCase();
|
|
812
|
-
if (profile
|
|
833
|
+
if (!profile || normalizedDeclaredSourceType === undefined || normalizedDeclaredSourceType !== normalizedSelectedSourceType)
|
|
813
834
|
return undefined;
|
|
814
835
|
const connectionTargetDigest = effectiveConnectionTargetDigest(projectDir, normalizedSelectedSourceType);
|
|
815
836
|
if (connectionTargetDigest === undefined)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a top-level `field: value` scalar from a `wren_project.yml`-shaped
|
|
3
|
+
* document and returns the YAML *value*, not the raw line remainder.
|
|
4
|
+
*
|
|
5
|
+
* `wren context init` scaffolds lines such as
|
|
6
|
+
* `data_source: # not set yet — ...`, and hand-edited projects keep trailing
|
|
7
|
+
* comments (`data_source: duckdb # change to your datasource type`). A raw
|
|
8
|
+
* remainder would hand that comment to callers as part of the value. This
|
|
9
|
+
* stays a single-purpose reader (no YAML dependency, matching the rest of
|
|
10
|
+
* `server/`); it only understands what a plain scalar line can contain:
|
|
11
|
+
*
|
|
12
|
+
* - an unquoted value ends at the first ` #` (a `#` preceded by whitespace);
|
|
13
|
+
* - a quoted value ends at its closing quote, so `"a#b"` stays `a#b` and
|
|
14
|
+
* anything after the closing quote is a comment;
|
|
15
|
+
* - a blank value, or one that is only a comment, reads as `""` (unset).
|
|
16
|
+
*
|
|
17
|
+
* Double-quoted escapes are NOT unescaped (`"a\"b"` yields `a\"b`): only plain
|
|
18
|
+
* identifiers (data source types, profile names) are ever read through here.
|
|
19
|
+
* A leading BOM is ignored.
|
|
20
|
+
*
|
|
21
|
+
* Returns `undefined` only when the field's key line is absent.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readYamlScalarField(content: string, field: string): string | undefined;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a top-level `field: value` scalar from a `wren_project.yml`-shaped
|
|
3
|
+
* document and returns the YAML *value*, not the raw line remainder.
|
|
4
|
+
*
|
|
5
|
+
* `wren context init` scaffolds lines such as
|
|
6
|
+
* `data_source: # not set yet — ...`, and hand-edited projects keep trailing
|
|
7
|
+
* comments (`data_source: duckdb # change to your datasource type`). A raw
|
|
8
|
+
* remainder would hand that comment to callers as part of the value. This
|
|
9
|
+
* stays a single-purpose reader (no YAML dependency, matching the rest of
|
|
10
|
+
* `server/`); it only understands what a plain scalar line can contain:
|
|
11
|
+
*
|
|
12
|
+
* - an unquoted value ends at the first ` #` (a `#` preceded by whitespace);
|
|
13
|
+
* - a quoted value ends at its closing quote, so `"a#b"` stays `a#b` and
|
|
14
|
+
* anything after the closing quote is a comment;
|
|
15
|
+
* - a blank value, or one that is only a comment, reads as `""` (unset).
|
|
16
|
+
*
|
|
17
|
+
* Double-quoted escapes are NOT unescaped (`"a\"b"` yields `a\"b`): only plain
|
|
18
|
+
* identifiers (data source types, profile names) are ever read through here.
|
|
19
|
+
* A leading BOM is ignored.
|
|
20
|
+
*
|
|
21
|
+
* Returns `undefined` only when the field's key line is absent.
|
|
22
|
+
*/
|
|
23
|
+
export function readYamlScalarField(content, field) {
|
|
24
|
+
const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25
|
+
const text = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
|
26
|
+
const match = new RegExp(`^${escaped}:[ \\t]*(.*)$`, "m").exec(text);
|
|
27
|
+
if (!match)
|
|
28
|
+
return undefined;
|
|
29
|
+
return parseScalarValue(match[1].replace(/\r$/, ""));
|
|
30
|
+
}
|
|
31
|
+
function parseScalarValue(rest) {
|
|
32
|
+
const quote = rest[0];
|
|
33
|
+
if (quote === '"' || quote === "'") {
|
|
34
|
+
const closing = findClosingQuote(rest, quote);
|
|
35
|
+
// An unterminated quote isn't a valid scalar; fall back to the plain-scalar rule rather than guessing.
|
|
36
|
+
if (closing !== -1)
|
|
37
|
+
return rest.slice(1, closing);
|
|
38
|
+
}
|
|
39
|
+
return stripPlainScalarComment(rest);
|
|
40
|
+
}
|
|
41
|
+
function findClosingQuote(rest, quote) {
|
|
42
|
+
for (let i = 1; i < rest.length; i++) {
|
|
43
|
+
if (quote === '"' && rest[i] === "\\") {
|
|
44
|
+
i++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (rest[i] === quote) {
|
|
48
|
+
if (quote === "'" && rest[i + 1] === "'") {
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
return i;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return -1;
|
|
56
|
+
}
|
|
57
|
+
function stripPlainScalarComment(rest) {
|
|
58
|
+
if (rest.startsWith("#"))
|
|
59
|
+
return "";
|
|
60
|
+
const comment = /\s#/.exec(rest);
|
|
61
|
+
return (comment ? rest.slice(0, comment.index) : rest).trim();
|
|
62
|
+
}
|
|
@@ -52,24 +52,8 @@
|
|
|
52
52
|
import { execFile } from "node:child_process";
|
|
53
53
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
54
54
|
import path from "node:path";
|
|
55
|
+
import { readYamlScalarField } from "../harness/setup/yaml-scalar.js";
|
|
55
56
|
import { listCandidateProfiles, loadProfileStore } from "./wren-profiles.js";
|
|
56
|
-
/**
|
|
57
|
-
* Matches a `wren_project.yml` top-level `field: value` scalar line — same
|
|
58
|
-
* single-purpose-regex convention as `server/app.ts`'s `ENV_KEY_LINE`. Only
|
|
59
|
-
* ever reads a handful of known scalar fields (`profile`, `data_source`);
|
|
60
|
-
* never a general YAML parser (matches this repo's existing convention of
|
|
61
|
-
* avoiding a YAML dependency for single-field reads).
|
|
62
|
-
*/
|
|
63
|
-
function readYamlScalarField(content, field) {
|
|
64
|
-
const re = new RegExp(`^${field}:\\s*(.+?)\\s*$`, "m");
|
|
65
|
-
const match = re.exec(content);
|
|
66
|
-
if (!match)
|
|
67
|
-
return undefined;
|
|
68
|
-
const raw = match[1];
|
|
69
|
-
// Strip one layer of matching quotes (wren_project.yml sometimes quotes scalars, e.g. `version: '1.0'`) — never unescape further, this only ever reads plain identifiers.
|
|
70
|
-
const unquoted = /^(['"])(.*)\1$/.exec(raw);
|
|
71
|
-
return unquoted ? unquoted[2] : raw;
|
|
72
|
-
}
|
|
73
57
|
function execWren(args, cwd) {
|
|
74
58
|
return new Promise((resolve) => {
|
|
75
59
|
execFile("wren", args, { cwd, maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { InteractiveTerminalSession } from "./interactive-terminal.js";
|
|
3
3
|
import type { TurnDeps } from "./turn.js";
|
|
4
|
+
export declare const GATEWAY_CATALOG_UNAVAILABLE = "gateway catalog unavailable: the optional dependency @earendil-works/pi-ai is not installed or Node >= 22.19 is required";
|
|
4
5
|
/** Applies only the narrow, size-bounded native-terminal WS wire contract. */
|
|
5
6
|
export declare function applyInteractiveTerminalFrame(session: Pick<InteractiveTerminalSession, "write" | "resize" | "close">, raw: string): void;
|
|
6
7
|
export declare function createApp(deps: TurnDeps): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
@@ -10,10 +10,11 @@ import path from "node:path";
|
|
|
10
10
|
import { Hono } from "hono";
|
|
11
11
|
import { streamSSE } from "hono/streaming";
|
|
12
12
|
import { upgradeWebSocket } from "@hono/node-server";
|
|
13
|
+
import { conversationSocket } from "./conversation-socket.js";
|
|
13
14
|
import { BUILD_CONTEXT_AGENT_ID, ComplianceError, CONNECT_SOURCE_AGENT_ID, contextLifecycleIdentityFingerprint, enforceCompliance, findLockedGatedCheck, resolveArtifactContent, resolveArtifactsDir } from "../harness/index.js";
|
|
14
15
|
import { adoptWithChosenProfile, verifyAdoptProject } from "./adopt.js";
|
|
15
16
|
import { mountSpaFallback } from "./spa.js";
|
|
16
|
-
import { toAuthChoiceFromRuntimeSettings } from "./auth-choice.js";
|
|
17
|
+
import { isRuntimeAuthMode, runtimeSettingsFromAuthChoice, toAuthChoiceFromRuntimeSettings } from "./auth-choice.js";
|
|
17
18
|
import { composeSetupPrompt, zeroMdlSchemaDiscoveryInstruction } from "./compose.js";
|
|
18
19
|
import { mapContextShowToOverview } from "./context-map.js";
|
|
19
20
|
import { buildContextFileTree, computeKnowledgeStatus } from "./context-files.js";
|
|
@@ -25,7 +26,10 @@ import { INTERACTIVE_TARGETS, InteractiveLaunchError } from "./interactive-termi
|
|
|
25
26
|
import { nativeSessionLaunchErrorCode, nativeSessionLaunchFailure, nativeSessionLifecycle, NATIVE_PURPOSES, NATIVE_SETUP_RECOVERY_MCP_TOOL_NAME, NativeSetupRecoveryError } from "./native-sessions.js";
|
|
26
27
|
import { NATIVE_MCP_PERSIST_ANSWER_TOOL_NAME, NATIVE_MCP_TOOL_NAME, NATIVE_PERSIST_ANSWER_CONTRACT, NATIVE_SAVE_DASHBOARD_CONTRACT, NativeArtifactError } from "./native-artifacts.js";
|
|
27
28
|
import { sameNativeRuntimeBinding } from "./native-dispatch-registry.js";
|
|
28
|
-
import {
|
|
29
|
+
import { ProfileRegistryError, toWarbleProfileDto } from "./profile-registry.js";
|
|
30
|
+
import { DEFAULT_CONVERSATION_PROFILE } from "./native-sessions.js";
|
|
31
|
+
import { detectAdapterEnv, GATEWAY_API_KEY_ENV } from "./env-detect.js";
|
|
32
|
+
import { listPiAiCatalog } from "../harness/providers/index.js";
|
|
29
33
|
import { redactPublicSetupText, redactSetupText, sanitizePublicSetupWorklog } from "./fold.js";
|
|
30
34
|
import { assertHarnessBundlePurpose, buildHarnessDto } from "./harness.js";
|
|
31
35
|
import { requiredInProcessCredentialEnvVars, RuntimeBindingError, runtimeSettingsCorrection, validateRuntimeTierBindings } from "./runtime-binding.js";
|
|
@@ -34,6 +38,7 @@ import { ArtifactNotFoundError, effectiveRouteOptions, getAskSessionData, invali
|
|
|
34
38
|
import { connectionVariableNames, loadSourceCatalog, supportedSourceTypes } from "./source-catalog.js";
|
|
35
39
|
import { driverProvisionNote, provisionSourceDriver } from "./source-driver.js";
|
|
36
40
|
import { annotateEnvFields } from "./env-field-hints.js";
|
|
41
|
+
export const GATEWAY_CATALOG_UNAVAILABLE = "gateway catalog unavailable: the optional dependency @earendil-works/pi-ai is not installed or Node >= 22.19 is required";
|
|
37
42
|
/** Only explicit persisted settings can supersede the boot route or require repair. */
|
|
38
43
|
function persistedRuntimeCorrection(deps) {
|
|
39
44
|
return deps.store.hasExplicitRuntimeSettings() ? runtimeSettingsCorrection(deps.store.getRuntimeSettings()) : undefined;
|
|
@@ -1009,11 +1014,29 @@ export function createApp(deps) {
|
|
|
1009
1014
|
// remains structured conversation storage and is never overloaded with PTYs.
|
|
1010
1015
|
app.post("/api/native-sessions", async (c) => {
|
|
1011
1016
|
const body = await c.req.json().catch(() => ({}));
|
|
1017
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
1018
|
+
return c.json({ error: "native session launch request is invalid" }, 400);
|
|
1012
1019
|
const intent = body.intent === undefined ? "open_existing" : body.intent;
|
|
1013
1020
|
const requiresAction = intent === "start_separate" || intent === "resume";
|
|
1014
|
-
|
|
1021
|
+
// `profile` names a registry id for a NEW analysis session only: a start-separate, or an
|
|
1022
|
+
// open-or-create with no sessionId. A resume or an explicit sessionId already carries its profile.
|
|
1023
|
+
const allowedKeys = intent === "start_separate" ? ["purpose", "intent", "idempotencyKey", "entryVerb", "profile"] : intent === "resume" ? ["purpose", "intent", "sessionId", "idempotencyKey"] : ["purpose", "intent", "sessionId", "profile"];
|
|
1015
1024
|
if (!NATIVE_PURPOSES.includes(body.purpose) || (intent !== "open_existing" && intent !== "start_separate" && intent !== "resume") || !Object.keys(body).every((key) => allowedKeys.includes(key)) || (requiresAction && (typeof body.idempotencyKey !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(body.idempotencyKey))) || ((intent === "resume" && (typeof body.sessionId !== "string" || !/^native-session-[0-9a-f-]{36}$/i.test(body.sessionId))) || (intent === "open_existing" && body.sessionId !== undefined && (typeof body.sessionId !== "string" || !/^native-session-[0-9a-f-]{36}$/i.test(body.sessionId)))))
|
|
1016
1025
|
return c.json({ error: "native session launch request is invalid" }, 400);
|
|
1026
|
+
if (body.entryVerb !== undefined && (body.purpose !== "analysis" || typeof body.entryVerb !== "string" || !["answer_query", "generate_dashboard"].includes(body.entryVerb)))
|
|
1027
|
+
return c.json({ error: "native session entry is invalid" }, 400);
|
|
1028
|
+
if (body.profile !== undefined) {
|
|
1029
|
+
if (typeof body.profile !== "string" || !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(body.profile))
|
|
1030
|
+
return c.json({ error: "native session profile is invalid" }, 400);
|
|
1031
|
+
if (body.purpose !== "analysis")
|
|
1032
|
+
return c.json({ error: "native session profile is only selectable for analysis" }, 400);
|
|
1033
|
+
if (intent === "open_existing" && body.sessionId !== undefined)
|
|
1034
|
+
return c.json({ error: "native session profile cannot be changed on an existing session" }, 400);
|
|
1035
|
+
// A selected profile's entry form was decided at admission; only the default takes an entry verb.
|
|
1036
|
+
if (body.profile !== DEFAULT_CONVERSATION_PROFILE && body.entryVerb !== undefined)
|
|
1037
|
+
return c.json({ error: "native session entry is invalid" }, 400);
|
|
1038
|
+
}
|
|
1039
|
+
const profile = typeof body.profile === "string" ? { profile: body.profile } : {};
|
|
1017
1040
|
if (!deps.nativeSessions)
|
|
1018
1041
|
return c.json({ error: "native sessions are not configured" }, 503);
|
|
1019
1042
|
const runtimeCorrection = persistedRuntimeCorrection(deps);
|
|
@@ -1021,12 +1044,12 @@ export function createApp(deps) {
|
|
|
1021
1044
|
return c.json({ error: runtimeCorrection, code: "runtime_correction_required" }, 409);
|
|
1022
1045
|
try {
|
|
1023
1046
|
const created = intent === "start_separate"
|
|
1024
|
-
? await deps.nativeSessions.startSeparate({ purpose: body.purpose, idempotencyKey: body.idempotencyKey })
|
|
1047
|
+
? await deps.nativeSessions.startSeparate({ purpose: body.purpose, idempotencyKey: body.idempotencyKey, ...(body.entryVerb !== undefined ? { entryVerb: body.entryVerb } : {}), ...profile })
|
|
1025
1048
|
: intent === "resume"
|
|
1026
1049
|
? await deps.nativeSessions.resume({ id: body.sessionId, idempotencyKey: body.idempotencyKey })
|
|
1027
1050
|
: typeof body.sessionId === "string"
|
|
1028
1051
|
? await deps.nativeSessions.openExisting({ purpose: body.purpose, id: body.sessionId })
|
|
1029
|
-
: await deps.nativeSessions.openOrCreate({ purpose: body.purpose });
|
|
1052
|
+
: await deps.nativeSessions.openOrCreate({ purpose: body.purpose, ...profile });
|
|
1030
1053
|
return c.json({ session: { ...created.row, lifecycle: nativeSessionLifecycle(created.row, nativeResumeAvailability(created.row)) }, ...(created.capability ? { capability: created.capability } : {}), ...(created.recoveryCapability ? { recoveryCapability: created.recoveryCapability } : {}) }, 201);
|
|
1031
1054
|
}
|
|
1032
1055
|
catch (error) {
|
|
@@ -1076,8 +1099,16 @@ export function createApp(deps) {
|
|
|
1076
1099
|
const body = await c.req.json().catch(() => ({}));
|
|
1077
1100
|
if (!deps.nativeSessions || typeof body.capability !== "string")
|
|
1078
1101
|
return c.json({ error: "native session unavailable" }, 404);
|
|
1079
|
-
|
|
1102
|
+
try {
|
|
1103
|
+
return await deps.nativeSessions.stopAndWait(c.req.param("id") ?? "", body.capability) ? c.body(null, 204) : c.json({ error: "native session unavailable" }, 404);
|
|
1104
|
+
}
|
|
1105
|
+
catch {
|
|
1106
|
+
return c.json({ error: "Native session cleanup could not be confirmed." }, 409);
|
|
1107
|
+
}
|
|
1080
1108
|
});
|
|
1109
|
+
app.get("/api/structured-runtime/readiness", async (c) => deps.structuredRuntime
|
|
1110
|
+
? c.json(await deps.structuredRuntime.readiness()) : c.json({ error: "Structured runtime is not configured." }, 503));
|
|
1111
|
+
app.get("/api/native-sessions/:id/conversation", upgradeWebSocket((c) => conversationSocket(deps.nativeSessions, c.req.param("id") ?? "", c.req.query("cap") ?? "", c.req.query("after") ?? "0")));
|
|
1081
1112
|
app.get("/api/native-sessions/:id/attach", upgradeWebSocket((c) => {
|
|
1082
1113
|
const id = c.req.param("id") ?? "";
|
|
1083
1114
|
const capability = c.req.query("cap") ?? "";
|
|
@@ -1165,7 +1196,7 @@ export function createApp(deps) {
|
|
|
1165
1196
|
};
|
|
1166
1197
|
const artifactTool = {
|
|
1167
1198
|
name: NATIVE_MCP_TOOL_NAME,
|
|
1168
|
-
description: "Save a
|
|
1199
|
+
description: "Save a dashboard to Artifacts. Use a prior persist_answer reference, or select this session's latest retained answer when the opaque reference is no longer available, without recomputing. The host marks the artifact verified only when the referenced answer is one it grounded itself; a dashboard built from a payload you write is stored as unverified. The existing payload form remains available for compatibility.",
|
|
1169
1200
|
inputSchema: NATIVE_SAVE_DASHBOARD_CONTRACT.inputSchema,
|
|
1170
1201
|
};
|
|
1171
1202
|
const persistAnswerTool = {
|
|
@@ -1196,11 +1227,15 @@ export function createApp(deps) {
|
|
|
1196
1227
|
if (request.method === "tools/list")
|
|
1197
1228
|
return isNotification ? invalidRequest() : c.json({
|
|
1198
1229
|
jsonrpc: "2.0", id,
|
|
1199
|
-
result: { tools: nativeToolsForPurpose(nativeSession.purpose) },
|
|
1230
|
+
result: { tools: [...nativeToolsForPurpose(nativeSession.purpose), ...(deps.nativeSessions?.componentTools(nativeSession.id) ?? [])] },
|
|
1200
1231
|
});
|
|
1201
1232
|
if (request.method !== "tools/call" || isNotification || !isRecord(request.params) || !Object.hasOwn(request.params, "arguments"))
|
|
1202
1233
|
return methodNotFound();
|
|
1203
1234
|
try {
|
|
1235
|
+
if (typeof request.params.name === "string" && request.params.name.startsWith("warble_run_") && deps.nativeSessions && id !== null) {
|
|
1236
|
+
const result = await deps.nativeSessions.callComponent(nativeSession.id, request.params.name, request.params.arguments, id, c.req.raw.signal);
|
|
1237
|
+
return c.json({ jsonrpc: "2.0", id, result: { isError: result.status !== "ok", content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result } });
|
|
1238
|
+
}
|
|
1204
1239
|
if (nativeSession.purpose === "setup" && request.params.name === NATIVE_SETUP_RECOVERY_MCP_TOOL_NAME && deps.nativeSessions) {
|
|
1205
1240
|
const recovery = deps.nativeSessions.reportSetupRecovery(nativeSession.id, request.params.arguments, isRecord(request.params.arguments) && request.params.arguments.state === "reported_complete" && validateNativeSetupCompletion(deps, request.params.arguments.phase === "context" ? "context" : "connect"));
|
|
1206
1241
|
return c.json({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify({ accepted: true, version: recovery.version }) }], structuredContent: { accepted: true, version: recovery.version } } });
|
|
@@ -1582,6 +1617,61 @@ export function createApp(deps) {
|
|
|
1582
1617
|
}
|
|
1583
1618
|
});
|
|
1584
1619
|
// ---------------------------------------------------------------------
|
|
1620
|
+
// Warble profile registry
|
|
1621
|
+
// ---------------------------------------------------------------------
|
|
1622
|
+
//
|
|
1623
|
+
// Conversation profiles are selected from a server-held registry by id. The
|
|
1624
|
+
// registry owns the directory, runs admission and resolves the launch tuple;
|
|
1625
|
+
// a browser never submits a path for a session to run inside. Adding a
|
|
1626
|
+
// profile takes a directory path on this machine, the same shape the adopt
|
|
1627
|
+
// flow accepts for a project, and the registry copies and checks it.
|
|
1628
|
+
const profileRegistryFailure = (c, error) => {
|
|
1629
|
+
if (error instanceof ProfileRegistryError)
|
|
1630
|
+
return c.json({ error: error.message }, error.status);
|
|
1631
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
1632
|
+
};
|
|
1633
|
+
app.get("/api/profiles", async (c) => {
|
|
1634
|
+
if (!deps.profileRegistry)
|
|
1635
|
+
return c.json({ error: "the profile registry is not configured on this BFF instance" }, 503);
|
|
1636
|
+
try {
|
|
1637
|
+
const rows = await deps.profileRegistry.list();
|
|
1638
|
+
const response = { profiles: rows.map(toWarbleProfileDto) };
|
|
1639
|
+
return c.json(response);
|
|
1640
|
+
}
|
|
1641
|
+
catch (error) {
|
|
1642
|
+
return profileRegistryFailure(c, error);
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
app.post("/api/profiles", async (c) => {
|
|
1646
|
+
if (!deps.profileRegistry)
|
|
1647
|
+
return c.json({ error: "the profile registry is not configured on this BFF instance" }, 503);
|
|
1648
|
+
const body = await c.req.json().catch(() => undefined);
|
|
1649
|
+
if (typeof body !== "object" || body === null || Array.isArray(body) || !Object.keys(body).every((key) => key === "sourcePath")) {
|
|
1650
|
+
return c.json({ error: "profile registration request is invalid: expected { sourcePath }" }, 400);
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
const row = await deps.profileRegistry.add(body.sourcePath);
|
|
1654
|
+
return c.json({ profile: toWarbleProfileDto(row) }, 201);
|
|
1655
|
+
}
|
|
1656
|
+
catch (error) {
|
|
1657
|
+
return profileRegistryFailure(c, error);
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
app.delete("/api/profiles/:id", async (c) => {
|
|
1661
|
+
if (!deps.profileRegistry)
|
|
1662
|
+
return c.json({ error: "the profile registry is not configured on this BFF instance" }, 503);
|
|
1663
|
+
const id = c.req.param("id");
|
|
1664
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(id))
|
|
1665
|
+
return c.json({ error: "profile id is invalid" }, 400);
|
|
1666
|
+
try {
|
|
1667
|
+
await deps.profileRegistry.remove(id);
|
|
1668
|
+
return c.body(null, 204);
|
|
1669
|
+
}
|
|
1670
|
+
catch (error) {
|
|
1671
|
+
return profileRegistryFailure(c, error);
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
// ---------------------------------------------------------------------
|
|
1585
1675
|
// Harness introspection
|
|
1586
1676
|
// ---------------------------------------------------------------------
|
|
1587
1677
|
app.get("/api/harness", async (c) => {
|
|
@@ -1590,16 +1680,44 @@ export function createApp(deps) {
|
|
|
1590
1680
|
return c.json({ error: "harness purpose must be exactly one of: setup, analysis, context_enrichment" }, 400);
|
|
1591
1681
|
}
|
|
1592
1682
|
const purpose = purposes[0];
|
|
1683
|
+
// An optional registry id selects which conversation profile the analysis purpose describes.
|
|
1684
|
+
// The server resolves it; the browser never names a directory.
|
|
1685
|
+
const profiles = c.req.queries("profile") ?? [];
|
|
1686
|
+
if (profiles.length > 1)
|
|
1687
|
+
return c.json({ error: "harness profile must be given at most once" }, 400);
|
|
1688
|
+
const requestedProfile = profiles[0];
|
|
1689
|
+
if (requestedProfile !== undefined) {
|
|
1690
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(requestedProfile))
|
|
1691
|
+
return c.json({ error: "harness profile is invalid" }, 400);
|
|
1692
|
+
if (purpose !== "analysis")
|
|
1693
|
+
return c.json({ error: "harness profile is only selectable for analysis" }, 400);
|
|
1694
|
+
}
|
|
1695
|
+
const selectedProfile = requestedProfile !== undefined && requestedProfile !== DEFAULT_CONVERSATION_PROFILE ? requestedProfile : undefined;
|
|
1593
1696
|
// Setup describes the raw bootstrap profile, before a project exists. The
|
|
1594
1697
|
// other two profiles consume a bound project's context and stay gated.
|
|
1595
1698
|
if (purpose !== "setup" && !isProjectBound(deps))
|
|
1596
1699
|
return c.json({ error: PROJECT_NOT_BOUND_MESSAGE }, 409);
|
|
1597
1700
|
if (!deps.describeHarnessBundle)
|
|
1598
1701
|
return c.json({ error: "harness introspection is not configured" }, 500);
|
|
1702
|
+
let selectedSource;
|
|
1703
|
+
if (selectedProfile !== undefined) {
|
|
1704
|
+
if (!deps.profileRegistry)
|
|
1705
|
+
return c.json({ error: "the profile registry is not configured on this BFF instance" }, 503);
|
|
1706
|
+
try {
|
|
1707
|
+
selectedSource = (await deps.profileRegistry.resolveConversationProfileSource(selectedProfile)).sourceDir;
|
|
1708
|
+
}
|
|
1709
|
+
catch (error) {
|
|
1710
|
+
return profileRegistryFailure(c, error);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1599
1713
|
try {
|
|
1600
1714
|
const routeOptions = effectiveRouteOptions(deps, { allowUnbound: purpose === "setup" });
|
|
1601
|
-
|
|
1602
|
-
|
|
1715
|
+
// Keep the two-argument call for the fixed profiles so existing describers and their tests see
|
|
1716
|
+
// exactly the shape they always did; only a selection adds the third argument.
|
|
1717
|
+
const bundle = selectedSource !== undefined
|
|
1718
|
+
? await deps.describeHarnessBundle(purpose, routeOptions, selectedSource)
|
|
1719
|
+
: await deps.describeHarnessBundle(purpose, routeOptions);
|
|
1720
|
+
assertHarnessBundlePurpose(bundle, purpose, selectedProfile);
|
|
1603
1721
|
const setupRuntimeCorrection = purpose === "setup" ? persistedRuntimeCorrection(deps) : undefined;
|
|
1604
1722
|
const setupReadiness = purpose === "setup"
|
|
1605
1723
|
? setupRuntimeCorrection !== undefined
|
|
@@ -1616,7 +1734,7 @@ export function createApp(deps) {
|
|
|
1616
1734
|
const nativeReadiness = purpose === "setup"
|
|
1617
1735
|
? await deps.nativeSessions?.readiness().catch(() => undefined)
|
|
1618
1736
|
: await deps.nativeSessions?.readiness();
|
|
1619
|
-
return c.json(buildHarnessDto(bundle, deps.store, routeOptions, purpose, nativeReadiness, setupReadiness));
|
|
1737
|
+
return c.json(buildHarnessDto(bundle, deps.store, routeOptions, purpose, nativeReadiness, setupReadiness, selectedProfile));
|
|
1620
1738
|
}
|
|
1621
1739
|
catch (err) {
|
|
1622
1740
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1626,7 +1744,12 @@ export function createApp(deps) {
|
|
|
1626
1744
|
// ---------------------------------------------------------------------
|
|
1627
1745
|
// Config / setup pages
|
|
1628
1746
|
// ---------------------------------------------------------------------
|
|
1629
|
-
|
|
1747
|
+
// Until the user saves, the effective runtime is the boot auth env, so report that rather than
|
|
1748
|
+
// the seeded display default. Nothing here is persisted and no credential is read.
|
|
1749
|
+
app.get("/api/config/runtime", (c) => {
|
|
1750
|
+
const stored = deps.store.getRuntimeSettings();
|
|
1751
|
+
return c.json(deps.store.hasExplicitRuntimeSettings() ? stored : runtimeSettingsFromAuthChoice(deps.baseRouteOptions.authChoice, stored));
|
|
1752
|
+
});
|
|
1630
1753
|
app.get("/api/config/runtime/readiness", (c) => {
|
|
1631
1754
|
const correction = persistedRuntimeCorrection(deps);
|
|
1632
1755
|
return c.json(correction ? { valid: false, correction } : { valid: true });
|
|
@@ -1673,9 +1796,31 @@ export function createApp(deps) {
|
|
|
1673
1796
|
: { version: 1, status: "unavailable", provider, code: "runtime_unavailable", retryable: true };
|
|
1674
1797
|
return c.json(result);
|
|
1675
1798
|
});
|
|
1799
|
+
/** pi-ai's built-in catalog (offline, no credentials) for the gateway option's suggestions. */
|
|
1800
|
+
app.get("/api/harness/gateway-models", async (c) => {
|
|
1801
|
+
const provider = c.req.query("provider")?.trim() || undefined;
|
|
1802
|
+
try {
|
|
1803
|
+
const catalog = await listPiAiCatalog(provider);
|
|
1804
|
+
return c.json({
|
|
1805
|
+
status: "ready",
|
|
1806
|
+
providers: catalog.providers,
|
|
1807
|
+
models: catalog.models.map((model) => ({ model: model.id, displayName: model.name })),
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
catch {
|
|
1811
|
+
// A fixed reason: the load error is Node's module-resolution text, which can name local paths.
|
|
1812
|
+
return c.json({ status: "unavailable", reason: GATEWAY_CATALOG_UNAVAILABLE });
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1676
1815
|
app.put("/api/config/runtime", async (c) => {
|
|
1677
1816
|
const patch = await c.req.json().catch(() => ({}));
|
|
1678
|
-
|
|
1817
|
+
// Seed the merge from what GET reports, so a first save patches the effective (boot) runtime.
|
|
1818
|
+
const stored = deps.store.getRuntimeSettings();
|
|
1819
|
+
const effective = deps.store.hasExplicitRuntimeSettings() ? stored : runtimeSettingsFromAuthChoice(deps.baseRouteOptions.authChoice, stored);
|
|
1820
|
+
const updated = { ...effective, ...patch };
|
|
1821
|
+
if (!isRuntimeAuthMode(updated.authMode)) {
|
|
1822
|
+
return c.json({ error: "authMode must be one of subscription, byo, local, gateway" }, 400);
|
|
1823
|
+
}
|
|
1679
1824
|
// Derive the candidate live AuthChoice from the merged (not-yet-persisted)
|
|
1680
1825
|
// settings and validate it BEFORE any mutation — a rejected save must leave
|
|
1681
1826
|
// both the persisted settings and the live auth binding untouched.
|
|
@@ -1712,7 +1857,9 @@ export function createApp(deps) {
|
|
|
1712
1857
|
if (candidateAuthChoice.mode !== "subscription") {
|
|
1713
1858
|
const envStatus = detectAdapterEnv();
|
|
1714
1859
|
for (const envVar of requiredInProcessCredentialEnvVars(updated)) {
|
|
1715
|
-
const present = envVar === "OPENAI_API_KEY" ? envStatus.openaiCompatible
|
|
1860
|
+
const present = envVar === "OPENAI_API_KEY" ? envStatus.openaiCompatible
|
|
1861
|
+
: envVar === GATEWAY_API_KEY_ENV ? envStatus.gateway === true
|
|
1862
|
+
: envStatus.anthropic;
|
|
1716
1863
|
if (!present)
|
|
1717
1864
|
return c.json({ error: `${envVar} is not set on the server — required by a configured tier adapter` }, 400);
|
|
1718
1865
|
}
|
|
@@ -1728,6 +1875,7 @@ export function createApp(deps) {
|
|
|
1728
1875
|
}
|
|
1729
1876
|
const revokedNativeSessionIds = deps.store.setRuntimeSettingsAndRevokeIncompatibleNativeSessions(updated);
|
|
1730
1877
|
deps.nativeSessions?.revokeRuntimeCapabilities(revokedNativeSessionIds);
|
|
1878
|
+
deps.structuredRuntime?.revoke();
|
|
1731
1879
|
deps.revokeInteractiveTerminals?.();
|
|
1732
1880
|
deps.setAuthChoice?.(candidateAuthChoice);
|
|
1733
1881
|
// describeBundle branches its target on authChoice.mode, so a cached
|
|
@@ -1909,6 +2057,7 @@ export function createApp(deps) {
|
|
|
1909
2057
|
}
|
|
1910
2058
|
const revokedNativeSessionIds = deps.store.resetSetup();
|
|
1911
2059
|
deps.nativeSessions?.revokeRuntimeCapabilities(revokedNativeSessionIds);
|
|
2060
|
+
deps.structuredRuntime?.revoke();
|
|
1912
2061
|
deps.revokeInteractiveTerminals?.();
|
|
1913
2062
|
deps.unbindProject?.();
|
|
1914
2063
|
// Reset also revokes the explicit runtime override. Restore the boot auth
|
|
@@ -3,14 +3,26 @@ import type { RuntimeSettings } from "./wire-types.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Maps the wizard's persisted `RuntimeSettings` to the harness-level `AuthChoice` that should
|
|
5
5
|
* actually be dispatched with. `RuntimeSettings.authMode` is the wire/persisted vocabulary
|
|
6
|
-
* (`"subscription" | "byo" | "local"`); `AuthChoice.mode` is the harness vocabulary
|
|
6
|
+
* (`"subscription" | "byo" | "local" | "gateway"`); `AuthChoice.mode` is the harness vocabulary
|
|
7
7
|
* (`"subscription" | "api-key" | "local" | "gateway"`) — `"byo"` maps to `"api-key"`.
|
|
8
8
|
*
|
|
9
9
|
* For the `openai-compatible` adapter, the BFF reads `OPENAI_API_KEY` from its own process env
|
|
10
10
|
* and injects it explicitly as `config.apiKey` — that adapter has no env self-read of its own.
|
|
11
11
|
* For `anthropic`, `config.apiKey` is deliberately omitted so the adapter self-reads
|
|
12
12
|
* `ANTHROPIC_API_KEY` from the process env itself (see `harness/providers/adapters/anthropic.ts`).
|
|
13
|
+
* For `gateway` (the `pi-ai` adapter), the BFF reads `GENBI_GATEWAY_API_KEY` and injects it as
|
|
14
|
+
* `config.apiKey` the same way — pi-ai never reads env vars or credential files on its own.
|
|
13
15
|
* The key value is never read into `RuntimeSettings`, SQLite, or any log — only referenced here,
|
|
14
16
|
* transiently, to build the in-memory `AuthChoice` passed straight to the adapter factory.
|
|
15
17
|
*/
|
|
16
18
|
export declare function toAuthChoiceFromRuntimeSettings(settings: RuntimeSettings): AuthChoice;
|
|
19
|
+
/** True for the wire `authMode` values `toAuthChoiceFromRuntimeSettings` can map. */
|
|
20
|
+
export declare function isRuntimeAuthMode(value: unknown): value is RuntimeSettings["authMode"];
|
|
21
|
+
/**
|
|
22
|
+
* The inverse of `toAuthChoiceFromRuntimeSettings`, for reporting the auth the BFF booted with
|
|
23
|
+
* (`WREN_HARNESS_MODE` / `MODEL` / `ENDPOINT` / flags) before the user has saved a runtime.
|
|
24
|
+
* Overlays only the auth-selection fields onto `base`; tier rows, deployment and hybrid stay the
|
|
25
|
+
* store's. Credentials never appear in the result. A boot adapter the wizard cannot represent
|
|
26
|
+
* leaves `base` untouched.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runtimeSettingsFromAuthChoice(choice: AuthChoice, base: RuntimeSettings): RuntimeSettings;
|