triflux 10.3.4 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- 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 +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- 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 +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
package/hub/team/conductor.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// 3. Auto-restart (maxRestarts=3)
|
|
9
9
|
// 4. JSONL event log (블랙박스 리코더)
|
|
10
10
|
|
|
11
|
-
import { execFile, spawn } from "
|
|
11
|
+
import { execFile, spawn } from "../lib/spawn-trace.mjs";
|
|
12
12
|
import { EventEmitter } from "node:events";
|
|
13
13
|
import {
|
|
14
14
|
copyFileSync,
|
|
@@ -22,7 +22,10 @@ import { createRegistry } from "../../mesh/mesh-registry.mjs";
|
|
|
22
22
|
import { broker } from "../account-broker.mjs";
|
|
23
23
|
import { killProcess } from "../platform.mjs";
|
|
24
24
|
import { createConductorMeshBridge } from "./conductor-mesh-bridge.mjs";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
ensureConductorRegistry,
|
|
27
|
+
getConductorRegistry,
|
|
28
|
+
} from "./conductor-registry.mjs";
|
|
26
29
|
import { createEventLog } from "./event-log.mjs";
|
|
27
30
|
import { createHealthProbe } from "./health-probe.mjs";
|
|
28
31
|
import { buildLauncher } from "./launcher-template.mjs";
|
|
@@ -86,7 +89,7 @@ export function createConductor(opts = {}) {
|
|
|
86
89
|
const emitter = new EventEmitter();
|
|
87
90
|
const sessions = new Map();
|
|
88
91
|
let shuttingDown = false;
|
|
89
|
-
|
|
92
|
+
const publicApi = null;
|
|
90
93
|
|
|
91
94
|
// 공유 event log (모든 세션 이벤트를 하나의 JSONL에)
|
|
92
95
|
const eventLog = createEventLog(join(logsDir, "conductor-events.jsonl"));
|
|
@@ -180,7 +183,7 @@ export function createConductor(opts = {}) {
|
|
|
180
183
|
execFn(
|
|
181
184
|
"ssh",
|
|
182
185
|
[`${sshUser}@${sshIp}`, "psmux", "kill-session", "-t", session.id],
|
|
183
|
-
{ timeout: 10_000 },
|
|
186
|
+
{ timeout: 10_000, reason: "conductor:killRemoteSession" },
|
|
184
187
|
() => {},
|
|
185
188
|
);
|
|
186
189
|
eventLog.append("remote_kill", {
|
|
@@ -376,6 +379,7 @@ export function createConductor(opts = {}) {
|
|
|
376
379
|
child = spawn(launcher.command, {
|
|
377
380
|
shell: true,
|
|
378
381
|
env: { ...process.env, ...launcher.env, ...(session.config.env || {}) },
|
|
382
|
+
reason: `conductor:respawnSession:${session.id}`,
|
|
379
383
|
stdio: ["pipe", "pipe", "pipe"],
|
|
380
384
|
windowsHide: true,
|
|
381
385
|
});
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from "
|
|
1
|
+
import { spawn } from "../lib/spawn-trace.mjs";
|
|
2
2
|
|
|
3
3
|
import { psmuxExec } from "./psmux.mjs";
|
|
4
4
|
import {
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
} from "./session.mjs";
|
|
11
11
|
|
|
12
12
|
function sanitizeWindowTitle(value, fallback = "triflux") {
|
|
13
|
-
const text = String(value || "")
|
|
13
|
+
const text = String(value || "")
|
|
14
|
+
.replace(/[\r\n]+/g, " ")
|
|
15
|
+
.trim();
|
|
14
16
|
return text || fallback;
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -19,7 +21,9 @@ function sanitizeSessionName(value) {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function sanitizeWorkingDirectory(value) {
|
|
22
|
-
const text = String(value || "")
|
|
24
|
+
const text = String(value || "")
|
|
25
|
+
.replace(/[\r\n\x00-\x1f]/g, "")
|
|
26
|
+
.trim();
|
|
23
27
|
return text || process.cwd();
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -32,7 +36,10 @@ export function parseWorkerNumber(value) {
|
|
|
32
36
|
return null;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
export function decideDashboardOpenMode({
|
|
39
|
+
export function decideDashboardOpenMode({
|
|
40
|
+
openAll = false,
|
|
41
|
+
hasWtSession = !!process.env.WT_SESSION,
|
|
42
|
+
} = {}) {
|
|
36
43
|
if (openAll) return hasWtSession ? "tab" : "window";
|
|
37
44
|
return hasWtSession ? "split" : "window";
|
|
38
45
|
}
|
|
@@ -44,19 +51,32 @@ function spawnWindowsTerminal(spec, opts = {}) {
|
|
|
44
51
|
mode = "window",
|
|
45
52
|
title = "triflux",
|
|
46
53
|
cwd = process.cwd(),
|
|
47
|
-
split = { orientation: "H", size: 0.
|
|
54
|
+
split = { orientation: "H", size: 0.5 },
|
|
48
55
|
} = opts;
|
|
49
56
|
|
|
50
57
|
const safeTitle = sanitizeWindowTitle(title);
|
|
51
58
|
const safeCwd = sanitizeWorkingDirectory(cwd);
|
|
52
59
|
const orientation = split?.orientation === "V" ? "V" : "H";
|
|
53
|
-
const size = Number.isFinite(split?.size)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const size = Number.isFinite(split?.size)
|
|
61
|
+
? Math.min(0.8, Math.max(0.2, split.size))
|
|
62
|
+
: 0.5;
|
|
63
|
+
const baseArgs = [
|
|
64
|
+
"--profile",
|
|
65
|
+
"triflux",
|
|
66
|
+
"--title",
|
|
67
|
+
safeTitle,
|
|
68
|
+
"-d",
|
|
69
|
+
safeCwd,
|
|
70
|
+
"--",
|
|
71
|
+
spec.command,
|
|
72
|
+
...spec.args,
|
|
73
|
+
];
|
|
74
|
+
const args =
|
|
75
|
+
mode === "split"
|
|
76
|
+
? ["-w", "0", "sp", `-${orientation}`, "-s", String(size), ...baseArgs]
|
|
77
|
+
: mode === "tab"
|
|
78
|
+
? ["-w", "0", "nt", ...baseArgs]
|
|
79
|
+
: ["-w", "new", ...baseArgs];
|
|
60
80
|
|
|
61
81
|
const child = spawn("wt.exe", args, {
|
|
62
82
|
detached: true,
|
|
@@ -78,7 +98,8 @@ export function focusManagedPane(target, opts = {}) {
|
|
|
78
98
|
|
|
79
99
|
if (!paneRef) return false;
|
|
80
100
|
try {
|
|
81
|
-
if (detectMultiplexer() === "psmux")
|
|
101
|
+
if (detectMultiplexer() === "psmux")
|
|
102
|
+
psmuxExec(["select-pane", "-t", paneRef]);
|
|
82
103
|
else tmuxExec(`select-pane -t ${paneRef}`);
|
|
83
104
|
return true;
|
|
84
105
|
} catch {
|
|
@@ -87,12 +108,7 @@ export function focusManagedPane(target, opts = {}) {
|
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
export function openHeadlessDashboardTarget(sessionName, opts = {}) {
|
|
90
|
-
const {
|
|
91
|
-
worker = null,
|
|
92
|
-
openAll = false,
|
|
93
|
-
cwd = process.cwd(),
|
|
94
|
-
title,
|
|
95
|
-
} = opts;
|
|
111
|
+
const { worker = null, openAll = false, cwd = process.cwd(), title } = opts;
|
|
96
112
|
|
|
97
113
|
const safeSession = sanitizeSessionName(sessionName);
|
|
98
114
|
const workerNumber = worker == null ? null : parseWorkerNumber(worker);
|
|
@@ -136,12 +152,16 @@ export function openDashboardRuntimeTarget(runtime, opts = {}) {
|
|
|
136
152
|
});
|
|
137
153
|
}
|
|
138
154
|
|
|
139
|
-
if (
|
|
155
|
+
if (
|
|
156
|
+
(teammateMode === "wt" || String(targetPane).startsWith("wt:")) &&
|
|
157
|
+
!openAll
|
|
158
|
+
) {
|
|
140
159
|
return focusManagedPane(targetPane, { teammateMode: "wt", layout });
|
|
141
160
|
}
|
|
142
161
|
|
|
143
162
|
try {
|
|
144
|
-
if (!openAll && targetPane)
|
|
163
|
+
if (!openAll && targetPane)
|
|
164
|
+
focusManagedPane(targetPane, { teammateMode, layout });
|
|
145
165
|
return spawnWindowsTerminal(resolveAttachCommand(sessionName), {
|
|
146
166
|
mode: decideDashboardOpenMode({ openAll }),
|
|
147
167
|
title: title || `▲ ${sanitizeSessionName(sessionName)}`,
|
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
|
}
|
package/hub/team/handoff.mjs
CHANGED
|
@@ -44,8 +44,7 @@ Rules:
|
|
|
44
44
|
/**
|
|
45
45
|
* CLI 프롬프트 길이 제한을 고려한 축약 HANDOFF 지시
|
|
46
46
|
*/
|
|
47
|
-
export const HANDOFF_INSTRUCTION_SHORT =
|
|
48
|
-
`After completing, output this block at the end:
|
|
47
|
+
export const HANDOFF_INSTRUCTION_SHORT = `After completing, output this block at the end:
|
|
49
48
|
--- HANDOFF ---
|
|
50
49
|
status: ok | partial | failed
|
|
51
50
|
lead_action: accept | needs_read | retry | reassign
|
|
@@ -71,13 +70,20 @@ export function parseHandoff(rawText) {
|
|
|
71
70
|
const endIdx = rest.indexOf("\n---");
|
|
72
71
|
const block = endIdx === -1 ? rest : rest.slice(0, endIdx);
|
|
73
72
|
|
|
74
|
-
const lines = block
|
|
73
|
+
const lines = block
|
|
74
|
+
.split("\n")
|
|
75
|
+
.map((l) => l.trim())
|
|
76
|
+
.filter(Boolean);
|
|
75
77
|
const parsed = {};
|
|
76
78
|
|
|
77
79
|
for (const line of lines) {
|
|
78
80
|
const colonIdx = line.indexOf(":");
|
|
79
81
|
if (colonIdx === -1) continue;
|
|
80
|
-
const key = line
|
|
82
|
+
const key = line
|
|
83
|
+
.slice(0, colonIdx)
|
|
84
|
+
.trim()
|
|
85
|
+
.toLowerCase()
|
|
86
|
+
.replace(/\s+/g, "_");
|
|
81
87
|
const value = line.slice(colonIdx + 1).trim();
|
|
82
88
|
if (key && value) parsed[key] = value;
|
|
83
89
|
}
|
|
@@ -169,14 +175,18 @@ export function validateHandoff(parsed, context = {}) {
|
|
|
169
175
|
if (h.status === "failed" || h.status === "partial") {
|
|
170
176
|
if (h.error_stage) {
|
|
171
177
|
const v = validateEnum(h.error_stage, ERROR_STAGE_VALUES);
|
|
172
|
-
if (!v) {
|
|
173
|
-
|
|
178
|
+
if (!v) {
|
|
179
|
+
warnings.push(`error_stage: "${h.error_stage}" invalid`);
|
|
180
|
+
delete h.error_stage;
|
|
181
|
+
} else h.error_stage = v;
|
|
174
182
|
}
|
|
175
183
|
for (const f of ["retryable", "partial_output"]) {
|
|
176
184
|
if (h[f]) {
|
|
177
185
|
const v = validateEnum(h[f], YES_NO);
|
|
178
|
-
if (!v) {
|
|
179
|
-
|
|
186
|
+
if (!v) {
|
|
187
|
+
warnings.push(`${f}: "${h[f]}" invalid`);
|
|
188
|
+
delete h[f];
|
|
189
|
+
} else h[f] = v;
|
|
180
190
|
}
|
|
181
191
|
}
|
|
182
192
|
}
|
|
@@ -204,7 +214,9 @@ export function validateHandoff(parsed, context = {}) {
|
|
|
204
214
|
const rest = h.files_changed.length - 3;
|
|
205
215
|
h.files_changed = [...h.files_changed.slice(0, 3), `+${rest} more`];
|
|
206
216
|
}
|
|
207
|
-
warnings.push(
|
|
217
|
+
warnings.push(
|
|
218
|
+
`token cap exceeded (${tokens} > ${TOKEN_HARD_CAP}), trimmed`,
|
|
219
|
+
);
|
|
208
220
|
}
|
|
209
221
|
|
|
210
222
|
const missingCore = coreRequired.filter((f) => !h[f]);
|
|
@@ -232,10 +244,12 @@ export function buildFallbackHandoff(exitCode, resultFile, cli) {
|
|
|
232
244
|
confidence: "low",
|
|
233
245
|
risk: "low",
|
|
234
246
|
detail: resultFile || "none",
|
|
235
|
-
...(ok
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
...(ok
|
|
248
|
+
? {}
|
|
249
|
+
: {
|
|
250
|
+
error_stage: exitCode === 124 ? "timeout" : "execution",
|
|
251
|
+
retryable: exitCode === 124 ? "no" : "yes",
|
|
252
|
+
}),
|
|
239
253
|
_fallback: true,
|
|
240
254
|
};
|
|
241
255
|
}
|
|
@@ -248,8 +262,10 @@ export function buildFallbackHandoff(exitCode, resultFile, cli) {
|
|
|
248
262
|
export function formatHandoffForLead(handoff) {
|
|
249
263
|
const h = handoff;
|
|
250
264
|
const files = Array.isArray(h.files_changed)
|
|
251
|
-
?
|
|
252
|
-
|
|
265
|
+
? h.files_changed.length > 0
|
|
266
|
+
? h.files_changed.join(", ")
|
|
267
|
+
: "none"
|
|
268
|
+
: h.files_changed || "none";
|
|
253
269
|
|
|
254
270
|
const lines = [
|
|
255
271
|
`[HANDOFF] status=${h.status || "?"} action=${h.lead_action || "?"} confidence=${h.confidence || "?"}`,
|
package/hub/team/headless.mjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// v6.0.0: Lead-direct 모드 (runHeadlessInteractive, autoAttachTerminal)
|
|
5
5
|
// 의존성: psmux.mjs (Node.js 내장 모듈만 사용)
|
|
6
6
|
|
|
7
|
-
import { execSync
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
import { spawn } from "../lib/spawn-trace.mjs";
|
|
8
9
|
import { randomUUID } from "node:crypto";
|
|
9
10
|
import {
|
|
10
11
|
existsSync,
|