@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,175 @@
|
|
|
1
|
+
import type { EnrichmentBinding } from "./enrichment.js";
|
|
2
|
+
export { InteractiveLaunchError, legacyInteractiveWorkspace } from "./native-session-workspace.js";
|
|
3
|
+
import type { NativeSessionStateBase } from "./native-session-workspace.js";
|
|
4
|
+
export declare const INTERACTIVE_TARGETS: readonly ["claude-code:interactive", "codex:interactive"];
|
|
5
|
+
export type InteractiveTarget = (typeof INTERACTIVE_TARGETS)[number];
|
|
6
|
+
export interface InteractiveLaunchSpec {
|
|
7
|
+
readonly version: "1" | "2" | "3" | "4";
|
|
8
|
+
readonly target: InteractiveTarget;
|
|
9
|
+
readonly executable: "claude" | "codex";
|
|
10
|
+
readonly argv: readonly string[];
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly artifact_root: string;
|
|
13
|
+
readonly handoff_path: string;
|
|
14
|
+
/** Host-attested absolute executable; never read from the producer file. */
|
|
15
|
+
readonly hostExecutable?: string;
|
|
16
|
+
/** Present only for native Setup after the BFF revalidates its sealed root. */
|
|
17
|
+
readonly bootstrap_root?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PtyProcess {
|
|
20
|
+
onData(listener: (data: string) => void): {
|
|
21
|
+
dispose(): void;
|
|
22
|
+
};
|
|
23
|
+
onExit(listener: (event: {
|
|
24
|
+
exitCode: number;
|
|
25
|
+
signal?: number;
|
|
26
|
+
}) => void): {
|
|
27
|
+
dispose(): void;
|
|
28
|
+
};
|
|
29
|
+
write(data: string): void;
|
|
30
|
+
resize(columns: number, rows: number): void;
|
|
31
|
+
kill(): void;
|
|
32
|
+
}
|
|
33
|
+
export interface PtyFactory {
|
|
34
|
+
spawn(file: string, args: readonly string[], options: {
|
|
35
|
+
cwd: string;
|
|
36
|
+
cols: number;
|
|
37
|
+
rows: number;
|
|
38
|
+
env?: NodeJS.ProcessEnv;
|
|
39
|
+
}): PtyProcess;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The embedded terminal is a color-capable product surface, not a reflection
|
|
43
|
+
* of a browser or caller preference. Keep this at the server/PTTY boundary so
|
|
44
|
+
* every native launch receives the same contract.
|
|
45
|
+
*
|
|
46
|
+
* `NO_COLOR` is deliberately removed. It is a useful convention for ordinary
|
|
47
|
+
* command output, but honoring an inherited value here would make our
|
|
48
|
+
* advertised xterm ANSI palette silently optional and allow a caller to
|
|
49
|
+
* weaken the native-terminal contract.
|
|
50
|
+
*/
|
|
51
|
+
export declare function nativeTerminalEnvironment(base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
52
|
+
/**
|
|
53
|
+
* Mirrors the PATH lookup a shell-free PTY launch relies on. A shell alias or
|
|
54
|
+
* function is deliberately not accepted: node-pty cannot execute either.
|
|
55
|
+
*/
|
|
56
|
+
export declare function interactiveExecutableAvailable(executable: "claude" | "codex", pathValue?: string | undefined): boolean;
|
|
57
|
+
export interface InteractiveTerminalSession {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly capability: string;
|
|
60
|
+
readonly target: InteractiveTarget;
|
|
61
|
+
readonly handoffPath: string;
|
|
62
|
+
readonly fallbackCommand: string;
|
|
63
|
+
claim(capability: string): boolean;
|
|
64
|
+
detach(): void;
|
|
65
|
+
close(): void;
|
|
66
|
+
write(data: string): void;
|
|
67
|
+
resize(columns: number, rows: number): void;
|
|
68
|
+
/**
|
|
69
|
+
* Subscribe to terminal output. The optional callback is called exactly once
|
|
70
|
+
* before this attachment's replay, and makes any retained-output truncation
|
|
71
|
+
* explicit to transports that can surface it to their users.
|
|
72
|
+
*/
|
|
73
|
+
onData(listener: (data: string) => void, onReplay?: (metadata: TerminalReplayMetadata) => void): () => void;
|
|
74
|
+
onExit(listener: (exitCode: number) => void): () => void;
|
|
75
|
+
}
|
|
76
|
+
/** Public replay contract for a terminal attachment. */
|
|
77
|
+
export interface TerminalReplayMetadata {
|
|
78
|
+
/** True when output older than the retained tail was omitted. */
|
|
79
|
+
readonly truncated: boolean;
|
|
80
|
+
/** UTF-8 bytes available in this attachment's retained replay. */
|
|
81
|
+
readonly retainedBytes: number;
|
|
82
|
+
/** Hard upper bound for the process-local retained tail. */
|
|
83
|
+
readonly retentionLimitBytes: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The legacy Context terminal owns a short local lease. Durable native
|
|
87
|
+
* Sessions deliberately pass `NO_INTERACTIVE_TERMINAL_LEASES`: their service
|
|
88
|
+
* owns both the initial-attachment and post-claim lifecycle transitions.
|
|
89
|
+
*/
|
|
90
|
+
export interface InteractiveTerminalLeasePolicy {
|
|
91
|
+
readonly initialAttachmentTimeoutMs?: number;
|
|
92
|
+
readonly postClaimDetachTimeoutMs?: number;
|
|
93
|
+
}
|
|
94
|
+
export declare const LEGACY_INTERACTIVE_TERMINAL_LEASES: InteractiveTerminalLeasePolicy;
|
|
95
|
+
export declare const NO_INTERACTIVE_TERMINAL_LEASES: InteractiveTerminalLeasePolicy;
|
|
96
|
+
/**
|
|
97
|
+
* Terminal output is process-local and deliberately bounded. Attachments get
|
|
98
|
+
* only this UTF-8 tail; `TerminalReplayMetadata.truncated` says when the
|
|
99
|
+
* beginning was omitted instead of silently presenting an incomplete log.
|
|
100
|
+
*/
|
|
101
|
+
export declare const TERMINAL_OUTPUT_RETENTION_LIMIT_BYTES: number;
|
|
102
|
+
export interface PreparedInteractiveHandoff {
|
|
103
|
+
readonly target: InteractiveTarget;
|
|
104
|
+
readonly fallbackCommand: string;
|
|
105
|
+
}
|
|
106
|
+
export interface InteractiveTargetReadiness {
|
|
107
|
+
readonly copyAvailable: boolean;
|
|
108
|
+
readonly embeddedTerminalAvailable: boolean;
|
|
109
|
+
readonly copyReason?: string;
|
|
110
|
+
readonly embeddedTerminalReason?: string;
|
|
111
|
+
}
|
|
112
|
+
export declare function unavailableInteractiveReadiness(reason: string): Record<InteractiveTarget, InteractiveTargetReadiness>;
|
|
113
|
+
/** Clipboard-only text. It is never passed to a shell or subprocess. */
|
|
114
|
+
export declare function launchFallbackCommand(spec: Pick<InteractiveLaunchSpec, "cwd" | "executable" | "argv" | "hostExecutable">): string;
|
|
115
|
+
/** Parse exactly the public v1 producer contract; unknown fields fail closed. */
|
|
116
|
+
export declare function readInteractiveLaunchSpec(boundProject: string, requestedTarget: InteractiveTarget, materializationRoot?: string, materializationState?: NativeSessionStateBase): InteractiveLaunchSpec;
|
|
117
|
+
export declare function dispatchInteractiveArtifacts(input: {
|
|
118
|
+
readonly warbleBin: string;
|
|
119
|
+
readonly irPath: string;
|
|
120
|
+
readonly target: InteractiveTarget;
|
|
121
|
+
readonly cwd: string;
|
|
122
|
+
readonly boundProject?: string;
|
|
123
|
+
readonly materializationState?: NativeSessionStateBase;
|
|
124
|
+
}): Promise<void>;
|
|
125
|
+
export interface InteractivePreparationInput {
|
|
126
|
+
readonly target: InteractiveTarget;
|
|
127
|
+
readonly binding: EnrichmentBinding;
|
|
128
|
+
/** A BFF-owned external root, never browser-supplied. */
|
|
129
|
+
readonly artifactRoot?: string;
|
|
130
|
+
/** Required whenever artifactRoot is external to the bound project. */
|
|
131
|
+
readonly materializationState?: NativeSessionStateBase;
|
|
132
|
+
readonly materialize: () => Promise<void>;
|
|
133
|
+
}
|
|
134
|
+
export interface InteractivePreparationDeps {
|
|
135
|
+
readonly getCurrentBinding: () => EnrichmentBinding | undefined;
|
|
136
|
+
readonly executableAvailable?: (executable: "claude" | "codex") => boolean;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Materializes and validates the clipboard/launch contract without loading a
|
|
140
|
+
* PTY implementation or creating a process. This is deliberately shared by
|
|
141
|
+
* copy and embedded-terminal paths so their binding fence cannot drift.
|
|
142
|
+
*/
|
|
143
|
+
export declare function prepareInteractiveHandoff(input: InteractivePreparationInput, deps: InteractivePreparationDeps): Promise<{
|
|
144
|
+
readonly spec: InteractiveLaunchSpec;
|
|
145
|
+
readonly handoff: PreparedInteractiveHandoff;
|
|
146
|
+
}>;
|
|
147
|
+
/**
|
|
148
|
+
* Evaluates copy and optional embedded-host capability separately per target.
|
|
149
|
+
*
|
|
150
|
+
* This must stay a pure probe: the producer owns a shared output root and can
|
|
151
|
+
* materialize only one target there at a time. Reading Context must never
|
|
152
|
+
* create, replace, or inspect target artifacts, because doing so would make
|
|
153
|
+
* one target's availability depend on whichever target readiness happened to
|
|
154
|
+
* probe first. Artifact materialization remains exclusively in the selected
|
|
155
|
+
* Copy or embedded Launch action via `prepareInteractiveHandoff`.
|
|
156
|
+
*/
|
|
157
|
+
export declare function getInteractiveTerminalReadiness(input: {
|
|
158
|
+
readonly getCurrentBinding: () => EnrichmentBinding | undefined;
|
|
159
|
+
readonly terminalHostAvailable: () => Promise<boolean>;
|
|
160
|
+
readonly executableAvailable?: (executable: "claude" | "codex") => boolean;
|
|
161
|
+
/** BFF-owned base; validation is non-mutating and never reads the project root. */
|
|
162
|
+
readonly materializationState?: NativeSessionStateBase;
|
|
163
|
+
}): Promise<Record<InteractiveTarget, InteractiveTargetReadiness>>;
|
|
164
|
+
export declare class InteractiveTerminalManager {
|
|
165
|
+
private readonly pty;
|
|
166
|
+
private readonly sessions;
|
|
167
|
+
constructor(pty: PtyFactory);
|
|
168
|
+
start(spec: InteractiveLaunchSpec, identity?: {
|
|
169
|
+
readonly id: string;
|
|
170
|
+
readonly capability: string;
|
|
171
|
+
}, env?: NodeJS.ProcessEnv, leasePolicy?: InteractiveTerminalLeasePolicy): InteractiveTerminalSession;
|
|
172
|
+
get(id: string): InteractiveTerminalSession | undefined;
|
|
173
|
+
/** Runtime changes revoke every non-durable compatibility terminal immediately. */
|
|
174
|
+
closeAll(): void;
|
|
175
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native interactive enrichment is intentionally a very small boundary:
|
|
3
|
+
* Warble materializes a versioned launch file and the BFF validates it before
|
|
4
|
+
* a PTY starts a vendor-owned TUI. Nothing supplied by a browser becomes a
|
|
5
|
+
* command, argument, cwd, prompt, or environment value.
|
|
6
|
+
*/
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { existsSync, realpathSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { spawn as spawnProcess } from "node:child_process";
|
|
11
|
+
export { InteractiveLaunchError, legacyInteractiveWorkspace } from "./native-session-workspace.js";
|
|
12
|
+
import { InteractiveLaunchError, nativeSessionStateBaseAvailable, validateLegacyInteractiveWorkspace } from "./native-session-workspace.js";
|
|
13
|
+
export const INTERACTIVE_TARGETS = ["claude-code:interactive", "codex:interactive"];
|
|
14
|
+
const EXECUTABLE = {
|
|
15
|
+
"claude-code:interactive": "claude",
|
|
16
|
+
"codex:interactive": "codex",
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The embedded terminal is a color-capable product surface, not a reflection
|
|
20
|
+
* of a browser or caller preference. Keep this at the server/PTTY boundary so
|
|
21
|
+
* every native launch receives the same contract.
|
|
22
|
+
*
|
|
23
|
+
* `NO_COLOR` is deliberately removed. It is a useful convention for ordinary
|
|
24
|
+
* command output, but honoring an inherited value here would make our
|
|
25
|
+
* advertised xterm ANSI palette silently optional and allow a caller to
|
|
26
|
+
* weaken the native-terminal contract.
|
|
27
|
+
*/
|
|
28
|
+
export function nativeTerminalEnvironment(base = process.env) {
|
|
29
|
+
const { NO_COLOR: _noColor, ...inherited } = base;
|
|
30
|
+
return { ...inherited, TERM: "xterm-256color", COLORTERM: "truecolor" };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Mirrors the PATH lookup a shell-free PTY launch relies on. A shell alias or
|
|
34
|
+
* function is deliberately not accepted: node-pty cannot execute either.
|
|
35
|
+
*/
|
|
36
|
+
export function interactiveExecutableAvailable(executable, pathValue = process.env["PATH"]) {
|
|
37
|
+
if (!pathValue)
|
|
38
|
+
return false;
|
|
39
|
+
return pathValue.split(path.delimiter).some((directory) => {
|
|
40
|
+
if (!directory)
|
|
41
|
+
return false;
|
|
42
|
+
try {
|
|
43
|
+
const candidate = path.join(directory, executable);
|
|
44
|
+
const stat = statSync(candidate);
|
|
45
|
+
return stat.isFile() && (stat.mode & 0o111) !== 0;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export const LEGACY_INTERACTIVE_TERMINAL_LEASES = {
|
|
53
|
+
initialAttachmentTimeoutMs: 15_000,
|
|
54
|
+
postClaimDetachTimeoutMs: 15_000,
|
|
55
|
+
};
|
|
56
|
+
export const NO_INTERACTIVE_TERMINAL_LEASES = {};
|
|
57
|
+
const TERMINAL_EXIT_RETENTION_MS = 15_000;
|
|
58
|
+
/**
|
|
59
|
+
* Terminal output is process-local and deliberately bounded. Attachments get
|
|
60
|
+
* only this UTF-8 tail; `TerminalReplayMetadata.truncated` says when the
|
|
61
|
+
* beginning was omitted instead of silently presenting an incomplete log.
|
|
62
|
+
*/
|
|
63
|
+
export const TERMINAL_OUTPUT_RETENTION_LIMIT_BYTES = 64 * 1024;
|
|
64
|
+
export function unavailableInteractiveReadiness(reason) {
|
|
65
|
+
return Object.fromEntries(INTERACTIVE_TARGETS.map((target) => [target, { copyAvailable: false, embeddedTerminalAvailable: false, copyReason: reason, embeddedTerminalReason: reason }]));
|
|
66
|
+
}
|
|
67
|
+
/** Clipboard-only text. It is never passed to a shell or subprocess. */
|
|
68
|
+
export function launchFallbackCommand(spec) {
|
|
69
|
+
const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
70
|
+
const executable = spec.hostExecutable ? quote(spec.hostExecutable) : spec.executable;
|
|
71
|
+
return `cd -- ${quote(spec.cwd)} && ${executable}${spec.argv.map((arg) => ` ${quote(arg)}`).join("")}`;
|
|
72
|
+
}
|
|
73
|
+
function record(value) {
|
|
74
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
75
|
+
}
|
|
76
|
+
function isTarget(value) {
|
|
77
|
+
return typeof value === "string" && INTERACTIVE_TARGETS.includes(value);
|
|
78
|
+
}
|
|
79
|
+
function contained(root, candidate) {
|
|
80
|
+
const relative = path.relative(root, candidate);
|
|
81
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
82
|
+
}
|
|
83
|
+
/** Parse exactly the public v1 producer contract; unknown fields fail closed. */
|
|
84
|
+
export function readInteractiveLaunchSpec(boundProject, requestedTarget, materializationRoot, materializationState) {
|
|
85
|
+
const canonicalProject = realpathSync(boundProject);
|
|
86
|
+
const canonicalArtifactRoot = materializationRoot === undefined
|
|
87
|
+
? canonicalProject
|
|
88
|
+
: materializationState === undefined
|
|
89
|
+
? realpathSync(materializationRoot)
|
|
90
|
+
: validateLegacyInteractiveWorkspace(materializationState, canonicalProject, materializationRoot);
|
|
91
|
+
if (materializationState === undefined && canonicalArtifactRoot !== canonicalProject)
|
|
92
|
+
throw new InteractiveLaunchError("interactive enrichment launch paths are outside the bound project");
|
|
93
|
+
const specPath = path.join(canonicalArtifactRoot, ".warble", "interactive-launch.json");
|
|
94
|
+
if (!existsSync(specPath))
|
|
95
|
+
throw new InteractiveLaunchError("interactive enrichment artifacts are not available yet");
|
|
96
|
+
let raw;
|
|
97
|
+
try {
|
|
98
|
+
raw = JSON.parse(readFileSync(specPath, "utf8"));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new InteractiveLaunchError("interactive enrichment launch specification is invalid");
|
|
102
|
+
}
|
|
103
|
+
const value = record(raw);
|
|
104
|
+
const expectedKeys = ["version", "target", "executable", "argv", "cwd", "artifact_root", "handoff_path"];
|
|
105
|
+
if (!value || Object.keys(value).length !== expectedKeys.length || !expectedKeys.every((key) => Object.hasOwn(value, key))) {
|
|
106
|
+
throw new InteractiveLaunchError("interactive enrichment launch specification is invalid");
|
|
107
|
+
}
|
|
108
|
+
if (value.version !== "1" || !isTarget(value.target) || value.target !== requestedTarget || value.executable !== EXECUTABLE[requestedTarget] || !Array.isArray(value.argv) || value.argv.length !== 0 || typeof value.cwd !== "string" || typeof value.artifact_root !== "string" || typeof value.handoff_path !== "string") {
|
|
109
|
+
throw new InteractiveLaunchError("interactive enrichment launch specification is incompatible");
|
|
110
|
+
}
|
|
111
|
+
let cwd;
|
|
112
|
+
let artifactRoot;
|
|
113
|
+
let handoffPath;
|
|
114
|
+
try {
|
|
115
|
+
cwd = realpathSync(value.cwd);
|
|
116
|
+
artifactRoot = realpathSync(value.artifact_root);
|
|
117
|
+
handoffPath = realpathSync(value.handoff_path);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw new InteractiveLaunchError("interactive enrichment launch paths are unavailable");
|
|
121
|
+
}
|
|
122
|
+
if (cwd !== canonicalArtifactRoot || artifactRoot !== canonicalArtifactRoot || !contained(artifactRoot, handoffPath)) {
|
|
123
|
+
throw new InteractiveLaunchError("interactive enrichment launch paths are outside the bound project");
|
|
124
|
+
}
|
|
125
|
+
return { version: "1", target: requestedTarget, executable: EXECUTABLE[requestedTarget], argv: [], cwd, artifact_root: artifactRoot, handoff_path: handoffPath };
|
|
126
|
+
}
|
|
127
|
+
export async function dispatchInteractiveArtifacts(input) {
|
|
128
|
+
// Structured hardcoded argv only. In particular, never append browser or
|
|
129
|
+
// proposal text to this invocation and never use a shell.
|
|
130
|
+
if ((input.boundProject === undefined) !== (input.materializationState === undefined))
|
|
131
|
+
throw new InteractiveLaunchError("interactive enrichment materialization failed");
|
|
132
|
+
if (input.boundProject && input.materializationState)
|
|
133
|
+
validateLegacyInteractiveWorkspace(input.materializationState, input.boundProject, input.cwd);
|
|
134
|
+
await new Promise((resolve, reject) => {
|
|
135
|
+
const child = spawnProcess(input.warbleBin, ["dispatch", input.irPath, "--target", input.target, "--out", input.cwd], { cwd: input.cwd, shell: false, stdio: "ignore" });
|
|
136
|
+
child.once("error", () => reject(new InteractiveLaunchError("interactive enrichment materialization failed")));
|
|
137
|
+
child.once("exit", (code) => code === 0 ? resolve() : reject(new InteractiveLaunchError("interactive enrichment materialization failed")));
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Materializes and validates the clipboard/launch contract without loading a
|
|
142
|
+
* PTY implementation or creating a process. This is deliberately shared by
|
|
143
|
+
* copy and embedded-terminal paths so their binding fence cannot drift.
|
|
144
|
+
*/
|
|
145
|
+
export async function prepareInteractiveHandoff(input, deps) {
|
|
146
|
+
const executableAvailable = deps.executableAvailable ?? interactiveExecutableAvailable;
|
|
147
|
+
if (!executableAvailable(EXECUTABLE[input.target])) {
|
|
148
|
+
throw new InteractiveLaunchError(`the ${EXECUTABLE[input.target]} interactive CLI is not available on this machine`);
|
|
149
|
+
}
|
|
150
|
+
await input.materialize();
|
|
151
|
+
const current = deps.getCurrentBinding();
|
|
152
|
+
if (!current || current.path !== input.binding.path || current.identity !== input.binding.identity || current.generation !== input.binding.generation || current.revision !== input.binding.revision) {
|
|
153
|
+
throw new InteractiveLaunchError("interactive enrichment is stale because the bound project changed");
|
|
154
|
+
}
|
|
155
|
+
const spec = readInteractiveLaunchSpec(input.binding.path, input.target, input.artifactRoot, input.materializationState);
|
|
156
|
+
return { spec, handoff: { target: spec.target, fallbackCommand: launchFallbackCommand(spec) } };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Evaluates copy and optional embedded-host capability separately per target.
|
|
160
|
+
*
|
|
161
|
+
* This must stay a pure probe: the producer owns a shared output root and can
|
|
162
|
+
* materialize only one target there at a time. Reading Context must never
|
|
163
|
+
* create, replace, or inspect target artifacts, because doing so would make
|
|
164
|
+
* one target's availability depend on whichever target readiness happened to
|
|
165
|
+
* probe first. Artifact materialization remains exclusively in the selected
|
|
166
|
+
* Copy or embedded Launch action via `prepareInteractiveHandoff`.
|
|
167
|
+
*/
|
|
168
|
+
export async function getInteractiveTerminalReadiness(input) {
|
|
169
|
+
const binding = input.getCurrentBinding();
|
|
170
|
+
if (!binding)
|
|
171
|
+
return unavailableInteractiveReadiness("interactive enrichment requires a current bound project");
|
|
172
|
+
if (input.materializationState && !nativeSessionStateBaseAvailable(input.materializationState))
|
|
173
|
+
return unavailableInteractiveReadiness("interactive enrichment workspace is unavailable");
|
|
174
|
+
const hostAvailable = await input.terminalHostAvailable();
|
|
175
|
+
const executableAvailable = input.executableAvailable ?? interactiveExecutableAvailable;
|
|
176
|
+
const result = {};
|
|
177
|
+
for (const target of INTERACTIVE_TARGETS) {
|
|
178
|
+
if (!executableAvailable(EXECUTABLE[target])) {
|
|
179
|
+
const reason = `the ${EXECUTABLE[target]} interactive CLI is not available on this machine`;
|
|
180
|
+
result[target] = { copyAvailable: false, embeddedTerminalAvailable: false, copyReason: reason, embeddedTerminalReason: reason };
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
result[target] = hostAvailable
|
|
184
|
+
? { copyAvailable: true, embeddedTerminalAvailable: true }
|
|
185
|
+
: { copyAvailable: true, embeddedTerminalAvailable: false, embeddedTerminalReason: "interactive terminal host cannot spawn local processes on this machine" };
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
export class InteractiveTerminalManager {
|
|
190
|
+
pty;
|
|
191
|
+
sessions = new Map();
|
|
192
|
+
constructor(pty) {
|
|
193
|
+
this.pty = pty;
|
|
194
|
+
}
|
|
195
|
+
start(spec, identity, env, leasePolicy = LEGACY_INTERACTIVE_TERMINAL_LEASES) {
|
|
196
|
+
const process = this.pty.spawn(spec.hostExecutable ?? spec.executable, spec.argv, {
|
|
197
|
+
cwd: spec.cwd,
|
|
198
|
+
cols: 100,
|
|
199
|
+
rows: 30,
|
|
200
|
+
env: nativeTerminalEnvironment(env),
|
|
201
|
+
});
|
|
202
|
+
const dataListeners = new Set();
|
|
203
|
+
const exitListeners = new Set();
|
|
204
|
+
const id = identity?.id ?? randomUUID();
|
|
205
|
+
const capability = identity?.capability ?? randomUUID();
|
|
206
|
+
let claimed = false;
|
|
207
|
+
let exited;
|
|
208
|
+
let buffer = "";
|
|
209
|
+
let outputTruncated = false;
|
|
210
|
+
let leaseTimer;
|
|
211
|
+
const clearLease = () => {
|
|
212
|
+
if (leaseTimer)
|
|
213
|
+
clearTimeout(leaseTimer);
|
|
214
|
+
leaseTimer = undefined;
|
|
215
|
+
};
|
|
216
|
+
const close = () => {
|
|
217
|
+
clearLease();
|
|
218
|
+
try {
|
|
219
|
+
if (exited === undefined)
|
|
220
|
+
process.kill();
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
this.sessions.delete(id);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const armLease = (timeoutMs) => {
|
|
227
|
+
clearLease();
|
|
228
|
+
if (timeoutMs !== undefined)
|
|
229
|
+
leaseTimer = setTimeout(close, timeoutMs);
|
|
230
|
+
};
|
|
231
|
+
const session = {
|
|
232
|
+
id, capability, target: spec.target, handoffPath: spec.handoff_path, fallbackCommand: launchFallbackCommand(spec),
|
|
233
|
+
claim: (presented) => {
|
|
234
|
+
if (presented !== capability || claimed)
|
|
235
|
+
return false;
|
|
236
|
+
claimed = true;
|
|
237
|
+
clearLease();
|
|
238
|
+
return true;
|
|
239
|
+
},
|
|
240
|
+
detach: () => {
|
|
241
|
+
if (!claimed || exited !== undefined)
|
|
242
|
+
return;
|
|
243
|
+
claimed = false;
|
|
244
|
+
armLease(leasePolicy.postClaimDetachTimeoutMs);
|
|
245
|
+
},
|
|
246
|
+
close,
|
|
247
|
+
write: (data) => process.write(data),
|
|
248
|
+
resize: (columns, rows) => process.resize(columns, rows),
|
|
249
|
+
onData: (listener) => { dataListeners.add(listener); return () => dataListeners.delete(listener); },
|
|
250
|
+
onExit: (listener) => { exitListeners.add(listener); return () => exitListeners.delete(listener); },
|
|
251
|
+
};
|
|
252
|
+
process.onData((data) => {
|
|
253
|
+
const next = `${buffer}${data}`;
|
|
254
|
+
const retained = retainTerminalOutput(next);
|
|
255
|
+
if (retained.length !== next.length)
|
|
256
|
+
outputTruncated = true;
|
|
257
|
+
buffer = retained;
|
|
258
|
+
dataListeners.forEach((listener) => listener(data));
|
|
259
|
+
});
|
|
260
|
+
process.onExit(({ exitCode }) => { exited = exitCode; exitListeners.forEach((listener) => listener(exitCode)); clearLease(); setTimeout(() => this.sessions.delete(id), TERMINAL_EXIT_RETENTION_MS); });
|
|
261
|
+
this.sessions.set(session.id, session);
|
|
262
|
+
// A legacy PTY must not survive if the browser never completes its WS
|
|
263
|
+
// upgrade. Native Sessions opt out: their durable service records the
|
|
264
|
+
// timeout state and revokes browser/MCP/recovery capabilities atomically.
|
|
265
|
+
armLease(leasePolicy.initialAttachmentTimeoutMs);
|
|
266
|
+
// An attachment receives one replay followed by live data. Register its
|
|
267
|
+
// live listener before invoking either callback, then queue any
|
|
268
|
+
// synchronous re-entrant data until the replay has completed. This closes
|
|
269
|
+
// the snapshot/listener gap without duplicating output on reconnect.
|
|
270
|
+
const originalOnData = session.onData;
|
|
271
|
+
const originalOnExit = session.onExit;
|
|
272
|
+
session.onData = (listener, onReplay) => {
|
|
273
|
+
const replay = buffer;
|
|
274
|
+
const metadata = {
|
|
275
|
+
truncated: outputTruncated,
|
|
276
|
+
retainedBytes: Buffer.byteLength(replay, "utf8"),
|
|
277
|
+
retentionLimitBytes: TERMINAL_OUTPUT_RETENTION_LIMIT_BYTES,
|
|
278
|
+
};
|
|
279
|
+
const queued = [];
|
|
280
|
+
let nextQueued = 0;
|
|
281
|
+
let replaying = true;
|
|
282
|
+
const remove = originalOnData((data) => {
|
|
283
|
+
if (replaying)
|
|
284
|
+
queued.push(data);
|
|
285
|
+
else
|
|
286
|
+
listener(data);
|
|
287
|
+
});
|
|
288
|
+
try {
|
|
289
|
+
onReplay?.(metadata);
|
|
290
|
+
if (replay)
|
|
291
|
+
listener(replay);
|
|
292
|
+
// Keep replay mode active until the queue is empty. A transport
|
|
293
|
+
// callback can synchronously cause more PTY output; index-based
|
|
294
|
+
// draining consumes those additions after the already queued bytes.
|
|
295
|
+
while (nextQueued < queued.length)
|
|
296
|
+
listener(queued[nextQueued++]);
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
replaying = false;
|
|
300
|
+
queued.length = 0;
|
|
301
|
+
}
|
|
302
|
+
return remove;
|
|
303
|
+
};
|
|
304
|
+
session.onExit = (listener) => { if (exited !== undefined)
|
|
305
|
+
listener(exited); return originalOnExit(listener); };
|
|
306
|
+
return session;
|
|
307
|
+
}
|
|
308
|
+
get(id) { return this.sessions.get(id); }
|
|
309
|
+
/** Runtime changes revoke every non-durable compatibility terminal immediately. */
|
|
310
|
+
closeAll() {
|
|
311
|
+
for (const session of [...this.sessions.values()])
|
|
312
|
+
session.close();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Retain a UTF-8-safe tail without splitting a Unicode code point. */
|
|
316
|
+
function retainTerminalOutput(value) {
|
|
317
|
+
if (Buffer.byteLength(value, "utf8") <= TERMINAL_OUTPUT_RETENTION_LIMIT_BYTES)
|
|
318
|
+
return value;
|
|
319
|
+
let retainedBytes = 0;
|
|
320
|
+
let start = value.length;
|
|
321
|
+
while (start > 0) {
|
|
322
|
+
let next = start - 1;
|
|
323
|
+
const code = value.charCodeAt(next);
|
|
324
|
+
if (code >= 0xdc00 && code <= 0xdfff && next > 0) {
|
|
325
|
+
const previous = value.charCodeAt(next - 1);
|
|
326
|
+
if (previous >= 0xd800 && previous <= 0xdbff)
|
|
327
|
+
next -= 1;
|
|
328
|
+
}
|
|
329
|
+
const bytes = Buffer.byteLength(value.slice(next, start), "utf8");
|
|
330
|
+
if (retainedBytes + bytes > TERMINAL_OUTPUT_RETENTION_LIMIT_BYTES)
|
|
331
|
+
break;
|
|
332
|
+
retainedBytes += bytes;
|
|
333
|
+
start = next;
|
|
334
|
+
}
|
|
335
|
+
return value.slice(start);
|
|
336
|
+
}
|