@triflux/remote 10.1.0 → 10.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hub/pipe.mjs +239 -121
- package/hub/public/dashboard.html +61 -0
- package/hub/server.mjs +2165 -708
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +26 -7
- package/hub/team/ansi.mjs +245 -146
- package/hub/team/backend.mjs +84 -17
- package/hub/team/build-worker-prompt.mjs +42 -0
- package/hub/team/check-mcp-hub.mjs +49 -0
- package/hub/team/claude-daemon-control.mjs +219 -0
- package/hub/team/claude-native-bridge.mjs +784 -0
- package/hub/team/claude-session-projection.mjs +65 -0
- package/hub/team/cli/commands/attach.mjs +57 -37
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +95 -74
- package/hub/team/cli/commands/focus.mjs +80 -53
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +31 -24
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -33
- package/hub/team/cli/commands/start/parse-args.mjs +89 -12
- package/hub/team/cli/commands/start/start-headless.mjs +125 -37
- package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
- package/hub/team/cli/commands/start/start-mux.mjs +122 -73
- package/hub/team/cli/commands/start/start-wt.mjs +86 -69
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +13 -13
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +52 -30
- package/hub/team/cli/services/attach-fallback.mjs +74 -54
- package/hub/team/cli/services/hub-client.mjs +44 -16
- package/hub/team/cli/services/member-selector.mjs +37 -30
- package/hub/team/cli/services/native-control.mjs +144 -116
- package/hub/team/cli/services/runtime-mode.mjs +55 -1
- package/hub/team/cli/services/state-store.mjs +66 -9
- package/hub/team/cli/services/task-model.mjs +40 -30
- package/hub/team/codex-review.mjs +521 -0
- package/hub/team/conductor-mesh-bridge.mjs +25 -24
- package/hub/team/conductor-registry.mjs +121 -0
- package/hub/team/conductor.mjs +1379 -671
- package/hub/team/consensus-meta.mjs +176 -0
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +20 -118
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/execution-mode.mjs +241 -0
- package/hub/team/extract-completion-payload.mjs +110 -0
- package/hub/team/git-preflight.mjs +286 -0
- package/hub/team/handoff.mjs +49 -18
- package/hub/team/headless-bridge-session.mjs +17 -0
- package/hub/team/headless.mjs +2065 -1156
- package/hub/team/health-probe.mjs +232 -69
- package/hub/team/launcher-template.mjs +23 -13
- package/hub/team/lead-control.mjs +91 -4
- package/hub/team/mcp-selector.mjs +145 -0
- package/hub/team/native-supervisor.mjs +90 -26
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +147 -78
- package/hub/team/orchestrator.mjs +173 -161
- package/hub/team/pane.mjs +43 -15
- package/hub/team/process-cleanup.mjs +69 -25
- package/hub/team/psmux.mjs +1020 -279
- package/hub/team/recovery-store.mjs +149 -0
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +131 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/retry-state-machine.mjs +267 -0
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +200 -0
- package/hub/team/schemas/worker-completion.json +39 -0
- package/hub/team/sentinel-capture.mjs +127 -0
- package/hub/team/session-sync.mjs +153 -6
- package/hub/team/session.mjs +605 -611
- package/hub/team/shared.mjs +13 -13
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-cli.mjs +390 -0
- package/hub/team/swarm-hypervisor.mjs +1522 -193
- package/hub/team/swarm-intent.mjs +216 -0
- package/hub/team/swarm-locks.mjs +140 -25
- package/hub/team/swarm-planner.mjs +121 -21
- package/hub/team/swarm-preflight.mjs +317 -0
- package/hub/team/swarm-reconciler.mjs +128 -23
- package/hub/team/synapse-cli.mjs +458 -0
- package/hub/team/synapse-http.mjs +60 -0
- package/hub/team/synapse-registry.mjs +329 -0
- package/hub/team/terminal-opener.mjs +216 -0
- package/hub/team/tui-core.mjs +288 -0
- package/hub/team/tui-lite.mjs +234 -146
- package/hub/team/tui-remote-adapter.mjs +18 -34
- package/hub/team/tui-synapse.mjs +223 -0
- package/hub/team/tui-viewer.mjs +104 -44
- package/hub/team/tui-widgets.mjs +262 -0
- package/hub/team/tui.mjs +927 -437
- package/hub/team/worker-completion-validator.mjs +84 -0
- package/hub/team/worker-sandbox.mjs +93 -0
- package/hub/team/worker-signal.mjs +263 -0
- package/hub/team/worker-signal.types.d.ts +52 -0
- package/hub/team/worktree-lifecycle.mjs +517 -63
- package/hub/team/wt-manager.mjs +297 -26
- package/hub/tools.mjs +474 -252
- package/hub/tray.mjs +411 -368
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-app-server-worker.mjs +1015 -0
- package/hub/workers/codex-mcp.mjs +724 -507
- package/hub/workers/delegator-mcp.mjs +662 -335
- package/hub/workers/factory.mjs +100 -21
- package/hub/workers/gemini-worker.mjs +179 -86
- package/hub/workers/interface.mjs +23 -2
- package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
- package/hub/workers/worker-utils.mjs +22 -16
- package/package.json +4 -3
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +223 -0
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +67 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +67 -0
- package/scripts/lib/cross-review-utils.mjs +59 -0
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +269 -0
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +119 -0
- package/scripts/lib/handoff.mjs +187 -0
- package/scripts/lib/hook-utils.mjs +16 -0
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +195 -0
- package/scripts/lib/logger.mjs +105 -0
- package/scripts/lib/mcp-filter.mjs +883 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-guard-engine.mjs +2120 -0
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +89 -0
- package/scripts/lib/mcp-server-catalog.mjs +355 -0
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +179 -0
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +226 -0
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +375 -0
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// hub/team/recovery-store.mjs — Best-effort preservation of worker changes
|
|
2
|
+
// before a dirty worktree is removed. Uses file-based `.patch` + JSON manifest
|
|
3
|
+
// instead of git stash (git-preflight flags stash-pop/worktree-remove as risky).
|
|
4
|
+
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_RECOVERY_DIR = ".codex-swarm/recovery";
|
|
10
|
+
|
|
11
|
+
function git(args, cwd) {
|
|
12
|
+
return new Promise((res) => {
|
|
13
|
+
execFile(
|
|
14
|
+
"git",
|
|
15
|
+
args,
|
|
16
|
+
{ cwd, windowsHide: true, timeout: 30_000, maxBuffer: 64 * 1024 * 1024 },
|
|
17
|
+
(err, stdout, stderr) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
res({ ok: false, stderr: stderr?.toString() || err.message });
|
|
20
|
+
} else {
|
|
21
|
+
res({ ok: true, stdout });
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function manifestPath(recoveryDir) {
|
|
29
|
+
return join(recoveryDir, "manifest.json");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readManifestFile(recoveryDir) {
|
|
33
|
+
const path = manifestPath(recoveryDir);
|
|
34
|
+
if (!existsSync(path)) return { entries: [] };
|
|
35
|
+
try {
|
|
36
|
+
const raw = readFileSync(path, "utf8");
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
if (parsed && Array.isArray(parsed.entries)) return parsed;
|
|
39
|
+
return { entries: [] };
|
|
40
|
+
} catch {
|
|
41
|
+
return { entries: [] };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeManifestFile(recoveryDir, data) {
|
|
46
|
+
writeFileSync(manifestPath(recoveryDir), JSON.stringify(data, null, 2));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Preserve an uncommitted worker patch before the worktree is removed.
|
|
51
|
+
*
|
|
52
|
+
* Contract:
|
|
53
|
+
* - Never throws.
|
|
54
|
+
* - Dirty worktree → write {recoveryDir}/{shardId}.patch, append manifest entry.
|
|
55
|
+
* - Clean worktree → return {ok: true, skipped: true}, no files written.
|
|
56
|
+
* - Missing/invalid → return {ok: false, reason}, no throw.
|
|
57
|
+
*
|
|
58
|
+
* Patch captures `git diff HEAD` output (unified diff) — this covers both
|
|
59
|
+
* staged and unstaged changes relative to the shard's base commit.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} opts
|
|
62
|
+
* @param {string} opts.worktreePath — absolute path to the worker worktree
|
|
63
|
+
* @param {string} opts.shardId — shard identifier (used for filename)
|
|
64
|
+
* @param {string} [opts.recoveryDir] — default: `<worktreePath>/.codex-swarm/recovery`
|
|
65
|
+
* when running inside worktree root;
|
|
66
|
+
* callers typically pass repo-root relative path.
|
|
67
|
+
* @returns {Promise<
|
|
68
|
+
* | { ok: true, patchPath: string, manifestPath: string }
|
|
69
|
+
* | { ok: true, skipped: true }
|
|
70
|
+
* | { ok: false, reason: string }
|
|
71
|
+
* >}
|
|
72
|
+
*/
|
|
73
|
+
export async function preserveWorktreePatch(opts = {}) {
|
|
74
|
+
const { worktreePath, shardId } = opts;
|
|
75
|
+
|
|
76
|
+
if (!worktreePath || typeof worktreePath !== "string") {
|
|
77
|
+
return { ok: false, reason: "missing_worktree_path" };
|
|
78
|
+
}
|
|
79
|
+
if (!shardId || typeof shardId !== "string") {
|
|
80
|
+
return { ok: false, reason: "missing_shard_id" };
|
|
81
|
+
}
|
|
82
|
+
if (!existsSync(worktreePath)) {
|
|
83
|
+
return { ok: false, reason: "worktree_path_not_found" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const recoveryDir = opts.recoveryDir
|
|
87
|
+
? resolve(opts.recoveryDir)
|
|
88
|
+
: resolve(worktreePath, DEFAULT_RECOVERY_DIR);
|
|
89
|
+
|
|
90
|
+
// Quickly classify dirty vs clean. `git status --porcelain` is safe and fast.
|
|
91
|
+
const status = await git(["status", "--porcelain"], worktreePath);
|
|
92
|
+
if (!status.ok) {
|
|
93
|
+
return { ok: false, reason: `git_status_failed: ${status.stderr}` };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (status.stdout.trim().length === 0) {
|
|
97
|
+
return { ok: true, skipped: true };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Dirty — capture diff.
|
|
101
|
+
const diff = await git(["diff", "HEAD"], worktreePath);
|
|
102
|
+
if (!diff.ok) {
|
|
103
|
+
return { ok: false, reason: `git_diff_failed: ${diff.stderr}` };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
mkdirSync(recoveryDir, { recursive: true });
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return { ok: false, reason: `mkdir_failed: ${err.message}` };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const safeShardId = shardId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
113
|
+
const patchPath = join(recoveryDir, `${safeShardId}.patch`);
|
|
114
|
+
try {
|
|
115
|
+
writeFileSync(patchPath, diff.stdout);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return { ok: false, reason: `patch_write_failed: ${err.message}` };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const manifest = readManifestFile(recoveryDir);
|
|
121
|
+
manifest.entries.push({
|
|
122
|
+
shard: shardId,
|
|
123
|
+
patch: patchPath,
|
|
124
|
+
timestamp: new Date().toISOString(),
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
writeManifestFile(recoveryDir, manifest);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return { ok: false, reason: `manifest_write_failed: ${err.message}` };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { ok: true, patchPath, manifestPath: manifestPath(recoveryDir) };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Read the recovery manifest. Returns {entries: []} if missing or invalid.
|
|
137
|
+
* @param {string} recoveryDir
|
|
138
|
+
* @returns {{ entries: Array<{ shard: string, patch: string, timestamp: string }> }}
|
|
139
|
+
*/
|
|
140
|
+
export function readManifest(recoveryDir) {
|
|
141
|
+
if (!recoveryDir || typeof recoveryDir !== "string") {
|
|
142
|
+
return { entries: [] };
|
|
143
|
+
}
|
|
144
|
+
return readManifestFile(resolve(recoveryDir));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const RECOVERY_DEFAULTS = Object.freeze({
|
|
148
|
+
dir: DEFAULT_RECOVERY_DIR,
|
|
149
|
+
});
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// L1.5: INPUT_WAIT 패턴 감지 (detectInputWait 재사용)
|
|
8
8
|
// L3: 완료 토큰 감지 (__TRIFLUX_DONE__ 또는 프롬프트 idle)
|
|
9
9
|
|
|
10
|
-
import { execFileSync } from
|
|
11
|
-
import { detectInputWait, PROBE_DEFAULTS } from
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
import { detectInputWait, PROBE_DEFAULTS } from "./health-probe.mjs";
|
|
12
12
|
|
|
13
13
|
/** 완료 토큰 패턴 */
|
|
14
14
|
const COMPLETION_TOKEN_RE = /__TRIFLUX_DONE__/;
|
|
@@ -27,19 +27,25 @@ const _PROMPT_IDLE_RE = /(\u276f|\u2795|>\s*$)/;
|
|
|
27
27
|
export function sshCapturePane(host, paneTarget, lines = 20, deps = {}) {
|
|
28
28
|
const execFn = deps.execFileSync || execFileSync;
|
|
29
29
|
try {
|
|
30
|
-
const output = execFn(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
const output = execFn(
|
|
31
|
+
"ssh",
|
|
32
|
+
[
|
|
33
|
+
"-o",
|
|
34
|
+
"ConnectTimeout=5",
|
|
35
|
+
"-o",
|
|
36
|
+
"BatchMode=yes",
|
|
37
|
+
host,
|
|
38
|
+
`psmux capture-pane -t ${paneTarget} -p -S -`,
|
|
39
|
+
],
|
|
40
|
+
{
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
timeout: 10_000,
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
const nonEmpty = output.split("\n").filter((line) => line.trim() !== "");
|
|
48
|
+
return nonEmpty.slice(-lines).join("\n");
|
|
43
49
|
} catch {
|
|
44
50
|
return null;
|
|
45
51
|
}
|
|
@@ -55,16 +61,22 @@ export function sshCapturePane(host, paneTarget, lines = 20, deps = {}) {
|
|
|
55
61
|
export function sshSessionExists(host, sessionName, deps = {}) {
|
|
56
62
|
const execFn = deps.execFileSync || execFileSync;
|
|
57
63
|
try {
|
|
58
|
-
execFn(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
execFn(
|
|
65
|
+
"ssh",
|
|
66
|
+
[
|
|
67
|
+
"-o",
|
|
68
|
+
"ConnectTimeout=5",
|
|
69
|
+
"-o",
|
|
70
|
+
"BatchMode=yes",
|
|
71
|
+
host,
|
|
72
|
+
`psmux has-session -t ${sessionName}`,
|
|
73
|
+
],
|
|
74
|
+
{
|
|
75
|
+
timeout: 10_000,
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
78
|
+
},
|
|
79
|
+
);
|
|
68
80
|
return true;
|
|
69
81
|
} catch {
|
|
70
82
|
return false;
|
|
@@ -92,7 +104,7 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
92
104
|
let started = false;
|
|
93
105
|
|
|
94
106
|
// L1 tracking — 출력 변화 감지
|
|
95
|
-
let lastCaptureHash =
|
|
107
|
+
let lastCaptureHash = "";
|
|
96
108
|
let lastOutputChangeAt = Date.now();
|
|
97
109
|
|
|
98
110
|
// L3 tracking — 완료 토큰 / prompt idle
|
|
@@ -102,7 +114,7 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
102
114
|
const status = {
|
|
103
115
|
l0: null,
|
|
104
116
|
l1: null,
|
|
105
|
-
l2:
|
|
117
|
+
l2: "skip", // 원격은 MCP L2 미지원
|
|
106
118
|
l3: null,
|
|
107
119
|
lastProbeAt: null,
|
|
108
120
|
inputWaitPattern: null,
|
|
@@ -113,7 +125,7 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
113
125
|
*/
|
|
114
126
|
function probeL0() {
|
|
115
127
|
const exists = sshSessionExists(session.host, session.sessionName, deps);
|
|
116
|
-
status.l0 = exists ?
|
|
128
|
+
status.l0 = exists ? "ok" : "fail";
|
|
117
129
|
return status.l0;
|
|
118
130
|
}
|
|
119
131
|
|
|
@@ -135,9 +147,9 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
135
147
|
if (hash !== lastCaptureHash) {
|
|
136
148
|
lastCaptureHash = hash;
|
|
137
149
|
lastOutputChangeAt = now;
|
|
138
|
-
status.l1 =
|
|
150
|
+
status.l1 = "ok";
|
|
139
151
|
status.inputWaitPattern = null;
|
|
140
|
-
return
|
|
152
|
+
return "ok";
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
const silenceMs = now - lastOutputChangeAt;
|
|
@@ -147,18 +159,18 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
147
159
|
const inputWait = detectInputWait(captured);
|
|
148
160
|
|
|
149
161
|
if (inputWait.detected) {
|
|
150
|
-
status.l1 =
|
|
162
|
+
status.l1 = "input_wait";
|
|
151
163
|
status.inputWaitPattern = inputWait.pattern;
|
|
152
|
-
return
|
|
164
|
+
return "input_wait";
|
|
153
165
|
}
|
|
154
166
|
|
|
155
|
-
status.l1 =
|
|
167
|
+
status.l1 = "stall";
|
|
156
168
|
status.inputWaitPattern = null;
|
|
157
|
-
return
|
|
169
|
+
return "stall";
|
|
158
170
|
}
|
|
159
171
|
|
|
160
|
-
status.l1 =
|
|
161
|
-
return
|
|
172
|
+
status.l1 = "ok";
|
|
173
|
+
return "ok";
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
/**
|
|
@@ -167,28 +179,28 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
167
179
|
*/
|
|
168
180
|
function probeL3(captured) {
|
|
169
181
|
if (promptAcked) {
|
|
170
|
-
status.l3 =
|
|
171
|
-
return
|
|
182
|
+
status.l3 = "ok";
|
|
183
|
+
return "ok";
|
|
172
184
|
}
|
|
173
185
|
|
|
174
186
|
if (captured != null && captured.length > 0) {
|
|
175
187
|
// 완료 토큰 감지 → 즉시 ok
|
|
176
188
|
if (COMPLETION_TOKEN_RE.test(captured)) {
|
|
177
189
|
promptAcked = true;
|
|
178
|
-
status.l3 =
|
|
179
|
-
return
|
|
190
|
+
status.l3 = "completed";
|
|
191
|
+
return "completed";
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
// 출력이 있으면 prompt acknowledged
|
|
183
195
|
promptAcked = true;
|
|
184
|
-
status.l3 =
|
|
185
|
-
return
|
|
196
|
+
status.l3 = "ok";
|
|
197
|
+
return "ok";
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
const elapsed = Date.now() - spawnedAt;
|
|
189
201
|
if (elapsed >= config.l3ThresholdMs) {
|
|
190
|
-
status.l3 =
|
|
191
|
-
return
|
|
202
|
+
status.l3 = "timeout";
|
|
203
|
+
return "timeout";
|
|
192
204
|
}
|
|
193
205
|
|
|
194
206
|
status.l3 = null;
|
|
@@ -204,7 +216,7 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
204
216
|
|
|
205
217
|
// L0 실패 시 나머지 probe 스킵
|
|
206
218
|
let captured = null;
|
|
207
|
-
if (l0 ===
|
|
219
|
+
if (l0 === "ok") {
|
|
208
220
|
captured = sshCapturePane(session.host, session.paneTarget, 20, deps);
|
|
209
221
|
}
|
|
210
222
|
|
|
@@ -214,14 +226,14 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
214
226
|
const result = {
|
|
215
227
|
l0,
|
|
216
228
|
l1,
|
|
217
|
-
l2:
|
|
229
|
+
l2: "skip",
|
|
218
230
|
l3,
|
|
219
231
|
inputWaitPattern: status.inputWaitPattern,
|
|
220
232
|
ts: Date.now(),
|
|
221
233
|
};
|
|
222
234
|
status.lastProbeAt = result.ts;
|
|
223
235
|
|
|
224
|
-
if (typeof config.onProbe ===
|
|
236
|
+
if (typeof config.onProbe === "function") {
|
|
225
237
|
config.onProbe(result);
|
|
226
238
|
}
|
|
227
239
|
|
|
@@ -233,10 +245,12 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
233
245
|
started = true;
|
|
234
246
|
spawnedAt = Date.now();
|
|
235
247
|
lastOutputChangeAt = Date.now();
|
|
236
|
-
lastCaptureHash =
|
|
248
|
+
lastCaptureHash = "";
|
|
237
249
|
promptAcked = false;
|
|
238
250
|
|
|
239
|
-
timer = setInterval(() => {
|
|
251
|
+
timer = setInterval(() => {
|
|
252
|
+
void probe();
|
|
253
|
+
}, config.intervalMs);
|
|
240
254
|
timer.unref?.();
|
|
241
255
|
|
|
242
256
|
// 즉시 첫 probe 실행
|
|
@@ -254,13 +268,13 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
254
268
|
|
|
255
269
|
/** tracking 리셋 (restart 후 호출) */
|
|
256
270
|
function resetTracking() {
|
|
257
|
-
lastCaptureHash =
|
|
271
|
+
lastCaptureHash = "";
|
|
258
272
|
lastOutputChangeAt = Date.now();
|
|
259
273
|
promptAcked = false;
|
|
260
274
|
spawnedAt = Date.now();
|
|
261
275
|
status.l0 = null;
|
|
262
276
|
status.l1 = null;
|
|
263
|
-
status.l2 =
|
|
277
|
+
status.l2 = "skip";
|
|
264
278
|
status.l3 = null;
|
|
265
279
|
status.inputWaitPattern = null;
|
|
266
280
|
}
|
|
@@ -271,6 +285,8 @@ export function createRemoteProbe(session, opts = {}) {
|
|
|
271
285
|
probe,
|
|
272
286
|
resetTracking,
|
|
273
287
|
getStatus: () => ({ ...status }),
|
|
274
|
-
get started() {
|
|
288
|
+
get started() {
|
|
289
|
+
return started;
|
|
290
|
+
},
|
|
275
291
|
});
|
|
276
292
|
}
|