@xenosystem/agent-interface-host 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/AcpAgentRegistryService.d.ts +43 -1
- package/dist/acp/AcpAgentRegistryService.d.ts.map +1 -1
- package/dist/acp/AcpAgentRegistryService.js +200 -1
- package/dist/acp/AcpAgentRegistryService.js.map +1 -1
- package/dist/acp/AcpAgentTurnExecutionAdapter.d.ts +66 -0
- package/dist/acp/AcpAgentTurnExecutionAdapter.d.ts.map +1 -1
- package/dist/acp/AcpAgentTurnExecutionAdapter.js +182 -1
- package/dist/acp/AcpAgentTurnExecutionAdapter.js.map +1 -1
- package/dist/acp/AcpPermissionBridge.d.ts.map +1 -1
- package/dist/acp/AcpPermissionBridge.js +3 -3
- package/dist/acp/AcpPermissionBridge.js.map +1 -1
- package/dist/acp/AcpProcessSupervisor.d.ts +81 -0
- package/dist/acp/AcpProcessSupervisor.d.ts.map +1 -1
- package/dist/acp/AcpProcessSupervisor.js +176 -2
- package/dist/acp/AcpProcessSupervisor.js.map +1 -1
- package/dist/acp/AcpRuntimeEventMapper.d.ts.map +1 -1
- package/dist/acp/AcpRuntimeEventMapper.js +9 -0
- package/dist/acp/AcpRuntimeEventMapper.js.map +1 -1
- package/dist/acp/AcpSessionService.d.ts +36 -0
- package/dist/acp/AcpSessionService.d.ts.map +1 -1
- package/dist/acp/AcpSessionService.js +57 -1
- package/dist/acp/AcpSessionService.js.map +1 -1
- package/dist/acp/DurableAcpPermissionPolicyStore.d.ts.map +1 -1
- package/dist/acp/DurableAcpPermissionPolicyStore.js +5 -3
- package/dist/acp/DurableAcpPermissionPolicyStore.js.map +1 -1
- package/dist/acp/acpRedaction.d.ts.map +1 -1
- package/dist/acp/acpRedaction.js +2 -0
- package/dist/acp/acpRedaction.js.map +1 -1
- package/dist/acp/acpTypes.d.ts +111 -1
- package/dist/acp/acpTypes.d.ts.map +1 -1
- package/dist/acp/acpTypes.js +21 -1
- package/dist/acp/acpTypes.js.map +1 -1
- package/dist/index.d.ts +108 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +236 -11
- package/dist/index.js.map +1 -1
- package/dist/isolation/CommandExecution.d.ts +7 -12
- package/dist/isolation/CommandExecution.d.ts.map +1 -1
- package/dist/isolation/CommandExecution.js +7 -12
- package/dist/isolation/CommandExecution.js.map +1 -1
- package/dist/isolation/CommandStream.d.ts +100 -0
- package/dist/isolation/CommandStream.d.ts.map +1 -0
- package/dist/isolation/CommandStream.js +47 -0
- package/dist/isolation/CommandStream.js.map +1 -0
- package/dist/isolation/RunExecutionEnvironment.d.ts +15 -0
- package/dist/isolation/RunExecutionEnvironment.d.ts.map +1 -1
- package/dist/isolation/RunExecutionEnvironment.js +45 -9
- package/dist/isolation/RunExecutionEnvironment.js.map +1 -1
- package/dist/isolation/RunIsolationService.d.ts +10 -5
- package/dist/isolation/RunIsolationService.d.ts.map +1 -1
- package/dist/isolation/RunIsolationService.js +7 -3
- package/dist/isolation/RunIsolationService.js.map +1 -1
- package/dist/isolation/SandboxCapabilities.d.ts +28 -0
- package/dist/isolation/SandboxCapabilities.d.ts.map +1 -0
- package/dist/isolation/SandboxCapabilities.js +33 -0
- package/dist/isolation/SandboxCapabilities.js.map +1 -0
- package/dist/isolation/SandboxCommandExecutor.d.ts +4 -7
- package/dist/isolation/SandboxCommandExecutor.d.ts.map +1 -1
- package/dist/isolation/SandboxCommandExecutor.js +6 -7
- package/dist/isolation/SandboxCommandExecutor.js.map +1 -1
- package/dist/isolation/SandboxCommandStream.d.ts +38 -0
- package/dist/isolation/SandboxCommandStream.d.ts.map +1 -0
- package/dist/isolation/SandboxCommandStream.js +232 -0
- package/dist/isolation/SandboxCommandStream.js.map +1 -0
- package/dist/isolation/shellArgv.d.ts +15 -0
- package/dist/isolation/shellArgv.d.ts.map +1 -0
- package/dist/isolation/shellArgv.js +31 -0
- package/dist/isolation/shellArgv.js.map +1 -0
- package/dist/isolation/useIdempotency.d.ts +19 -0
- package/dist/isolation/useIdempotency.d.ts.map +1 -0
- package/dist/isolation/useIdempotency.js +57 -0
- package/dist/isolation/useIdempotency.js.map +1 -0
- package/dist/localRpcTransport.d.ts +18 -0
- package/dist/localRpcTransport.d.ts.map +1 -1
- package/dist/localRpcTransport.js +38 -4
- package/dist/localRpcTransport.js.map +1 -1
- package/dist/node.d.ts +19 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +19 -0
- package/dist/node.js.map +1 -1
- package/dist/observation/XenoUseRunObserver.d.ts +20 -0
- package/dist/observation/XenoUseRunObserver.d.ts.map +1 -1
- package/dist/observation/XenoUseRunObserver.js +27 -13
- package/dist/observation/XenoUseRunObserver.js.map +1 -1
- package/dist/runtime/RuntimeTimelineProjection.d.ts +2 -2
- package/dist/runtime/RuntimeTimelineProjection.d.ts.map +1 -1
- package/dist/runtime/RuntimeTimelineProjection.js +194 -1
- package/dist/runtime/RuntimeTimelineProjection.js.map +1 -1
- package/dist/sdk/DurablePendingInjectionStore.d.ts +24 -0
- package/dist/sdk/DurablePendingInjectionStore.d.ts.map +1 -0
- package/dist/sdk/DurablePendingInjectionStore.js +103 -0
- package/dist/sdk/DurablePendingInjectionStore.js.map +1 -0
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.d.ts +81 -1
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.d.ts.map +1 -1
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.js +122 -3
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.js.map +1 -1
- package/dist/sdk/pendingInjections.d.ts +83 -0
- package/dist/sdk/pendingInjections.d.ts.map +1 -0
- package/dist/sdk/pendingInjections.js +79 -0
- package/dist/sdk/pendingInjections.js.map +1 -0
- package/dist/session/AgentSessionService.d.ts +34 -0
- package/dist/session/AgentSessionService.d.ts.map +1 -1
- package/dist/session/AgentSessionService.js +85 -1
- package/dist/session/AgentSessionService.js.map +1 -1
- package/dist/session/nativeTuiSessionPty.d.ts +40 -0
- package/dist/session/nativeTuiSessionPty.d.ts.map +1 -0
- package/dist/session/nativeTuiSessionPty.js +142 -0
- package/dist/session/nativeTuiSessionPty.js.map +1 -0
- package/dist/session/nodePtyLoader.d.ts +23 -0
- package/dist/session/nodePtyLoader.d.ts.map +1 -0
- package/dist/session/nodePtyLoader.js +41 -0
- package/dist/session/nodePtyLoader.js.map +1 -0
- package/dist/session/renderSupport.d.ts +34 -0
- package/dist/session/renderSupport.d.ts.map +1 -1
- package/dist/session/renderSupport.js +39 -1
- package/dist/session/renderSupport.js.map +1 -1
- package/dist/workspace/WorkspaceHostService.d.ts +27 -2
- package/dist/workspace/WorkspaceHostService.d.ts.map +1 -1
- package/dist/workspace/WorkspaceHostService.js +221 -25
- package/dist/workspace/WorkspaceHostService.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeTuiSessionPty.js","sourceRoot":"","sources":["../../src/session/nativeTuiSessionPty.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place `node-pty` is loaded.
|
|
3
|
+
*
|
|
4
|
+
* Lazy and failure-tolerant on purpose: it is a native module, it is absent in
|
|
5
|
+
* a headless or web composition, and a host that cannot load it must still
|
|
6
|
+
* serve every other method rather than fail to construct.
|
|
7
|
+
*
|
|
8
|
+
* 🔴 Extracted rather than copied. `WorkspaceHostService` had this logic
|
|
9
|
+
* inline, and the `native-tui` session PTY needs exactly the same thing — two
|
|
10
|
+
* copies of "how do we load the native terminal, and what do we say when we
|
|
11
|
+
* cannot" is how the two come to disagree about whether a terminal is
|
|
12
|
+
* available.
|
|
13
|
+
*/
|
|
14
|
+
export type NodePtyModule = typeof import('node-pty');
|
|
15
|
+
export interface NodePtyLoad {
|
|
16
|
+
module: NodePtyModule | null;
|
|
17
|
+
/** Why not, when not — never a bare null. */
|
|
18
|
+
error: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function loadNodePty(): Promise<NodePtyLoad>;
|
|
21
|
+
/** Test seam. Never called in production — the cache is process-lifetime. */
|
|
22
|
+
export declare function resetNodePtyLoaderForTests(): void;
|
|
23
|
+
//# sourceMappingURL=nodePtyLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodePtyLoader.d.ts","sourceRoot":"","sources":["../../src/session/nodePtyLoader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,aAAa,GAAG,cAAc,UAAU,CAAC,CAAA;AAErD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAID,wBAAgB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAqBlD;AAED,6EAA6E;AAC7E,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place `node-pty` is loaded.
|
|
3
|
+
*
|
|
4
|
+
* Lazy and failure-tolerant on purpose: it is a native module, it is absent in
|
|
5
|
+
* a headless or web composition, and a host that cannot load it must still
|
|
6
|
+
* serve every other method rather than fail to construct.
|
|
7
|
+
*
|
|
8
|
+
* 🔴 Extracted rather than copied. `WorkspaceHostService` had this logic
|
|
9
|
+
* inline, and the `native-tui` session PTY needs exactly the same thing — two
|
|
10
|
+
* copies of "how do we load the native terminal, and what do we say when we
|
|
11
|
+
* cannot" is how the two come to disagree about whether a terminal is
|
|
12
|
+
* available.
|
|
13
|
+
*/
|
|
14
|
+
let cached;
|
|
15
|
+
export function loadNodePty() {
|
|
16
|
+
if (!cached) {
|
|
17
|
+
cached = import('node-pty')
|
|
18
|
+
.then((loaded) => {
|
|
19
|
+
const namespace = loaded;
|
|
20
|
+
const module = (namespace.default || namespace);
|
|
21
|
+
// Present-but-wrong is a real case: a stub, a browser shim, or a build
|
|
22
|
+
// that resolved the package without its native binding.
|
|
23
|
+
if (typeof module.spawn !== 'function') {
|
|
24
|
+
throw new Error('node-pty did not expose a spawn function.');
|
|
25
|
+
}
|
|
26
|
+
return { module, error: null };
|
|
27
|
+
})
|
|
28
|
+
.catch((error) => ({
|
|
29
|
+
module: null,
|
|
30
|
+
error: error instanceof Error && error.message.trim()
|
|
31
|
+
? error.message.slice(0, 1_000)
|
|
32
|
+
: 'Failed to load node-pty.',
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
/** Test seam. Never called in production — the cache is process-lifetime. */
|
|
38
|
+
export function resetNodePtyLoaderForTests() {
|
|
39
|
+
cached = undefined;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=nodePtyLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodePtyLoader.js","sourceRoot":"","sources":["../../src/session/nodePtyLoader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,IAAI,MAAwC,CAAA;AAE5C,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;aACxB,IAAI,CAAC,CAAC,MAAe,EAAE,EAAE;YACxB,MAAM,SAAS,GAAG,MAA8D,CAAA;YAChF,MAAM,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,SAAS,CAAkB,CAAA;YAChE,uEAAuE;YACvE,wDAAwD;YACxD,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;YAC9D,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAChC,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC;YAC1B,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE;gBACnD,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;gBAC/B,CAAC,CAAC,0BAA0B;SAC/B,CAAC,CAAC,CAAA;IACP,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,0BAA0B;IACxC,MAAM,GAAG,SAAS,CAAA;AACpB,CAAC"}
|
|
@@ -15,6 +15,22 @@ import type { AgentRenderSupport } from '@xenosystem/agent-interface-core';
|
|
|
15
15
|
* PTY lane is built it replaces this function, and `renderSupportFor`'s tests
|
|
16
16
|
* already prove a `true` answer flows through — so the day it lands, the mode
|
|
17
17
|
* becomes available by construction instead of by someone remembering.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ That claim was NOT true until 2026-08-24, and the way it was false is
|
|
20
|
+
* worth keeping. Construction reached as far as this file and stopped one
|
|
21
|
+
* caller short, in two places at once:
|
|
22
|
+
*
|
|
23
|
+
* 1. `AgentHostCoordinator` never accepted a `nativeTuiLane`, so no
|
|
24
|
+
* composition could reach the seam. Injectable and unreachable.
|
|
25
|
+
* 2. `openSessionForRun` built the ACP protocol channel UNCONDITIONALLY,
|
|
26
|
+
* and `open()` refuses `native-tui` when one is present — so a lane
|
|
27
|
+
* returning true would have made every session on that provider fail
|
|
28
|
+
* with `protocol_channel_forbidden`.
|
|
29
|
+
*
|
|
30
|
+
* Both are fixed and mutation-checked at the caller. The lesson generalises:
|
|
31
|
+
* a seam proves a value can flow THROUGH the function that declares it, never
|
|
32
|
+
* that anything calls that function correctly. `renderSupport.test.ts` proved
|
|
33
|
+
* the first and read as proving the second.
|
|
18
34
|
*/
|
|
19
35
|
export declare function noNativeTuiLane(): boolean;
|
|
20
36
|
export interface RenderSupportOptions {
|
|
@@ -48,6 +64,24 @@ export interface RenderSupportOptions {
|
|
|
48
64
|
* A provider no adapter supports gets `{ nativeTui: false, acp: false }`, and
|
|
49
65
|
* `selectSessionRenderMode` REFUSES it by name. That is the honest outcome: the
|
|
50
66
|
* alternative is a session that opens, renders nothing, and reports no error.
|
|
67
|
+
*
|
|
68
|
+
* 🔴 AND BEING BLOCKED COUNTS AS "cannot run" — measured 2026-08-24.
|
|
69
|
+
*
|
|
70
|
+
* `AcpAgentTurnExecutionAdapter.supports()` is `provider.kind === 'acp'`. It
|
|
71
|
+
* answers "is this the right KIND", not "can this actually run", so a blocked
|
|
72
|
+
* endpoint reported `acp: true` and, with a TUI present, `switchable: true` —
|
|
73
|
+
* a toggle that switched into an empty terminal.
|
|
74
|
+
*
|
|
75
|
+
* Latent until now, because a blocked provider cannot start a turn and no
|
|
76
|
+
* session was ever opened for one. It stops being latent the moment a TUI-only
|
|
77
|
+
* agent is registered — Grok Build, agy and opencode have no ACP adapter in
|
|
78
|
+
* `xeno-acp`, so they arrive blocked AND with a real interface, which is
|
|
79
|
+
* exactly the combination that had no honest answer.
|
|
80
|
+
*
|
|
81
|
+
* Fixed HERE rather than in `supports()`: that predicate also routes turn
|
|
82
|
+
* EXECUTION, and narrowing it would change which adapter is found for a run
|
|
83
|
+
* that is refused for other reasons. This question — "can we render this
|
|
84
|
+
* agent's structured stream" — is the one that needs the stricter answer.
|
|
51
85
|
*/
|
|
52
86
|
export declare function renderSupportFor(provider: AgentRuntimeProviderDescriptor, options: RenderSupportOptions): AgentRenderSupport;
|
|
53
87
|
//# sourceMappingURL=renderSupport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderSupport.d.ts","sourceRoot":"","sources":["../../src/session/renderSupport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,sCAAsC,CAAA;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAE1E
|
|
1
|
+
{"version":3,"file":"renderSupport.d.ts","sourceRoot":"","sources":["../../src/session/renderSupport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,sCAAsC,CAAA;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,8BAA8B,GAAG,OAAO,CAAA;KAAE,EAAE,CAAA;IACpF,+EAA+E;IAC/E,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,8BAA8B,KAAK,OAAO,CAAA;CACtE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,8BAA8B,EACxC,OAAO,EAAE,oBAAoB,GAC5B,kBAAkB,CAUpB"}
|
|
@@ -13,6 +13,22 @@
|
|
|
13
13
|
* PTY lane is built it replaces this function, and `renderSupportFor`'s tests
|
|
14
14
|
* already prove a `true` answer flows through — so the day it lands, the mode
|
|
15
15
|
* becomes available by construction instead of by someone remembering.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ That claim was NOT true until 2026-08-24, and the way it was false is
|
|
18
|
+
* worth keeping. Construction reached as far as this file and stopped one
|
|
19
|
+
* caller short, in two places at once:
|
|
20
|
+
*
|
|
21
|
+
* 1. `AgentHostCoordinator` never accepted a `nativeTuiLane`, so no
|
|
22
|
+
* composition could reach the seam. Injectable and unreachable.
|
|
23
|
+
* 2. `openSessionForRun` built the ACP protocol channel UNCONDITIONALLY,
|
|
24
|
+
* and `open()` refuses `native-tui` when one is present — so a lane
|
|
25
|
+
* returning true would have made every session on that provider fail
|
|
26
|
+
* with `protocol_channel_forbidden`.
|
|
27
|
+
*
|
|
28
|
+
* Both are fixed and mutation-checked at the caller. The lesson generalises:
|
|
29
|
+
* a seam proves a value can flow THROUGH the function that declares it, never
|
|
30
|
+
* that anything calls that function correctly. `renderSupport.test.ts` proved
|
|
31
|
+
* the first and read as proving the second.
|
|
16
32
|
*/
|
|
17
33
|
export function noNativeTuiLane() {
|
|
18
34
|
return false;
|
|
@@ -30,12 +46,34 @@ export function noNativeTuiLane() {
|
|
|
30
46
|
* A provider no adapter supports gets `{ nativeTui: false, acp: false }`, and
|
|
31
47
|
* `selectSessionRenderMode` REFUSES it by name. That is the honest outcome: the
|
|
32
48
|
* alternative is a session that opens, renders nothing, and reports no error.
|
|
49
|
+
*
|
|
50
|
+
* 🔴 AND BEING BLOCKED COUNTS AS "cannot run" — measured 2026-08-24.
|
|
51
|
+
*
|
|
52
|
+
* `AcpAgentTurnExecutionAdapter.supports()` is `provider.kind === 'acp'`. It
|
|
53
|
+
* answers "is this the right KIND", not "can this actually run", so a blocked
|
|
54
|
+
* endpoint reported `acp: true` and, with a TUI present, `switchable: true` —
|
|
55
|
+
* a toggle that switched into an empty terminal.
|
|
56
|
+
*
|
|
57
|
+
* Latent until now, because a blocked provider cannot start a turn and no
|
|
58
|
+
* session was ever opened for one. It stops being latent the moment a TUI-only
|
|
59
|
+
* agent is registered — Grok Build, agy and opencode have no ACP adapter in
|
|
60
|
+
* `xeno-acp`, so they arrive blocked AND with a real interface, which is
|
|
61
|
+
* exactly the combination that had no honest answer.
|
|
62
|
+
*
|
|
63
|
+
* Fixed HERE rather than in `supports()`: that predicate also routes turn
|
|
64
|
+
* EXECUTION, and narrowing it would change which adapter is found for a run
|
|
65
|
+
* that is refused for other reasons. This question — "can we render this
|
|
66
|
+
* agent's structured stream" — is the one that needs the stricter answer.
|
|
33
67
|
*/
|
|
34
68
|
export function renderSupportFor(provider, options) {
|
|
35
69
|
const nativeTuiLane = options.nativeTuiLane ?? noNativeTuiLane;
|
|
36
70
|
return {
|
|
37
71
|
nativeTui: nativeTuiLane(provider),
|
|
38
|
-
|
|
72
|
+
// Both halves, and `blocked` fails the second one: a run that cannot
|
|
73
|
+
// execute emits no runtime events, so there is no structured stream to
|
|
74
|
+
// render and claiming otherwise offers an empty terminal.
|
|
75
|
+
acp: provider.availability !== 'blocked'
|
|
76
|
+
&& options.adapters.some((adapter) => adapter.supports(provider)),
|
|
39
77
|
};
|
|
40
78
|
}
|
|
41
79
|
//# sourceMappingURL=renderSupport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderSupport.js","sourceRoot":"","sources":["../../src/session/renderSupport.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"renderSupport.js","sourceRoot":"","sources":["../../src/session/renderSupport.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,CAAA;AACd,CAAC;AAmBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAwC,EACxC,OAA6B;IAE7B,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,eAAe,CAAA;IAC9D,OAAO;QACL,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC;QAClC,qEAAqE;QACrE,uEAAuE;QACvE,0DAA0D;QAC1D,GAAG,EAAE,QAAQ,CAAC,YAAY,KAAK,SAAS;eACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;KACpE,CAAA;AACH,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CommandExecutionPort } from '../isolation/CommandExecution.js';
|
|
2
|
+
import type { CommandStreamPort } from '../isolation/CommandStream.js';
|
|
2
3
|
import type { AgentHostEventType, AgentHostClientIdentity, AgentWorkspaceAccessContext, AgentWorkspaceCommandRunRequest, AgentWorkspaceCommandRunResult, AgentWorkspaceDirectoryListRequest, AgentWorkspaceDirectoryListResult, AgentWorkspaceFileReadRequest, AgentWorkspaceFileReadResult, AgentWorkspaceFileWriteRequest, AgentWorkspaceFileWriteResult, AgentWorkspaceGrantRequest, AgentWorkspaceGrantResult, AgentWorkspacePtyInputRequest, AgentWorkspacePtyResizeRequest, AgentWorkspacePtySnapshotRequest, AgentWorkspacePtySnapshotResult, AgentWorkspacePtyStartRequest, AgentWorkspacePtyStartResult, AgentWorkspaceTerminalControlRequest, AgentWorkspaceTerminalControlResult, AgentWorkspaceTerminalStartRequest, AgentWorkspaceTerminalStartResult } from '@xenosystem/agent-interface-contract';
|
|
3
4
|
type WorkspaceEventSink = (type: AgentHostEventType, payload: Record<string, unknown>) => void;
|
|
4
5
|
interface PersistedApplicationGrant {
|
|
@@ -33,7 +34,21 @@ export interface WorkspaceHostServiceOptions {
|
|
|
33
34
|
* which executor serves a conversation.
|
|
34
35
|
*/
|
|
35
36
|
export interface RunCommandExecutionEnvironment {
|
|
36
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Executor AND containment for this conversation's current run.
|
|
39
|
+
*
|
|
40
|
+
* 🔴 One lookup, not two. argv depends on the destination — a Linux sandbox
|
|
41
|
+
* cannot run `powershell.exe` — so a caller that asked for the executor and
|
|
42
|
+
* then guessed the shell would pick the host's argv the moment isolation
|
|
43
|
+
* turned on. See `commandShellArgv`.
|
|
44
|
+
*/
|
|
45
|
+
resolutionForConversation(conversationId: string | undefined): {
|
|
46
|
+
executor: CommandExecutionPort;
|
|
47
|
+
isolation: {
|
|
48
|
+
isolation: string;
|
|
49
|
+
};
|
|
50
|
+
stream?: CommandStreamPort;
|
|
51
|
+
};
|
|
37
52
|
}
|
|
38
53
|
export interface WorkspacePathAuthorizationRequest extends AgentWorkspaceAccessContext {
|
|
39
54
|
path: string;
|
|
@@ -102,7 +117,7 @@ export declare class WorkspaceHostService {
|
|
|
102
117
|
readFile(request: AgentWorkspaceFileReadRequest): AgentWorkspaceFileReadResult;
|
|
103
118
|
writeFile(request: AgentWorkspaceFileWriteRequest): AgentWorkspaceFileWriteResult;
|
|
104
119
|
runCommand(request: AgentWorkspaceCommandRunRequest): Promise<AgentWorkspaceCommandRunResult>;
|
|
105
|
-
startTerminal(request: AgentWorkspaceTerminalStartRequest): AgentWorkspaceTerminalStartResult
|
|
120
|
+
startTerminal(request: AgentWorkspaceTerminalStartRequest): Promise<AgentWorkspaceTerminalStartResult>;
|
|
106
121
|
stopTerminal(request: AgentWorkspaceTerminalControlRequest): AgentWorkspaceTerminalControlResult;
|
|
107
122
|
startPty(request: AgentWorkspacePtyStartRequest): Promise<AgentWorkspacePtyStartResult>;
|
|
108
123
|
snapshotPty(request: AgentWorkspacePtySnapshotRequest): AgentWorkspacePtySnapshotResult;
|
|
@@ -111,6 +126,16 @@ export declare class WorkspaceHostService {
|
|
|
111
126
|
clearPty(request: AgentWorkspaceTerminalControlRequest): AgentWorkspaceTerminalControlResult;
|
|
112
127
|
stopPty(request: AgentWorkspaceTerminalControlRequest): AgentWorkspaceTerminalControlResult;
|
|
113
128
|
close(): void;
|
|
129
|
+
/**
|
|
130
|
+
* The stream for a confined run — or a refusal.
|
|
131
|
+
*
|
|
132
|
+
* 🔴 When the run reports `sandboxed` there is no host fallback. A missing
|
|
133
|
+
* stream here is a composition bug; spawning `node-pty` as the signed-in
|
|
134
|
+
* user would be the lie `isolationIsNotObservation` exists to refuse.
|
|
135
|
+
*/
|
|
136
|
+
private confineStream;
|
|
137
|
+
private startSandboxedTerminal;
|
|
138
|
+
private startSandboxedPty;
|
|
114
139
|
private authorizePath;
|
|
115
140
|
private authorizeSession;
|
|
116
141
|
private effectiveAllowedRoots;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceHostService.d.ts","sourceRoot":"","sources":["../../src/workspace/WorkspaceHostService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,oBAAoB,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"WorkspaceHostService.d.ts","sourceRoot":"","sources":["../../src/workspace/WorkspaceHostService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { commandSucceeded } from '../isolation/CommandExecution.js';
|
|
3
|
+
import { Utf8StreamDecoder } from '../isolation/CommandStream.js';
|
|
3
4
|
import { LocalCommandExecutor } from '../isolation/LocalCommandExecutor.js';
|
|
5
|
+
import { SANDBOX_STREAM_MAX_TIMEOUT_MS } from '../isolation/SandboxCommandStream.js';
|
|
6
|
+
import { commandShellArgv } from '../isolation/shellArgv.js';
|
|
4
7
|
/**
|
|
5
|
-
* ⚠️ This file has THREE process starts.
|
|
6
|
-
*
|
|
7
|
-
* this adoption", which was true and skipped `startTerminal` entirely. Naming
|
|
8
|
-
* one of several paths is the same mistake as naming the wrong one.
|
|
8
|
+
* ⚠️ This file has THREE process starts. Two of them are adopted when a run is
|
|
9
|
+
* sandboxed, and naming all three still matters.
|
|
9
10
|
*
|
|
10
11
|
* 1. `runCommand` — ADOPTED. Runs through `CommandExecutionPort`.
|
|
11
|
-
* 2. `startTerminal` —
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* buffers to completion — so streaming exec is recorded there as a new
|
|
17
|
-
* capability rather than a defect. Forcing either through this port would
|
|
18
|
-
* mean inventing a shape neither side has.
|
|
12
|
+
* 2. `startTerminal` — ADOPTED when confined (`CommandStreamPort` / exec-stream).
|
|
13
|
+
* Unconfined still spawns on the host.
|
|
14
|
+
* 3. `startPty` — ADOPTED when confined, through `CommandStreamPort`. The
|
|
15
|
+
* session's `tty` is asked from the runtime (`supports_exec_tty`), never
|
|
16
|
+
* assumed. Unconfined still uses `node-pty`.
|
|
19
17
|
*/
|
|
20
18
|
const commandExecutor = new LocalCommandExecutor();
|
|
21
19
|
import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync, } from 'node:fs';
|
|
@@ -300,20 +298,19 @@ export class WorkspaceHostService {
|
|
|
300
298
|
* shell it runs in does not confine anything; these reduce what gets asked
|
|
301
299
|
* for, and the substrate is what will refuse.
|
|
302
300
|
*
|
|
303
|
-
* `
|
|
304
|
-
*
|
|
305
|
-
* the pattern the port's rule generalises rather than imposes.
|
|
301
|
+
* argv comes from `commandShellArgv` so a sandboxed run does not send
|
|
302
|
+
* `powershell.exe` into a Linux container.
|
|
306
303
|
*/
|
|
307
|
-
const invocation = shellInvocation(command, true);
|
|
308
304
|
// 🔴 Resolved PER CALL, from the conversation this request belongs to.
|
|
309
305
|
// Caching an executor on the service would outlive the run that justified
|
|
310
306
|
// it and keep sending commands to a torn-down container — or, worse, keep
|
|
311
307
|
// reporting containment after it ended. With no environment injected this
|
|
312
308
|
// is the module's local executor, exactly as before.
|
|
313
|
-
const
|
|
314
|
-
|
|
309
|
+
const resolution = this.executionEnvironment?.resolutionForConversation(request.conversationId);
|
|
310
|
+
const executor = resolution?.executor ?? commandExecutor;
|
|
311
|
+
const destination = resolution?.isolation.isolation === 'sandboxed' ? 'sandbox' : 'host';
|
|
315
312
|
const result = await executor.execute({
|
|
316
|
-
argv:
|
|
313
|
+
argv: commandShellArgv(command, destination),
|
|
317
314
|
cwd,
|
|
318
315
|
timeoutMs,
|
|
319
316
|
maxOutputBytes: MAX_COMMAND_OUTPUT_BYTES,
|
|
@@ -332,7 +329,7 @@ export class WorkspaceHostService {
|
|
|
332
329
|
truncated: result.truncated,
|
|
333
330
|
};
|
|
334
331
|
}
|
|
335
|
-
startTerminal(request) {
|
|
332
|
+
async startTerminal(request) {
|
|
336
333
|
const command = String(request.command || '').trim();
|
|
337
334
|
if (!command)
|
|
338
335
|
return terminalStartFailure('Command is empty.');
|
|
@@ -344,8 +341,21 @@ export class WorkspaceHostService {
|
|
|
344
341
|
const cwd = denied.path;
|
|
345
342
|
const timeoutMs = clampInteger(request.timeoutMs, 300_000, 1_000, 900_000);
|
|
346
343
|
const terminalId = uniqueId('terminal');
|
|
347
|
-
const
|
|
344
|
+
const confined = this.confineStream(request.conversationId);
|
|
345
|
+
if (confined.error)
|
|
346
|
+
return terminalStartFailure(confined.error);
|
|
347
|
+
if (confined.stream) {
|
|
348
|
+
return this.startSandboxedTerminal({
|
|
349
|
+
request,
|
|
350
|
+
command,
|
|
351
|
+
cwd,
|
|
352
|
+
terminalId,
|
|
353
|
+
timeoutMs,
|
|
354
|
+
stream: confined.stream,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
348
357
|
try {
|
|
358
|
+
const invocation = shellInvocation(command, false);
|
|
349
359
|
const child = spawn(invocation.command, invocation.args, {
|
|
350
360
|
cwd,
|
|
351
361
|
windowsHide: true,
|
|
@@ -357,7 +367,7 @@ export class WorkspaceHostService {
|
|
|
357
367
|
ownerInstanceId: request.client.instanceId,
|
|
358
368
|
command,
|
|
359
369
|
cwd,
|
|
360
|
-
process: child,
|
|
370
|
+
process: { kill: () => { child.kill(); } },
|
|
361
371
|
timer: null,
|
|
362
372
|
timedOut: false,
|
|
363
373
|
};
|
|
@@ -425,6 +435,19 @@ export class WorkspaceHostService {
|
|
|
425
435
|
const terminalId = uniqueId('pty');
|
|
426
436
|
const cols = clampInteger(request.cols, 80, 20, 240);
|
|
427
437
|
const rows = clampInteger(request.rows, 24, 6, 120);
|
|
438
|
+
const confined = this.confineStream(request.conversationId);
|
|
439
|
+
if (confined.error)
|
|
440
|
+
return ptyStartFailure(confined.error, cwd);
|
|
441
|
+
if (confined.stream) {
|
|
442
|
+
return this.startSandboxedPty({
|
|
443
|
+
request,
|
|
444
|
+
cwd,
|
|
445
|
+
terminalId,
|
|
446
|
+
cols,
|
|
447
|
+
rows,
|
|
448
|
+
stream: confined.stream,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
428
451
|
try {
|
|
429
452
|
const nodePty = await this.loadNodePty();
|
|
430
453
|
if (!nodePty) {
|
|
@@ -444,7 +467,15 @@ export class WorkspaceHostService {
|
|
|
444
467
|
id: terminalId,
|
|
445
468
|
ownerInstanceId: request.client.instanceId,
|
|
446
469
|
cwd,
|
|
447
|
-
process:
|
|
470
|
+
process: {
|
|
471
|
+
tty: true,
|
|
472
|
+
write: (data) => { processHandle.write(data); },
|
|
473
|
+
resize: (nextCols, nextRows) => {
|
|
474
|
+
processHandle.resize(nextCols, nextRows);
|
|
475
|
+
return { ok: true };
|
|
476
|
+
},
|
|
477
|
+
kill: () => { processHandle.kill(); },
|
|
478
|
+
},
|
|
448
479
|
output: new ScrollbackBuffer(MAX_PTY_SCROLLBACK_BYTES),
|
|
449
480
|
};
|
|
450
481
|
this.ptySessions.set(terminalId, session);
|
|
@@ -465,7 +496,7 @@ export class WorkspaceHostService {
|
|
|
465
496
|
ownerInstanceId: active?.ownerInstanceId || request.client.instanceId,
|
|
466
497
|
});
|
|
467
498
|
});
|
|
468
|
-
return { success: true, terminalId, cwd, error: null };
|
|
499
|
+
return { success: true, terminalId, cwd, error: null, tty: true };
|
|
469
500
|
}
|
|
470
501
|
catch (error) {
|
|
471
502
|
return { success: false, terminalId: null, cwd, error: safeError(error, 'Failed to start PTY terminal.') };
|
|
@@ -493,7 +524,9 @@ export class WorkspaceHostService {
|
|
|
493
524
|
const denied = this.authorizeSession(request, session);
|
|
494
525
|
if (denied)
|
|
495
526
|
return { success: false, error: denied };
|
|
496
|
-
session.process.resize(clampInteger(request.cols, 80, 20, 240), clampInteger(request.rows, 24, 6, 120));
|
|
527
|
+
const resized = session.process.resize(clampInteger(request.cols, 80, 20, 240), clampInteger(request.rows, 24, 6, 120));
|
|
528
|
+
if (!resized.ok)
|
|
529
|
+
return { success: false, error: resized.error };
|
|
497
530
|
return { success: true, error: null };
|
|
498
531
|
}
|
|
499
532
|
clearPty(request) {
|
|
@@ -531,6 +564,155 @@ export class WorkspaceHostService {
|
|
|
531
564
|
}
|
|
532
565
|
this.ptySessions.clear();
|
|
533
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* The stream for a confined run — or a refusal.
|
|
569
|
+
*
|
|
570
|
+
* 🔴 When the run reports `sandboxed` there is no host fallback. A missing
|
|
571
|
+
* stream here is a composition bug; spawning `node-pty` as the signed-in
|
|
572
|
+
* user would be the lie `isolationIsNotObservation` exists to refuse.
|
|
573
|
+
*/
|
|
574
|
+
confineStream(conversationId) {
|
|
575
|
+
const resolution = this.executionEnvironment?.resolutionForConversation(conversationId);
|
|
576
|
+
if (resolution?.isolation.isolation !== 'sandboxed')
|
|
577
|
+
return {};
|
|
578
|
+
if (!resolution.stream) {
|
|
579
|
+
return { error: 'This run is sandboxed but has no command stream, so the terminal was not started.' };
|
|
580
|
+
}
|
|
581
|
+
return { stream: resolution.stream };
|
|
582
|
+
}
|
|
583
|
+
async startSandboxedTerminal(input) {
|
|
584
|
+
const decoder = new Utf8StreamDecoder();
|
|
585
|
+
let live;
|
|
586
|
+
this.terminalSessions.set(input.terminalId, {
|
|
587
|
+
id: input.terminalId,
|
|
588
|
+
ownerInstanceId: input.request.client.instanceId,
|
|
589
|
+
command: input.command,
|
|
590
|
+
cwd: input.cwd,
|
|
591
|
+
process: { kill: () => { void live?.kill(); } },
|
|
592
|
+
timer: null,
|
|
593
|
+
timedOut: false,
|
|
594
|
+
});
|
|
595
|
+
const started = await input.stream.start({
|
|
596
|
+
argv: commandShellArgv(input.command, 'sandbox'),
|
|
597
|
+
cwd: input.cwd,
|
|
598
|
+
timeoutMs: input.timeoutMs,
|
|
599
|
+
}, {
|
|
600
|
+
onChunk: (chunk) => {
|
|
601
|
+
const text = decoder.push(chunk.bytes);
|
|
602
|
+
if (!text)
|
|
603
|
+
return;
|
|
604
|
+
this.eventSink('workspace.terminal.data', {
|
|
605
|
+
terminalId: input.terminalId,
|
|
606
|
+
stream: chunk.stream,
|
|
607
|
+
chunk: text,
|
|
608
|
+
});
|
|
609
|
+
},
|
|
610
|
+
onExit: (exit) => {
|
|
611
|
+
const flushed = decoder.flush();
|
|
612
|
+
if (flushed) {
|
|
613
|
+
this.eventSink('workspace.terminal.data', {
|
|
614
|
+
terminalId: input.terminalId,
|
|
615
|
+
stream: 'stdout',
|
|
616
|
+
chunk: flushed,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
const active = this.cleanupTerminal(input.terminalId);
|
|
620
|
+
if (!active)
|
|
621
|
+
return;
|
|
622
|
+
this.eventSink('workspace.terminal.exit', {
|
|
623
|
+
terminalId: input.terminalId,
|
|
624
|
+
command: input.command,
|
|
625
|
+
cwd: input.cwd,
|
|
626
|
+
success: !active.timedOut && exit.exitCode === 0,
|
|
627
|
+
exitCode: exit.exitCode,
|
|
628
|
+
error: active.timedOut || exit.timedOut
|
|
629
|
+
? `Command timed out after ${input.timeoutMs}ms.`
|
|
630
|
+
: null,
|
|
631
|
+
ownerInstanceId: active.ownerInstanceId,
|
|
632
|
+
});
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
if (started.error || !started.session) {
|
|
636
|
+
this.terminalSessions.delete(input.terminalId);
|
|
637
|
+
return terminalStartFailure(started.error || 'Failed to start sandboxed terminal command.');
|
|
638
|
+
}
|
|
639
|
+
live = started.session;
|
|
640
|
+
return { success: true, terminalId: input.terminalId, cwd: input.cwd, error: null };
|
|
641
|
+
}
|
|
642
|
+
async startSandboxedPty(input) {
|
|
643
|
+
const decoder = new Utf8StreamDecoder();
|
|
644
|
+
const output = new ScrollbackBuffer(MAX_PTY_SCROLLBACK_BYTES);
|
|
645
|
+
let live;
|
|
646
|
+
this.ptySessions.set(input.terminalId, {
|
|
647
|
+
id: input.terminalId,
|
|
648
|
+
ownerInstanceId: input.request.client.instanceId,
|
|
649
|
+
cwd: input.cwd,
|
|
650
|
+
process: sandboxPtyHandle({
|
|
651
|
+
get tty() { return live?.tty === true; },
|
|
652
|
+
write: (bytes) => live ? live.write(bytes) : Promise.resolve(),
|
|
653
|
+
closeStdin: () => live ? live.closeStdin() : Promise.resolve(),
|
|
654
|
+
resize: (cols, rows) => live
|
|
655
|
+
? live.resize(cols, rows)
|
|
656
|
+
: Promise.resolve({ ok: false, error: 'the sandbox PTY has not started' }),
|
|
657
|
+
kill: () => live ? live.kill() : Promise.resolve(),
|
|
658
|
+
}),
|
|
659
|
+
output,
|
|
660
|
+
});
|
|
661
|
+
const started = await input.stream.start({
|
|
662
|
+
argv: ['/bin/sh', '-i'],
|
|
663
|
+
cwd: input.cwd,
|
|
664
|
+
timeoutMs: SANDBOX_STREAM_MAX_TIMEOUT_MS,
|
|
665
|
+
tty: true,
|
|
666
|
+
}, {
|
|
667
|
+
onChunk: (chunk) => {
|
|
668
|
+
const text = decoder.push(chunk.bytes);
|
|
669
|
+
if (!text)
|
|
670
|
+
return;
|
|
671
|
+
const active = this.ptySessions.get(input.terminalId);
|
|
672
|
+
if (!active)
|
|
673
|
+
return;
|
|
674
|
+
active.output.append(text);
|
|
675
|
+
this.eventSink('workspace.pty.data', {
|
|
676
|
+
terminalId: input.terminalId,
|
|
677
|
+
data: text,
|
|
678
|
+
ownerInstanceId: active.ownerInstanceId,
|
|
679
|
+
});
|
|
680
|
+
},
|
|
681
|
+
onExit: (exit) => {
|
|
682
|
+
const flushed = decoder.flush();
|
|
683
|
+
const active = this.ptySessions.get(input.terminalId);
|
|
684
|
+
if (flushed && active)
|
|
685
|
+
active.output.append(flushed);
|
|
686
|
+
this.ptySessions.delete(input.terminalId);
|
|
687
|
+
this.eventSink('workspace.pty.exit', {
|
|
688
|
+
terminalId: input.terminalId,
|
|
689
|
+
exitCode: exit.exitCode,
|
|
690
|
+
signal: null,
|
|
691
|
+
ownerInstanceId: active?.ownerInstanceId || input.request.client.instanceId,
|
|
692
|
+
});
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
if (started.error || !started.session) {
|
|
696
|
+
this.ptySessions.delete(input.terminalId);
|
|
697
|
+
return ptyStartFailure(started.error || 'Failed to start sandboxed PTY.', input.cwd);
|
|
698
|
+
}
|
|
699
|
+
live = started.session;
|
|
700
|
+
if (started.session.tty) {
|
|
701
|
+
const sized = await started.session.resize(input.cols, input.rows);
|
|
702
|
+
if (!sized.ok) {
|
|
703
|
+
await started.session.kill();
|
|
704
|
+
this.ptySessions.delete(input.terminalId);
|
|
705
|
+
return ptyStartFailure(sized.error, input.cwd);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return {
|
|
709
|
+
success: true,
|
|
710
|
+
terminalId: input.terminalId,
|
|
711
|
+
cwd: input.cwd,
|
|
712
|
+
error: null,
|
|
713
|
+
tty: started.session.tty,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
534
716
|
authorizePath(request, rawPath, action, id, allowMissing = false) {
|
|
535
717
|
const path = cleanPath(rawPath);
|
|
536
718
|
if (!path)
|
|
@@ -835,6 +1017,20 @@ function terminalStartFailure(error) {
|
|
|
835
1017
|
function ptyStartFailure(error, cwd = null) {
|
|
836
1018
|
return { success: false, terminalId: null, cwd, error };
|
|
837
1019
|
}
|
|
1020
|
+
function sandboxPtyHandle(session) {
|
|
1021
|
+
return {
|
|
1022
|
+
get tty() { return session.tty; },
|
|
1023
|
+
write: (data) => { void session.write(Buffer.from(data, 'utf8')); },
|
|
1024
|
+
resize: (cols, rows) => {
|
|
1025
|
+
if (!session.tty) {
|
|
1026
|
+
return { ok: false, error: 'this runtime has no sandbox TTY (supports_exec_tty is false)' };
|
|
1027
|
+
}
|
|
1028
|
+
void session.resize(cols, rows);
|
|
1029
|
+
return { ok: true };
|
|
1030
|
+
},
|
|
1031
|
+
kill: () => { void session.kill(); },
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
838
1034
|
function safeError(error, fallback) {
|
|
839
1035
|
return error instanceof Error && error.message.trim() ? error.message.slice(0, 1_000) : fallback;
|
|
840
1036
|
}
|