@wrenai/genbi 0.0.1
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/LICENSE +201 -0
- package/README.md +141 -0
- package/bin/genbi.mjs +31 -0
- package/dist/assets/echarts-LTFC-ysd.js +40 -0
- package/dist/assets/index-Bo--QMUN.js +441 -0
- package/dist/assets/index-Xs4q7pNo.css +1 -0
- package/dist/index.html +14 -0
- package/dist/wrenai_logo.png +0 -0
- package/dist/wrenai_logo_white.png +0 -0
- package/dist-server/harness/auth/detect.d.ts +18 -0
- package/dist-server/harness/auth/detect.js +37 -0
- package/dist-server/harness/auth/index.d.ts +4 -0
- package/dist-server/harness/auth/index.js +2 -0
- package/dist-server/harness/auth/probe.d.ts +18 -0
- package/dist-server/harness/auth/probe.js +52 -0
- package/dist-server/harness/auth/types.d.ts +42 -0
- package/dist-server/harness/auth/types.js +9 -0
- package/dist-server/harness/bundle/loader.d.ts +32 -0
- package/dist-server/harness/bundle/loader.js +43 -0
- package/dist-server/harness/bundle/schema.d.ts +199 -0
- package/dist-server/harness/bundle/schema.js +127 -0
- package/dist-server/harness/bundle/version.d.ts +10 -0
- package/dist-server/harness/bundle/version.js +43 -0
- package/dist-server/harness/capability/gate.d.ts +6 -0
- package/dist-server/harness/capability/gate.js +29 -0
- package/dist-server/harness/capability/registry.d.ts +6 -0
- package/dist-server/harness/capability/registry.js +17 -0
- package/dist-server/harness/cli-args.d.ts +123 -0
- package/dist-server/harness/cli-args.js +247 -0
- package/dist-server/harness/cli.d.ts +2 -0
- package/dist-server/harness/cli.js +151 -0
- package/dist-server/harness/compile/cache.d.ts +23 -0
- package/dist-server/harness/compile/cache.js +109 -0
- package/dist-server/harness/compile/compose-profile.d.ts +34 -0
- package/dist-server/harness/compile/compose-profile.js +95 -0
- package/dist-server/harness/compile/errors.d.ts +22 -0
- package/dist-server/harness/compile/errors.js +41 -0
- package/dist-server/harness/compile/fingerprint.d.ts +22 -0
- package/dist-server/harness/compile/fingerprint.js +84 -0
- package/dist-server/harness/compile/index.d.ts +8 -0
- package/dist-server/harness/compile/index.js +7 -0
- package/dist-server/harness/compile/pipeline.d.ts +66 -0
- package/dist-server/harness/compile/pipeline.js +176 -0
- package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
- package/dist-server/harness/compile/resolve-binary.js +133 -0
- package/dist-server/harness/compile/types.d.ts +107 -0
- package/dist-server/harness/compile/types.js +1 -0
- package/dist-server/harness/compile/warble-identity.d.ts +12 -0
- package/dist-server/harness/compile/warble-identity.js +33 -0
- package/dist-server/harness/compliance/gate.d.ts +35 -0
- package/dist-server/harness/compliance/gate.js +48 -0
- package/dist-server/harness/compliance/index.d.ts +2 -0
- package/dist-server/harness/compliance/index.js +1 -0
- package/dist-server/harness/compliance/types.d.ts +16 -0
- package/dist-server/harness/compliance/types.js +1 -0
- package/dist-server/harness/events/emitter.d.ts +34 -0
- package/dist-server/harness/events/emitter.js +25 -0
- package/dist-server/harness/events/index.d.ts +4 -0
- package/dist-server/harness/events/index.js +2 -0
- package/dist-server/harness/events/sse.d.ts +20 -0
- package/dist-server/harness/events/sse.js +48 -0
- package/dist-server/harness/events/types.d.ts +131 -0
- package/dist-server/harness/events/types.js +1 -0
- package/dist-server/harness/exec/errors.d.ts +16 -0
- package/dist-server/harness/exec/errors.js +29 -0
- package/dist-server/harness/exec/index.d.ts +5 -0
- package/dist-server/harness/exec/index.js +2 -0
- package/dist-server/harness/exec/local.d.ts +32 -0
- package/dist-server/harness/exec/local.js +134 -0
- package/dist-server/harness/exec/policy.d.ts +12 -0
- package/dist-server/harness/exec/policy.js +1 -0
- package/dist-server/harness/exec/types.d.ts +63 -0
- package/dist-server/harness/exec/types.js +1 -0
- package/dist-server/harness/guardrails/index.d.ts +2 -0
- package/dist-server/harness/guardrails/index.js +1 -0
- package/dist-server/harness/guardrails/policy.d.ts +45 -0
- package/dist-server/harness/guardrails/policy.js +57 -0
- package/dist-server/harness/index.d.ts +37 -0
- package/dist-server/harness/index.js +19 -0
- package/dist-server/harness/loop/errors.d.ts +53 -0
- package/dist-server/harness/loop/errors.js +72 -0
- package/dist-server/harness/loop/executor.d.ts +123 -0
- package/dist-server/harness/loop/executor.js +380 -0
- package/dist-server/harness/loop/guard.d.ts +16 -0
- package/dist-server/harness/loop/guard.js +1 -0
- package/dist-server/harness/loop/index.d.ts +5 -0
- package/dist-server/harness/loop/index.js +3 -0
- package/dist-server/harness/native-producer/index.d.ts +109 -0
- package/dist-server/harness/native-producer/index.js +302 -0
- package/dist-server/harness/npm-package-resolve.d.ts +28 -0
- package/dist-server/harness/npm-package-resolve.js +47 -0
- package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
- package/dist-server/harness/providers/adapters/anthropic.js +6 -0
- package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
- package/dist-server/harness/providers/adapters/mock.js +5 -0
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
- package/dist-server/harness/providers/binding.d.ts +23 -0
- package/dist-server/harness/providers/binding.js +13 -0
- package/dist-server/harness/providers/errors.d.ts +6 -0
- package/dist-server/harness/providers/errors.js +12 -0
- package/dist-server/harness/providers/index.d.ts +18 -0
- package/dist-server/harness/providers/index.js +22 -0
- package/dist-server/harness/providers/registry.d.ts +16 -0
- package/dist-server/harness/providers/registry.js +24 -0
- package/dist-server/harness/render/envelope.d.ts +145 -0
- package/dist-server/harness/render/envelope.js +428 -0
- package/dist-server/harness/render/errors.d.ts +33 -0
- package/dist-server/harness/render/errors.js +46 -0
- package/dist-server/harness/render/index.d.ts +5 -0
- package/dist-server/harness/render/index.js +3 -0
- package/dist-server/harness/render/validate.d.ts +13 -0
- package/dist-server/harness/render/validate.js +88 -0
- package/dist-server/harness/route/adapter-spec.d.ts +40 -0
- package/dist-server/harness/route/adapter-spec.js +61 -0
- package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
- package/dist-server/harness/route/agent-sdk-cli.js +86 -0
- package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
- package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
- package/dist-server/harness/route/artifact-content.d.ts +53 -0
- package/dist-server/harness/route/artifact-content.js +83 -0
- package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
- package/dist-server/harness/route/chat-event-mapper.js +164 -0
- package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
- package/dist-server/harness/route/codex-ask-events.js +395 -0
- package/dist-server/harness/route/codex-ask.d.ts +15 -0
- package/dist-server/harness/route/codex-ask.js +242 -0
- package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
- package/dist-server/harness/route/codex-local-cli.js +78 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
- package/dist-server/harness/route/codex-local-manifest.js +180 -0
- package/dist-server/harness/route/describe.d.ts +54 -0
- package/dist-server/harness/route/describe.js +84 -0
- package/dist-server/harness/route/dispatched.d.ts +138 -0
- package/dist-server/harness/route/dispatched.js +339 -0
- package/dist-server/harness/route/in-process.d.ts +43 -0
- package/dist-server/harness/route/in-process.js +106 -0
- package/dist-server/harness/route/index.d.ts +19 -0
- package/dist-server/harness/route/index.js +12 -0
- package/dist-server/harness/route/profile-source.d.ts +37 -0
- package/dist-server/harness/route/profile-source.js +76 -0
- package/dist-server/harness/route/route.d.ts +33 -0
- package/dist-server/harness/route/route.js +114 -0
- package/dist-server/harness/route/tier-binding.d.ts +36 -0
- package/dist-server/harness/route/tier-binding.js +63 -0
- package/dist-server/harness/route/types.d.ts +286 -0
- package/dist-server/harness/route/types.js +1 -0
- package/dist-server/harness/session/errors.d.ts +4 -0
- package/dist-server/harness/session/errors.js +7 -0
- package/dist-server/harness/session/gate.d.ts +11 -0
- package/dist-server/harness/session/gate.js +13 -0
- package/dist-server/harness/session/index.d.ts +5 -0
- package/dist-server/harness/session/index.js +3 -0
- package/dist-server/harness/session/run.d.ts +60 -0
- package/dist-server/harness/session/run.js +333 -0
- package/dist-server/harness/session/types.d.ts +29 -0
- package/dist-server/harness/session/types.js +1 -0
- package/dist-server/harness/setup/codex-events.d.ts +12 -0
- package/dist-server/harness/setup/codex-events.js +108 -0
- package/dist-server/harness/setup/runner.d.ts +497 -0
- package/dist-server/harness/setup/runner.js +1314 -0
- package/dist-server/harness/tools/errors.d.ts +107 -0
- package/dist-server/harness/tools/errors.js +141 -0
- package/dist-server/harness/tools/index.d.ts +13 -0
- package/dist-server/harness/tools/index.js +8 -0
- package/dist-server/harness/tools/mcp.d.ts +26 -0
- package/dist-server/harness/tools/mcp.js +15 -0
- package/dist-server/harness/tools/native.d.ts +179 -0
- package/dist-server/harness/tools/native.js +322 -0
- package/dist-server/harness/tools/parse.d.ts +14 -0
- package/dist-server/harness/tools/parse.js +18 -0
- package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
- package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
- package/dist-server/harness/tools/resolve.d.ts +51 -0
- package/dist-server/harness/tools/resolve.js +79 -0
- package/dist-server/harness/tools/setup-native.d.ts +84 -0
- package/dist-server/harness/tools/setup-native.js +353 -0
- package/dist-server/harness/tools/wiring.d.ts +10 -0
- package/dist-server/harness/tools/wiring.js +16 -0
- package/dist-server/providers/setup.provider.yaml +31 -0
- package/dist-server/providers/wren.provider.yaml +33 -0
- package/dist-server/server/adopt.d.ts +87 -0
- package/dist-server/server/adopt.js +263 -0
- package/dist-server/server/app.d.ts +6 -0
- package/dist-server/server/app.js +2521 -0
- package/dist-server/server/auth-choice.d.ts +16 -0
- package/dist-server/server/auth-choice.js +36 -0
- package/dist-server/server/bin.d.ts +2 -0
- package/dist-server/server/bin.js +601 -0
- package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
- package/dist-server/server/bootstrap-project-binding.js +71 -0
- package/dist-server/server/clarify.d.ts +10 -0
- package/dist-server/server/clarify.js +17 -0
- package/dist-server/server/codex-setup-mcp.d.ts +2 -0
- package/dist-server/server/codex-setup-mcp.js +147 -0
- package/dist-server/server/compose.d.ts +89 -0
- package/dist-server/server/compose.js +271 -0
- package/dist-server/server/conn-config.d.ts +30 -0
- package/dist-server/server/conn-config.js +253 -0
- package/dist-server/server/context-files.d.ts +12 -0
- package/dist-server/server/context-files.js +117 -0
- package/dist-server/server/context-map.d.ts +30 -0
- package/dist-server/server/context-map.js +95 -0
- package/dist-server/server/context-source.d.ts +68 -0
- package/dist-server/server/context-source.js +85 -0
- package/dist-server/server/db.d.ts +862 -0
- package/dist-server/server/db.js +1839 -0
- package/dist-server/server/enrichment-runner.d.ts +184 -0
- package/dist-server/server/enrichment-runner.js +213 -0
- package/dist-server/server/enrichment-verify.d.ts +73 -0
- package/dist-server/server/enrichment-verify.js +290 -0
- package/dist-server/server/enrichment.d.ts +232 -0
- package/dist-server/server/enrichment.js +199 -0
- package/dist-server/server/env-detect.d.ts +8 -0
- package/dist-server/server/env-detect.js +17 -0
- package/dist-server/server/env-field-hints.d.ts +46 -0
- package/dist-server/server/env-field-hints.js +48 -0
- package/dist-server/server/fold.d.ts +133 -0
- package/dist-server/server/fold.js +407 -0
- package/dist-server/server/harness-profile-sources.d.ts +3 -0
- package/dist-server/server/harness-profile-sources.js +24 -0
- package/dist-server/server/harness.d.ts +19 -0
- package/dist-server/server/harness.js +439 -0
- package/dist-server/server/impact.d.ts +18 -0
- package/dist-server/server/impact.js +85 -0
- package/dist-server/server/interactive-terminal.d.ts +175 -0
- package/dist-server/server/interactive-terminal.js +336 -0
- package/dist-server/server/native-artifacts.d.ts +1004 -0
- package/dist-server/server/native-artifacts.js +564 -0
- package/dist-server/server/native-dispatch-registry.d.ts +42 -0
- package/dist-server/server/native-dispatch-registry.js +45 -0
- package/dist-server/server/native-producer-fence.d.ts +14 -0
- package/dist-server/server/native-producer-fence.js +48 -0
- package/dist-server/server/native-session-resume.d.ts +6 -0
- package/dist-server/server/native-session-resume.js +103 -0
- package/dist-server/server/native-session-workspace.d.ts +40 -0
- package/dist-server/server/native-session-workspace.js +175 -0
- package/dist-server/server/native-sessions.d.ts +374 -0
- package/dist-server/server/native-sessions.js +1517 -0
- package/dist-server/server/native-wren-runtime.d.ts +20 -0
- package/dist-server/server/native-wren-runtime.js +259 -0
- package/dist-server/server/node-pty-host.d.ts +31 -0
- package/dist-server/server/node-pty-host.js +100 -0
- package/dist-server/server/route-intent.d.ts +40 -0
- package/dist-server/server/route-intent.js +23 -0
- package/dist-server/server/runtime-binding.d.ts +52 -0
- package/dist-server/server/runtime-binding.js +258 -0
- package/dist-server/server/source-catalog.d.ts +84 -0
- package/dist-server/server/source-catalog.js +221 -0
- package/dist-server/server/source-driver.d.ts +35 -0
- package/dist-server/server/source-driver.js +124 -0
- package/dist-server/server/spa.d.ts +2 -0
- package/dist-server/server/spa.js +51 -0
- package/dist-server/server/subscription-model-catalog.d.ts +27 -0
- package/dist-server/server/subscription-model-catalog.js +203 -0
- package/dist-server/server/turn.d.ts +249 -0
- package/dist-server/server/turn.js +1074 -0
- package/dist-server/server/wire-types.d.ts +799 -0
- package/dist-server/server/wire-types.js +1 -0
- package/dist-server/server/wren-home.d.ts +19 -0
- package/dist-server/server/wren-home.js +33 -0
- package/dist-server/server/wren-profiles.d.ts +43 -0
- package/dist-server/server/wren-profiles.js +123 -0
- package/package.json +93 -0
- package/profiles/genbi-default/context/binding.yml +9 -0
- package/profiles/genbi-default/ir.golden.json +506 -0
- package/profiles/genbi-default/profile.yml +16 -0
- package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
- package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
- package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
- package/profiles/genbi-enrich-context/context/binding.yml +4 -0
- package/profiles/genbi-enrich-context/ir.golden.json +302 -0
- package/profiles/genbi-enrich-context/profile.yml +15 -0
- package/profiles/genbi-monitor/context/binding.yml +7 -0
- package/profiles/genbi-monitor/ir.golden.json +188 -0
- package/profiles/genbi-monitor/profile.yml +27 -0
- package/profiles/genbi-setup/components/build_context/component.yml +44 -0
- package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
- package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
- package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
- package/profiles/genbi-setup/context/binding.yml +8 -0
- package/profiles/genbi-setup/ir.golden.json +140 -0
- package/profiles/genbi-setup/profile.yml +18 -0
- package/profiles/genbi-setup/raw/schema.json +5 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createFileSystemCompileCache } from "./cache.js";
|
|
7
|
+
import { composeUserProfile } from "./compose-profile.js";
|
|
8
|
+
import { WarbleCommandFailedError } from "./errors.js";
|
|
9
|
+
import { hashDirectory, hashFiles } from "./fingerprint.js";
|
|
10
|
+
import { resolveHubDir, resolveWarbleBinary } from "./resolve-binary.js";
|
|
11
|
+
import { getWarbleIdentity } from "./warble-identity.js";
|
|
12
|
+
/** Fixed `providerFragmentHash` for `"native"` mode, which never reads `--provider` fragments at all. */
|
|
13
|
+
const NATIVE_MODE_PROVIDER_HASH = "native:no-providers";
|
|
14
|
+
/**
|
|
15
|
+
* This package's bundled wren capability-provider fragment (`providers/wren.provider.yaml`) — the
|
|
16
|
+
* default `--provider` for in-process dispatch, matching how `fixtures/genbi-default.native.bundle.json`
|
|
17
|
+
* was produced (see `test/e2e-wren-native.test.ts`'s comment).
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_WREN_PROVIDER_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "providers", "wren.provider.yaml");
|
|
20
|
+
// guardrail-enforcement fix: a hung `warble compile`/`dispatch` invocation
|
|
21
|
+
// previously blocked forever. 2 minutes is a generous ceiling for a local
|
|
22
|
+
// compile/dispatch step that normally finishes in seconds; guards against a
|
|
23
|
+
// hang, not meant to ever fire in normal operation.
|
|
24
|
+
const WARBLE_COMMAND_TIMEOUT_MS = 2 * 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* Compiles a Warble profile bound to a specific user's wren project (the per-user compile
|
|
27
|
+
* pipeline): composes a copy of `profileSource` with its context binding rebound to `userProject`
|
|
28
|
+
* (see `composeUserProfile`), shells out to `warble compile` for the IR, and — in `"agnostic"`
|
|
29
|
+
* mode — additionally `warble dispatch --target vercel` for the vercel bundle this package's
|
|
30
|
+
* `runAgent()` consumes.
|
|
31
|
+
*
|
|
32
|
+
* A cache hit (see below) never *executes* `warble`, but by default DOES need to resolve + read
|
|
33
|
+
* the binary — its content hash and the Hub root derived from its location are both cache-key
|
|
34
|
+
* inputs (see `warbleIdentity` and `hubDir` below), so a hit can't be trusted without knowing which
|
|
35
|
+
* binary is current and which component library it reads. Pass both `options.warbleIdentity` and
|
|
36
|
+
* `options.hubDir` explicitly to skip touching the binary at all even on a hit.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately shells via plain `node:child_process` (`execFile`, no shell) rather than this
|
|
39
|
+
* package's `ExecutionEnv` seam (`harness/exec/`): `ExecutionEnv` gates *runtime* native-tool side
|
|
40
|
+
* effects against a bundle's guardrail-derived `EnforcementPolicy` (read-only / write-scope /
|
|
41
|
+
* egress) — there is no bundle or agent yet at this point, compiling happens before any of that
|
|
42
|
+
* exists, so synthesizing a policy for it would be a semantic mismatch, not a reuse. This mirrors
|
|
43
|
+
* the same no-shell, structured-result `execFile` convention `harness/exec/local.ts` already uses.
|
|
44
|
+
*
|
|
45
|
+
* Results are cached on (profile content hash x user-context fingerprint x mode x resolved
|
|
46
|
+
* provider-fragment content hash x warble binary identity x resolved Hub root) — see `./cache.js` —
|
|
47
|
+
* so an unchanged profile/context/providers/compiler/Hub tuple returns the previous artifact
|
|
48
|
+
* instead of recompiling. Folding in the provider-fragment hash, warble identity and Hub root
|
|
49
|
+
* (rather than just profile+context+mode) matters: otherwise a custom `options.providers` and this
|
|
50
|
+
* package's default fragment collide on the same key, and editing a provider fragment, rebuilding
|
|
51
|
+
* `warble`, or compiling against a different component library would keep serving a stale artifact
|
|
52
|
+
* forever, since there's no TTL/eviction.
|
|
53
|
+
*
|
|
54
|
+
* The `warble` invocations run inside a temporary scratch directory (the composed profile + raw
|
|
55
|
+
* compiler output). When the cache relocates the artifacts to a durable home outside that scratch
|
|
56
|
+
* dir — as the default persistent {@link createFileSystemCompileCache} does — the scratch dir is
|
|
57
|
+
* removed and the durable cache paths are returned. When the cache does NOT relocate them (e.g. a
|
|
58
|
+
* non-persistent in-memory cache that just stores the paths), the scratch dir is retained so the
|
|
59
|
+
* returned paths stay valid. Either way the returned `irPath`/`bundlePath` are always readable, and
|
|
60
|
+
* a caller-supplied `workDir` is never deleted.
|
|
61
|
+
*/
|
|
62
|
+
export async function compileProfile(options) {
|
|
63
|
+
return compileProfileSource(options, await hashDirectory(path.resolve(options.userProject)), (workDir) => composeUserProfile({
|
|
64
|
+
profileSource: options.profileSource,
|
|
65
|
+
userProject: options.userProject,
|
|
66
|
+
destDir: workDir,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Compiles a profile's authored context without composing it against a bound
|
|
71
|
+
* project. The fixed context fingerprint keeps raw-profile artifacts distinct
|
|
72
|
+
* from every user-project-bound compile while `profileHash` still invalidates
|
|
73
|
+
* the entry whenever the source profile changes.
|
|
74
|
+
*/
|
|
75
|
+
export async function compileRawProfile(options) {
|
|
76
|
+
return compileProfileSource(options, "raw-profile-context", async () => path.resolve(options.profileSource));
|
|
77
|
+
}
|
|
78
|
+
async function compileProfileSource(options, contextFingerprint, prepareProfile) {
|
|
79
|
+
const cache = options.cache ?? createFileSystemCompileCache();
|
|
80
|
+
const profileHash = await hashDirectory(path.resolve(options.profileSource));
|
|
81
|
+
const providerPaths = options.providers ?? [DEFAULT_WREN_PROVIDER_PATH];
|
|
82
|
+
const providerFragmentHash = options.mode === "agnostic" ? await hashFiles(providerPaths) : NATIVE_MODE_PROVIDER_HASH;
|
|
83
|
+
// Resolving `warble` is memoized here so it happens at most once per call no matter how many of
|
|
84
|
+
// the two spots below need it (identity computation, and — on a miss — actually running it).
|
|
85
|
+
let resolvedWarbleBin;
|
|
86
|
+
const ensureWarbleBin = async () => {
|
|
87
|
+
if (resolvedWarbleBin === undefined) {
|
|
88
|
+
resolvedWarbleBin = await resolveWarbleBinary(options.warbleBin);
|
|
89
|
+
}
|
|
90
|
+
return resolvedWarbleBin;
|
|
91
|
+
};
|
|
92
|
+
// Without an explicit `options.warbleIdentity`, computing the key requires resolving the binary
|
|
93
|
+
// even for what would otherwise be a cache hit — the whole point of folding warble's identity in
|
|
94
|
+
// is to invalidate a hit whose binary has since been rebuilt, which is unknowable without
|
|
95
|
+
// checking. A caller who wants the old "no warble touched at all on a hit" property back can get
|
|
96
|
+
// it by passing `warbleIdentity` explicitly (see `CompileProfileOptions.warbleIdentity`).
|
|
97
|
+
const warbleIdentity = options.warbleIdentity ?? (await getWarbleIdentity(await ensureWarbleBin()));
|
|
98
|
+
// Derived from the resolved binary, never configured on its own, so the compiler and the Hub it
|
|
99
|
+
// reads always come from the same warble (see `resolveHubDir`). That means it shares
|
|
100
|
+
// `warbleIdentity`'s caveat: without an explicit `options.hubDir` the key can't be computed
|
|
101
|
+
// without resolving the binary. `undefined` here means no `--hub-dir` is passed at all and
|
|
102
|
+
// warble's compiled-in default applies — today's behaviour, preserved.
|
|
103
|
+
const hubDir = options.hubDir ?? resolveHubDir(await ensureWarbleBin());
|
|
104
|
+
const cacheKey = { profileHash, contextFingerprint, mode: options.mode, providerFragmentHash, warbleIdentity, hubDir };
|
|
105
|
+
const cached = await cache.get(cacheKey);
|
|
106
|
+
if (cached !== undefined) {
|
|
107
|
+
return { ...cached, cacheHit: true, ...(resolvedWarbleBin !== undefined ? { warbleBin: resolvedWarbleBin } : {}) };
|
|
108
|
+
}
|
|
109
|
+
const warbleBin = await ensureWarbleBin();
|
|
110
|
+
const callerSuppliedWorkDir = options.workDir !== undefined;
|
|
111
|
+
const workDir = options.workDir ?? (await mkdtemp(path.join(os.tmpdir(), "wren-harness-compile-")));
|
|
112
|
+
// Only ever remove a scratch dir we created ourselves; refine to `false` if the cache leaves the
|
|
113
|
+
// artifacts inside it (removing them would invalidate the returned paths).
|
|
114
|
+
let removeWorkDir = !callerSuppliedWorkDir;
|
|
115
|
+
try {
|
|
116
|
+
const compiledProfile = await prepareProfile(workDir);
|
|
117
|
+
const irPath = path.join(workDir, "ir.json");
|
|
118
|
+
// `--hub-dir` reached the compiler in July 2026, well before the IR version this harness
|
|
119
|
+
// requires, so any binary new enough to satisfy that contract accepts the flag. A binary
|
|
120
|
+
// older than both was already unsupported — but it used to say so as an IR version
|
|
121
|
+
// mismatch, and now clap rejects the argument first. The configuration is no more broken
|
|
122
|
+
// than before; only the message got less helpful, which is worth knowing when one appears.
|
|
123
|
+
const hubDirArgs = hubDir !== undefined ? ["--hub-dir", hubDir] : [];
|
|
124
|
+
await runWarble(warbleBin, ["compile", compiledProfile, "-o", irPath, ...hubDirArgs]);
|
|
125
|
+
let bundlePath;
|
|
126
|
+
if (options.mode === "agnostic") {
|
|
127
|
+
const bundleOutDir = path.join(workDir, "bundle");
|
|
128
|
+
const providerArgs = providerPaths.flatMap((provider) => ["--provider", provider]);
|
|
129
|
+
await runWarble(warbleBin, ["dispatch", "--target", "vercel", ...providerArgs, irPath, "--out", bundleOutDir]);
|
|
130
|
+
bundlePath = path.join(bundleOutDir, "bundle.json");
|
|
131
|
+
}
|
|
132
|
+
const freshEntry = { irPath, ...(bundlePath !== undefined ? { bundlePath } : {}) };
|
|
133
|
+
await cache.set(cacheKey, freshEntry);
|
|
134
|
+
// Prefer the cache's persisted paths; if the cache relocated the IR out of the scratch dir the
|
|
135
|
+
// scratch dir is pure garbage now and safe to delete, otherwise keep it so paths stay valid.
|
|
136
|
+
const persisted = (await cache.get(cacheKey)) ?? freshEntry;
|
|
137
|
+
if (persisted.irPath === freshEntry.irPath) {
|
|
138
|
+
removeWorkDir = false;
|
|
139
|
+
}
|
|
140
|
+
return { ...persisted, cacheHit: false, warbleBin };
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (removeWorkDir) {
|
|
144
|
+
await rm(workDir, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Shells a `warble` subcommand (no shell interpolation — `execFile`, argv
|
|
150
|
+
* array only). Exported for `InProcessSetupRunner` (`harness/setup/runner.ts`),
|
|
151
|
+
* which dispatches the pre-committed `genbi-setup/ir.golden.json` straight to
|
|
152
|
+
* `warble dispatch --target vercel`, bypassing `compileProfile`'s
|
|
153
|
+
* `composeUserProfile` rewrite entirely (there is no user project to bind
|
|
154
|
+
* context against at setup time — see that runner's doc comment). Reusing
|
|
155
|
+
* this rather than duplicating an `execFile` wrapper keeps the two callers'
|
|
156
|
+
* timeout/max-buffer/error-shaping behavior identical.
|
|
157
|
+
*/
|
|
158
|
+
export function runWarble(bin, args) {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
execFile(bin, [...args], { maxBuffer: 32 * 1024 * 1024, timeout: WARBLE_COMMAND_TIMEOUT_MS }, (error, stdout, stderr) => {
|
|
161
|
+
if (error) {
|
|
162
|
+
const exitCode = typeof error.code === "number" ? error.code : 1;
|
|
163
|
+
const clarifiedStderr = stderr.length > 0
|
|
164
|
+
? stderr
|
|
165
|
+
: error.killed === true
|
|
166
|
+
? `warble subprocess timed out after ${WARBLE_COMMAND_TIMEOUT_MS}ms`
|
|
167
|
+
: error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
|
|
168
|
+
? "warble subprocess exceeded the 32 MiB output buffer"
|
|
169
|
+
: stderr;
|
|
170
|
+
reject(new WarbleCommandFailedError(bin, args, exitCode, clarifiedStderr, stdout));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
resolve({ stdout, stderr });
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the `warble` binary in four tiers, in order:
|
|
3
|
+
*
|
|
4
|
+
* 1. `explicit` — if given, it must exist on disk (no PATH lookup, no fallback).
|
|
5
|
+
* 2. The pinned `@warble/cli` npm package this workspace depends on (see
|
|
6
|
+
* `resolveInstalledPackageBin`) — deterministic regardless of `PATH` contents or how the
|
|
7
|
+
* process was launched, so the reviewable version in `package.json` always wins.
|
|
8
|
+
* 3. `PATH` — a bare `"warble"` that `warble --help` succeeds against. NOT `--version`: the warble
|
|
9
|
+
* CLI has no `--version` flag (clap rejects it with exit 2 — the same fact
|
|
10
|
+
* `test/compile-warble-identity.test.ts` notes for a different reason), so probing with it made
|
|
11
|
+
* this tier fail for a binary that was on `PATH` and working, silently pushing every caller past
|
|
12
|
+
* it. `warble --help` exits 0 and is what the sibling `warble-agent-sdk` probe already uses.
|
|
13
|
+
* Kept as a fallback for a standalone install (e.g. `cargo install warble-cli`) on a machine
|
|
14
|
+
* that hasn't run `pnpm install` against this package's pinned dependency.
|
|
15
|
+
* 4. A sibling `warble` repo's release build, gated behind the explicit
|
|
16
|
+
* `WREN_HARNESS_ALLOW_WARBLE_SIBLING_CHECKOUT=1` development-mode opt-in (see
|
|
17
|
+
* `isWarbleSiblingCheckoutDevModeEnabled`) — this walks up from this package's own install
|
|
18
|
+
* location looking for a `warble/target/release/warble` next to an ancestor directory, so it
|
|
19
|
+
* works whether the caller runs from the main checkout or from a git worktree nested several
|
|
20
|
+
* levels under it. Off by default: now that the pinned `@warble/cli` package is tier 2, this
|
|
21
|
+
* tier exists only for someone deliberately developing GenBI against an unreleased, uninstalled
|
|
22
|
+
* Warble checkout, and must not silently win over the pinned package.
|
|
23
|
+
*
|
|
24
|
+
* Throws {@link WarbleBinaryNotFoundError} (loud-fail, no silent degrade) if none of the four work.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveWarbleBinary(explicit?: string): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the Hub component-library root to pass as `warble compile --hub-dir`, derived from the
|
|
29
|
+
* **already-resolved** `warble` binary rather than configured on its own.
|
|
30
|
+
*
|
|
31
|
+
* warble bakes its default Hub path in at *build* time (its own doc says the Hub is known at
|
|
32
|
+
* compile time and never discovered by walking the filesystem at run time), so a binary built
|
|
33
|
+
* inside a warble checkout still finds its own Hub with no flag at all. That stops being true the
|
|
34
|
+
* moment the compiler is not a checkout-local build — installed from a release, found on `PATH`,
|
|
35
|
+
* built in CI, or a checkout that has since moved — and the failure is silent: components resolve
|
|
36
|
+
* against the wrong library, or against none. Naming the root turns that into a loud "component
|
|
37
|
+
* not found in any configured source".
|
|
38
|
+
*
|
|
39
|
+
* Deriving it from the binary is the whole point. The compiler and the Hub it reads must come from
|
|
40
|
+
* the same warble version; an independently configured Hub root can silently pair a new library
|
|
41
|
+
* with an old compiler, which is the same class of mismatch `CompileCacheKey.warbleIdentity`
|
|
42
|
+
* exists to catch.
|
|
43
|
+
*
|
|
44
|
+
* Two tiers, then give up:
|
|
45
|
+
*
|
|
46
|
+
* 1. The binary's own checkout — a `<root>/target/release/warble` puts the Hub at
|
|
47
|
+
* `<root>/hub/components`. Skipped for a bare `"warble"` resolved off `PATH`, which names no
|
|
48
|
+
* directory to walk up from.
|
|
49
|
+
* 2. A sibling `warble` checkout's `hub/components`, via the same ancestor walk tier 3 of
|
|
50
|
+
* {@link resolveWarbleBinary} uses.
|
|
51
|
+
*
|
|
52
|
+
* Returns `undefined` when neither resolves; callers then pass no `--hub-dir` at all, leaving
|
|
53
|
+
* today's compiled-in default in charge rather than pointing warble at a directory we guessed.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveHubDir(warbleBin: string): string | undefined;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { WarbleBinaryNotFoundError } from "./errors.js";
|
|
6
|
+
import { isWarbleSiblingCheckoutDevModeEnabled, resolveInstalledPackageBin } from "../npm-package-resolve.js";
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the `warble` binary in four tiers, in order:
|
|
9
|
+
*
|
|
10
|
+
* 1. `explicit` — if given, it must exist on disk (no PATH lookup, no fallback).
|
|
11
|
+
* 2. The pinned `@warble/cli` npm package this workspace depends on (see
|
|
12
|
+
* `resolveInstalledPackageBin`) — deterministic regardless of `PATH` contents or how the
|
|
13
|
+
* process was launched, so the reviewable version in `package.json` always wins.
|
|
14
|
+
* 3. `PATH` — a bare `"warble"` that `warble --help` succeeds against. NOT `--version`: the warble
|
|
15
|
+
* CLI has no `--version` flag (clap rejects it with exit 2 — the same fact
|
|
16
|
+
* `test/compile-warble-identity.test.ts` notes for a different reason), so probing with it made
|
|
17
|
+
* this tier fail for a binary that was on `PATH` and working, silently pushing every caller past
|
|
18
|
+
* it. `warble --help` exits 0 and is what the sibling `warble-agent-sdk` probe already uses.
|
|
19
|
+
* Kept as a fallback for a standalone install (e.g. `cargo install warble-cli`) on a machine
|
|
20
|
+
* that hasn't run `pnpm install` against this package's pinned dependency.
|
|
21
|
+
* 4. A sibling `warble` repo's release build, gated behind the explicit
|
|
22
|
+
* `WREN_HARNESS_ALLOW_WARBLE_SIBLING_CHECKOUT=1` development-mode opt-in (see
|
|
23
|
+
* `isWarbleSiblingCheckoutDevModeEnabled`) — this walks up from this package's own install
|
|
24
|
+
* location looking for a `warble/target/release/warble` next to an ancestor directory, so it
|
|
25
|
+
* works whether the caller runs from the main checkout or from a git worktree nested several
|
|
26
|
+
* levels under it. Off by default: now that the pinned `@warble/cli` package is tier 2, this
|
|
27
|
+
* tier exists only for someone deliberately developing GenBI against an unreleased, uninstalled
|
|
28
|
+
* Warble checkout, and must not silently win over the pinned package.
|
|
29
|
+
*
|
|
30
|
+
* Throws {@link WarbleBinaryNotFoundError} (loud-fail, no silent degrade) if none of the four work.
|
|
31
|
+
*/
|
|
32
|
+
export async function resolveWarbleBinary(explicit) {
|
|
33
|
+
if (explicit !== undefined) {
|
|
34
|
+
if (!existsSync(explicit)) {
|
|
35
|
+
throw new WarbleBinaryNotFoundError([`explicit warbleBin "${explicit}" does not exist`]);
|
|
36
|
+
}
|
|
37
|
+
return explicit;
|
|
38
|
+
}
|
|
39
|
+
const attempts = [];
|
|
40
|
+
const packageBin = resolveInstalledPackageBin("@warble/cli", "warble");
|
|
41
|
+
if (packageBin !== undefined) {
|
|
42
|
+
return packageBin;
|
|
43
|
+
}
|
|
44
|
+
attempts.push(`"@warble/cli" is not installed (or declares no "warble" bin) in this workspace`);
|
|
45
|
+
if (await isExecutableOnPath("warble")) {
|
|
46
|
+
return "warble";
|
|
47
|
+
}
|
|
48
|
+
attempts.push(`not found on PATH (tried running "warble --help")`);
|
|
49
|
+
if (isWarbleSiblingCheckoutDevModeEnabled()) {
|
|
50
|
+
const sibling = findSiblingReleaseBuild();
|
|
51
|
+
if (sibling !== undefined) {
|
|
52
|
+
return sibling;
|
|
53
|
+
}
|
|
54
|
+
attempts.push(`no sibling "warble" repo release build found (searched ancestors of this package for ` +
|
|
55
|
+
`"<ancestor's parent>/warble/target/release/warble")`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
attempts.push(`sibling "warble" checkout dev mode is disabled (set WREN_HARNESS_ALLOW_WARBLE_SIBLING_CHECKOUT=1 to enable it)`);
|
|
59
|
+
}
|
|
60
|
+
throw new WarbleBinaryNotFoundError(attempts);
|
|
61
|
+
}
|
|
62
|
+
function isExecutableOnPath(bin) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
// Callback overload (no options): output is buffered and discarded — we only care whether the
|
|
65
|
+
// process launched and exited 0. `warble --help` output is tiny, so no maxBuffer concern.
|
|
66
|
+
execFile(bin, ["--help"], (error) => resolve(!error));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolves the Hub component-library root to pass as `warble compile --hub-dir`, derived from the
|
|
71
|
+
* **already-resolved** `warble` binary rather than configured on its own.
|
|
72
|
+
*
|
|
73
|
+
* warble bakes its default Hub path in at *build* time (its own doc says the Hub is known at
|
|
74
|
+
* compile time and never discovered by walking the filesystem at run time), so a binary built
|
|
75
|
+
* inside a warble checkout still finds its own Hub with no flag at all. That stops being true the
|
|
76
|
+
* moment the compiler is not a checkout-local build — installed from a release, found on `PATH`,
|
|
77
|
+
* built in CI, or a checkout that has since moved — and the failure is silent: components resolve
|
|
78
|
+
* against the wrong library, or against none. Naming the root turns that into a loud "component
|
|
79
|
+
* not found in any configured source".
|
|
80
|
+
*
|
|
81
|
+
* Deriving it from the binary is the whole point. The compiler and the Hub it reads must come from
|
|
82
|
+
* the same warble version; an independently configured Hub root can silently pair a new library
|
|
83
|
+
* with an old compiler, which is the same class of mismatch `CompileCacheKey.warbleIdentity`
|
|
84
|
+
* exists to catch.
|
|
85
|
+
*
|
|
86
|
+
* Two tiers, then give up:
|
|
87
|
+
*
|
|
88
|
+
* 1. The binary's own checkout — a `<root>/target/release/warble` puts the Hub at
|
|
89
|
+
* `<root>/hub/components`. Skipped for a bare `"warble"` resolved off `PATH`, which names no
|
|
90
|
+
* directory to walk up from.
|
|
91
|
+
* 2. A sibling `warble` checkout's `hub/components`, via the same ancestor walk tier 3 of
|
|
92
|
+
* {@link resolveWarbleBinary} uses.
|
|
93
|
+
*
|
|
94
|
+
* Returns `undefined` when neither resolves; callers then pass no `--hub-dir` at all, leaving
|
|
95
|
+
* today's compiled-in default in charge rather than pointing warble at a directory we guessed.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveHubDir(warbleBin) {
|
|
98
|
+
// `path.dirname("warble") === "."` is exactly the bare-PATH case: resolving `..` against the
|
|
99
|
+
// process cwd would name some unrelated ancestor, so only a binary that names a directory
|
|
100
|
+
// gets tier 1.
|
|
101
|
+
if (path.dirname(warbleBin) !== ".") {
|
|
102
|
+
const candidate = path.resolve(warbleBin, "..", "..", "..", "hub", "components");
|
|
103
|
+
if (existsSync(candidate))
|
|
104
|
+
return candidate;
|
|
105
|
+
}
|
|
106
|
+
return findSiblingWarbleEntry("hub", "components");
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Walks up from this package's own directory (`harness/compile/` in source, `dist/compile/` once
|
|
110
|
+
* built — either way two levels below the package root) looking for a `warble` directory sibling
|
|
111
|
+
* to each ancestor, returning `.../warble/target/release/warble` the first time it exists.
|
|
112
|
+
*/
|
|
113
|
+
function findSiblingReleaseBuild() {
|
|
114
|
+
return findSiblingWarbleEntry("target", "release", "warble");
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The shared ancestor walk behind both sibling lookups above: for each ancestor of this package,
|
|
118
|
+
* test `<ancestor's parent>/warble/<...segments>` and return the first that exists.
|
|
119
|
+
*/
|
|
120
|
+
function findSiblingWarbleEntry(...segments) {
|
|
121
|
+
let dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
122
|
+
const maxDepth = 10;
|
|
123
|
+
for (let i = 0; i < maxDepth; i += 1) {
|
|
124
|
+
const parent = path.dirname(dir);
|
|
125
|
+
const candidate = path.join(parent, "warble", ...segments);
|
|
126
|
+
if (existsSync(candidate))
|
|
127
|
+
return candidate;
|
|
128
|
+
if (parent === dir)
|
|
129
|
+
break; // reached filesystem root
|
|
130
|
+
dir = parent;
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which back-end a compiled IR is realized for.
|
|
3
|
+
*
|
|
4
|
+
* - `"agnostic"` — in-process: the compiled IR is additionally dispatched to a **vercel bundle**
|
|
5
|
+
* (`warble dispatch --target vercel`), the format this package's `runAgent()` consumes.
|
|
6
|
+
* - `"native"` — dispatched: IR only. Dispatching to a file-based target (`claude-code:*`) produces
|
|
7
|
+
* agent files on disk, not something this harness loads/runs — out of scope for this package.
|
|
8
|
+
*/
|
|
9
|
+
export type CompileMode = "agnostic" | "native";
|
|
10
|
+
export interface CompileCacheKey {
|
|
11
|
+
/** Content fingerprint of the source profile directory (profile.yml + components + fixture binding). */
|
|
12
|
+
readonly profileHash: string;
|
|
13
|
+
/** Content fingerprint of the user's wren project directory (the per-user context binding). */
|
|
14
|
+
readonly contextFingerprint: string;
|
|
15
|
+
readonly mode: CompileMode;
|
|
16
|
+
/**
|
|
17
|
+
* Content hash of the resolved `--provider` fragment file(s) actually used for `"agnostic"` mode's
|
|
18
|
+
* vercel dispatch (order-sensitive — see `hashFiles`), so a custom `options.providers` and this
|
|
19
|
+
* package's default fragment never collide on the same key. Fixed sentinel for `"native"` mode,
|
|
20
|
+
* which never reads providers at all.
|
|
21
|
+
*/
|
|
22
|
+
readonly providerFragmentHash: string;
|
|
23
|
+
/**
|
|
24
|
+
* Content identity of the resolved `warble` binary (see `getWarbleIdentity`) — folds the compiler
|
|
25
|
+
* itself into the key so rebuilding/replacing the binary invalidates entries it previously
|
|
26
|
+
* compiled, instead of silently serving an artifact built by a now-stale `warble`.
|
|
27
|
+
*/
|
|
28
|
+
readonly warbleIdentity: string;
|
|
29
|
+
/**
|
|
30
|
+
* The Hub component-library root passed to `warble compile --hub-dir` (see `resolveHubDir`), or
|
|
31
|
+
* `undefined` when none could be derived and the compile therefore ran against warble's own
|
|
32
|
+
* compiled-in default. Two compiles that differ only by Hub root read different component
|
|
33
|
+
* libraries and must not share a cached artifact — the same reason `warbleIdentity` is here.
|
|
34
|
+
*/
|
|
35
|
+
readonly hubDir: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
export interface CompileCacheEntry {
|
|
38
|
+
readonly irPath: string;
|
|
39
|
+
readonly bundlePath?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Pluggable cache seam, keyed on (profile content hash x user-context fingerprint x mode) — see
|
|
43
|
+
* `./fingerprint.js`. `get` returns `undefined` on a miss (including a "hit" whose files were
|
|
44
|
+
* since deleted from disk); `set` persists an entry produced by a fresh compile so a later `get`
|
|
45
|
+
* with the same key can skip recompiling.
|
|
46
|
+
*/
|
|
47
|
+
export interface CompileCache {
|
|
48
|
+
get(key: CompileCacheKey): Promise<CompileCacheEntry | undefined>;
|
|
49
|
+
set(key: CompileCacheKey, entry: CompileCacheEntry): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
export interface CompileProfileOptions {
|
|
52
|
+
/** Directory of the source Warble profile (profile.yml + components/ + context/binding.yml), e.g. `genbi-default`. */
|
|
53
|
+
readonly profileSource: string;
|
|
54
|
+
/** Directory of the per-user wren project (MDL + Knowledge) the profile's context binding is rebound to. */
|
|
55
|
+
readonly userProject: string;
|
|
56
|
+
readonly mode: CompileMode;
|
|
57
|
+
/** Explicit path to the `warble` binary. See `resolveWarbleBinary` for the full resolution order when omitted. */
|
|
58
|
+
readonly warbleBin?: string;
|
|
59
|
+
/**
|
|
60
|
+
* `--provider` fragment paths for the in-process vercel dispatch (ignored in `"native"` mode).
|
|
61
|
+
* Defaults to this package's bundled `providers/wren.provider.yaml` (the wren capability-provider
|
|
62
|
+
* fragment already used to produce `fixtures/genbi-default.native.bundle.json`). Pass `[]`
|
|
63
|
+
* explicitly to dispatch with no provider fragments at all.
|
|
64
|
+
*/
|
|
65
|
+
readonly providers?: readonly string[];
|
|
66
|
+
readonly cache?: CompileCache;
|
|
67
|
+
/** Base scratch directory for the composed profile + compiler output. Defaults to a fresh `os.tmpdir()` mkdtemp. */
|
|
68
|
+
readonly workDir?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Precomputed identity for the resolved `warble` binary (see `CompileCacheKey.warbleIdentity`).
|
|
71
|
+
* When omitted, `compileProfile` resolves `warbleBin` and computes it via `getWarbleIdentity` —
|
|
72
|
+
* which, for a cache MISS, it would need to do anyway to run the compile, but for what would
|
|
73
|
+
* otherwise be a cache HIT this means resolving the binary even though nothing gets executed.
|
|
74
|
+
* Pass this explicitly (e.g. computed once at process startup, or stubbed in a test) to make a
|
|
75
|
+
* hit skip touching the binary at all.
|
|
76
|
+
*/
|
|
77
|
+
readonly warbleIdentity?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Explicit Hub component-library root for `warble compile --hub-dir`. When omitted it is derived
|
|
80
|
+
* from the resolved `warble` binary (see `resolveHubDir`) so the compiler and its Hub always come
|
|
81
|
+
* from the same warble version — prefer that over setting this. Like `warbleIdentity`, this also
|
|
82
|
+
* doubles as an escape hatch: because the derived value is a cache-key input, computing the key
|
|
83
|
+
* without it has to resolve the binary even for what would otherwise be a cache hit. Pass both to
|
|
84
|
+
* make a hit touch the binary not at all.
|
|
85
|
+
*/
|
|
86
|
+
readonly hubDir?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compiles a profile exactly as authored, without rebinding its context to a
|
|
90
|
+
* user project. Bootstrap profiles intentionally use this path because their
|
|
91
|
+
* context is part of the profile source rather than a completed Setup result.
|
|
92
|
+
*/
|
|
93
|
+
export type CompileRawProfileOptions = Omit<CompileProfileOptions, "userProject">;
|
|
94
|
+
export interface CompileProfileResult {
|
|
95
|
+
readonly irPath: string;
|
|
96
|
+
readonly bundlePath?: string;
|
|
97
|
+
/** `true` when the result came from the cache without invoking `warble` at all. */
|
|
98
|
+
readonly cacheHit: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* The resolved `warble` binary path used for this call, when resolution actually happened —
|
|
101
|
+
* lets a caller that hits a downstream `BundleCompatError` name which on-disk checkout produced
|
|
102
|
+
* the bundle (see `loadBundleWithProvenance`, `harness/bundle/loader.ts`). `undefined` only in
|
|
103
|
+
* the one case where resolution is skippable: a cache hit with an explicit
|
|
104
|
+
* `options.warbleIdentity` (see that option's doc comment) never touches the binary at all.
|
|
105
|
+
*/
|
|
106
|
+
readonly warbleBin?: string;
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A content identity for the resolved `warble` binary at `binPath`, folded into the compile cache
|
|
3
|
+
* key (see `CompileCacheKey.warbleIdentity`) so rebuilding/replacing the binary a cache slot was
|
|
4
|
+
* compiled with invalidates it, instead of silently serving an artifact built by a now-stale
|
|
5
|
+
* `warble`.
|
|
6
|
+
*
|
|
7
|
+
* Hashes the binary's file *content* rather than shelling out to `warble --version`: the real
|
|
8
|
+
* `warble` CLI has no `--version` flag (its `clap`-based parser rejects it, exit code 2), and
|
|
9
|
+
* content hashing needs no subprocess at all — so under the default (no `options.warbleIdentity`
|
|
10
|
+
* override), a cache hit still never *executes* warble, only resolves + reads its file.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getWarbleIdentity(binPath: string): Promise<string>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Per-process memoization keyed by resolved absolute binary path: computing the identity means
|
|
6
|
+
* reading the (multi-MB) binary's full contents, so within one process a given resolved path is
|
|
7
|
+
* hashed at most once no matter how many `compileProfile` calls reuse it.
|
|
8
|
+
*/
|
|
9
|
+
const identityCache = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* A content identity for the resolved `warble` binary at `binPath`, folded into the compile cache
|
|
12
|
+
* key (see `CompileCacheKey.warbleIdentity`) so rebuilding/replacing the binary a cache slot was
|
|
13
|
+
* compiled with invalidates it, instead of silently serving an artifact built by a now-stale
|
|
14
|
+
* `warble`.
|
|
15
|
+
*
|
|
16
|
+
* Hashes the binary's file *content* rather than shelling out to `warble --version`: the real
|
|
17
|
+
* `warble` CLI has no `--version` flag (its `clap`-based parser rejects it, exit code 2), and
|
|
18
|
+
* content hashing needs no subprocess at all — so under the default (no `options.warbleIdentity`
|
|
19
|
+
* override), a cache hit still never *executes* warble, only resolves + reads its file.
|
|
20
|
+
*/
|
|
21
|
+
export async function getWarbleIdentity(binPath) {
|
|
22
|
+
const resolved = path.resolve(binPath);
|
|
23
|
+
let cached = identityCache.get(resolved);
|
|
24
|
+
if (cached === undefined) {
|
|
25
|
+
cached = hashFile(resolved);
|
|
26
|
+
identityCache.set(resolved, cached);
|
|
27
|
+
}
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
async function hashFile(filePath) {
|
|
31
|
+
const content = await readFile(filePath);
|
|
32
|
+
return createHash("sha256").update(content).digest("hex");
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AuthChoice } from "../auth/index.js";
|
|
2
|
+
import type { ComplianceResult, Deployment } from "./types.js";
|
|
3
|
+
/** Thrown when `enforceCompliance` rejects an `AuthChoice`/`Deployment` combination. Never thrown silently-downgraded — callers must handle it (route reject -> caller picks a different mode). */
|
|
4
|
+
export declare class ComplianceError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Warning surfaced whenever `subscription` auth is used for `personal`
|
|
9
|
+
* deployment. Wording aligned with LangChain's own subscription-auth
|
|
10
|
+
* disclaimer: this is your personal subscription, it may violate the
|
|
11
|
+
* provider's Terms of Service (that risk is yours), the consumer backend
|
|
12
|
+
* and its policies are volatile and may change or break without notice,
|
|
13
|
+
* and this is for personal, single-operator use only — never multi-user or
|
|
14
|
+
* hosted. Not legal advice; verify against the provider's current ToS
|
|
15
|
+
* before relying on this.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SUBSCRIPTION_TOS_WARNING: string;
|
|
18
|
+
/**
|
|
19
|
+
* The compliance gate: a pure decision over an already-resolved
|
|
20
|
+
* `AuthChoice` and the operator-declared `deployment` context, meant to be
|
|
21
|
+
* called at the route()/onboarding seam *before* any back-end runs.
|
|
22
|
+
*
|
|
23
|
+
* - `subscription` + `hosted` -> throws {@link ComplianceError}. Personal
|
|
24
|
+
* subscription auth used behind a shared/multi-tenant/always-on server is
|
|
25
|
+
* account sharing, which the ToS warning below cannot excuse — there is
|
|
26
|
+
* no silent downgrade to another mode; the caller must pick `api-key` or
|
|
27
|
+
* `gateway` explicitly.
|
|
28
|
+
* - `subscription` + `personal` (the default) -> allowed, returning the
|
|
29
|
+
* ToS warning the caller must surface before running.
|
|
30
|
+
* - `api-key` / `local` / `gateway` -> always allowed, no warning (only
|
|
31
|
+
* `subscription` auth carries the ToS exposure this gate is about).
|
|
32
|
+
*/
|
|
33
|
+
export declare function enforceCompliance(authChoice: AuthChoice, options?: {
|
|
34
|
+
readonly deployment?: Deployment;
|
|
35
|
+
}): ComplianceResult;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Thrown when `enforceCompliance` rejects an `AuthChoice`/`Deployment` combination. Never thrown silently-downgraded — callers must handle it (route reject -> caller picks a different mode). */
|
|
2
|
+
export class ComplianceError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "ComplianceError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Warning surfaced whenever `subscription` auth is used for `personal`
|
|
10
|
+
* deployment. Wording aligned with LangChain's own subscription-auth
|
|
11
|
+
* disclaimer: this is your personal subscription, it may violate the
|
|
12
|
+
* provider's Terms of Service (that risk is yours), the consumer backend
|
|
13
|
+
* and its policies are volatile and may change or break without notice,
|
|
14
|
+
* and this is for personal, single-operator use only — never multi-user or
|
|
15
|
+
* hosted. Not legal advice; verify against the provider's current ToS
|
|
16
|
+
* before relying on this.
|
|
17
|
+
*/
|
|
18
|
+
export const SUBSCRIPTION_TOS_WARNING = "using your personal subscription to authenticate may violate the provider's Terms of Service " +
|
|
19
|
+
"(that's your responsibility); the consumer backend and its policies are volatile and may change " +
|
|
20
|
+
"or break at any time; this is for personal, single-operator use only, never multi-user or hosted; " +
|
|
21
|
+
"verify against the provider's current ToS before relying on this — this is not legal advice.";
|
|
22
|
+
/**
|
|
23
|
+
* The compliance gate: a pure decision over an already-resolved
|
|
24
|
+
* `AuthChoice` and the operator-declared `deployment` context, meant to be
|
|
25
|
+
* called at the route()/onboarding seam *before* any back-end runs.
|
|
26
|
+
*
|
|
27
|
+
* - `subscription` + `hosted` -> throws {@link ComplianceError}. Personal
|
|
28
|
+
* subscription auth used behind a shared/multi-tenant/always-on server is
|
|
29
|
+
* account sharing, which the ToS warning below cannot excuse — there is
|
|
30
|
+
* no silent downgrade to another mode; the caller must pick `api-key` or
|
|
31
|
+
* `gateway` explicitly.
|
|
32
|
+
* - `subscription` + `personal` (the default) -> allowed, returning the
|
|
33
|
+
* ToS warning the caller must surface before running.
|
|
34
|
+
* - `api-key` / `local` / `gateway` -> always allowed, no warning (only
|
|
35
|
+
* `subscription` auth carries the ToS exposure this gate is about).
|
|
36
|
+
*/
|
|
37
|
+
export function enforceCompliance(authChoice, options = {}) {
|
|
38
|
+
const deployment = options.deployment ?? "personal";
|
|
39
|
+
if (authChoice.mode !== "subscription") {
|
|
40
|
+
return { authChoice, warnings: [] };
|
|
41
|
+
}
|
|
42
|
+
if (deployment === "hosted") {
|
|
43
|
+
throw new ComplianceError("compliance: subscription auth is personal-use only and cannot serve a hosted " +
|
|
44
|
+
"(multi-tenant / shared / always-on-server) deployment — this is account sharing, which " +
|
|
45
|
+
"violates the provider's ToS; use --mode api-key or --mode gateway for hosted deployments.");
|
|
46
|
+
}
|
|
47
|
+
return { authChoice, warnings: [SUBSCRIPTION_TOS_WARNING] };
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ComplianceError, enforceCompliance, SUBSCRIPTION_TOS_WARNING } from "./gate.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AuthChoice } from "../auth/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The deployment context a compliance decision turns on. The harness has no
|
|
4
|
+
* way to self-detect multi-tenancy — whether it's serving one person on
|
|
5
|
+
* their own machine or sitting behind a shared, always-on endpoint is a
|
|
6
|
+
* fact only the operator knows — so it's taken as an explicit input rather
|
|
7
|
+
* than inferred. `"hosted"` means multi-tenant, shared, or always-on-server
|
|
8
|
+
* use; `"personal"` (the default) means a single operator on their own
|
|
9
|
+
* machine.
|
|
10
|
+
*/
|
|
11
|
+
export type Deployment = "personal" | "hosted";
|
|
12
|
+
/** The gate's non-throwing outcome: the `AuthChoice` passed through, plus any warnings the caller must surface before a back-end runs. */
|
|
13
|
+
export interface ComplianceResult {
|
|
14
|
+
readonly authChoice: AuthChoice;
|
|
15
|
+
readonly warnings: readonly string[];
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|