ds-harness-remote 0.4.26 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/acp/adapter.d.ts +19 -0
- package/dist/acp/adapter.d.ts.map +1 -0
- package/dist/acp/adapter.js +14 -0
- package/dist/acp/adapter.js.map +1 -0
- package/dist/acp/adapters/antigravity/executor.d.ts +20 -0
- package/dist/acp/adapters/antigravity/executor.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/executor.js +99 -0
- package/dist/acp/adapters/antigravity/executor.js.map +1 -0
- package/dist/acp/adapters/antigravity/image-store.d.ts +9 -0
- package/dist/acp/adapters/antigravity/image-store.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/image-store.js +116 -0
- package/dist/acp/adapters/antigravity/image-store.js.map +1 -0
- package/dist/acp/adapters/antigravity/index.d.ts +5 -0
- package/dist/acp/adapters/antigravity/index.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/index.js +5 -0
- package/dist/acp/adapters/antigravity/index.js.map +1 -0
- package/dist/acp/adapters/antigravity/models.d.ts +35 -0
- package/dist/acp/adapters/antigravity/models.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/models.js +59 -0
- package/dist/acp/adapters/antigravity/models.js.map +1 -0
- package/dist/acp/adapters/antigravity/service.d.ts +35 -0
- package/dist/acp/adapters/antigravity/service.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/service.js +67 -0
- package/dist/acp/adapters/antigravity/service.js.map +1 -0
- package/dist/acp/adapters/antigravity/transcript-loader.d.ts +21 -0
- package/dist/acp/adapters/antigravity/transcript-loader.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/transcript-loader.js +356 -0
- package/dist/acp/adapters/antigravity/transcript-loader.js.map +1 -0
- package/dist/acp/adapters/antigravity/transcript-watcher.d.ts +28 -0
- package/dist/acp/adapters/antigravity/transcript-watcher.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/transcript-watcher.js +157 -0
- package/dist/acp/adapters/antigravity/transcript-watcher.js.map +1 -0
- package/dist/acp/adapters/antigravity/types.d.ts +56 -0
- package/dist/acp/adapters/antigravity/types.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity/types.js +2 -0
- package/dist/acp/adapters/antigravity/types.js.map +1 -0
- package/dist/acp/adapters/antigravity-process.d.ts +69 -0
- package/dist/acp/adapters/antigravity-process.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity-process.js +566 -0
- package/dist/acp/adapters/antigravity-process.js.map +1 -0
- package/dist/acp/adapters/antigravity.d.ts +8 -0
- package/dist/acp/adapters/antigravity.d.ts.map +1 -0
- package/dist/acp/adapters/antigravity.js +9 -0
- package/dist/acp/adapters/antigravity.js.map +1 -0
- package/dist/acp/adapters/cursor-process.d.ts +75 -0
- package/dist/acp/adapters/cursor-process.d.ts.map +1 -0
- package/dist/acp/adapters/cursor-process.js +273 -0
- package/dist/acp/adapters/cursor-process.js.map +1 -0
- package/dist/acp/adapters/cursor.d.ts +8 -0
- package/dist/acp/adapters/cursor.d.ts.map +1 -0
- package/dist/acp/adapters/cursor.js +8 -0
- package/dist/acp/adapters/cursor.js.map +1 -0
- package/dist/acp/gateway.d.ts +108 -0
- package/dist/acp/gateway.d.ts.map +1 -0
- package/dist/acp/gateway.js +1040 -0
- package/dist/acp/gateway.js.map +1 -0
- package/dist/acp/image-content.d.ts +19 -0
- package/dist/acp/image-content.d.ts.map +1 -0
- package/dist/acp/image-content.js +76 -0
- package/dist/acp/image-content.js.map +1 -0
- package/dist/acp/method-policy.d.ts +239 -0
- package/dist/acp/method-policy.d.ts.map +1 -0
- package/dist/acp/method-policy.js +102 -0
- package/dist/acp/method-policy.js.map +1 -0
- package/dist/acp/peer-bridge.d.ts +59 -0
- package/dist/acp/peer-bridge.d.ts.map +1 -0
- package/dist/acp/peer-bridge.js +367 -0
- package/dist/acp/peer-bridge.js.map +1 -0
- package/dist/acp/virtual-harness.d.ts +181 -0
- package/dist/acp/virtual-harness.d.ts.map +1 -0
- package/dist/acp/virtual-harness.js +1556 -0
- package/dist/acp/virtual-harness.js.map +1 -0
- package/dist/acp/workspace-tools.d.ts +13 -0
- package/dist/acp/workspace-tools.d.ts.map +1 -0
- package/dist/acp/workspace-tools.js +46 -0
- package/dist/acp/workspace-tools.js.map +1 -0
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +1 -0
- package/dist/acp.js.map +1 -1
- package/dist/api-proxy-switch.d.ts +2 -0
- package/dist/api-proxy-switch.d.ts.map +1 -1
- package/dist/api-proxy-switch.js +9 -0
- package/dist/api-proxy-switch.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -3
- package/dist/cli.js.map +1 -1
- package/dist/client-runtime.d.ts +25 -1
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +222 -8
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.github.js +327 -190
- package/dist/client.js +327 -190
- package/dist/client.js.map +1 -1
- package/dist/codex/domain.d.ts +4 -1
- package/dist/codex/domain.d.ts.map +1 -1
- package/dist/codex/domain.js +54 -4
- package/dist/codex/domain.js.map +1 -1
- package/dist/codex/virtual-harness.d.ts.map +1 -1
- package/dist/codex/virtual-harness.js +3 -0
- package/dist/codex/virtual-harness.js.map +1 -1
- package/dist/codex-workspace-bridge.d.ts +7 -105
- package/dist/codex-workspace-bridge.d.ts.map +1 -1
- package/dist/codex-workspace-bridge.js +10 -498
- package/dist/codex-workspace-bridge.js.map +1 -1
- package/dist/config.d.ts +28 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +49 -5
- package/dist/config.js.map +1 -1
- package/dist/connection-controller.d.ts.map +1 -1
- package/dist/connection-controller.js +7 -4
- package/dist/connection-controller.js.map +1 -1
- package/dist/control-runtime.d.ts +1 -0
- package/dist/control-runtime.d.ts.map +1 -1
- package/dist/control-runtime.js +65 -10
- package/dist/control-runtime.js.map +1 -1
- package/dist/harness-version.d.ts.map +1 -1
- package/dist/harness-version.js +16 -3
- package/dist/harness-version.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13197 -8204
- package/dist/index.js.map +4 -4
- package/dist/loopback-host.d.ts +2 -2
- package/dist/loopback-host.d.ts.map +1 -1
- package/dist/loopback-host.js +41 -16
- package/dist/loopback-host.js.map +1 -1
- package/dist/remote-typert-gateway.d.ts.map +1 -1
- package/dist/remote-typert-gateway.js +7 -4
- package/dist/remote-typert-gateway.js.map +1 -1
- package/dist/rpc-router.d.ts +6 -5
- package/dist/rpc-router.d.ts.map +1 -1
- package/dist/rpc-router.js +49 -18
- package/dist/rpc-router.js.map +1 -1
- package/dist/server-api.d.ts +2 -0
- package/dist/server-api.d.ts.map +1 -1
- package/dist/server-api.js +5 -0
- package/dist/server-api.js.map +1 -1
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/server-connection.js +15 -0
- package/dist/server-connection.js.map +1 -1
- package/dist/service.d.ts +30 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +114 -12
- package/dist/service.js.map +1 -1
- package/dist/session-format-compat.d.ts +1 -0
- package/dist/session-format-compat.d.ts.map +1 -1
- package/dist/session-format-compat.js +14 -0
- package/dist/session-format-compat.js.map +1 -1
- package/dist/tui-command.js +2 -0
- package/dist/tui-command.js.map +1 -1
- package/dist/typert-gateway-switch.d.ts +2 -0
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +16 -0
- package/dist/typert-gateway-switch.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/workspace-bridge.d.ts +117 -0
- package/dist/workspace-bridge.d.ts.map +1 -0
- package/dist/workspace-bridge.js +512 -0
- package/dist/workspace-bridge.js.map +1 -0
- package/dsh-plugin.json +1 -1
- package/package.json +1 -1
|
@@ -1,108 +1,10 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
args: string[];
|
|
9
|
-
name: string;
|
|
10
|
-
}
|
|
11
|
-
/** Request for one Host-owned terminal process. */
|
|
12
|
-
export interface RemoteTerminalSpec {
|
|
13
|
-
argv: readonly string[];
|
|
14
|
-
cwd: string;
|
|
15
|
-
cols: number;
|
|
16
|
-
rows: number;
|
|
17
|
-
terminalType: string;
|
|
18
|
-
env: Record<string, string>;
|
|
19
|
-
}
|
|
20
|
-
/** Terminal process surface the bridge needs; PTY-backed when the Host provides one. */
|
|
21
|
-
export interface RemoteTerminalProcess {
|
|
22
|
-
output: AsyncIterable<string>;
|
|
23
|
-
write(data: string): void | Promise<void>;
|
|
24
|
-
resize(cols: number, rows: number): void | Promise<void>;
|
|
25
|
-
terminate(): void | Promise<void>;
|
|
26
|
-
completed: Promise<{
|
|
27
|
-
exitCode: number | null;
|
|
28
|
-
}>;
|
|
29
|
-
}
|
|
30
|
-
export type RemoteTerminalSpawner = (spec: RemoteTerminalSpec) => Promise<RemoteTerminalProcess>;
|
|
31
|
-
/** Structural view of the Host `subprocess` service this carrier can use. */
|
|
32
|
-
export interface HostSubprocessLike {
|
|
33
|
-
spawnTerminal(spec: Record<string, unknown>): Promise<unknown>;
|
|
34
|
-
}
|
|
35
|
-
interface TerminalContext {
|
|
36
|
-
sessionId: string;
|
|
37
|
-
id: string;
|
|
38
|
-
title: string;
|
|
39
|
-
shell: ShellSpec;
|
|
40
|
-
cwd: string;
|
|
41
|
-
cols: number;
|
|
42
|
-
rows: number;
|
|
43
|
-
process?: RemoteTerminalProcess;
|
|
44
|
-
/** Attachment that currently owns input; absent while nobody is attached. */
|
|
45
|
-
controllerId?: string;
|
|
46
|
-
/** Monotonic across the Host terminal lifetime and never reset on reconnect. */
|
|
47
|
-
sequence: number;
|
|
48
|
-
/** Bounded raw output replayed to a reconnecting emulator. */
|
|
49
|
-
screen: string[];
|
|
50
|
-
screenBytes: number;
|
|
51
|
-
truncated: boolean;
|
|
52
|
-
subscribers: Set<AsyncQueue<unknown>>;
|
|
53
|
-
state: 'running' | 'exited' | 'failed';
|
|
54
|
-
exitCode: number | null;
|
|
55
|
-
error?: string;
|
|
56
|
-
}
|
|
57
|
-
declare class AsyncQueue<T> implements AsyncIterable<T> {
|
|
58
|
-
private values;
|
|
59
|
-
private waiters;
|
|
60
|
-
private ended;
|
|
61
|
-
push(value: T): void;
|
|
62
|
-
end(): void;
|
|
63
|
-
next(): Promise<IteratorResult<T>>;
|
|
64
|
-
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
65
|
-
}
|
|
66
|
-
/** Host-owned CodeX file and terminal carrier used by Harness Remote RPC. */
|
|
67
|
-
export declare class CodexWorkspaceBridge {
|
|
68
|
-
private readonly resolveCwd;
|
|
69
|
-
private readonly terminalEnabled;
|
|
70
|
-
private readonly terminals;
|
|
71
|
-
private readonly ownedSubscribers;
|
|
72
|
-
private readonly spawnTerminal;
|
|
73
|
-
constructor(resolveCwd: CodexCwdResolver, terminalEnabled: () => boolean, state?: CodexWorkspaceState, spawnTerminal?: RemoteTerminalSpawner);
|
|
1
|
+
import { WorkspaceBridge, WorkspaceBridgeState, type WorkspaceCwdResolver, type RemoteTerminalSpawner } from './workspace-bridge.js';
|
|
2
|
+
export * from './workspace-bridge.js';
|
|
3
|
+
export { WorkspaceBridgeState as CodexWorkspaceState };
|
|
4
|
+
export type CodexCwdResolver = WorkspaceCwdResolver;
|
|
5
|
+
/** CodeX supplies thread authority to the shared workspace implementation. */
|
|
6
|
+
export declare class CodexWorkspaceBridge extends WorkspaceBridge {
|
|
7
|
+
constructor(resolveCwd: WorkspaceCwdResolver, terminalEnabled: () => boolean, state?: WorkspaceBridgeState, spawnTerminal?: RemoteTerminalSpawner);
|
|
74
8
|
isCodeXScope(value: unknown): boolean;
|
|
75
|
-
call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<TypertRpcResult | undefined>;
|
|
76
|
-
open(endpoint: string, payload: unknown, signal: AbortSignal): Promise<AsyncIterable<unknown> | undefined>;
|
|
77
|
-
closeAll(): Promise<void>;
|
|
78
|
-
private fileCall;
|
|
79
|
-
private watchChanges;
|
|
80
|
-
/**
|
|
81
|
-
* `terminal/retain` only acknowledges the retention window: it never takes
|
|
82
|
-
* input ownership and never replays output. Recovery reads the next snapshot.
|
|
83
|
-
*/
|
|
84
|
-
private retain;
|
|
85
|
-
/** `terminal/follow` takes input ownership and starts with a screen snapshot. */
|
|
86
|
-
private follow;
|
|
87
|
-
private terminalCall;
|
|
88
|
-
private createTerminal;
|
|
89
|
-
/** Streams process output as ordered `output` frames, then reports the exit. */
|
|
90
|
-
private pump;
|
|
91
|
-
private appendScreen;
|
|
92
|
-
private rootFor;
|
|
93
|
-
private safePath;
|
|
94
|
-
private emit;
|
|
95
|
-
private endSubscribers;
|
|
96
|
-
private requireTerminal;
|
|
97
|
-
private assertTerminalEnabled;
|
|
98
|
-
private disposeTerminal;
|
|
99
9
|
}
|
|
100
|
-
/** Default terminal process: a plain child process, used when the Host exposes no PTY provider. */
|
|
101
|
-
export declare function pipeTerminalSpawner(): RemoteTerminalSpawner;
|
|
102
|
-
/**
|
|
103
|
-
* Terminal process backed by the Host `subprocess` service, which owns PTY
|
|
104
|
-
* allocation, containment, and process-range termination.
|
|
105
|
-
*/
|
|
106
|
-
export declare function subprocessTerminalSpawner(subprocess: HostSubprocessLike): RemoteTerminalSpawner;
|
|
107
|
-
export {};
|
|
108
10
|
//# sourceMappingURL=codex-workspace-bridge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-workspace-bridge.d.ts","sourceRoot":"","sources":["../src/codex-workspace-bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"codex-workspace-bridge.d.ts","sourceRoot":"","sources":["../src/codex-workspace-bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,KAAK,qBAAqB,EAAuB,MAAM,uBAAuB,CAAA;AACzJ,cAAc,uBAAuB,CAAA;AACrC,OAAO,EAAE,oBAAoB,IAAI,mBAAmB,EAAE,CAAA;AACtD,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAA;AAEnD,8EAA8E;AAC9E,qBAAa,oBAAqB,SAAQ,eAAe;gBAC3C,UAAU,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,OAAO,EAC1E,KAAK,uBAA6B,EAAE,aAAa,GAAE,qBAA6C;IAKlG,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;CACtC"}
|
|
@@ -1,502 +1,14 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { realpath, lstat, readdir, readFile, stat, watch } from 'node:fs/promises';
|
|
3
|
-
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
|
-
import { RpcError } from './safe-error.js';
|
|
5
1
|
import { parseCodexSessionId } from './codex/session-id.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const TERMINAL_SCROLLBACK = 2000;
|
|
15
|
-
const TERMINAL_TYPE = 'xterm-256color';
|
|
16
|
-
export class CodexWorkspaceState {
|
|
17
|
-
terminals = new Map();
|
|
18
|
-
}
|
|
19
|
-
/** One shell this carrier can start; the client only ever picks from `terminal/shells`. */
|
|
20
|
-
const DEFAULT_SHELL = process.platform === 'win32'
|
|
21
|
-
? { path: 'cmd.exe', args: [], name: 'Command Prompt' }
|
|
22
|
-
: { path: '/bin/sh', args: [], name: 'sh' };
|
|
23
|
-
class AsyncQueue {
|
|
24
|
-
values = [];
|
|
25
|
-
waiters = [];
|
|
26
|
-
ended = false;
|
|
27
|
-
push(value) {
|
|
28
|
-
if (this.ended)
|
|
29
|
-
return;
|
|
30
|
-
const waiter = this.waiters.shift();
|
|
31
|
-
if (waiter)
|
|
32
|
-
waiter({ done: false, value });
|
|
33
|
-
else
|
|
34
|
-
this.values.push(value);
|
|
35
|
-
}
|
|
36
|
-
end() {
|
|
37
|
-
this.ended = true;
|
|
38
|
-
while (this.waiters.length)
|
|
39
|
-
this.waiters.shift()({ done: true, value: undefined });
|
|
40
|
-
}
|
|
41
|
-
next() {
|
|
42
|
-
const value = this.values.shift();
|
|
43
|
-
if (value !== undefined)
|
|
44
|
-
return Promise.resolve({ done: false, value });
|
|
45
|
-
if (this.ended)
|
|
46
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
47
|
-
return new Promise(resolve => this.waiters.push(resolve));
|
|
48
|
-
}
|
|
49
|
-
[Symbol.asyncIterator]() { return this; }
|
|
50
|
-
}
|
|
51
|
-
/** Host-owned CodeX file and terminal carrier used by Harness Remote RPC. */
|
|
52
|
-
export class CodexWorkspaceBridge {
|
|
53
|
-
resolveCwd;
|
|
54
|
-
terminalEnabled;
|
|
55
|
-
terminals;
|
|
56
|
-
ownedSubscribers = new Set();
|
|
57
|
-
spawnTerminal;
|
|
58
|
-
constructor(resolveCwd, terminalEnabled, state = new CodexWorkspaceState(), spawnTerminal = pipeTerminalSpawner()) {
|
|
59
|
-
this.resolveCwd = resolveCwd;
|
|
60
|
-
this.terminalEnabled = terminalEnabled;
|
|
61
|
-
this.terminals = state.terminals;
|
|
62
|
-
this.spawnTerminal = spawnTerminal;
|
|
63
|
-
}
|
|
64
|
-
isCodeXScope(value) {
|
|
65
|
-
return parseCodexSessionId(value) !== undefined;
|
|
66
|
-
}
|
|
67
|
-
async call(endpoint, payload, signal) {
|
|
68
|
-
const args = argsOf(payload);
|
|
69
|
-
const scope = args.workspaceFileScopeId;
|
|
70
|
-
const session = args.agentId ?? args.sessionId;
|
|
71
|
-
const raw = scope ?? session;
|
|
72
|
-
const codex = parseCodexSessionId(raw);
|
|
73
|
-
if (codex === undefined) {
|
|
74
|
-
if (typeof raw === 'string' && raw.startsWith('codex:'))
|
|
75
|
-
throw new RpcError('CODEX_SESSION_INVALID', 'The CodeX session identifier is invalid.');
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
if (endpoint.startsWith('workspaceFiles/'))
|
|
79
|
-
return this.fileCall(endpoint, codex.sessionId, args, signal);
|
|
80
|
-
if (endpoint.startsWith('terminal/'))
|
|
81
|
-
return this.terminalCall(endpoint, codex.sessionId, args, signal);
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
async open(endpoint, payload, signal) {
|
|
85
|
-
const args = argsOf(payload);
|
|
86
|
-
const raw = args.workspaceFileScopeId ?? args.agentId ?? args.sessionId;
|
|
87
|
-
const codex = parseCodexSessionId(raw);
|
|
88
|
-
if (codex === undefined) {
|
|
89
|
-
if (typeof raw === 'string' && raw.startsWith('codex:'))
|
|
90
|
-
throw new RpcError('CODEX_SESSION_INVALID', 'The CodeX session identifier is invalid.');
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
if (endpoint === 'workspaceFiles/changes')
|
|
94
|
-
return this.watchChanges(codex.sessionId, args, signal);
|
|
95
|
-
if (endpoint === 'terminal/retain')
|
|
96
|
-
return this.retain(codex.sessionId, args, signal);
|
|
97
|
-
if (endpoint === 'terminal/follow')
|
|
98
|
-
return this.follow(codex.sessionId, args, signal);
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
async closeAll() {
|
|
102
|
-
// A transport disconnect closes subscriptions but retains Host-owned terminal
|
|
103
|
-
// processes so a reconnect can use terminal/retain, matching Harness policy.
|
|
104
|
-
for (const queue of this.ownedSubscribers)
|
|
105
|
-
queue.end();
|
|
106
|
-
this.ownedSubscribers.clear();
|
|
107
|
-
}
|
|
108
|
-
async fileCall(endpoint, sessionId, args, signal) {
|
|
109
|
-
const root = await this.rootFor(sessionId, signal);
|
|
110
|
-
const path = typeof args.path === 'string' ? args.path : '.';
|
|
111
|
-
const target = await this.safePath(root, path, endpoint === 'workspaceFiles/list');
|
|
112
|
-
try {
|
|
113
|
-
if (endpoint === 'workspaceFiles/list') {
|
|
114
|
-
const entries = await readdir(target, { withFileTypes: true });
|
|
115
|
-
const result = [];
|
|
116
|
-
for (const entry of entries.slice(0, 500)) {
|
|
117
|
-
const item = join(target, entry.name);
|
|
118
|
-
const info = await lstat(item);
|
|
119
|
-
if (info.isSymbolicLink())
|
|
120
|
-
continue;
|
|
121
|
-
result.push({ name: entry.name, type: info.isDirectory() ? 'directory' : info.isFile() ? 'file' : 'other', ...(info.isFile() ? { size: info.size } : {}) });
|
|
122
|
-
}
|
|
123
|
-
return { ok: true, value: { path, entries: result, truncated: entries.length > 500 } };
|
|
124
|
-
}
|
|
125
|
-
const info = await stat(target);
|
|
126
|
-
const absolutePath = target;
|
|
127
|
-
const version = `${info.mtimeMs}:${info.size}`;
|
|
128
|
-
if (endpoint === 'workspaceFiles/stat') {
|
|
129
|
-
return { ok: true, value: { absolutePath, version, bytes: info.size } };
|
|
130
|
-
}
|
|
131
|
-
if (!info.isFile())
|
|
132
|
-
throw new RpcError('CODEX_WORKSPACE_INVALID_PATH', 'The requested workspace path is not a file.');
|
|
133
|
-
const offset = readOffset(args);
|
|
134
|
-
const limit = readLimit(args);
|
|
135
|
-
const bytes = await readFile(target);
|
|
136
|
-
if (bytes.byteLength > MAX_READ_BYTES)
|
|
137
|
-
throw new RpcError('CODEX_WORKSPACE_TOO_LARGE', 'The requested workspace file is too large.');
|
|
138
|
-
if (endpoint === 'workspaceFiles/readBytes') {
|
|
139
|
-
const slice = bytes.subarray(offset, Math.min(offset + limit, bytes.length));
|
|
140
|
-
return {
|
|
141
|
-
ok: true,
|
|
142
|
-
value: {
|
|
143
|
-
absolutePath,
|
|
144
|
-
version,
|
|
145
|
-
bytes: bytes.length,
|
|
146
|
-
offset,
|
|
147
|
-
data: slice.toString('base64'),
|
|
148
|
-
eof: offset + slice.length >= bytes.length,
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
const text = bytes.toString('utf8');
|
|
153
|
-
const slice = text.slice(offset, offset + limit);
|
|
154
|
-
return { ok: true, value: { absolutePath: target, version: `${info.mtimeMs}:${info.size}`, text: slice, offset, lines: slice.split('\n').length, eof: offset + slice.length >= text.length } };
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
if (error instanceof RpcError)
|
|
158
|
-
throw error;
|
|
159
|
-
throw new RpcError('CODEX_WORKSPACE_UNAVAILABLE', 'The CodeX workspace file is unavailable.');
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
async watchChanges(sessionId, args, signal) {
|
|
163
|
-
const root = await this.rootFor(sessionId, signal);
|
|
164
|
-
const target = await this.safePath(root, typeof args.path === 'string' ? args.path : '.', true);
|
|
165
|
-
const queue = new AsyncQueue();
|
|
166
|
-
const watcher = watch(target, { recursive: false });
|
|
167
|
-
const abort = () => { watcher.return?.(); queue.end(); };
|
|
168
|
-
signal.addEventListener('abort', abort, { once: true });
|
|
169
|
-
void (async () => {
|
|
170
|
-
try {
|
|
171
|
-
for await (const event of watcher)
|
|
172
|
-
queue.push({ type: event.eventType, path: event.filename ?? '' });
|
|
173
|
-
}
|
|
174
|
-
catch { /* stream close is reported by the gateway */ }
|
|
175
|
-
finally {
|
|
176
|
-
signal.removeEventListener('abort', abort);
|
|
177
|
-
queue.end();
|
|
178
|
-
}
|
|
179
|
-
})();
|
|
180
|
-
return queue;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* `terminal/retain` only acknowledges the retention window: it never takes
|
|
184
|
-
* input ownership and never replays output. Recovery reads the next snapshot.
|
|
185
|
-
*/
|
|
186
|
-
async retain(sessionId, args, signal) {
|
|
187
|
-
this.assertTerminalEnabled();
|
|
188
|
-
await this.rootFor(sessionId, signal);
|
|
189
|
-
this.requireTerminal(sessionId, String(args.id));
|
|
190
|
-
const queue = new AsyncQueue();
|
|
191
|
-
queue.push({ type: 'retained' });
|
|
192
|
-
this.ownedSubscribers.add(queue);
|
|
193
|
-
signal.addEventListener('abort', () => { this.ownedSubscribers.delete(queue); queue.end(); }, { once: true });
|
|
194
|
-
return queue;
|
|
195
|
-
}
|
|
196
|
-
/** `terminal/follow` takes input ownership and starts with a screen snapshot. */
|
|
197
|
-
async follow(sessionId, args, signal) {
|
|
198
|
-
this.assertTerminalEnabled();
|
|
199
|
-
await this.rootFor(sessionId, signal);
|
|
200
|
-
const attachmentId = typeof args.attachmentId === 'string' && args.attachmentId.length > 0 ? args.attachmentId : undefined;
|
|
201
|
-
if (attachmentId === undefined)
|
|
202
|
-
throw new RpcError('INVALID_MESSAGE', 'The terminal attachment is invalid.');
|
|
203
|
-
const terminal = this.requireTerminal(sessionId, String(args.id));
|
|
204
|
-
const queue = new AsyncQueue();
|
|
205
|
-
if (terminal.controllerId !== attachmentId) {
|
|
206
|
-
terminal.controllerId = attachmentId;
|
|
207
|
-
// Existing followers keep their output but lose input to the new attachment.
|
|
208
|
-
this.emit(terminal, { type: 'state', info: terminalInfo(terminal) });
|
|
209
|
-
}
|
|
210
|
-
queue.push({ type: 'snapshot', sequence: terminal.sequence, screen: screenOf(terminal), info: terminalInfo(terminal) });
|
|
211
|
-
terminal.subscribers.add(queue);
|
|
212
|
-
this.ownedSubscribers.add(queue);
|
|
213
|
-
signal.addEventListener('abort', () => {
|
|
214
|
-
terminal.subscribers.delete(queue);
|
|
215
|
-
this.ownedSubscribers.delete(queue);
|
|
216
|
-
queue.end();
|
|
217
|
-
if (terminal.controllerId === attachmentId) {
|
|
218
|
-
terminal.controllerId = undefined;
|
|
219
|
-
this.emit(terminal, { type: 'state', info: terminalInfo(terminal) });
|
|
220
|
-
}
|
|
221
|
-
}, { once: true });
|
|
222
|
-
return queue;
|
|
223
|
-
}
|
|
224
|
-
async terminalCall(endpoint, sessionId, args, signal) {
|
|
225
|
-
this.assertTerminalEnabled();
|
|
226
|
-
const cwd = await this.rootFor(sessionId, signal);
|
|
227
|
-
if (endpoint === 'terminal/environment') {
|
|
228
|
-
return { ok: true, value: { cwd, maxInputBytes: MAX_INPUT_BYTES, maxCols: MAX_COLS, maxRows: MAX_ROWS, scrollback: TERMINAL_SCROLLBACK } };
|
|
229
|
-
}
|
|
230
|
-
if (endpoint === 'terminal/shells')
|
|
231
|
-
return { ok: true, value: [shellInfo(DEFAULT_SHELL)] };
|
|
232
|
-
if (endpoint === 'terminal/list')
|
|
233
|
-
return { ok: true, value: [...this.terminals.values()].filter(item => item.sessionId === sessionId).map(terminalInfo) };
|
|
234
|
-
if (endpoint === 'terminal/create')
|
|
235
|
-
return this.createTerminal(sessionId, cwd, args);
|
|
236
|
-
const id = stringId(args.id);
|
|
237
|
-
const terminal = this.requireTerminal(sessionId, id);
|
|
238
|
-
if (endpoint === 'terminal/write') {
|
|
239
|
-
const data = typeof args.data === 'string' ? args.data : '';
|
|
240
|
-
if (Buffer.byteLength(data) > MAX_INPUT_BYTES)
|
|
241
|
-
throw new RpcError('INVALID_MESSAGE', 'Terminal input is too large.');
|
|
242
|
-
if (terminal.process === undefined)
|
|
243
|
-
throw new RpcError('CODEX_TERMINAL_NOT_FOUND', 'The CodeX terminal is no longer available.');
|
|
244
|
-
await terminal.process.write(data);
|
|
245
|
-
return { ok: true };
|
|
246
|
-
}
|
|
247
|
-
if (endpoint === 'terminal/resize') {
|
|
248
|
-
terminal.cols = bounded(args.cols, terminal.cols, MAX_COLS);
|
|
249
|
-
terminal.rows = bounded(args.rows, terminal.rows, MAX_ROWS);
|
|
250
|
-
await terminal.process?.resize(terminal.cols, terminal.rows);
|
|
251
|
-
return { ok: true };
|
|
252
|
-
}
|
|
253
|
-
if (endpoint === 'terminal/rename') {
|
|
254
|
-
if (typeof args.title === 'string' && args.title.length > 0)
|
|
255
|
-
terminal.title = args.title.slice(0, 128);
|
|
256
|
-
return { ok: true };
|
|
257
|
-
}
|
|
258
|
-
if (endpoint === 'terminal/close') {
|
|
259
|
-
this.disposeTerminal(terminal);
|
|
260
|
-
this.terminals.delete(`${sessionId}/${id}`);
|
|
261
|
-
return { ok: true };
|
|
262
|
-
}
|
|
263
|
-
throw new RpcError('METHOD_NOT_FOUND', 'The requested terminal method does not exist.');
|
|
264
|
-
}
|
|
265
|
-
async createTerminal(sessionId, cwd, args) {
|
|
266
|
-
const request = isRecord(args.request) ? args.request : {};
|
|
267
|
-
const id = stringId(request.id);
|
|
268
|
-
if ([...this.terminals.values()].some(item => item.sessionId === sessionId && item.id === id))
|
|
269
|
-
throw new RpcError('REQUEST_CONFLICT', 'The terminal id is already active.');
|
|
270
|
-
if (this.terminals.size >= MAX_TERMINALS)
|
|
271
|
-
throw new RpcError('RATE_LIMITED', 'Too many remote terminals are active.', undefined, true);
|
|
272
|
-
if (request.shellPath !== undefined && request.shellPath !== DEFAULT_SHELL.path)
|
|
273
|
-
throw new RpcError('INVALID_MESSAGE', 'The requested shell is not available for this workspace.');
|
|
274
|
-
const cols = bounded(request.cols, 80, MAX_COLS);
|
|
275
|
-
const rows = bounded(request.rows, 24, MAX_ROWS);
|
|
276
|
-
const terminal = {
|
|
277
|
-
sessionId, id, title: DEFAULT_SHELL.name, shell: DEFAULT_SHELL, cwd, cols, rows,
|
|
278
|
-
sequence: 0, screen: [], screenBytes: 0, truncated: false, subscribers: new Set(),
|
|
279
|
-
state: 'running', exitCode: null,
|
|
280
|
-
};
|
|
281
|
-
this.terminals.set(`${sessionId}/${id}`, terminal);
|
|
282
|
-
try {
|
|
283
|
-
terminal.process = await this.spawnTerminal({
|
|
284
|
-
argv: [DEFAULT_SHELL.path, ...DEFAULT_SHELL.args],
|
|
285
|
-
cwd,
|
|
286
|
-
cols,
|
|
287
|
-
rows,
|
|
288
|
-
terminalType: TERMINAL_TYPE,
|
|
289
|
-
env: { DSH_SESSION_ID: sessionId },
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
this.terminals.delete(`${sessionId}/${id}`);
|
|
294
|
-
throw new RpcError('CODEX_TERMINAL_UNAVAILABLE', 'The CodeX terminal could not be started.');
|
|
295
|
-
}
|
|
296
|
-
void this.pump(terminal, terminal.process);
|
|
297
|
-
return { ok: true, value: terminalInfo(terminal) };
|
|
298
|
-
}
|
|
299
|
-
/** Streams process output as ordered `output` frames, then reports the exit. */
|
|
300
|
-
async pump(terminal, process) {
|
|
301
|
-
try {
|
|
302
|
-
for await (const chunk of process.output) {
|
|
303
|
-
if (chunk.length === 0)
|
|
304
|
-
continue;
|
|
305
|
-
terminal.sequence += 1;
|
|
306
|
-
this.appendScreen(terminal, chunk);
|
|
307
|
-
this.emit(terminal, { type: 'output', sequence: terminal.sequence, data: chunk });
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
catch {
|
|
311
|
-
terminal.state = 'failed';
|
|
312
|
-
terminal.error = 'The terminal output stream failed.';
|
|
313
|
-
}
|
|
314
|
-
const outcome = await process.completed;
|
|
315
|
-
terminal.exitCode = outcome.exitCode;
|
|
316
|
-
if (terminal.state === 'running')
|
|
317
|
-
terminal.state = 'exited';
|
|
318
|
-
this.emit(terminal, { type: 'state', info: terminalInfo(terminal) });
|
|
319
|
-
this.endSubscribers(terminal);
|
|
320
|
-
}
|
|
321
|
-
appendScreen(terminal, chunk) {
|
|
322
|
-
terminal.screen.push(chunk);
|
|
323
|
-
terminal.screenBytes += Buffer.byteLength(chunk);
|
|
324
|
-
while (terminal.screenBytes > MAX_SCREEN_BYTES && terminal.screen.length > 1) {
|
|
325
|
-
terminal.screenBytes -= Buffer.byteLength(terminal.screen.shift());
|
|
326
|
-
terminal.truncated = true;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
async rootFor(sessionId, signal) {
|
|
330
|
-
const parsed = parseCodexSessionId(sessionId);
|
|
331
|
-
if (!parsed)
|
|
332
|
-
throw new RpcError('CODEX_SESSION_INVALID', 'The CodeX session identifier is invalid.');
|
|
333
|
-
const cwd = await this.resolveCwd(parsed.threadId, signal);
|
|
334
|
-
if (!cwd)
|
|
335
|
-
throw new RpcError('CODEX_WORKSPACE_UNAVAILABLE', 'The CodeX thread has no available workspace.');
|
|
336
|
-
try {
|
|
337
|
-
const root = await realpath(cwd);
|
|
338
|
-
const info = await stat(root);
|
|
339
|
-
if (!info.isDirectory())
|
|
340
|
-
throw new Error();
|
|
341
|
-
return root;
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
throw new RpcError('CODEX_WORKSPACE_UNAVAILABLE', 'The CodeX workspace is unavailable.');
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
async safePath(root, path, directory) {
|
|
348
|
-
// Native clients may pass the displayed absolute workspace path (not only
|
|
349
|
-
// a relative child). Accept it only when lexical and canonical containment
|
|
350
|
-
// still prove that it belongs to this authenticated workspace root.
|
|
351
|
-
const candidate = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
352
|
-
const rel = relative(root, candidate);
|
|
353
|
-
if (rel.startsWith('..') || isAbsolute(rel))
|
|
354
|
-
throw new RpcError('CODEX_WORKSPACE_PATH_DENIED', 'The requested workspace path is outside the CodeX workspace.');
|
|
355
|
-
try {
|
|
356
|
-
const info = await lstat(candidate);
|
|
357
|
-
if (info.isSymbolicLink())
|
|
358
|
-
throw new Error();
|
|
359
|
-
const canonical = await realpath(candidate);
|
|
360
|
-
const canonicalRel = relative(root, canonical);
|
|
361
|
-
if (canonicalRel.startsWith('..') || isAbsolute(canonicalRel))
|
|
362
|
-
throw new Error();
|
|
363
|
-
if (directory && !info.isDirectory())
|
|
364
|
-
throw new Error();
|
|
365
|
-
return canonical;
|
|
366
|
-
}
|
|
367
|
-
catch {
|
|
368
|
-
throw new RpcError('CODEX_WORKSPACE_PATH_DENIED', 'The requested workspace path is unavailable.');
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
emit(terminal, value) {
|
|
372
|
-
for (const subscriber of terminal.subscribers)
|
|
373
|
-
subscriber.push(value);
|
|
374
|
-
}
|
|
375
|
-
endSubscribers(terminal) {
|
|
376
|
-
for (const subscriber of terminal.subscribers)
|
|
377
|
-
subscriber.end();
|
|
378
|
-
terminal.subscribers.clear();
|
|
379
|
-
}
|
|
380
|
-
requireTerminal(sessionId, id) {
|
|
381
|
-
const terminal = this.terminals.get(`${sessionId}/${id}`);
|
|
382
|
-
if (!terminal)
|
|
383
|
-
throw new RpcError('CODEX_TERMINAL_NOT_FOUND', 'The CodeX terminal is no longer available.');
|
|
384
|
-
return terminal;
|
|
385
|
-
}
|
|
386
|
-
assertTerminalEnabled() { if (!this.terminalEnabled())
|
|
387
|
-
throw new RpcError('TERMINAL_DISABLED', 'Remote terminal is disabled on this Host.'); }
|
|
388
|
-
disposeTerminal(terminal) {
|
|
389
|
-
void Promise.resolve(terminal.process?.terminate()).catch(() => undefined);
|
|
390
|
-
if (terminal.state === 'running') {
|
|
391
|
-
// Followers see a terminal state before their stream ends, so a close from
|
|
392
|
-
// another connection cannot leave a view stuck on "running".
|
|
393
|
-
terminal.state = 'exited';
|
|
394
|
-
this.emit(terminal, { type: 'state', info: terminalInfo(terminal) });
|
|
395
|
-
}
|
|
396
|
-
this.endSubscribers(terminal);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
/** Default terminal process: a plain child process, used when the Host exposes no PTY provider. */
|
|
400
|
-
export function pipeTerminalSpawner() {
|
|
401
|
-
return async (spec) => {
|
|
402
|
-
const child = spawn(spec.argv[0], spec.argv.slice(1), {
|
|
403
|
-
cwd: spec.cwd,
|
|
404
|
-
stdio: 'pipe',
|
|
405
|
-
windowsHide: true,
|
|
406
|
-
env: { ...process.env, ...spec.env },
|
|
2
|
+
import { WorkspaceBridge, WorkspaceBridgeState, pipeTerminalSpawner } from './workspace-bridge.js';
|
|
3
|
+
export * from './workspace-bridge.js';
|
|
4
|
+
export { WorkspaceBridgeState as CodexWorkspaceState };
|
|
5
|
+
/** CodeX supplies thread authority to the shared workspace implementation. */
|
|
6
|
+
export class CodexWorkspaceBridge extends WorkspaceBridge {
|
|
7
|
+
constructor(resolveCwd, terminalEnabled, state = new WorkspaceBridgeState(), spawnTerminal = pipeTerminalSpawner()) {
|
|
8
|
+
super(resolveCwd, terminalEnabled, state, spawnTerminal, {
|
|
9
|
+
parse: value => { const parsed = parseCodexSessionId(value); return parsed ? { sessionId: parsed.sessionId, nativeId: parsed.threadId } : undefined; }, domain: 'CODEX', owns: value => typeof value === 'string' && value.startsWith('codex:'),
|
|
407
10
|
});
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
child.stderr.on('data', data => queue.push(Buffer.from(data).toString('utf8')));
|
|
411
|
-
child.on('error', () => queue.end());
|
|
412
|
-
const completed = new Promise(resolve => {
|
|
413
|
-
child.on('error', () => { queue.end(); resolve({ exitCode: null }); });
|
|
414
|
-
child.on('exit', code => { queue.end(); resolve({ exitCode: code }); });
|
|
415
|
-
});
|
|
416
|
-
return {
|
|
417
|
-
output: queue,
|
|
418
|
-
write: data => { child.stdin.write(data); },
|
|
419
|
-
resize: () => undefined,
|
|
420
|
-
terminate: () => { if (!child.killed)
|
|
421
|
-
child.kill(); },
|
|
422
|
-
completed,
|
|
423
|
-
};
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Terminal process backed by the Host `subprocess` service, which owns PTY
|
|
428
|
-
* allocation, containment, and process-range termination.
|
|
429
|
-
*/
|
|
430
|
-
export function subprocessTerminalSpawner(subprocess) {
|
|
431
|
-
return async (spec) => {
|
|
432
|
-
const handle = await subprocess.spawnTerminal({
|
|
433
|
-
argv: [...spec.argv],
|
|
434
|
-
cwd: spec.cwd,
|
|
435
|
-
cols: spec.cols,
|
|
436
|
-
rows: spec.rows,
|
|
437
|
-
terminalType: spec.terminalType,
|
|
438
|
-
env: spec.env,
|
|
439
|
-
});
|
|
440
|
-
const queue = new AsyncQueue();
|
|
441
|
-
handle.output.setEncoding?.('utf8');
|
|
442
|
-
handle.output.on('data', chunk => queue.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')));
|
|
443
|
-
handle.output.once('close', () => queue.end());
|
|
444
|
-
handle.output.once('error', () => queue.end());
|
|
445
|
-
return {
|
|
446
|
-
output: queue,
|
|
447
|
-
write: data => handle.write(data),
|
|
448
|
-
resize: (cols, rows) => handle.resize(cols, rows),
|
|
449
|
-
terminate: () => handle.terminate(),
|
|
450
|
-
completed: handle.done
|
|
451
|
-
.then(outcome => ({ exitCode: typeof outcome?.exitCode === 'number' ? outcome.exitCode : null }))
|
|
452
|
-
.catch(() => ({ exitCode: null })),
|
|
453
|
-
};
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
function shellInfo(shell) {
|
|
457
|
-
return { path: shell.path, args: [...shell.args], name: shell.name };
|
|
458
|
-
}
|
|
459
|
-
/** Official WebTerminalInfo shape, including the attachment that owns input. */
|
|
460
|
-
function terminalInfo(terminal) {
|
|
461
|
-
return {
|
|
462
|
-
id: terminal.id,
|
|
463
|
-
title: terminal.title,
|
|
464
|
-
shell: shellInfo(terminal.shell),
|
|
465
|
-
cwd: terminal.cwd,
|
|
466
|
-
cols: terminal.cols,
|
|
467
|
-
rows: terminal.rows,
|
|
468
|
-
state: terminal.state,
|
|
469
|
-
exitCode: terminal.exitCode,
|
|
470
|
-
...(terminal.error === undefined ? {} : { error: terminal.error }),
|
|
471
|
-
...(terminal.controllerId === undefined ? {} : { controllerId: terminal.controllerId }),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* The carrier has no terminal emulator: the bounded raw journal is replayed into
|
|
476
|
-
* the client emulator, which re-executes the same control sequences. A truncated
|
|
477
|
-
* journal starts with a reset so a half-captured sequence cannot leak in.
|
|
478
|
-
*/
|
|
479
|
-
function screenOf(terminal) {
|
|
480
|
-
return (terminal.truncated ? '\u001bc' : '') + terminal.screen.join('');
|
|
481
|
-
}
|
|
482
|
-
function argsOf(payload) { const value = isRecord(payload) ? payload : {}; return isRecord(value.args) ? value.args : value; }
|
|
483
|
-
function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); }
|
|
484
|
-
function stringId(value) { if (typeof value !== 'string' || !/^[A-Za-z0-9_.:-]{1,128}$/.test(value))
|
|
485
|
-
throw new RpcError('INVALID_MESSAGE', 'The terminal identifier is invalid.'); return value; }
|
|
486
|
-
function bounded(value, fallback, max) { return typeof value === 'number' && Number.isInteger(value) && value > 0 ? Math.min(value, max) : fallback; }
|
|
487
|
-
function byteOrLineRange(args) {
|
|
488
|
-
const options = isRecord(args.options) ? args.options : undefined;
|
|
489
|
-
if (options !== undefined && isRecord(options.range))
|
|
490
|
-
return options.range;
|
|
491
|
-
return isRecord(args.range) ? args.range : args;
|
|
492
|
-
}
|
|
493
|
-
function readOffset(args) {
|
|
494
|
-
const range = byteOrLineRange(args);
|
|
495
|
-
return typeof range.offset === 'number' && Number.isInteger(range.offset) && range.offset >= 0 ? range.offset : 0;
|
|
496
|
-
}
|
|
497
|
-
function readLimit(args) {
|
|
498
|
-
const range = byteOrLineRange(args);
|
|
499
|
-
const value = typeof range.length === 'number' ? range.length : range.limit;
|
|
500
|
-
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? Math.min(value, MAX_READ_BYTES) : MAX_READ_BYTES;
|
|
11
|
+
}
|
|
12
|
+
isCodeXScope(value) { return this.isScope(value); }
|
|
501
13
|
}
|
|
502
14
|
//# sourceMappingURL=codex-workspace-bridge.js.map
|