@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,176 @@
|
|
|
1
|
+
const VALID_SHAPES = new Set(["consensus", "debate", "panel"]);
|
|
2
|
+
const VALID_ACTIONS = new Set([
|
|
3
|
+
"merge",
|
|
4
|
+
"FIX_FIRST",
|
|
5
|
+
"close",
|
|
6
|
+
"defer",
|
|
7
|
+
"split",
|
|
8
|
+
]);
|
|
9
|
+
const DEFAULT_SHAPE = "consensus";
|
|
10
|
+
|
|
11
|
+
export const CONSENSUS_SHAPE_COMPLEXITY = Object.freeze({
|
|
12
|
+
consensus: 1.0,
|
|
13
|
+
debate: 1.2,
|
|
14
|
+
panel: 1.4,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const CONSENSUS_MARKDOWN_SECTIONS = Object.freeze({
|
|
18
|
+
consensus: Object.freeze([
|
|
19
|
+
"## 합의 결과: {topic}",
|
|
20
|
+
"### Consensus Score",
|
|
21
|
+
"### 합의 항목",
|
|
22
|
+
"### disputed items",
|
|
23
|
+
"### resolved items",
|
|
24
|
+
"### user decision needed",
|
|
25
|
+
"### meta judgment",
|
|
26
|
+
]),
|
|
27
|
+
debate: Object.freeze([
|
|
28
|
+
"## 토론 결과: {topic}",
|
|
29
|
+
"### 비교 대상",
|
|
30
|
+
"### 평가 기준",
|
|
31
|
+
"### 합의 사항",
|
|
32
|
+
"### 최종 추천",
|
|
33
|
+
"### 리스크 및 완화 방안",
|
|
34
|
+
"### meta judgment",
|
|
35
|
+
]),
|
|
36
|
+
panel: Object.freeze([
|
|
37
|
+
"## 전문가 패널 보고서: {topic}",
|
|
38
|
+
"### 패널 구성",
|
|
39
|
+
"### 패널 합의",
|
|
40
|
+
"### 소수 견해",
|
|
41
|
+
"### 핵심 추천",
|
|
42
|
+
"### 미해결 쟁점",
|
|
43
|
+
"### 다음 단계",
|
|
44
|
+
"### meta judgment",
|
|
45
|
+
]),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function toArray(value) {
|
|
49
|
+
if (Array.isArray(value)) return value.filter(Boolean);
|
|
50
|
+
if (value == null) return [];
|
|
51
|
+
return [value].filter(Boolean);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeShape(shape) {
|
|
55
|
+
return VALID_SHAPES.has(shape) ? shape : DEFAULT_SHAPE;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeRecommendedAction(action) {
|
|
59
|
+
return VALID_ACTIONS.has(action) ? action : "defer";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeStringList(items) {
|
|
63
|
+
return [
|
|
64
|
+
...new Set(
|
|
65
|
+
toArray(items)
|
|
66
|
+
.map((item) => String(item).trim())
|
|
67
|
+
.filter(Boolean),
|
|
68
|
+
),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeSeverityBucket(items) {
|
|
73
|
+
return toArray(items).map((item) =>
|
|
74
|
+
typeof item === "string" ? { id: item, summary: item } : { ...item },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildSeverityClassification({ p1 = [], p2 = [], p3 = [] } = {}) {
|
|
79
|
+
return {
|
|
80
|
+
p1: normalizeSeverityBucket(p1),
|
|
81
|
+
p2: normalizeSeverityBucket(p2),
|
|
82
|
+
p3: normalizeSeverityBucket(p3),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildConsensusVsDispute({ agreements = [], conflicts = [] } = {}) {
|
|
87
|
+
return {
|
|
88
|
+
agreements: toArray(agreements).map((item) =>
|
|
89
|
+
typeof item === "string" ? { summary: item } : { ...item },
|
|
90
|
+
),
|
|
91
|
+
conflicts: toArray(conflicts).map((item) =>
|
|
92
|
+
typeof item === "string"
|
|
93
|
+
? { summary: item, parties: [] }
|
|
94
|
+
: { parties: [], ...item },
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function inferStatus(participants) {
|
|
100
|
+
const statuses = participants.map((participant) => participant.status);
|
|
101
|
+
if (statuses.length === 0) return "needs_user_input";
|
|
102
|
+
if (statuses.every((status) => status === "success")) return "complete";
|
|
103
|
+
if (statuses.some((status) => status === "needs_user_input"))
|
|
104
|
+
return "needs_user_input";
|
|
105
|
+
return "partial";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function normalizeParticipants(participants = []) {
|
|
109
|
+
return toArray(participants).map((participant) => {
|
|
110
|
+
if (typeof participant === "string") {
|
|
111
|
+
return { name: participant, status: "success" };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
name: participant?.name ?? "unknown",
|
|
116
|
+
status: participant?.status ?? "success",
|
|
117
|
+
...participant,
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function buildMetaJudgment({
|
|
123
|
+
severity = {},
|
|
124
|
+
consensus = {},
|
|
125
|
+
recommendedAction = "defer",
|
|
126
|
+
followupIssues = [],
|
|
127
|
+
modeSpecificMeta = {},
|
|
128
|
+
} = {}) {
|
|
129
|
+
return {
|
|
130
|
+
severity_classification: buildSeverityClassification(severity),
|
|
131
|
+
consensus_vs_dispute: buildConsensusVsDispute(consensus),
|
|
132
|
+
recommended_action: normalizeRecommendedAction(recommendedAction),
|
|
133
|
+
followup_issues: normalizeStringList(followupIssues),
|
|
134
|
+
mode_specific_meta: { ...modeSpecificMeta },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function buildConsensusArtifactMeta({
|
|
139
|
+
shape = DEFAULT_SHAPE,
|
|
140
|
+
topic = "",
|
|
141
|
+
cliSet = "triad",
|
|
142
|
+
participants = [],
|
|
143
|
+
status,
|
|
144
|
+
} = {}) {
|
|
145
|
+
const normalizedParticipants = normalizeParticipants(participants);
|
|
146
|
+
return {
|
|
147
|
+
mode: "consensus",
|
|
148
|
+
shape: normalizeShape(shape),
|
|
149
|
+
topic,
|
|
150
|
+
cli_set: cliSet,
|
|
151
|
+
participants: normalizedParticipants,
|
|
152
|
+
status: status ?? inferStatus(normalizedParticipants),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function buildConsensusEnvelope({
|
|
157
|
+
shape = DEFAULT_SHAPE,
|
|
158
|
+
topic = "",
|
|
159
|
+
cliSet = "triad",
|
|
160
|
+
participants = [],
|
|
161
|
+
metaJudgment = {},
|
|
162
|
+
} = {}) {
|
|
163
|
+
return {
|
|
164
|
+
...buildConsensusArtifactMeta({ shape, topic, cliSet, participants }),
|
|
165
|
+
meta_judgment: buildMetaJudgment(metaJudgment),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getShapeContract(shape = DEFAULT_SHAPE) {
|
|
170
|
+
const normalizedShape = normalizeShape(shape);
|
|
171
|
+
return {
|
|
172
|
+
shape: normalizedShape,
|
|
173
|
+
complexity: CONSENSUS_SHAPE_COMPLEXITY[normalizedShape],
|
|
174
|
+
required_markdown_sections: CONSENSUS_MARKDOWN_SECTIONS[normalizedShape],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
const DASHBOARD_ANCHORS = new Set([
|
|
2
|
-
"window",
|
|
3
|
-
"tab",
|
|
4
|
-
]);
|
|
1
|
+
const DASHBOARD_ANCHORS = new Set(["window", "tab"]);
|
|
5
2
|
|
|
6
3
|
export function normalizeDashboardAnchor(value) {
|
|
7
|
-
const normalized = String(value ?? "")
|
|
4
|
+
const normalized = String(value ?? "")
|
|
5
|
+
.trim()
|
|
6
|
+
.toLowerCase();
|
|
8
7
|
if (!normalized) return "window";
|
|
9
8
|
return DASHBOARD_ANCHORS.has(normalized) ? normalized : "window";
|
|
10
9
|
}
|
|
@@ -12,7 +12,9 @@ const DASHBOARD_LAYOUTS = new Set([
|
|
|
12
12
|
]);
|
|
13
13
|
|
|
14
14
|
export function normalizeDashboardLayout(value, { allowAuto = true } = {}) {
|
|
15
|
-
const normalized = String(value ?? "")
|
|
15
|
+
const normalized = String(value ?? "")
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase();
|
|
16
18
|
if (!normalized) return "single";
|
|
17
19
|
if (normalized === "auto" && !allowAuto) return "single";
|
|
18
20
|
return DASHBOARD_LAYOUTS.has(normalized) ? normalized : "single";
|
|
@@ -1,28 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { psmuxExec } from "./psmux.mjs";
|
|
4
|
-
import {
|
|
5
|
-
detectMultiplexer,
|
|
6
|
-
focusWtPane,
|
|
7
|
-
hasWindowsTerminal,
|
|
8
|
-
resolveAttachCommand,
|
|
9
|
-
tmuxExec,
|
|
10
|
-
} from "./session.mjs";
|
|
11
|
-
|
|
12
|
-
function sanitizeWindowTitle(value, fallback = "triflux") {
|
|
13
|
-
const text = String(value || "").replace(/[\r\n]+/g, " ").trim();
|
|
14
|
-
return text || fallback;
|
|
15
|
-
}
|
|
1
|
+
import { createTerminalOpener } from "./terminal-opener.mjs";
|
|
16
2
|
|
|
17
3
|
function sanitizeSessionName(value) {
|
|
18
4
|
return String(value || "").replace(/[^a-zA-Z0-9_-]/g, "") || "tfx-session";
|
|
19
5
|
}
|
|
20
6
|
|
|
21
|
-
function sanitizeWorkingDirectory(value) {
|
|
22
|
-
const text = String(value || "").replace(/[\r\n\x00-\x1f]/g, "").trim();
|
|
23
|
-
return text || process.cwd();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
7
|
export function parseWorkerNumber(value) {
|
|
27
8
|
const text = String(value || "").trim();
|
|
28
9
|
const workerMatch = text.match(/^worker-(\d+)$/i);
|
|
@@ -32,121 +13,42 @@ export function parseWorkerNumber(value) {
|
|
|
32
13
|
return null;
|
|
33
14
|
}
|
|
34
15
|
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
return hasWtSession ? "split" : "window";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function spawnWindowsTerminal(spec, opts = {}) {
|
|
41
|
-
if (!hasWindowsTerminal()) return false;
|
|
42
|
-
|
|
43
|
-
const {
|
|
44
|
-
mode = "window",
|
|
45
|
-
title = "triflux",
|
|
46
|
-
cwd = process.cwd(),
|
|
47
|
-
split = { orientation: "H", size: 0.50 },
|
|
48
|
-
} = opts;
|
|
49
|
-
|
|
50
|
-
const safeTitle = sanitizeWindowTitle(title);
|
|
51
|
-
const safeCwd = sanitizeWorkingDirectory(cwd);
|
|
52
|
-
const orientation = split?.orientation === "V" ? "V" : "H";
|
|
53
|
-
const size = Number.isFinite(split?.size) ? Math.min(0.8, Math.max(0.2, split.size)) : 0.50;
|
|
54
|
-
const baseArgs = ["--profile", "triflux", "--title", safeTitle, "-d", safeCwd, "--", spec.command, ...spec.args];
|
|
55
|
-
const args = mode === "split"
|
|
56
|
-
? ["-w", "0", "sp", `-${orientation}`, "-s", String(size), ...baseArgs]
|
|
57
|
-
: mode === "tab"
|
|
58
|
-
? ["-w", "0", "nt", ...baseArgs]
|
|
59
|
-
: ["-w", "new", ...baseArgs];
|
|
60
|
-
|
|
61
|
-
const child = spawn("wt.exe", args, {
|
|
62
|
-
detached: true,
|
|
63
|
-
stdio: "ignore",
|
|
64
|
-
windowsHide: false,
|
|
65
|
-
});
|
|
66
|
-
child.unref();
|
|
67
|
-
return true;
|
|
16
|
+
function ignoreAsyncFailure(value) {
|
|
17
|
+
if (value && typeof value.then === "function") void value.catch(() => {});
|
|
68
18
|
}
|
|
69
19
|
|
|
70
|
-
export function
|
|
71
|
-
const {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return paneIndex != null && focusWtPane(paneIndex, { layout });
|
|
77
|
-
}
|
|
20
|
+
export async function openHeadlessDashboardTarget(sessionName, opts = {}) {
|
|
21
|
+
const { openAll = false, cwd = process.cwd(), title } = opts;
|
|
22
|
+
const safeSession = sanitizeSessionName(sessionName);
|
|
23
|
+
const workerNumber =
|
|
24
|
+
opts.workerNumber ??
|
|
25
|
+
(opts.worker == null ? null : parseWorkerNumber(opts.worker));
|
|
78
26
|
|
|
79
|
-
|
|
27
|
+
let opener;
|
|
80
28
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
29
|
+
const deps = opts._deps ?? {};
|
|
30
|
+
const openerFactory = deps.createTerminalOpener ?? createTerminalOpener;
|
|
31
|
+
opener = openerFactory(deps);
|
|
84
32
|
} catch {
|
|
85
|
-
return
|
|
33
|
+
return !openAll && workerNumber != null;
|
|
86
34
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function openHeadlessDashboardTarget(sessionName, opts = {}) {
|
|
90
|
-
const {
|
|
91
|
-
worker = null,
|
|
92
|
-
openAll = false,
|
|
93
|
-
cwd = process.cwd(),
|
|
94
|
-
title,
|
|
95
|
-
} = opts;
|
|
96
|
-
|
|
97
|
-
const safeSession = sanitizeSessionName(sessionName);
|
|
98
|
-
const workerNumber = worker == null ? null : parseWorkerNumber(worker);
|
|
99
35
|
|
|
100
36
|
// 선택 워커 → pane focus만 (새 창 열지 않음)
|
|
101
37
|
if (!openAll && workerNumber != null) {
|
|
102
38
|
try {
|
|
103
|
-
|
|
39
|
+
ignoreAsyncFailure(opener.focusPane(safeSession, workerNumber));
|
|
104
40
|
} catch {}
|
|
105
41
|
return true;
|
|
106
42
|
}
|
|
107
43
|
|
|
108
|
-
// 전체 열기 (Shift+Enter) → 새
|
|
109
|
-
return spawnWindowsTerminal(
|
|
110
|
-
{ command: "psmux", args: ["attach-session", "-t", safeSession] },
|
|
111
|
-
{
|
|
112
|
-
mode: decideDashboardOpenMode({ openAll }),
|
|
113
|
-
title: title || `▲ ${safeSession}`,
|
|
114
|
-
cwd,
|
|
115
|
-
},
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function openDashboardRuntimeTarget(runtime, opts = {}) {
|
|
120
|
-
const {
|
|
121
|
-
teammateMode = "",
|
|
122
|
-
sessionName = "",
|
|
123
|
-
targetPane = "",
|
|
124
|
-
layout = "1xN",
|
|
125
|
-
openAll = false,
|
|
126
|
-
cwd = process.cwd(),
|
|
127
|
-
title = "",
|
|
128
|
-
} = { ...runtime, ...opts };
|
|
129
|
-
|
|
130
|
-
if (teammateMode === "headless") {
|
|
131
|
-
return openHeadlessDashboardTarget(sessionName, {
|
|
132
|
-
worker: openAll ? null : targetPane,
|
|
133
|
-
openAll,
|
|
134
|
-
cwd,
|
|
135
|
-
title,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if ((teammateMode === "wt" || String(targetPane).startsWith("wt:")) && !openAll) {
|
|
140
|
-
return focusManagedPane(targetPane, { teammateMode: "wt", layout });
|
|
141
|
-
}
|
|
142
|
-
|
|
44
|
+
// 전체 열기 (Shift+Enter) → 새 창으로 세션 attach
|
|
143
45
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
mode: decideDashboardOpenMode({ openAll }),
|
|
147
|
-
title: title || `▲ ${sanitizeSessionName(sessionName)}`,
|
|
46
|
+
const opened = opener.openSession(safeSession, {
|
|
47
|
+
title: title || `▲ ${safeSession}`,
|
|
148
48
|
cwd,
|
|
49
|
+
profile: opts.profile ?? "triflux",
|
|
149
50
|
});
|
|
51
|
+
return await opened;
|
|
150
52
|
} catch {
|
|
151
53
|
return false;
|
|
152
54
|
}
|
package/hub/team/dashboard.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// node hub/team/dashboard.mjs --session <세션이름> [--interval 2]
|
|
7
7
|
// node hub/team/dashboard.mjs --team <팀이름> [--interval 2]
|
|
8
8
|
import { get } from "node:http";
|
|
9
|
-
import { AMBER,
|
|
9
|
+
import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET } from "./shared.mjs";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* HTTP GET JSON
|
|
@@ -19,11 +19,18 @@ function fetchJson(url) {
|
|
|
19
19
|
let data = "";
|
|
20
20
|
res.on("data", (chunk) => (data += chunk));
|
|
21
21
|
res.on("end", () => {
|
|
22
|
-
try {
|
|
22
|
+
try {
|
|
23
|
+
resolve(JSON.parse(data));
|
|
24
|
+
} catch {
|
|
25
|
+
resolve(null);
|
|
26
|
+
}
|
|
23
27
|
});
|
|
24
28
|
});
|
|
25
29
|
req.on("error", () => resolve(null));
|
|
26
|
-
req.on("timeout", () => {
|
|
30
|
+
req.on("timeout", () => {
|
|
31
|
+
req.destroy();
|
|
32
|
+
resolve(null);
|
|
33
|
+
});
|
|
27
34
|
});
|
|
28
35
|
}
|
|
29
36
|
|
|
@@ -77,10 +84,14 @@ function formatUptime(ms) {
|
|
|
77
84
|
*/
|
|
78
85
|
function statusIcon(status) {
|
|
79
86
|
switch (status) {
|
|
80
|
-
case "completed":
|
|
81
|
-
|
|
82
|
-
case "
|
|
83
|
-
|
|
87
|
+
case "completed":
|
|
88
|
+
return `${GREEN}✓${RESET}`;
|
|
89
|
+
case "in_progress":
|
|
90
|
+
return `${AMBER}●${RESET}`;
|
|
91
|
+
case "failed":
|
|
92
|
+
return `${RED}✗${RESET}`;
|
|
93
|
+
default:
|
|
94
|
+
return `${GRAY}○${RESET}`;
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
|
|
@@ -118,7 +129,12 @@ function buildMemberList(hubTasks, supervisorMembers, teamState) {
|
|
|
118
129
|
members.push({
|
|
119
130
|
name: m.name,
|
|
120
131
|
cli: m.cli || "",
|
|
121
|
-
status:
|
|
132
|
+
status:
|
|
133
|
+
m.status === "running"
|
|
134
|
+
? "in_progress"
|
|
135
|
+
: m.status === "exited"
|
|
136
|
+
? "completed"
|
|
137
|
+
: m.status,
|
|
122
138
|
subject: "",
|
|
123
139
|
preview: m.lastPreview || "",
|
|
124
140
|
});
|
|
@@ -128,7 +144,9 @@ function buildMemberList(hubTasks, supervisorMembers, teamState) {
|
|
|
128
144
|
|
|
129
145
|
// teamState 폴백 (하위 호환)
|
|
130
146
|
const panes = teamState?.panes || {};
|
|
131
|
-
for (const [, paneInfo] of Object.entries(panes).filter(
|
|
147
|
+
for (const [, paneInfo] of Object.entries(panes).filter(
|
|
148
|
+
([, v]) => v.role !== "dashboard" && v.role !== "lead",
|
|
149
|
+
)) {
|
|
132
150
|
members.push({
|
|
133
151
|
name: paneInfo.agentId || paneInfo.name || "?",
|
|
134
152
|
cli: paneInfo.cli || "",
|
|
@@ -162,27 +180,41 @@ export async function renderDashboard(sessionName, opts = {}) {
|
|
|
162
180
|
// 데이터 수집 (병렬)
|
|
163
181
|
const [hubStatus, taskListRes, supervisorRes] = await Promise.all([
|
|
164
182
|
fetchJson(`${hubUrl}/status`),
|
|
165
|
-
teamName
|
|
183
|
+
teamName
|
|
184
|
+
? fetchPost(`${hubUrl}/bridge/team/task-list`, { team_name: teamName })
|
|
185
|
+
: null,
|
|
166
186
|
supervisorUrl ? fetchJson(`${supervisorUrl}/status`) : null,
|
|
167
187
|
]);
|
|
168
188
|
|
|
169
189
|
const hubOnline = !!hubStatus;
|
|
170
|
-
const hubState = hubOnline
|
|
171
|
-
|
|
190
|
+
const hubState = hubOnline
|
|
191
|
+
? `${GREEN}● online${RESET}`
|
|
192
|
+
: `${RED}● offline${RESET}`;
|
|
193
|
+
const uptime = hubStatus?.hub?.uptime
|
|
194
|
+
? formatUptime(hubStatus.hub.uptime)
|
|
195
|
+
: "-";
|
|
172
196
|
const queueSize = hubStatus?.hub?.queue_depth ?? 0;
|
|
173
197
|
|
|
174
198
|
// Hub task 데이터
|
|
175
|
-
const hubTasks = taskListRes?.ok ?
|
|
176
|
-
const completedCount = hubTasks.filter(
|
|
199
|
+
const hubTasks = taskListRes?.ok ? taskListRes.data?.tasks || [] : [];
|
|
200
|
+
const completedCount = hubTasks.filter(
|
|
201
|
+
(t) => t.status === "completed",
|
|
202
|
+
).length;
|
|
177
203
|
const totalCount = hubTasks.length;
|
|
178
204
|
|
|
179
205
|
// Supervisor 멤버 데이터
|
|
180
|
-
const supervisorMembers = supervisorRes?.ok
|
|
206
|
+
const supervisorMembers = supervisorRes?.ok
|
|
207
|
+
? supervisorRes.data?.members || []
|
|
208
|
+
: [];
|
|
181
209
|
|
|
182
210
|
// 헤더
|
|
183
211
|
const progress = totalCount > 0 ? ` ${completedCount}/${totalCount}` : "";
|
|
184
|
-
console.log(
|
|
185
|
-
|
|
212
|
+
console.log(
|
|
213
|
+
`${AMBER}┌─ ${sessionName}${progress} ${GRAY}${"─".repeat(Math.max(0, W - sessionName.length - progress.length - 3))}${AMBER}┐${RESET}`,
|
|
214
|
+
);
|
|
215
|
+
console.log(
|
|
216
|
+
`${AMBER}│${RESET} Hub: ${hubState} Uptime: ${DIM}${uptime}${RESET} Queue: ${DIM}${queueSize}${RESET}`,
|
|
217
|
+
);
|
|
186
218
|
console.log(`${AMBER}│${RESET}`);
|
|
187
219
|
|
|
188
220
|
// 멤버/워커 렌더링
|
|
@@ -194,20 +226,29 @@ export async function renderDashboard(sessionName, opts = {}) {
|
|
|
194
226
|
for (const m of members) {
|
|
195
227
|
const icon = statusIcon(m.status);
|
|
196
228
|
const label = `[${m.name}]`;
|
|
197
|
-
const cliTag = m.cli
|
|
229
|
+
const cliTag = m.cli
|
|
230
|
+
? m.cli.charAt(0).toUpperCase() + m.cli.slice(1)
|
|
231
|
+
: "";
|
|
198
232
|
|
|
199
233
|
// 진행률 추정
|
|
200
|
-
const pct =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
234
|
+
const pct =
|
|
235
|
+
m.status === "completed"
|
|
236
|
+
? 100
|
|
237
|
+
: m.status === "in_progress"
|
|
238
|
+
? 50
|
|
239
|
+
: m.status === "failed"
|
|
240
|
+
? 100
|
|
241
|
+
: 0;
|
|
204
242
|
|
|
205
|
-
console.log(
|
|
243
|
+
console.log(
|
|
244
|
+
`${AMBER}│${RESET} ${BOLD}${label}${RESET} ${cliTag} ${icon} ${m.status || "pending"} ${progressBar(pct)}`,
|
|
245
|
+
);
|
|
206
246
|
|
|
207
247
|
// 미리보기: supervisor lastPreview > task subject
|
|
208
248
|
const preview = m.preview || m.subject || "";
|
|
209
249
|
if (preview) {
|
|
210
|
-
const truncated =
|
|
250
|
+
const truncated =
|
|
251
|
+
preview.length > W - 8 ? preview.slice(0, W - 11) + "..." : preview;
|
|
211
252
|
console.log(`${AMBER}│${RESET} ${DIM}> ${truncated}${RESET}`);
|
|
212
253
|
}
|
|
213
254
|
console.log(`${AMBER}│${RESET}`);
|
|
@@ -228,10 +269,12 @@ async function loadTeamState() {
|
|
|
228
269
|
const sessionId = process.env.CLAUDE_SESSION_ID;
|
|
229
270
|
if (sessionId) {
|
|
230
271
|
const sessionPath = join(hubDir, `team-state-${sessionId}.json`);
|
|
231
|
-
if (existsSync(sessionPath))
|
|
272
|
+
if (existsSync(sessionPath))
|
|
273
|
+
return JSON.parse(readFileSync(sessionPath, "utf8"));
|
|
232
274
|
}
|
|
233
275
|
const legacyPath = join(hubDir, "team-state.json");
|
|
234
|
-
if (existsSync(legacyPath))
|
|
276
|
+
if (existsSync(legacyPath))
|
|
277
|
+
return JSON.parse(readFileSync(legacyPath, "utf8"));
|
|
235
278
|
return {};
|
|
236
279
|
} catch {
|
|
237
280
|
return {};
|
|
@@ -244,11 +287,16 @@ if (process.argv[1]?.includes("dashboard.mjs")) {
|
|
|
244
287
|
const teamIdx = process.argv.indexOf("--team");
|
|
245
288
|
const sessionName = sessionIdx !== -1 ? process.argv[sessionIdx + 1] : null;
|
|
246
289
|
const teamName = teamIdx !== -1 ? process.argv[teamIdx + 1] : null;
|
|
247
|
-
const intervalSec = parseInt(
|
|
290
|
+
const intervalSec = parseInt(
|
|
291
|
+
process.argv[process.argv.indexOf("--interval") + 1] || "2",
|
|
292
|
+
10,
|
|
293
|
+
);
|
|
248
294
|
|
|
249
295
|
const displayName = sessionName || teamName;
|
|
250
296
|
if (!displayName) {
|
|
251
|
-
console.error(
|
|
297
|
+
console.error(
|
|
298
|
+
"사용법: node dashboard.mjs --session <세션이름> [--team <팀이름>] [--interval 2]",
|
|
299
|
+
);
|
|
252
300
|
process.exit(1);
|
|
253
301
|
}
|
|
254
302
|
|
package/hub/team/event-log.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Conductor 세션 lifecycle 이벤트를 JSONL 파일에 기록한다.
|
|
3
3
|
// 기존 hub/server.mjs의 batch-events.jsonl(MCP 이벤트)과 독립. 공존.
|
|
4
4
|
|
|
5
|
-
import { createWriteStream, mkdirSync } from
|
|
6
|
-
import { dirname } from
|
|
5
|
+
import { createWriteStream, mkdirSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* JSONL event log 팩토리.
|
|
@@ -16,7 +16,7 @@ export function createEventLog(filePath, opts = {}) {
|
|
|
16
16
|
const { sessionId } = opts;
|
|
17
17
|
|
|
18
18
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
19
|
-
const stream = createWriteStream(filePath, { flags:
|
|
19
|
+
const stream = createWriteStream(filePath, { flags: "a" });
|
|
20
20
|
|
|
21
21
|
let closed = false;
|
|
22
22
|
let pending = 0;
|
|
@@ -35,7 +35,9 @@ export function createEventLog(filePath, opts = {}) {
|
|
|
35
35
|
...data,
|
|
36
36
|
};
|
|
37
37
|
pending += 1;
|
|
38
|
-
stream.write(JSON.stringify(entry) +
|
|
38
|
+
stream.write(JSON.stringify(entry) + "\n", () => {
|
|
39
|
+
pending -= 1;
|
|
40
|
+
});
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -45,9 +47,9 @@ export function createEventLog(filePath, opts = {}) {
|
|
|
45
47
|
function flush() {
|
|
46
48
|
if (closed) return Promise.resolve();
|
|
47
49
|
return new Promise((resolve, reject) => {
|
|
48
|
-
stream.once(
|
|
49
|
-
stream.write(
|
|
50
|
-
stream.removeListener(
|
|
50
|
+
stream.once("error", reject);
|
|
51
|
+
stream.write("", () => {
|
|
52
|
+
stream.removeListener("error", reject);
|
|
51
53
|
resolve();
|
|
52
54
|
});
|
|
53
55
|
});
|
|
@@ -69,8 +71,14 @@ export function createEventLog(filePath, opts = {}) {
|
|
|
69
71
|
append,
|
|
70
72
|
flush,
|
|
71
73
|
close,
|
|
72
|
-
get filePath() {
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
get filePath() {
|
|
75
|
+
return filePath;
|
|
76
|
+
},
|
|
77
|
+
get pending() {
|
|
78
|
+
return pending;
|
|
79
|
+
},
|
|
80
|
+
get closed() {
|
|
81
|
+
return closed;
|
|
82
|
+
},
|
|
75
83
|
});
|
|
76
84
|
}
|