@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,14 @@
|
|
|
1
|
+
import type { NativeProducerHostFence } from "../harness/native-producer/index.js";
|
|
2
|
+
import type { EnrichmentBinding } from "./enrichment.js";
|
|
3
|
+
import type { Store } from "./db.js";
|
|
4
|
+
/**
|
|
5
|
+
* Bridges the producer's public-safe scope to the same durable native-session
|
|
6
|
+
* and live-binding fence used by native artifact persistence. No path, token,
|
|
7
|
+
* or session credential is returned to the producer contract.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createNativeProducerHostFence(input: {
|
|
10
|
+
readonly store: Store;
|
|
11
|
+
readonly getBinding: () => EnrichmentBinding | undefined;
|
|
12
|
+
/** Selected by the server from durable native-session state; never public. */
|
|
13
|
+
readonly nativeSessionId: string;
|
|
14
|
+
}): NativeProducerHostFence;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Bridges the producer's public-safe scope to the same durable native-session
|
|
4
|
+
* and live-binding fence used by native artifact persistence. No path, token,
|
|
5
|
+
* or session credential is returned to the producer contract.
|
|
6
|
+
*/
|
|
7
|
+
export function createNativeProducerHostFence(input) {
|
|
8
|
+
const session = input.store.getNativeSession(input.nativeSessionId);
|
|
9
|
+
const binding = input.getBinding();
|
|
10
|
+
if (!session || !binding || !matchesBinding(session, binding)) {
|
|
11
|
+
throw new Error("cannot create a native producer fence without a live bound native session");
|
|
12
|
+
}
|
|
13
|
+
const bindingDigest = digestBinding(session.id, binding);
|
|
14
|
+
const validRun = (request, userProject) => {
|
|
15
|
+
const live = input.getBinding();
|
|
16
|
+
const currentSession = input.store.getNativeSession(input.nativeSessionId);
|
|
17
|
+
return Boolean(live && currentSession && userProject !== undefined && live.path === userProject &&
|
|
18
|
+
request.scope.bindingDigest === bindingDigest &&
|
|
19
|
+
matchesBinding(currentSession, live) && currentSession.vendor === request.vendor &&
|
|
20
|
+
(currentSession.status === "running" || currentSession.status === "detached"));
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
bindingDigest,
|
|
24
|
+
validateRun: (request) => validRun(request, input.getBinding()?.path),
|
|
25
|
+
validateRoute: validRun,
|
|
26
|
+
validateArtifact: (artifact, request) => {
|
|
27
|
+
const live = input.getBinding();
|
|
28
|
+
const currentSession = input.store.getNativeSession(input.nativeSessionId);
|
|
29
|
+
if (!live || !currentSession || !validRun(request, live.path) || !matchesBinding(currentSession, live))
|
|
30
|
+
return false;
|
|
31
|
+
const row = input.store.getArtifact(artifact.id);
|
|
32
|
+
return Boolean(row && row.nativeSessionId === input.nativeSessionId &&
|
|
33
|
+
row.projectIdentity === live.identity && row.bindingGeneration === live.generation && row.projectRevision === live.revision &&
|
|
34
|
+
row.contentDigest === artifact.digest && row.idempotencyKey === artifact.idempotencyKey);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function matchesBinding(session, binding) {
|
|
39
|
+
return session.projectIdentity === binding.identity &&
|
|
40
|
+
session.bindingGeneration === binding.generation && session.projectRevision === binding.revision;
|
|
41
|
+
}
|
|
42
|
+
function digestBinding(nativeSessionId, binding) {
|
|
43
|
+
// Delimit and length-prefix each private value so different tuples cannot
|
|
44
|
+
// collide through concatenation. Only this one-way digest leaves the host.
|
|
45
|
+
const fields = [nativeSessionId, binding.identity, String(binding.generation), binding.revision];
|
|
46
|
+
const encoded = fields.map((field) => `${Buffer.byteLength(field)}:${field}`).join("|");
|
|
47
|
+
return `sha256:${createHash("sha256").update(encoded).digest("hex")}`;
|
|
48
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { NativeSessionStateBase } from "./native-session-workspace.js";
|
|
2
|
+
export type NativeResumeProvider = "claude" | "codex";
|
|
3
|
+
export declare function sealNativeResumeHandle(state: NativeSessionStateBase, sessionId: string, provider: NativeResumeProvider, handle: string): string;
|
|
4
|
+
export declare function unsealNativeResumeHandle(state: NativeSessionStateBase, sessionId: string, provider: NativeResumeProvider, sealed: string): string;
|
|
5
|
+
export declare const sealNativeClaudeResumeHandle: (state: NativeSessionStateBase, sessionId: string, handle: string) => string;
|
|
6
|
+
export declare const unsealNativeClaudeResumeHandle: (state: NativeSessionStateBase, sessionId: string, sealed: string) => string;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent sealing for provider-owned native conversation identifiers.
|
|
3
|
+
*
|
|
4
|
+
* The SQLite database holds ciphertext only. Its AES key is a separate,
|
|
5
|
+
* private BFF-state file, so a copied database neither reveals nor authorizes
|
|
6
|
+
* a provider conversation. This is intentionally local-state encryption, not
|
|
7
|
+
* a browser capability or a general-purpose secret store.
|
|
8
|
+
*/
|
|
9
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
10
|
+
import { closeSync, constants, existsSync, lstatSync, openSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { InteractiveLaunchError, validateNativeSessionStateBase } from "./native-session-workspace.js";
|
|
13
|
+
const KEY_FILE = "native-resume-key-v1";
|
|
14
|
+
const KEY_BYTES = 32;
|
|
15
|
+
const IV_BYTES = 12;
|
|
16
|
+
const TAG_BYTES = 16;
|
|
17
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
18
|
+
function keyPath(state) {
|
|
19
|
+
const root = validateNativeSessionStateBase(state).root;
|
|
20
|
+
const file = path.join(root, KEY_FILE);
|
|
21
|
+
if (path.dirname(file) !== root)
|
|
22
|
+
throw new InteractiveLaunchError("native session resume storage is unavailable");
|
|
23
|
+
return file;
|
|
24
|
+
}
|
|
25
|
+
function loadOrCreateKey(state) {
|
|
26
|
+
const file = keyPath(state);
|
|
27
|
+
if (!existsSync(file)) {
|
|
28
|
+
const key = randomBytes(KEY_BYTES);
|
|
29
|
+
try {
|
|
30
|
+
const fd = openSync(file, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
|
|
31
|
+
try {
|
|
32
|
+
writeFileSync(fd, key);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
closeSync(fd);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code !== "EEXIST")
|
|
40
|
+
throw new InteractiveLaunchError("native session resume storage is unavailable");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const entry = lstatSync(file);
|
|
45
|
+
if (!entry.isFile() || entry.isSymbolicLink() || (entry.mode & 0o777) !== 0o600 || realpathSync(file) !== file)
|
|
46
|
+
throw new Error("unsafe key file");
|
|
47
|
+
const key = readFileSync(file);
|
|
48
|
+
if (key.length !== KEY_BYTES)
|
|
49
|
+
throw new Error("wrong key length");
|
|
50
|
+
return key;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new InteractiveLaunchError("native session resume storage is unavailable");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function parseEnvelope(value) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(value);
|
|
59
|
+
if (parsed.version !== "1" || typeof parsed.iv !== "string" || typeof parsed.tag !== "string" || typeof parsed.ciphertext !== "string")
|
|
60
|
+
return undefined;
|
|
61
|
+
const iv = Buffer.from(parsed.iv, "base64url");
|
|
62
|
+
const tag = Buffer.from(parsed.tag, "base64url");
|
|
63
|
+
const ciphertext = Buffer.from(parsed.ciphertext, "base64url");
|
|
64
|
+
if (iv.length !== IV_BYTES || tag.length !== TAG_BYTES || ciphertext.length < 1)
|
|
65
|
+
return undefined;
|
|
66
|
+
return { version: "1", iv: parsed.iv, tag: parsed.tag, ciphertext: parsed.ciphertext };
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function aad(sessionId, provider) { return Buffer.from(`genbi-native-resume-v1\u0000${provider}\u0000${sessionId}`, "utf8"); }
|
|
73
|
+
export function sealNativeResumeHandle(state, sessionId, provider, handle) {
|
|
74
|
+
if (!UUID.test(handle))
|
|
75
|
+
throw new InteractiveLaunchError("native session resume handle is invalid");
|
|
76
|
+
const iv = randomBytes(IV_BYTES);
|
|
77
|
+
const cipher = createCipheriv("aes-256-gcm", loadOrCreateKey(state), iv);
|
|
78
|
+
cipher.setAAD(aad(sessionId, provider));
|
|
79
|
+
const ciphertext = Buffer.concat([cipher.update(handle, "utf8"), cipher.final()]);
|
|
80
|
+
const envelope = { version: "1", iv: iv.toString("base64url"), tag: cipher.getAuthTag().toString("base64url"), ciphertext: ciphertext.toString("base64url") };
|
|
81
|
+
return JSON.stringify(envelope);
|
|
82
|
+
}
|
|
83
|
+
export function unsealNativeResumeHandle(state, sessionId, provider, sealed) {
|
|
84
|
+
const envelope = parseEnvelope(sealed);
|
|
85
|
+
if (!envelope)
|
|
86
|
+
throw new InteractiveLaunchError("native session resume handle is unavailable");
|
|
87
|
+
try {
|
|
88
|
+
const decipher = createDecipheriv("aes-256-gcm", loadOrCreateKey(state), Buffer.from(envelope.iv, "base64url"));
|
|
89
|
+
decipher.setAAD(aad(sessionId, provider));
|
|
90
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, "base64url"));
|
|
91
|
+
const handle = Buffer.concat([decipher.update(Buffer.from(envelope.ciphertext, "base64url")), decipher.final()]).toString("utf8");
|
|
92
|
+
if (!UUID.test(handle))
|
|
93
|
+
throw new Error("invalid handle");
|
|
94
|
+
return handle;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error instanceof InteractiveLaunchError)
|
|
98
|
+
throw error;
|
|
99
|
+
throw new InteractiveLaunchError("native session resume handle is unavailable");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export const sealNativeClaudeResumeHandle = (state, sessionId, handle) => sealNativeResumeHandle(state, sessionId, "claude", handle);
|
|
103
|
+
export const unsealNativeClaudeResumeHandle = (state, sessionId, sealed) => unsealNativeResumeHandle(state, sessionId, "claude", sealed);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { InteractiveTarget } from "./interactive-terminal.js";
|
|
2
|
+
export declare class InteractiveLaunchError extends Error {
|
|
3
|
+
}
|
|
4
|
+
export interface NativeSessionStateBase {
|
|
5
|
+
/** Canonical BFF-owned state root, initialized from the configured DB path. */
|
|
6
|
+
readonly root: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Initializes the only persistent materialization base for this BFF. The
|
|
10
|
+
* fixed digest makes it stable per configured SQLite state path without
|
|
11
|
+
* deriving any path from a browser-selected project.
|
|
12
|
+
*/
|
|
13
|
+
export declare function initializeNativeSessionStateBase(dbPath: string): NativeSessionStateBase;
|
|
14
|
+
/** Non-mutating validation used by readiness and every launch boundary. */
|
|
15
|
+
export declare function validateNativeSessionStateBase(state: NativeSessionStateBase): NativeSessionStateBase;
|
|
16
|
+
export declare function nativeSessionStateBaseAvailable(state: NativeSessionStateBase | undefined): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Non-mutating readiness validation for a specific authorized project root.
|
|
19
|
+
* This intentionally shares create-time's canonical containment check, so a
|
|
20
|
+
* valid BFF state base nested below a project is still unavailable before a
|
|
21
|
+
* session row or vendor artifact can be created.
|
|
22
|
+
*/
|
|
23
|
+
export declare function nativeSessionStateBaseAvailableForProject(state: NativeSessionStateBase | undefined, projectRoot: string): boolean;
|
|
24
|
+
/** Creates one atomic, private bound-session root outside the user project. */
|
|
25
|
+
export declare function createNativeSessionWorkspace(state: NativeSessionStateBase, projectRoot: string, sessionId: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Revalidates a native root immediately before dispatch, spec parsing, or PTY
|
|
28
|
+
* spawn. A same-UID actor can still replace a path after this last check and
|
|
29
|
+
* therefore has equivalent local authority to the BFF; that post-check race
|
|
30
|
+
* is outside this boundary's threat model. Symlink, mode, and containment
|
|
31
|
+
* replacement at every checked boundary fail closed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateNativeSessionWorkspace(state: NativeSessionStateBase, projectRoot: string, workspace: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* The compatibility terminal has no durable session ID. Its target is
|
|
36
|
+
* server-bound, and it is materialized under the distinct legacy namespace.
|
|
37
|
+
*/
|
|
38
|
+
export declare function legacyInteractiveWorkspace(state: NativeSessionStateBase, projectRoot: string, target: InteractiveTarget): string;
|
|
39
|
+
/** Revalidates the legacy root with the same post-check threat-model boundary. */
|
|
40
|
+
export declare function validateLegacyInteractiveWorkspace(state: NativeSessionStateBase, projectRoot: string, workspace: string): string;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BFF-owned native interactive materialization roots. Bound projects retain
|
|
3
|
+
* their identity and remain Warble's authorization root, but vendor-owned
|
|
4
|
+
* discovery files are emitted only under the BFF state directory. This keeps
|
|
5
|
+
* user project files out of the producer's ownership plan.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { accessSync, chmodSync, constants, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
export class InteractiveLaunchError extends Error {
|
|
11
|
+
}
|
|
12
|
+
const NATIVE_NAMESPACE = "native";
|
|
13
|
+
const LEGACY_NAMESPACE = "legacy";
|
|
14
|
+
const MODE = 0o700;
|
|
15
|
+
function unavailable() {
|
|
16
|
+
throw new InteractiveLaunchError("native session workspace is unavailable");
|
|
17
|
+
}
|
|
18
|
+
function contained(root, candidate) {
|
|
19
|
+
const relative = path.relative(root, candidate);
|
|
20
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
21
|
+
}
|
|
22
|
+
function canonicalDirectory(directory, requirePrivateMode, requireWritable = true) {
|
|
23
|
+
try {
|
|
24
|
+
const entry = lstatSync(directory);
|
|
25
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || (requirePrivateMode && (entry.mode & 0o777) !== MODE))
|
|
26
|
+
unavailable();
|
|
27
|
+
accessSync(directory, requireWritable ? constants.W_OK | constants.X_OK : constants.X_OK);
|
|
28
|
+
const canonical = realpathSync(directory);
|
|
29
|
+
const canonicalEntry = lstatSync(canonical);
|
|
30
|
+
if (!canonicalEntry.isDirectory() || canonicalEntry.isSymbolicLink() || (requirePrivateMode && (canonicalEntry.mode & 0o777) !== MODE))
|
|
31
|
+
unavailable();
|
|
32
|
+
accessSync(canonical, requireWritable ? constants.W_OK | constants.X_OK : constants.X_OK);
|
|
33
|
+
return canonical;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof InteractiveLaunchError)
|
|
37
|
+
throw error;
|
|
38
|
+
return unavailable();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function privateChild(parent, segment, create) {
|
|
42
|
+
const candidate = path.join(parent, segment);
|
|
43
|
+
if (!contained(parent, candidate) || path.basename(candidate) !== segment)
|
|
44
|
+
return unavailable();
|
|
45
|
+
if (create) {
|
|
46
|
+
try {
|
|
47
|
+
mkdirSync(candidate, { mode: MODE });
|
|
48
|
+
// mkdir honors umask; make the freshly-created component exact before
|
|
49
|
+
// validating it. Existing components are never repaired silently.
|
|
50
|
+
chmodSync(candidate, MODE);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error.code !== "EEXIST")
|
|
54
|
+
return unavailable();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const canonical = canonicalDirectory(candidate, true);
|
|
58
|
+
if (!contained(parent, canonical))
|
|
59
|
+
return unavailable();
|
|
60
|
+
return canonical;
|
|
61
|
+
}
|
|
62
|
+
function exclusivePrivateChild(parent, segment) {
|
|
63
|
+
const candidate = path.join(parent, segment);
|
|
64
|
+
if (!contained(parent, candidate) || path.basename(candidate) !== segment)
|
|
65
|
+
return unavailable();
|
|
66
|
+
try {
|
|
67
|
+
mkdirSync(candidate, { mode: MODE });
|
|
68
|
+
chmodSync(candidate, MODE);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return unavailable();
|
|
72
|
+
}
|
|
73
|
+
return privateChild(parent, segment, false);
|
|
74
|
+
}
|
|
75
|
+
function verifyOutsideProject(stateRoot, projectRoot) {
|
|
76
|
+
const project = canonicalDirectory(projectRoot, false, false);
|
|
77
|
+
if (contained(project, stateRoot))
|
|
78
|
+
unavailable();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Initializes the only persistent materialization base for this BFF. The
|
|
82
|
+
* fixed digest makes it stable per configured SQLite state path without
|
|
83
|
+
* deriving any path from a browser-selected project.
|
|
84
|
+
*/
|
|
85
|
+
export function initializeNativeSessionStateBase(dbPath) {
|
|
86
|
+
if (!dbPath || dbPath === ":memory:")
|
|
87
|
+
return unavailable();
|
|
88
|
+
const absoluteDbPath = path.resolve(dbPath);
|
|
89
|
+
const parent = canonicalDirectory(path.dirname(absoluteDbPath), false);
|
|
90
|
+
const digest = createHash("sha256").update(absoluteDbPath).digest("hex").slice(0, 24);
|
|
91
|
+
const root = privateChild(parent, `.genbi-native-state-${digest}`, true);
|
|
92
|
+
// Initialize both namespaces at startup so readiness can stay non-mutating.
|
|
93
|
+
privateChild(root, NATIVE_NAMESPACE, true);
|
|
94
|
+
privateChild(root, LEGACY_NAMESPACE, true);
|
|
95
|
+
return { root };
|
|
96
|
+
}
|
|
97
|
+
/** Non-mutating validation used by readiness and every launch boundary. */
|
|
98
|
+
export function validateNativeSessionStateBase(state) {
|
|
99
|
+
const root = canonicalDirectory(state.root, true);
|
|
100
|
+
privateChild(root, NATIVE_NAMESPACE, false);
|
|
101
|
+
privateChild(root, LEGACY_NAMESPACE, false);
|
|
102
|
+
return { root };
|
|
103
|
+
}
|
|
104
|
+
export function nativeSessionStateBaseAvailable(state) {
|
|
105
|
+
if (!state)
|
|
106
|
+
return false;
|
|
107
|
+
try {
|
|
108
|
+
validateNativeSessionStateBase(state);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Non-mutating readiness validation for a specific authorized project root.
|
|
117
|
+
* This intentionally shares create-time's canonical containment check, so a
|
|
118
|
+
* valid BFF state base nested below a project is still unavailable before a
|
|
119
|
+
* session row or vendor artifact can be created.
|
|
120
|
+
*/
|
|
121
|
+
export function nativeSessionStateBaseAvailableForProject(state, projectRoot) {
|
|
122
|
+
if (!state)
|
|
123
|
+
return false;
|
|
124
|
+
try {
|
|
125
|
+
verifyOutsideProject(validateNativeSessionStateBase(state).root, projectRoot);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Creates one atomic, private bound-session root outside the user project. */
|
|
133
|
+
export function createNativeSessionWorkspace(state, projectRoot, sessionId) {
|
|
134
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9-]{0,127}$/.test(sessionId))
|
|
135
|
+
return unavailable();
|
|
136
|
+
const root = validateNativeSessionStateBase(state).root;
|
|
137
|
+
verifyOutsideProject(root, projectRoot);
|
|
138
|
+
return exclusivePrivateChild(privateChild(root, NATIVE_NAMESPACE, false), sessionId);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Revalidates a native root immediately before dispatch, spec parsing, or PTY
|
|
142
|
+
* spawn. A same-UID actor can still replace a path after this last check and
|
|
143
|
+
* therefore has equivalent local authority to the BFF; that post-check race
|
|
144
|
+
* is outside this boundary's threat model. Symlink, mode, and containment
|
|
145
|
+
* replacement at every checked boundary fail closed.
|
|
146
|
+
*/
|
|
147
|
+
export function validateNativeSessionWorkspace(state, projectRoot, workspace) {
|
|
148
|
+
const root = validateNativeSessionStateBase(state).root;
|
|
149
|
+
verifyOutsideProject(root, projectRoot);
|
|
150
|
+
const native = privateChild(root, NATIVE_NAMESPACE, false);
|
|
151
|
+
const canonical = canonicalDirectory(workspace, true);
|
|
152
|
+
if (!contained(native, canonical) || canonical === native)
|
|
153
|
+
return unavailable();
|
|
154
|
+
return canonical;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The compatibility terminal has no durable session ID. Its target is
|
|
158
|
+
* server-bound, and it is materialized under the distinct legacy namespace.
|
|
159
|
+
*/
|
|
160
|
+
export function legacyInteractiveWorkspace(state, projectRoot, target) {
|
|
161
|
+
const root = validateNativeSessionStateBase(state).root;
|
|
162
|
+
verifyOutsideProject(root, projectRoot);
|
|
163
|
+
const targetRoot = target === "claude-code:interactive" ? "claude" : "codex";
|
|
164
|
+
return privateChild(privateChild(root, LEGACY_NAMESPACE, false), targetRoot, true);
|
|
165
|
+
}
|
|
166
|
+
/** Revalidates the legacy root with the same post-check threat-model boundary. */
|
|
167
|
+
export function validateLegacyInteractiveWorkspace(state, projectRoot, workspace) {
|
|
168
|
+
const root = validateNativeSessionStateBase(state).root;
|
|
169
|
+
verifyOutsideProject(root, projectRoot);
|
|
170
|
+
const legacy = privateChild(root, LEGACY_NAMESPACE, false);
|
|
171
|
+
const canonical = canonicalDirectory(workspace, true);
|
|
172
|
+
if (!contained(legacy, canonical) || canonical === legacy)
|
|
173
|
+
return unavailable();
|
|
174
|
+
return canonical;
|
|
175
|
+
}
|