better-dsh 0.3.2-e → 0.3.3-b
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/docs/remote-tools.md +6 -6
- package/lib/fs-aware/sandbox-plugin.js +3 -3
- package/lib/index.d.ts +16 -12
- package/lib/index.js +19 -25
- package/lib/remote/plugin.d.ts +93 -20
- package/lib/remote/plugin.js +921 -452
- package/lib/web-password.js +1 -1
- package/package.json +76 -60
package/docs/remote-tools.md
CHANGED
|
@@ -13,10 +13,10 @@ Better Dash and Super Dash each provide the native `remote` tool and REPL route.
|
|
|
13
13
|
| Register an Agent-local SSH identity (no dialing) | `remote {action:"add",target:"ssh:build",ssh:{host:"host",user:"user",key:"/origin/key"}}`; omit `target` for an auto id (`ssh-xxxx`) |
|
|
14
14
|
| Register a BYO PTY from a spawn command | `remote {action:"add",spawn:"docker exec -it img bash",target:"pty:build"}`; then address it like any session |
|
|
15
15
|
| Stateless command and stdin over an identity | `remote {action:"exec",target:"ssh:build",cmd:"cat",stdin:"hello"}` |
|
|
16
|
-
|
|
|
17
|
-
|
|
|
16
|
+
| Run a local script as one `bash -c` command (stdin = the script's own input) | `remote {action:"exec",target:"ssh:alias",scriptPath:"/absolute/script.sh",stdin:"payload\n"}` |
|
|
17
|
+
| Use a machine's persistent shell (default PTY) | `remote {action:"exec",target:"ssh:alias",cmd:"cd /tmp; export CHECK=ready"}` — consecutive execs reuse one shell (cwd/env persist) |
|
|
18
18
|
| Reuse a persistent shell | `remote {action:"exec",target:"pty:<id\|label>",cmd:"…"}` |
|
|
19
|
-
| Answer a prompt / feed a running command (password, OTP, token, choice, REPL line, `\u0003`/`\u0004`) | `remote {action:"
|
|
19
|
+
| Answer a prompt / feed a running command (password, OTP, token, choice, REPL line, `\u0003`/`\u0004`) | `remote {action:"exec",target:"pty:<id\|label>",stdin:"the-answer\n"}` (stdin-only raw write) — optional `commandId` guards against stale prompts |
|
|
20
20
|
| Close a PTY session | `remote {action:"close",target:"pty:<id\|label>",force:true}` |
|
|
21
21
|
| Native CLI copy | `remote_transfer {mode:"copy",src:"/absolute/source",dst:"ssh:alias:/absolute/destination",argv:["-p"]}` |
|
|
22
22
|
| Native rsync | `remote_transfer {mode:"rsync",src:"/absolute/source/",dst:"ssh:alias:/existing/destination/",argv:["-a","--partial","--dry-run"]}` |
|
|
@@ -29,16 +29,16 @@ Interactive flow — see a prompt, answer it, read the response:
|
|
|
29
29
|
|
|
30
30
|
1. Start the program: `exec` with `cmd` (foreground, `run_in_background`, or after a timeout handoff — identity is preserved either way). The running receipt carries the output so far plus `session` / `commandId` / `jobId`.
|
|
31
31
|
2. Watch for the prompt in that output; use `job_output <jobId>` when you need more.
|
|
32
|
-
3. Send the answer: `{action:"
|
|
32
|
+
3. Send the answer: `{action:"exec", target:"pty:<id|label>", stdin:"the-answer\n"}` (stdin alone = raw write; the `input` action is gone). `stdin` passes through verbatim — include the trailing `\n` to submit a line; `\u0003` is Ctrl-C and `\u0004` is Ctrl-D as raw characters; a literal backslash-x-0-4 stays four characters; `""` writes zero bytes (not EOF). The optional `commandId` must equal the running command's id; a stale id refuses the write instead of feeding the next command.
|
|
33
33
|
4. Read the response with `job_output` on the SAME job. The input receipt only confirms N bytes were written — it never means the password was accepted or the login succeeded; judge from the program's output and the job's exit.
|
|
34
34
|
|
|
35
35
|
Honest limits of the input lane: Ctrl-D is not a universal EOF (on a line without a trailing newline it first delivers that text — send it again on the emptied line to end a reader like `cat`); Ctrl-C only interrupts where the terminal mode allows; `job_kill` remains the explicit cancel; `input` never creates, dials, reconnects or replays — address a live session as `pty:<id|label>`, any transport. A command waiting for input IS the busy state — a new `cmd` gets `[E_SESSION_BUSY]`; feed the running command instead of queueing a second one. On a oneshot, `stdin` is startup-only: known data may ride the initial `exec`, but a oneshot cannot be fed after it starts and its timeout still cancels the whole group — a prompt there means cancel and switch to PTY. Heredocs and input redirection remain valid bulk-input alternatives; there is no global stdin ban. Cancelling is reliable (0.3.2-d): `job_kill` writes its status marker after the terminal's ^C input flush, so even a cancel that follows a bare Ctrl-C settles `killed · exit 130` with the session alive — and hub-lane control bytes cross the local raw half as bytes, interpreted by the peer pty, so ^C/^D behave like every other transport.
|
|
36
36
|
|
|
37
37
|
Use the stateless oneshot when a step is genuinely one-shot (a probe, a pipeline stage, a supervised build you will not revisit) or when the `runner` supervision guarantee matters. On dumb-pipe transports the supervisor is the pinned `superd-run` helper (or its POSIX wrapper) — receipts say which ran. On the Hub lane the peer IS our own server: supervision is native there (`bash -lc` in a detached group, TERM→2s→KILL, output rings, lease lifecycle) and receipts say `hub-lease`; the helper is a dumb-pipe artifact and is never uploaded over the Hub. Note that `exec` always executes something: a warm session is created by running a command (`true` is enough), never by omitting `cmd`.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
A machine target defaults to its reusable PTY; `mode:"oneshot"` selects the isolated stateless lane. The old `:pty` creation suffix and the `input` action are gone — multiple addressable shells come from `handoff {target:<machine>}` (detaches the default as `pty:<id>`), and prompt input is `exec` with `stdin` alone. `add` only registers (BYO identity or BYO PTY) and never runs a command. `scriptPath` is an alternative to `cmd` and may carry `stdin` (startup input rides after the script bytes) in both lanes. Direct local command execution is not provided; use native local shell tools. Background jobs require the official jobs service/controller and are read/killed with `job_output`/`job_kill`; on PTY lanes a background job's jobId is the commandId (a timeout handoff keeps them distinct until adoption backfills the job). SSH keys/config stay on the origin; BYO identities are not persisted across new/cold Agents. PTY backlog recovery and live probes are not part of this release.
|
|
40
40
|
|
|
41
|
-
PTY state is durable and readable: every dispatch returns exit, cwd and `envDelta` (variable names and change markers only — values never leave the endpoint; fetch them yourself if needed). `close` is idempotent (`closed:false, reason:"not-found"|"already-closed-or-reaped"`); a session with a running job refuses close unless `force:true`, which first interrupts the running command — its job settles in the killed family (`interrupted: exit 130`, or `session closed` when the frame cannot arrive) — and then kills the group. A busy session rejects new commands instead of queueing (`[E_SESSION_BUSY]` names the running id and the ways out:
|
|
41
|
+
PTY state is durable and readable: every dispatch returns exit, cwd and `envDelta` (variable names and change markers only — values never leave the endpoint; fetch them yourself if needed). `close` is idempotent (`closed:false, reason:"not-found"|"already-closed-or-reaped"`); a session with a running job refuses close unless `force:true`, which first interrupts the running command — its job settles in the killed family (`interrupted: exit 130`, or `session closed` when the frame cannot arrive) — and then kills the group. A busy session rejects new commands instead of queueing (`[E_SESSION_BUSY]` names the running id and the ways out: exec stdin-only, `job_output`, `job_kill`, handoff, or another session/oneshot); open another PTY or use a oneshot for parallel work. Idle PTYs are reaped after 15 minutes; an Agent holds at most 30 live sessions across connections (defaults and handed-off shells included; dead sessions do not count; the limit is disclosed only when reached). `hub:<m4>` as a machine target carries the same default-PTY semantics over the Hub WebSocket lane — identity, labels, stdin-only writes, parallelism and background work exactly like ssh/docker/incus/BYO.
|
|
42
42
|
|
|
43
43
|
Output returns IN FULL up to retention (0.3.2-e: the self-made 30K returned-tail window is removed — model-context sizing is the native agent runtime's job, e.g. the DSH spill policy or a foreign runtime's own guard). Native/PTY retains at most 4,000,000 UTF-16 units per stream; Hub oneshot 4 MiB bytes. Beyond retention the returned text is the retained tail, headed by a one-line disclosure banner, with `outputStats` giving total/retained/dropped/returned and `truncated` set to the original total. Read `outputStats` units and dropped/returned counters rather than treating character counts as bytes. Discarded output is not recoverable after restart.
|
|
44
44
|
|
|
@@ -87,9 +87,9 @@ async function isPathUnder(path$1, root, caseSensitive = process.platform !== "w
|
|
|
87
87
|
*
|
|
88
88
|
* Per-call policy: `read-only` denies every mutation; `workspace-write` allows
|
|
89
89
|
* a mutation only when the target canonicalizes under the policy's workspace
|
|
90
|
-
* root or a platform temp area from the
|
|
91
|
-
* `danger-full-access` delegates unfenced. A
|
|
92
|
-
* `FS_SANDBOX_DENIED`.
|
|
90
|
+
* root, an explicit additional writable root, or a platform temp area from the
|
|
91
|
+
* shared `writableRoots` policy; `danger-full-access` delegates unfenced. A
|
|
92
|
+
* denial throws the structured `FS_SANDBOX_DENIED`.
|
|
93
93
|
*
|
|
94
94
|
* @module @deepseek-ai/dsh-fs-sandbox
|
|
95
95
|
*/
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as Schema } from "./index2.js";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import { ToolDefinition, ToolExecutionInput, ToolRunContext, ToolRuntime } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
import { ToolExecutionPipeline } from "@deepseek-ai/dsh-tools/pipeline";
|
|
4
5
|
import { ContentBlock, HarnessError } from "@deepseek-ai/dsh-llm";
|
|
5
6
|
import { Scoped } from "@deepseek-ai/dsh-scope";
|
|
6
7
|
import { Agent } from "@deepseek-ai/dsh-agent";
|
|
@@ -622,10 +623,10 @@ declare module '@deepseek-ai/cordis' {
|
|
|
622
623
|
}
|
|
623
624
|
/**
|
|
624
625
|
* Capabilities the `eval` bridge closes over, mirroring upstream's
|
|
625
|
-
* `RunCodeBridgeOptions` (the `requireRuntime` idiom):
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
* the registry does not mint this tool for us.
|
|
626
|
+
* `RunCodeBridgeOptions` (the `requireRuntime` idiom): sub-dispatch rides a
|
|
627
|
+
* plugin-built `ToolExecutionPipeline` (0.2.2) rather than the pre-0.2.2
|
|
628
|
+
* registry-private staged scheduler, because — unlike upstream's PTC
|
|
629
|
+
* executor — the registry does not mint this tool for us.
|
|
629
630
|
*/
|
|
630
631
|
interface RunCellBridgeOptions {
|
|
631
632
|
/** Resolves `ctx.replRuntime` or throws the loud misconfiguration error (use-time read). */
|
|
@@ -649,17 +650,20 @@ interface RunCellBridgeOptions {
|
|
|
649
650
|
/**
|
|
650
651
|
* Build the `eval` {@link ToolDefinition}: required `cell` and
|
|
651
652
|
* `description` parameters, executed through the dispatch bridge described
|
|
652
|
-
* in the module doc. Sub-calls ride the
|
|
653
|
-
* (`prepare`/`dispatch`/`
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
653
|
+
* in the module doc. Sub-calls ride the plugin-built staged pipeline
|
|
654
|
+
* (`prepare`/`dispatch`/`complete` — the 0.2.2 `ToolExecutionPipeline`, the
|
|
655
|
+
* sanctioned successor of the retired `TOOL_RUNTIME_SCHEDULER` symbol) under
|
|
656
|
+
* the native concurrency contract; each sub-dispatch is logged for
|
|
657
|
+
* reconstruction (`tool/ptc-dispatch-start` / `tool/ptc-dispatch`) while only
|
|
658
|
+
* the outer curated result enters model history.
|
|
659
|
+
* @param registry - the host tool registry (bindings cover its registered
|
|
660
|
+
* tools; schema projection reads it live).
|
|
661
|
+
* @param pipeline - the staged invocation pipeline built over the same
|
|
662
|
+
* registry's mounting context (sub-calls dispatch through it).
|
|
659
663
|
* @param options - the bridge capabilities described above.
|
|
660
664
|
* @returns the registry-ready definition.
|
|
661
665
|
*/
|
|
662
|
-
declare function createRunCellTool(registry: ToolRuntime, options: RunCellBridgeOptions): ToolDefinition;
|
|
666
|
+
declare function createRunCellTool(registry: ToolRuntime, pipeline: ToolExecutionPipeline, options: RunCellBridgeOptions): ToolDefinition;
|
|
663
667
|
declare function apply(ctx: Context, config: Config): void;
|
|
664
668
|
declare const _default: {
|
|
665
669
|
name: string;
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { t as Schema } from "./lib.js";
|
|
|
2
2
|
import { a as RESERVED_ERROR_MEMBERS, i as RESERVED_BINDING_GLOBALS, n as DUNDER_MEMBER, o as ReplRuntime, r as PORTABLE_RESERVED_WORDS, t as isFlatBindableName } from "./py-sdk2.js";
|
|
3
3
|
import { n as resolveKernelEnv } from "./kernel-env2.js";
|
|
4
4
|
import { t as captureAllTools } from "./native-capture.js";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
5
|
import { Context } from "@deepseek-ai/cordis";
|
|
7
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
7
|
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
@@ -11,7 +10,8 @@ import { spawn } from "node:child_process";
|
|
|
11
10
|
import { createServer } from "node:http";
|
|
12
11
|
import { createInterface } from "node:readline";
|
|
13
12
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import {
|
|
13
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
14
|
+
import { ToolExecutionPipeline } from "@deepseek-ai/dsh-tools/pipeline";
|
|
15
15
|
import * as dshLlm from "@deepseek-ai/dsh-llm";
|
|
16
16
|
import { BlockAssembler, HarnessError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
17
17
|
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
@@ -2165,17 +2165,20 @@ function renderValue(value) {
|
|
|
2165
2165
|
/**
|
|
2166
2166
|
* Build the `eval` {@link ToolDefinition}: required `cell` and
|
|
2167
2167
|
* `description` parameters, executed through the dispatch bridge described
|
|
2168
|
-
* in the module doc. Sub-calls ride the
|
|
2169
|
-
* (`prepare`/`dispatch`/`
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2174
|
-
*
|
|
2168
|
+
* in the module doc. Sub-calls ride the plugin-built staged pipeline
|
|
2169
|
+
* (`prepare`/`dispatch`/`complete` — the 0.2.2 `ToolExecutionPipeline`, the
|
|
2170
|
+
* sanctioned successor of the retired `TOOL_RUNTIME_SCHEDULER` symbol) under
|
|
2171
|
+
* the native concurrency contract; each sub-dispatch is logged for
|
|
2172
|
+
* reconstruction (`tool/ptc-dispatch-start` / `tool/ptc-dispatch`) while only
|
|
2173
|
+
* the outer curated result enters model history.
|
|
2174
|
+
* @param registry - the host tool registry (bindings cover its registered
|
|
2175
|
+
* tools; schema projection reads it live).
|
|
2176
|
+
* @param pipeline - the staged invocation pipeline built over the same
|
|
2177
|
+
* registry's mounting context (sub-calls dispatch through it).
|
|
2175
2178
|
* @param options - the bridge capabilities described above.
|
|
2176
2179
|
* @returns the registry-ready definition.
|
|
2177
2180
|
*/
|
|
2178
|
-
function createRunCellTool(registry, options) {
|
|
2181
|
+
function createRunCellTool(registry, pipeline, options) {
|
|
2179
2182
|
const { requireRuntime, maxParallel, shapeDispatchLog, warn } = options;
|
|
2180
2183
|
return defineTool({
|
|
2181
2184
|
name: EVAL_NAME,
|
|
@@ -2320,16 +2323,6 @@ function createRunCellTool(registry, options) {
|
|
|
2320
2323
|
parent: exec.token,
|
|
2321
2324
|
signal: runController.signal
|
|
2322
2325
|
};
|
|
2323
|
-
const scheduler = registry[TOOL_RUNTIME_SCHEDULER];
|
|
2324
|
-
if (scheduler === void 0) {
|
|
2325
|
-
let resolvedFrom;
|
|
2326
|
-
try {
|
|
2327
|
-
resolvedFrom = createRequire(import.meta.url).resolve("@deepseek-ai/dsh-tools");
|
|
2328
|
-
} catch {
|
|
2329
|
-
resolvedFrom = "(unresolvable from the plugin position)";
|
|
2330
|
-
}
|
|
2331
|
-
throw new Error(`${EVAL_NAME}: the tools service mounted by this composition lacks the TOOL_RUNTIME_SCHEDULER symbol as this plugin sees it — REPL sub-dispatch cannot run. Most likely a dual-copy dsh-tools: the symbol keying the mounted ToolRuntime instance is not the symbol this plugin imported (named methods like executionMode keep working across copies; only the symbol-keyed field goes missing). This plugin resolves @deepseek-ai/dsh-tools from: ${resolvedFrom}. Check the deployed plugin's node_modules for stray @deepseek-ai entries (only schemastery and cosmokit belong there)`);
|
|
2332
|
-
}
|
|
2333
2326
|
const outcome = await new Promise((resolve$1, reject) => {
|
|
2334
2327
|
let parked;
|
|
2335
2328
|
const settle = (result) => {
|
|
@@ -2375,7 +2368,7 @@ function createRunCellTool(registry, options) {
|
|
|
2375
2368
|
pendingQueue.push({
|
|
2376
2369
|
flight: Promise.resolve(),
|
|
2377
2370
|
settled: false,
|
|
2378
|
-
classify: () =>
|
|
2371
|
+
classify: () => pipeline.executionMode(input).kind,
|
|
2379
2372
|
abandon: () => {
|
|
2380
2373
|
reject(/* @__PURE__ */ new Error(`${EVAL_NAME} run is over (${String(runController.signal.reason)}); ${name$1} tool call abandoned`));
|
|
2381
2374
|
},
|
|
@@ -2393,9 +2386,9 @@ function createRunCellTool(registry, options) {
|
|
|
2393
2386
|
arguments: normalized.logged
|
|
2394
2387
|
});
|
|
2395
2388
|
try {
|
|
2396
|
-
const prepared = await
|
|
2389
|
+
const prepared = await pipeline.prepare(input);
|
|
2397
2390
|
if (prepared.kind === "dispatch") {
|
|
2398
|
-
this.flight =
|
|
2391
|
+
this.flight = pipeline.dispatch(prepared.exec).then((dispatchOutcome) => {
|
|
2399
2392
|
parked = {
|
|
2400
2393
|
kind: dispatchOutcome.kind,
|
|
2401
2394
|
exec: prepared.exec,
|
|
@@ -2422,7 +2415,7 @@ function createRunCellTool(registry, options) {
|
|
|
2422
2415
|
async commit() {
|
|
2423
2416
|
if (parked === void 0) return;
|
|
2424
2417
|
try {
|
|
2425
|
-
const result =
|
|
2418
|
+
const result = await pipeline.complete(parked.exec, parked);
|
|
2426
2419
|
for (const context of result.additionalContexts ?? []) exec.deferContext(context);
|
|
2427
2420
|
if (result.concludesTurn) exec.concludeTurn();
|
|
2428
2421
|
settle(result);
|
|
@@ -2552,7 +2545,8 @@ function apply(ctx, config) {
|
|
|
2552
2545
|
}
|
|
2553
2546
|
return runtimeCtx.get("workflowEngine");
|
|
2554
2547
|
};
|
|
2555
|
-
runtimeCtx
|
|
2548
|
+
const pipeline = new ToolExecutionPipeline(runtimeCtx);
|
|
2549
|
+
runtimeCtx.tools.register(createRunCellTool(registry, pipeline, {
|
|
2556
2550
|
requireRuntime,
|
|
2557
2551
|
maxParallel,
|
|
2558
2552
|
shapeDispatchLog,
|
package/lib/remote/plugin.d.ts
CHANGED
|
@@ -148,9 +148,13 @@ interface ProcessOptions {
|
|
|
148
148
|
onOutput?: () => void;
|
|
149
149
|
/** Overlay on process.env (tool-built values only, e.g. RSYNC_RSH). */
|
|
150
150
|
env?: NodeJS.ProcessEnv;
|
|
151
|
+
/** Task E(SSH MUX):取消(timeout 或显式 cancel)即同步回调,不等 close。mux master 会经
|
|
152
|
+
* fd-passing 持有被杀客户端的会话 fd 直到远端会话结束——若远端清理 killer 等 close 后才发射,
|
|
153
|
+
* 就成了「close 等远端结束、远端结束等 killer」死锁。此处先行启动 killer 才能解开 close。至多一次。 */
|
|
154
|
+
onCancel?: () => void;
|
|
151
155
|
}
|
|
152
156
|
/** Real detached process. Completion includes group cleanup, even after its leader closes. */
|
|
153
|
-
declare function startBinaryProcess(argv: string[], options?: Pick<ProcessOptions, 'cwd' | 'timeoutSec' | 'env'>): {
|
|
157
|
+
declare function startBinaryProcess(argv: string[], options?: Pick<ProcessOptions, 'cwd' | 'timeoutSec' | 'env' | 'onCancel'>): {
|
|
154
158
|
stdout: Readable;
|
|
155
159
|
stderr: Readable;
|
|
156
160
|
stdin: Writable;
|
|
@@ -257,15 +261,6 @@ interface PtyDispatchResult {
|
|
|
257
261
|
running?: boolean;
|
|
258
262
|
job?: PtyRunningJob;
|
|
259
263
|
}
|
|
260
|
-
/** 账本条目(§5):commandId / 摘要 / exit / 耗时;保留最近 20 条(user 定案)。 */
|
|
261
|
-
interface LedgerEntry {
|
|
262
|
-
commandId: string;
|
|
263
|
-
jobId?: string;
|
|
264
|
-
summary: string;
|
|
265
|
-
exit: number | null;
|
|
266
|
-
durationMs: number;
|
|
267
|
-
running: boolean;
|
|
268
|
-
}
|
|
269
264
|
type PtyState = 'cold' | 'starting' | 'ready' | 'dead' | 'disposed';
|
|
270
265
|
/** 会话运行时快照(status 面消费,Ruling 16 词汇源)。 */
|
|
271
266
|
interface SessionSnapshot {
|
|
@@ -284,12 +279,11 @@ declare class PtySession {
|
|
|
284
279
|
private deathWaiters;
|
|
285
280
|
private diag;
|
|
286
281
|
private inFlight;
|
|
282
|
+
private cmdSeq;
|
|
287
283
|
/** P4/P5:当前占用前台的 detached job(background/超时 handoff)。非 null 时新命令拒绝(Q6)。 */
|
|
288
284
|
private detachedJob;
|
|
289
285
|
/** §3.2(2026-10-25):当前占用会话的命令——dispatch 同步占位,settle/cleanup 释放;input 的唯一写入对象。 */
|
|
290
286
|
private activeCommand;
|
|
291
|
-
/** 账本(§5):本会话最近 20 条命令(FIFO,超出丢最旧)。 */
|
|
292
|
-
private ledger;
|
|
293
287
|
/** env 侧信道基线(init E 帧);undefined = 侧信道不可用(不报假 delta)。 */
|
|
294
288
|
private envBaseline;
|
|
295
289
|
/** idle 起点(ready / settle 时重置);null = 尚无 idle 时钟(cold 或已 dispose)。 */
|
|
@@ -310,7 +304,12 @@ declare class PtySession {
|
|
|
310
304
|
get lastCwd(): string | null;
|
|
311
305
|
/** 占用前台的 detached job 的 commandId(无 = null;close 拒绝与 busy 文案共用)。 */
|
|
312
306
|
get detachedCommandId(): string | null;
|
|
313
|
-
/**
|
|
307
|
+
/** handoff/card 面(2026-11-01 默认 PTY 轮):当前占用会话的命令与可选 job 句柄;无 = null。 */
|
|
308
|
+
get active(): {
|
|
309
|
+
commandId: string;
|
|
310
|
+
jobId?: string;
|
|
311
|
+
} | null;
|
|
312
|
+
/** job 通道收养后回填 jobId(timeout handoff 时 registry id ≠ commandId);运行中的命令同步携带(stdin-only 写入回执用)。 */
|
|
314
313
|
attachJob(commandId: string, jobId: string): void;
|
|
315
314
|
/** Workbench 身份(§3):id + 可选 label + connection + byo 标记。 */
|
|
316
315
|
get identity(): {
|
|
@@ -320,8 +319,6 @@ declare class PtySession {
|
|
|
320
319
|
byo: boolean;
|
|
321
320
|
builtOn: boolean;
|
|
322
321
|
};
|
|
323
|
-
/** 账本快照(session card 面,只读)。 */
|
|
324
|
-
ledgerSnapshot(): readonly LedgerEntry[];
|
|
325
322
|
/** spawn 创建原文(仅 BYO 会话;v2 起仅存档,不参与复用判定)。 */
|
|
326
323
|
get origin(): string | undefined;
|
|
327
324
|
/** status 面快照:state + busy(有命令在跑)+ idleMs(上次 settle 至今)+ pid(进程组头,kill 兜底可观测)。 */
|
|
@@ -520,6 +517,41 @@ interface RsyncReceipt extends RunResult {
|
|
|
520
517
|
*/
|
|
521
518
|
declare function runRsync(request: RsyncRequest, source: RsyncEndpoint, destination: RsyncEndpoint, signal: AbortSignal, options?: RelayRsyncOptions): Promise<RsyncReceipt>;
|
|
522
519
|
//#endregion
|
|
520
|
+
//#region src/remote/ssh-mux.d.ts
|
|
521
|
+
interface SshMuxIdentity {
|
|
522
|
+
/** 原始 target 串(如 `ssh:dev4` / `dev4`)——同一 effective 身份不同拼法也不串 socket。 */
|
|
523
|
+
target: string;
|
|
524
|
+
connection: SshConnection;
|
|
525
|
+
}
|
|
526
|
+
interface SshMuxOptions {
|
|
527
|
+
persistSec?: number;
|
|
528
|
+
/** socket 目录根(默认 os.tmpdir());测试注入口。 */
|
|
529
|
+
rootDir?: string;
|
|
530
|
+
runner?: (argv: string[], opts?: {
|
|
531
|
+
timeoutSec?: number;
|
|
532
|
+
}) => Promise<{
|
|
533
|
+
exit: number | null;
|
|
534
|
+
}>;
|
|
535
|
+
}
|
|
536
|
+
/** 每 Agent 一个(driver 私有):tool-owned 0700 socket 目录 + 按连接身份的 socket 名。
|
|
537
|
+
* 目录名含随机 owner 后缀,跨 Agent 天然不共享;dispose 逐 socket `-O exit` 后整目录删除。 */
|
|
538
|
+
declare class SshMux {
|
|
539
|
+
private readonly persistSec;
|
|
540
|
+
private readonly rootDir;
|
|
541
|
+
private readonly runner;
|
|
542
|
+
private readonly owner;
|
|
543
|
+
private readonly sockets;
|
|
544
|
+
private dir;
|
|
545
|
+
private disposal;
|
|
546
|
+
constructor(options?: SshMuxOptions);
|
|
547
|
+
/** 注入 SSH argv 的 MUX 选项(trusted internal argv,非模型输入)。目录懒建(纯 docker/hub
|
|
548
|
+
* Agent 永不触盘);超限/已 dispose 返回 [] = 诚实退化为普通连接。 */
|
|
549
|
+
argvFor(identity: SshMuxIdentity): string[];
|
|
550
|
+
/** Teardown:先 `-O exit` 逐 socket 礼貌拆除 master(不拨号、5s 上限、错误全吞——
|
|
551
|
+
* master 已死/残 socket 属正常),再删目录。单飞(重复调用同一 Promise)。 */
|
|
552
|
+
dispose(): Promise<void>;
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
523
555
|
//#region src/remote/oneshot.d.ts
|
|
524
556
|
interface OneShotOptions {
|
|
525
557
|
stdin?: string;
|
|
@@ -539,6 +571,8 @@ interface ProbeOptions {
|
|
|
539
571
|
connectTimeoutSec?: number;
|
|
540
572
|
hardTimeoutSec?: number;
|
|
541
573
|
runner?: (argv: string[], opts?: OneShotOptions) => Promise<OneShotResult>;
|
|
574
|
+
/** Task E:SSH 探测与 oneshot 执行链共用 MUX(同一身份 → 同一 master,探测零额外握手)。 */
|
|
575
|
+
muxArgvFor?: (plan: TargetPlan) => readonly string[];
|
|
542
576
|
}
|
|
543
577
|
//#endregion
|
|
544
578
|
//#region src/remote/roster.d.ts
|
|
@@ -603,6 +637,7 @@ interface RemoteDriverOptions {
|
|
|
603
637
|
onAudit?: (r: RemoteAuditRecord) => void;
|
|
604
638
|
oneshotArgvFor?: (plan: TargetPlan, cmd: string, options: {
|
|
605
639
|
cleanupTag?: string;
|
|
640
|
+
muxArgv?: readonly string[];
|
|
606
641
|
}) => string[];
|
|
607
642
|
ptyArgvFor?: (plan: TargetPlan) => string[];
|
|
608
643
|
spawnArgvFor?: (spawnCommand: string) => string[];
|
|
@@ -611,6 +646,8 @@ interface RemoteDriverOptions {
|
|
|
611
646
|
probeRunner?: ProbeOptions['runner'];
|
|
612
647
|
/** 测试/验收缝:helper 资产来源(默认 lib 旁挂 helper-bin);显式 null 强制 shim 路径。 */
|
|
613
648
|
helperAssetsFor?: () => HelperAssets | null;
|
|
649
|
+
/** 测试/验收缝:SSH oneshot MUX 状态(默认每 driver 一个 SshMux = 每 Agent 隔离)。 */
|
|
650
|
+
sshMux?: SshMux;
|
|
614
651
|
/** 测试缝:roster 扫描执行器(裸名解析复用)。 */
|
|
615
652
|
rosterRunner?: RosterOptions['runner'];
|
|
616
653
|
/** 测试缝:ssh config 读取(裸名解析复用)。 */
|
|
@@ -626,6 +663,10 @@ declare function hubPtyAssets(): {
|
|
|
626
663
|
declare class RemoteDriver {
|
|
627
664
|
private readonly opts;
|
|
628
665
|
private readonly pool;
|
|
666
|
+
/** 默认 PTY 槽(spec remote-default-pty §1):sessionKey → 归一 machineKey → pool key。
|
|
667
|
+
* 会话本体是普通 PtySession(id 创建即认领,roster 全可见);槽登记在 dispose/TTL 后悬空,
|
|
668
|
+
* 取槽时经 pool.get(disposed 视同出池)惰性清理。 */
|
|
669
|
+
private readonly defaultSlots;
|
|
629
670
|
private readonly labels;
|
|
630
671
|
private readonly hubOwner;
|
|
631
672
|
private readonly hubProviders;
|
|
@@ -638,6 +679,7 @@ declare class RemoteDriver {
|
|
|
638
679
|
private readonly runnerOf;
|
|
639
680
|
private disposal;
|
|
640
681
|
private readonly ptyIdleTtlSec;
|
|
682
|
+
private readonly mux;
|
|
641
683
|
constructor(opts?: RemoteDriverOptions);
|
|
642
684
|
call(params: RemoteCallParams, callCtx?: {
|
|
643
685
|
sessionKey?: string;
|
|
@@ -676,6 +718,10 @@ declare class RemoteDriver {
|
|
|
676
718
|
private planOf;
|
|
677
719
|
/** ssh:<name> 的池感知解析:BYO 身份 → 其 connection;否则按 config 别名/主机名。 */
|
|
678
720
|
private sshPlanFor;
|
|
721
|
+
/** Task E MUX 身份:仅 SSH plan 注入(docker/incus/hub 返回 undefined)。身份键 = 原始
|
|
722
|
+
* target 串 + 有效连接字段(host/user/port/key/ProxyJump)——同身份共享 master,跨身份
|
|
723
|
+
* 不串 socket;目录/寿命/拆除全部由 SshMux 自持。 */
|
|
724
|
+
private muxArgvFor;
|
|
679
725
|
/** BYO ssh 注册(v2:无 spec 对比——label 占用即拒;注册成功才认领)。返回实际名(label 或 s-id)。 */
|
|
680
726
|
private registerByoSsh;
|
|
681
727
|
/** PTY 会话创建(native/spawn 同一形状):上限 → label 认领 → 惰性会话(失败由 onDead 释放 label)。 */
|
|
@@ -683,8 +729,36 @@ declare class RemoteDriver {
|
|
|
683
729
|
private ttlMinutes;
|
|
684
730
|
/** connection 由 BYO ssh 身份建立 → card 面标 `(built on)`(与 roster 同词,R2 复核 P2-10)。 */
|
|
685
731
|
private isBuiltOn;
|
|
686
|
-
/** PTY 创建形唯一实现(execute 与 background 预建共用):spawn BYO
|
|
732
|
+
/** PTY 创建形唯一实现(execute 与 background 预建共用):spawn BYO(spec 2026-11-01:native `:pty`/hub-pty 创建语法已删,machine 会话经默认槽 + handoff)。 */
|
|
687
733
|
private createPtyLane;
|
|
734
|
+
/** 池条目的槽键归一:同一实体(host/container/machine 身份)跨裸名/前缀/撞名前缀共享同一默认槽。 */
|
|
735
|
+
private machineKeyOfEntry;
|
|
736
|
+
/** 默认槽的 machine 规格:归一 key + 创建 argv + 展示 connection(hub 每次经 ptyEndpoint 重验 pairing,P10)。 */
|
|
737
|
+
private machineSlotSpec;
|
|
738
|
+
/** 取/懒建 machine 的默认 PTY 槽:dead/disposed 槽惰性重建;id 创建即认领(roster 全可见 + 防撞)。 */
|
|
739
|
+
private defaultSlot;
|
|
740
|
+
private releaseDefaultSlot;
|
|
741
|
+
/** 按池键反查并解绑默认槽(background 提交即 handoff 用);非槽会话无操作。 */
|
|
742
|
+
private releaseSlotByPoolKey;
|
|
743
|
+
/** roster/status 面:本 session 的默认槽标注(session id → machineKey;悬空条目过滤)。 */
|
|
744
|
+
private defaultMarks;
|
|
745
|
+
/** spec §1 handoff(2026-11-01 二/三轮裁决):把 machine 默认 PTY 解绑成可寻址 pty:<id>——同引用移动
|
|
746
|
+
* (不重启、不复制输出流);fresh(尚无默认槽)= 后台创建空闲 shell 再隔离(默认 PTY 思维模型恒
|
|
747
|
+
* ready,创建时机是实现细节);busy = 附当前命令与 job 句柄。machine 下次 exec 按需新建默认 PTY。 */
|
|
748
|
+
handoff(target: string, callCtx?: {
|
|
749
|
+
sessionKey?: string;
|
|
750
|
+
}): Promise<RemoteCallResult>;
|
|
751
|
+
/** close 统一入口(spec §1 + 三轮补充裁决):machine target 只关默认槽;pty selector 关该会话;
|
|
752
|
+
* 不存在 job 定位——取消运行中命令的唯一入口是官方 job_kill。 */
|
|
753
|
+
close(target: string, sessionKey: string, force?: boolean): Promise<{
|
|
754
|
+
session: string;
|
|
755
|
+
connection: string;
|
|
756
|
+
closed: boolean;
|
|
757
|
+
reason?: string;
|
|
758
|
+
killed: string[];
|
|
759
|
+
message?: string;
|
|
760
|
+
}>;
|
|
761
|
+
private closeDefault;
|
|
688
762
|
/** background PTY lane:会话先占位(不拨号)再进官方 jobs registry——首回执必须能寻址;
|
|
689
763
|
* registry 拒绝时把新建的会话拆掉(复用形的会话绝不拆)。返回 undefined = 走普通 oneshot job。 */
|
|
690
764
|
preparePtyLane(params: RemoteCallParams, callCtx?: {
|
|
@@ -709,10 +783,9 @@ declare class RemoteDriver {
|
|
|
709
783
|
};
|
|
710
784
|
/** job 通道收养回填(timeout handoff 时 registry id ≠ commandId,账本记录两者)。 */
|
|
711
785
|
attachPtyJob(commandId: string, jobId: string, sessionKey: string): void;
|
|
712
|
-
/** §
|
|
713
|
-
* 追加 stdin
|
|
714
|
-
*
|
|
715
|
-
* 一律明确拒绝并给出可恢复指引。回执附 connection/label 供工具层渲染。 */
|
|
786
|
+
/** spec remote-default-pty §1(2026-11-01):exec stdin-only 写入核心——向已有 live PTY 的当前运行命令
|
|
787
|
+
* 追加 exact-byte stdin。寻址:pty:<id|label> / 裸 label 命中 pty 项 / machine target(其默认槽的
|
|
788
|
+
* 当前命令,无槽不懒建、如实报错)。不触发拨号、创建或重连;回执附 connection/label 供工具层渲染。 */
|
|
716
789
|
input(target: string, options: PtyInputOptions, callCtx?: {
|
|
717
790
|
sessionKey?: string;
|
|
718
791
|
}): Promise<PtyInputReceipt & {
|