@triflux/remote 10.50.1 → 10.51.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/cto/brief.mjs +0 -12
- package/cto/collect.mjs +16 -247
- package/cto/current.schema.json +4 -20
- package/cto/events.mjs +13 -294
- package/cto/hygiene.mjs +26 -346
- package/cto/lake-root.mjs +114 -16
- package/cto/status.mjs +33 -128
- package/hub/index.mjs +0 -8
- package/hub/store-adapter.mjs +1 -1
- package/hub/team/agent-map.json +0 -1
- package/hub/team/ansi.mjs +1 -35
- package/hub/team/backend.mjs +9 -145
- package/hub/team/cli/commands/attach.mjs +1 -17
- package/hub/team/cli/commands/control.mjs +12 -38
- package/hub/team/cli/commands/debug.mjs +1 -37
- package/hub/team/cli/commands/focus.mjs +5 -31
- package/hub/team/cli/commands/interrupt.mjs +6 -25
- package/hub/team/cli/commands/kill.mjs +1 -30
- package/hub/team/cli/commands/list.mjs +0 -16
- package/hub/team/cli/commands/send.mjs +6 -26
- package/hub/team/cli/commands/start/index.mjs +41 -133
- package/hub/team/cli/commands/start/parse-args.mjs +15 -40
- package/hub/team/cli/commands/start/start-headless.mjs +138 -105
- package/hub/team/cli/commands/start/start-mux.mjs +0 -3
- package/hub/team/cli/commands/status.mjs +2 -57
- package/hub/team/cli/commands/stop.mjs +14 -19
- package/hub/team/cli/help.mjs +4 -5
- package/hub/team/cli/manifest.mjs +5 -1
- package/hub/team/cli/render.mjs +0 -12
- package/hub/team/cli/services/hub-client.mjs +2 -88
- package/hub/team/cli/services/runtime-mode.mjs +27 -98
- package/hub/team/handoff.mjs +3 -34
- package/hub/team/headless.mjs +14 -755
- package/hub/team/orchestrator.mjs +4 -30
- package/hub/team/pane.mjs +6 -28
- package/hub/team/psmux.mjs +16 -403
- package/hub/team/remote-session.mjs +1 -2
- package/hub/team/session.mjs +8 -119
- package/hub/team/swarm-intent.mjs +0 -198
- package/hub/team/terminal-opener.mjs +11 -35
- package/hub/team/tui-core.mjs +1 -1
- package/hub/team/tui-viewer.mjs +2 -2
- package/hub/team/tui.mjs +8 -269
- package/hub/team/wt-manager.mjs +2 -2
- package/hub/workers/factory.mjs +4 -37
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +162 -14
- package/scripts/lib/claudemd-scanner.mjs +2 -199
- package/scripts/lib/cli-claude.mjs +0 -2
- package/scripts/lib/context.mjs +3 -19
- package/scripts/lib/env-probe.mjs +5 -64
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +25 -104
- package/scripts/lib/skill-template.mjs +0 -271
- package/cto/hygiene-actions.mjs +0 -355
- package/cto/hygiene-notify.mjs +0 -170
- package/hub/team/build-worker-prompt.mjs +0 -92
- package/hub/team/check-mcp-hub.mjs +0 -49
- package/hub/team/claude-native-bridge.mjs +0 -987
- package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
- package/hub/team/cli/commands/start/start-wt.mjs +0 -86
- package/hub/team/cli/services/native-control.mjs +0 -145
- package/hub/team/codex-review.mjs +0 -520
- package/hub/team/conductor-mesh-bridge.mjs +0 -122
- package/hub/team/conductor.mjs +0 -1529
- package/hub/team/consensus-meta.mjs +0 -176
- package/hub/team/cto-pull.mjs +0 -31
- package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
- package/hub/team/dashboard.mjs +0 -322
- package/hub/team/event-log.mjs +0 -84
- package/hub/team/execution-mode.mjs +0 -324
- package/hub/team/extract-completion-payload.mjs +0 -110
- package/hub/team/headless-bridge-session.mjs +0 -17
- package/hub/team/health-probe.mjs +0 -435
- package/hub/team/interactive-native-launcher.mjs +0 -119
- package/hub/team/interactive-native-worker.mjs +0 -80
- package/hub/team/interactive-tui-transport.mjs +0 -479
- package/hub/team/launcher-template.mjs +0 -144
- package/hub/team/mcp-selector.mjs +0 -145
- package/hub/team/native-supervisor.mjs +0 -460
- package/hub/team/native.mjs +0 -727
- package/hub/team/recovery-store.mjs +0 -149
- package/hub/team/remote-probe.mjs +0 -292
- package/hub/team/remote-watcher.mjs +0 -506
- package/hub/team/runtime-strategy.mjs +0 -200
- package/hub/team/schemas/worker-completion.json +0 -39
- package/hub/team/sentinel-capture.mjs +0 -127
- package/hub/team/swarm-cli.mjs +0 -390
- package/hub/team/swarm-hypervisor.mjs +0 -2206
- package/hub/team/swarm-locks.mjs +0 -335
- package/hub/team/swarm-planner.mjs +0 -371
- package/hub/team/swarm-preflight.mjs +0 -366
- package/hub/team/swarm-reconciler.mjs +0 -242
- package/hub/team/tui-remote-adapter.mjs +0 -377
- package/hub/team/worker-completion-validator.mjs +0 -79
- package/hub/team/worktree-lifecycle.mjs +0 -639
- package/hub/team/wt-templates.json +0 -43
- package/hub/workers/antigravity-route-worker.mjs +0 -179
- package/hub/workers/gemini-worker.mjs +0 -217
- package/scripts/lib/agent-json.mjs +0 -27
- package/scripts/lib/async.mjs +0 -174
- package/scripts/lib/cli-agy.mjs +0 -85
- package/scripts/lib/cli-codex.mjs +0 -62
- package/scripts/lib/cli-gemini.mjs +0 -37
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
- package/scripts/lib/env.mjs +0 -14
- package/scripts/lib/hub.mjs +0 -48
- package/scripts/lib/pid.mjs +0 -63
- package/scripts/lib/quota.mjs +0 -47
- package/scripts/lib/skill-state.mjs +0 -220
- package/scripts/lib/team.mjs +0 -75
- package/scripts/lib/timeout.mjs +0 -66
- package/scripts/lib/tmp.mjs +0 -26
- package/scripts/lib/toml.mjs +0 -132
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Extracted from scripts/remote-spawn.mjs for reuse by swarm-hypervisor.
|
|
1
|
+
// Remote session primitives.
|
|
3
2
|
// Pure functions + SSH operations. No psmux, no WT, no CLI arg parsing.
|
|
4
3
|
|
|
5
4
|
import { execFileSync } from "node:child_process";
|
package/hub/team/session.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// 의존성: child_process (Node.js 내장)만 사용
|
|
1
|
+
// tmux/psmux 세션 생명주기 관리
|
|
3
2
|
import { execSync, spawnSync } from "node:child_process";
|
|
4
3
|
import { resolveGitBashExecutable } from "@triflux/core/hub/lib/bash-path.mjs";
|
|
5
4
|
import { getEnvironment } from "@triflux/core/hub/lib/env-detect.mjs";
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
psmuxExec,
|
|
17
16
|
psmuxSessionExists,
|
|
18
17
|
} from "./psmux.mjs";
|
|
19
|
-
import { createWtManager } from "./wt-manager.mjs";
|
|
20
18
|
|
|
21
19
|
/** Windows Terminal 실행 파일 존재 여부 */
|
|
22
20
|
export function hasWindowsTerminal() {
|
|
@@ -55,25 +53,6 @@ function hasGitBashTmux() {
|
|
|
55
53
|
}
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
function getCommandVersion(command) {
|
|
59
|
-
const r = spawnSync(command, ["-V"], {
|
|
60
|
-
encoding: "utf8",
|
|
61
|
-
timeout: 3000,
|
|
62
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
63
|
-
windowsHide: true,
|
|
64
|
-
});
|
|
65
|
-
if ((r.status ?? 1) !== 0) return null;
|
|
66
|
-
return `${r.stdout || ""}${r.stderr || ""}`.trim();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isPsmuxVersion(command) {
|
|
70
|
-
try {
|
|
71
|
-
return /\bpsmux\b/i.test(getCommandVersion(command) || "");
|
|
72
|
-
} catch {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
56
|
function isPsmuxCommandName(command) {
|
|
78
57
|
const name = String(command || "")
|
|
79
58
|
.replace(/\\/g, "/")
|
|
@@ -83,10 +62,6 @@ function isPsmuxCommandName(command) {
|
|
|
83
62
|
return name === "psmux" || name === "psmux.exe" || name === "psmux.cmd";
|
|
84
63
|
}
|
|
85
64
|
|
|
86
|
-
function hasLiteralPsmuxBinary(command = process.env.PSMUX_BIN || "psmux") {
|
|
87
|
-
return isPsmuxVersion(command);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
65
|
/**
|
|
91
66
|
* 터미널 멀티플렉서 감지 (결과 캐싱 — 프로세스 수명 동안 불변)
|
|
92
67
|
* @returns {'tmux'|'git-bash-tmux'|'psmux'|null}
|
|
@@ -105,10 +80,6 @@ export function detectMultiplexer() {
|
|
|
105
80
|
_cachedMux = "tmux";
|
|
106
81
|
return _cachedMux;
|
|
107
82
|
}
|
|
108
|
-
if (hasLiteralPsmuxBinary()) {
|
|
109
|
-
_cachedMux = "psmux";
|
|
110
|
-
return _cachedMux;
|
|
111
|
-
}
|
|
112
83
|
_cachedMux = null;
|
|
113
84
|
return _cachedMux;
|
|
114
85
|
}
|
|
@@ -150,7 +121,7 @@ function tmux(args, opts = {}) {
|
|
|
150
121
|
return psmuxExec(args, opts);
|
|
151
122
|
}
|
|
152
123
|
if (mux === "git-bash-tmux") {
|
|
153
|
-
const bash =
|
|
124
|
+
const bash = resolveGitBashExecutable();
|
|
154
125
|
if (!bash) throw new Error("git-bash-tmux 감지 실패");
|
|
155
126
|
const r = spawnSync(bash, ["-lc", `tmux ${args}`], {
|
|
156
127
|
encoding: "utf8",
|
|
@@ -220,77 +191,6 @@ export function resolveAttachCommand(sessionName, opts = {}) {
|
|
|
220
191
|
};
|
|
221
192
|
}
|
|
222
193
|
|
|
223
|
-
/**
|
|
224
|
-
* Windows Terminal pane 포커스 이동
|
|
225
|
-
* @param {number} paneIndex - createWtSession()에서 생성한 pane 인덱스(0 기반)
|
|
226
|
-
* @param {object} opts
|
|
227
|
-
* @param {'1xN'|'Nx1'} opts.layout
|
|
228
|
-
* @returns {boolean}
|
|
229
|
-
*/
|
|
230
|
-
export function focusWtPane(paneIndex, opts = {}) {
|
|
231
|
-
// wt() 제거로 인해 일시 비활성화 (탭 기반 전환 필요)
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Windows Terminal에서 생성한 팀 pane 정리
|
|
237
|
-
* @param {object} opts
|
|
238
|
-
* @param {'1xN'|'Nx1'} opts.layout
|
|
239
|
-
* @param {number} opts.paneCount
|
|
240
|
-
* @returns {number} 닫힌 pane 수 (best-effort)
|
|
241
|
-
*/
|
|
242
|
-
export function closeWtSession(opts = {}) {
|
|
243
|
-
// wt() 제거로 인해 일시 비활성화 (탭 기반 전환 필요)
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Windows Terminal 독립 모드 세션 생성 (비동기)
|
|
249
|
-
* @param {string} sessionName
|
|
250
|
-
* @param {object} opts
|
|
251
|
-
* @param {'1xN'|'Nx1'} opts.layout
|
|
252
|
-
* @param {Array<{title:string,command:string,cwd?:string}>} opts.paneCommands
|
|
253
|
-
* @returns {Promise<{ sessionName: string, panes: string[], titles: string[], layout: '1xN'|'Nx1', paneCount: number, anchorPane: string }>}
|
|
254
|
-
*/
|
|
255
|
-
export async function createWtSession(sessionName, opts = {}) {
|
|
256
|
-
const { layout = "1xN", paneCommands = [] } = opts;
|
|
257
|
-
|
|
258
|
-
if (!hasWindowsTerminalSession()) {
|
|
259
|
-
throw new Error("WT_SESSION 미감지");
|
|
260
|
-
}
|
|
261
|
-
if (!getEnvironment().terminal.hasWt) {
|
|
262
|
-
throw new Error("wt.exe 미발견");
|
|
263
|
-
}
|
|
264
|
-
if (!Array.isArray(paneCommands) || paneCommands.length === 0) {
|
|
265
|
-
throw new Error("paneCommands가 비어 있음");
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const wtManager = createWtManager();
|
|
269
|
-
const panes = [];
|
|
270
|
-
const titles = [];
|
|
271
|
-
|
|
272
|
-
for (let i = 0; i < paneCommands.length; i++) {
|
|
273
|
-
const pane = paneCommands[i] || {};
|
|
274
|
-
const title = pane.title || `${sessionName}-${i + 1}`;
|
|
275
|
-
const command = String(pane.command || "").trim();
|
|
276
|
-
const cwd = pane.cwd || process.cwd();
|
|
277
|
-
if (!command) continue;
|
|
278
|
-
|
|
279
|
-
await wtManager.createTab({ title, command, cwd });
|
|
280
|
-
panes.push(`wt:${i}`);
|
|
281
|
-
titles.push(title);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
sessionName,
|
|
286
|
-
panes,
|
|
287
|
-
titles,
|
|
288
|
-
layout: layout === "Nx1" ? "Nx1" : "1xN",
|
|
289
|
-
paneCount: panes.length,
|
|
290
|
-
anchorPane: "wt:anchor",
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
194
|
/**
|
|
295
195
|
* tmux 세션 생성 + 레이아웃 분할
|
|
296
196
|
* @param {string} sessionName — 세션 이름
|
|
@@ -402,23 +302,12 @@ export function configureTeammateKeybindings(sessionName, opts = {}) {
|
|
|
402
302
|
? `'select-pane -t :.- \\; resize-pane -Z'`
|
|
403
303
|
: `'select-pane -t :.-'`;
|
|
404
304
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
`bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
|
|
412
|
-
);
|
|
413
|
-
} else {
|
|
414
|
-
// 분할 뷰에서 팀메이트 순환
|
|
415
|
-
tmux(
|
|
416
|
-
`bind-key -T root -n S-Down if-shell -F '${cond}' ${bindNext} 'send-keys S-Down'`,
|
|
417
|
-
);
|
|
418
|
-
tmux(
|
|
419
|
-
`bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
|
|
420
|
-
);
|
|
421
|
-
}
|
|
305
|
+
tmux(
|
|
306
|
+
`bind-key -T root -n S-Down if-shell -F '${cond}' ${bindNext} 'send-keys S-Down'`,
|
|
307
|
+
);
|
|
308
|
+
tmux(
|
|
309
|
+
`bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
|
|
310
|
+
);
|
|
422
311
|
|
|
423
312
|
// 대체 키: 일부 환경에서 S-Up이 누락될 때 사용
|
|
424
313
|
tmux(
|
|
@@ -1,127 +1,5 @@
|
|
|
1
|
-
// hub/team/swarm-intent.mjs — X-Intent helper utilities for semantic merge awareness
|
|
2
|
-
|
|
3
1
|
const INTENT_TRAILER_REGEX = /^X-Intent:\s*(.+)$/m;
|
|
4
2
|
|
|
5
|
-
function normalizeText(value) {
|
|
6
|
-
return typeof value === "string" ? value.trim() : "";
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function normalizeTouches(touches) {
|
|
10
|
-
if (!Array.isArray(touches)) return null;
|
|
11
|
-
return touches
|
|
12
|
-
.filter((item) => typeof item === "string")
|
|
13
|
-
.map((item) => item.trim().replace(/\\/g, "/"))
|
|
14
|
-
.filter(Boolean);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeIntent(intentObj) {
|
|
18
|
-
if (!intentObj || typeof intentObj !== "object" || Array.isArray(intentObj)) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const scope = normalizeText(intentObj.scope);
|
|
23
|
-
const action = normalizeText(intentObj.action);
|
|
24
|
-
const reason = normalizeText(intentObj.reason);
|
|
25
|
-
const touches = normalizeTouches(intentObj.touches);
|
|
26
|
-
|
|
27
|
-
if (!scope || !action || !reason || !touches) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
scope,
|
|
33
|
-
action,
|
|
34
|
-
reason,
|
|
35
|
-
touches,
|
|
36
|
-
invariant: normalizeText(intentObj.invariant),
|
|
37
|
-
conflictsWith: normalizeText(intentObj.conflictsWith),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function fallbackIntent(llmResponse) {
|
|
42
|
-
const responseText = String(llmResponse ?? "");
|
|
43
|
-
return {
|
|
44
|
-
scope: "unknown",
|
|
45
|
-
action: "unknown",
|
|
46
|
-
reason: responseText.slice(0, 100),
|
|
47
|
-
touches: [],
|
|
48
|
-
invariant: "",
|
|
49
|
-
conflictsWith: "",
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function includesEitherWay(a, b) {
|
|
54
|
-
if (!a || !b) return false;
|
|
55
|
-
return a.includes(b) || b.includes(a);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function hasConflict(intentA, intentB) {
|
|
59
|
-
const conflictsWith = normalizeText(intentA?.conflictsWith).toLowerCase();
|
|
60
|
-
const scope = normalizeText(intentB?.scope).toLowerCase();
|
|
61
|
-
const action = normalizeText(intentB?.action).toLowerCase();
|
|
62
|
-
|
|
63
|
-
if (!conflictsWith) return false;
|
|
64
|
-
return (
|
|
65
|
-
includesEitherWay(conflictsWith, scope) ||
|
|
66
|
-
includesEitherWay(conflictsWith, action)
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function getTouchesOverlap(intentA, intentB) {
|
|
71
|
-
const touchesA = new Set(
|
|
72
|
-
(normalizeTouches(intentA?.touches) ?? []).map((item) =>
|
|
73
|
-
item.toLowerCase(),
|
|
74
|
-
),
|
|
75
|
-
);
|
|
76
|
-
const touchesB = (normalizeTouches(intentB?.touches) ?? []).map((item) =>
|
|
77
|
-
item.toLowerCase(),
|
|
78
|
-
);
|
|
79
|
-
return touchesB.filter((item) => touchesA.has(item));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Build prompts for an LLM to generate an X-Intent JSON object.
|
|
84
|
-
* @param {string[]} changedFiles
|
|
85
|
-
* @param {string} commitDiff
|
|
86
|
-
* @param {string} taskContext
|
|
87
|
-
* @returns {{ systemPrompt: string, userPrompt: string }}
|
|
88
|
-
*/
|
|
89
|
-
export function generateIntentPrompt(changedFiles, commitDiff, taskContext) {
|
|
90
|
-
const files = Array.isArray(changedFiles)
|
|
91
|
-
? changedFiles.filter((filePath) => typeof filePath === "string")
|
|
92
|
-
: [];
|
|
93
|
-
const diffSnippet = String(commitDiff ?? "").slice(0, 4000);
|
|
94
|
-
const context = String(taskContext ?? "").trim();
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
systemPrompt:
|
|
98
|
-
"You generate commit intent metadata for semantic merge awareness. Return only valid JSON with keys: scope, action, reason, touches, invariant, conflictsWith.",
|
|
99
|
-
userPrompt: [
|
|
100
|
-
"Create an intent object from the following commit context.",
|
|
101
|
-
"- scope/action/reason must be concise strings.",
|
|
102
|
-
"- touches must be an array of touched file paths.",
|
|
103
|
-
"- invariant/conflictsWith should be strings (empty if unknown).",
|
|
104
|
-
"- Return JSON only (no markdown).",
|
|
105
|
-
"",
|
|
106
|
-
`Task context:\n${context || "(none provided)"}`,
|
|
107
|
-
"",
|
|
108
|
-
`Changed files (${files.length}):\n${files.join("\n") || "(none)"}`,
|
|
109
|
-
"",
|
|
110
|
-
"Commit diff (first 4000 chars):",
|
|
111
|
-
diffSnippet || "(empty)",
|
|
112
|
-
].join("\n"),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Format an intent object into an X-Intent commit trailer.
|
|
118
|
-
* @param {object} intentObj
|
|
119
|
-
* @returns {string}
|
|
120
|
-
*/
|
|
121
|
-
export function formatIntentTrailer(intentObj) {
|
|
122
|
-
return `X-Intent: ${JSON.stringify(intentObj)}`;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
3
|
/**
|
|
126
4
|
* Parse an X-Intent trailer from a commit message.
|
|
127
5
|
* @param {string} commitMessage
|
|
@@ -138,79 +16,3 @@ export function parseIntentTrailer(commitMessage) {
|
|
|
138
16
|
return null;
|
|
139
17
|
}
|
|
140
18
|
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Classify the relation between two intents.
|
|
144
|
-
* @param {object} intentA
|
|
145
|
-
* @param {object} intentB
|
|
146
|
-
* @returns {{ relation: "complementary" | "complementary-risky" | "contradictory" | "independent", reason: string }}
|
|
147
|
-
*/
|
|
148
|
-
export function classifyIntentPair(intentA, intentB) {
|
|
149
|
-
if (hasConflict(intentA, intentB) || hasConflict(intentB, intentA)) {
|
|
150
|
-
return {
|
|
151
|
-
relation: "contradictory",
|
|
152
|
-
reason:
|
|
153
|
-
"conflictsWith on one intent matches the other intent scope/action",
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const scopeA = normalizeText(intentA?.scope).toLowerCase();
|
|
158
|
-
const scopeB = normalizeText(intentB?.scope).toLowerCase();
|
|
159
|
-
|
|
160
|
-
if (scopeA && scopeB && scopeA === scopeB) {
|
|
161
|
-
const overlap = getTouchesOverlap(intentA, intentB);
|
|
162
|
-
if (overlap.length > 0) {
|
|
163
|
-
return {
|
|
164
|
-
relation: "complementary-risky",
|
|
165
|
-
reason: `same scope with overlapping touched files: ${overlap.join(", ")}`,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
relation: "complementary",
|
|
171
|
-
reason:
|
|
172
|
-
"same scope with no conflicting intent and no touched-file overlap",
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
relation: "independent",
|
|
178
|
-
reason: "different scopes and no conflictsWith match detected",
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Build a validated intent object from raw LLM response text.
|
|
184
|
-
* @param {string} llmResponse
|
|
185
|
-
* @returns {{ scope: string, action: string, reason: string, touches: string[], invariant: string, conflictsWith: string }}
|
|
186
|
-
*/
|
|
187
|
-
export function buildIntentFromLLMResponse(llmResponse) {
|
|
188
|
-
const responseText = String(llmResponse ?? "").trim();
|
|
189
|
-
const candidates = [];
|
|
190
|
-
|
|
191
|
-
const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
192
|
-
for (const match of responseText.matchAll(codeBlockRegex)) {
|
|
193
|
-
if (match[1]) candidates.push(match[1].trim());
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
candidates.push(responseText);
|
|
197
|
-
|
|
198
|
-
const firstBrace = responseText.indexOf("{");
|
|
199
|
-
const lastBrace = responseText.lastIndexOf("}");
|
|
200
|
-
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
201
|
-
candidates.push(responseText.slice(firstBrace, lastBrace + 1));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
for (const candidate of candidates) {
|
|
205
|
-
if (!candidate) continue;
|
|
206
|
-
try {
|
|
207
|
-
const parsed = JSON.parse(candidate);
|
|
208
|
-
const normalized = normalizeIntent(parsed);
|
|
209
|
-
if (normalized) return normalized;
|
|
210
|
-
} catch {
|
|
211
|
-
// ignore parse failures and continue trying other candidates
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return fallbackIntent(responseText);
|
|
216
|
-
}
|
|
@@ -56,16 +56,8 @@ function execOpenTerminal(execFn) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// tmux 어댑터와 동일하게 취급한다.
|
|
62
|
-
function isTmuxLikeMux(mux, platform) {
|
|
63
|
-
return TMUX_LIKE_MUXES.has(mux) || (platform !== "win32" && mux === "psmux");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function shellCommandName(value) {
|
|
67
|
-
const command = String(value);
|
|
68
|
-
return /^[A-Za-z0-9_./:-]+$/u.test(command) ? command : shellQuote(command);
|
|
59
|
+
function isTmuxLikeMux(mux) {
|
|
60
|
+
return TMUX_LIKE_MUXES.has(mux);
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
function powershellCommandName(value) {
|
|
@@ -94,17 +86,6 @@ function defaultPsmuxBinaryExists(command) {
|
|
|
94
86
|
}
|
|
95
87
|
}
|
|
96
88
|
|
|
97
|
-
function buildAttachCommand(mux, sessionName, deps = {}) {
|
|
98
|
-
if (mux === "psmux") {
|
|
99
|
-
const command = process.env.PSMUX_BIN || "psmux";
|
|
100
|
-
const psmuxBinaryExists =
|
|
101
|
-
deps.psmuxBinaryExists || defaultPsmuxBinaryExists;
|
|
102
|
-
if (!psmuxBinaryExists(command)) return null;
|
|
103
|
-
return `${shellCommandName(command)} attach-session -t ${shellQuote(sessionName)}`;
|
|
104
|
-
}
|
|
105
|
-
return `tmux attach-session -t ${shellQuote(sessionName)}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
89
|
// wt-manager.createTab은 (a) undefined/void 반환 — legacy success 의미 (b) {success:true|false, ...} 객체 반환의 두 형태가 공존.
|
|
109
90
|
// `result?.success !== false`는 undefined/null/{}을 모두 success로 취급하고 명시적 {success:false}만 실패로 본다.
|
|
110
91
|
function wtResultSucceeded(result) {
|
|
@@ -162,7 +143,7 @@ export function createTerminalOpener(deps = {}) {
|
|
|
162
143
|
}
|
|
163
144
|
|
|
164
145
|
const mux = resolveMux(deps);
|
|
165
|
-
if (isTmuxLikeMux(mux
|
|
146
|
+
if (isTmuxLikeMux(mux)) {
|
|
166
147
|
const targetPane = resolveLeadPane(spec.targetPane);
|
|
167
148
|
if (!targetPane) return false;
|
|
168
149
|
const targetWindow = resolveLeadWindow(tmuxExec, targetPane);
|
|
@@ -212,19 +193,14 @@ export function createTerminalOpener(deps = {}) {
|
|
|
212
193
|
}
|
|
213
194
|
|
|
214
195
|
const mux = resolveMux(deps);
|
|
215
|
-
if (isTmuxLikeMux(mux
|
|
196
|
+
if (isTmuxLikeMux(mux)) {
|
|
216
197
|
const targetPane = resolveLeadPane(opts.targetPane);
|
|
217
198
|
if (!targetPane) return false;
|
|
218
|
-
const socketPath =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
? buildAttachCommand(mux, sessionName, deps)
|
|
224
|
-
: `env -u TMUX tmux -S ${shellQuote(
|
|
225
|
-
socketPath,
|
|
226
|
-
)} attach-session -t ${shellQuote(sessionName)}`;
|
|
227
|
-
if (!attachCommand) return false;
|
|
199
|
+
const socketPath = resolveLeadSocket(tmuxExec, targetPane);
|
|
200
|
+
if (!socketPath) return false;
|
|
201
|
+
const attachCommand = `env -u TMUX tmux -S ${shellQuote(
|
|
202
|
+
socketPath,
|
|
203
|
+
)} attach-session -t ${shellQuote(sessionName)}`;
|
|
228
204
|
const targetWindow = resolveLeadWindow(tmuxExec, targetPane);
|
|
229
205
|
if (!targetWindow) return false;
|
|
230
206
|
try {
|
|
@@ -246,12 +222,12 @@ export function createTerminalOpener(deps = {}) {
|
|
|
246
222
|
const target = `${sessionName}:0.${workerNumber}`;
|
|
247
223
|
const mux = resolveMux(deps);
|
|
248
224
|
|
|
249
|
-
if (mux === "psmux") {
|
|
225
|
+
if (platform === "win32" && mux === "psmux") {
|
|
250
226
|
psmuxExec(["select-pane", "-t", target]);
|
|
251
227
|
return true;
|
|
252
228
|
}
|
|
253
229
|
|
|
254
|
-
if (isTmuxLikeMux(mux
|
|
230
|
+
if (isTmuxLikeMux(mux)) {
|
|
255
231
|
tmuxExec(`select-pane -t ${shellQuote(target)}`);
|
|
256
232
|
return true;
|
|
257
233
|
}
|
package/hub/team/tui-core.mjs
CHANGED
|
@@ -195,7 +195,7 @@ export function normalizeWorkerState(existing = {}, state = {}, opts = {}) {
|
|
|
195
195
|
|
|
196
196
|
// ── 색상 헬퍼 ─────────────────────────────────────────────────────────────
|
|
197
197
|
export function cliColor(cli) {
|
|
198
|
-
if (cli === "
|
|
198
|
+
if (cli === "antigravity" || cli === "agy") return FG.antigravity;
|
|
199
199
|
if (cli === "claude") return FG.claude;
|
|
200
200
|
if (cli === "codex") return FG.codex;
|
|
201
201
|
return FG.white;
|
package/hub/team/tui-viewer.mjs
CHANGED
|
@@ -358,8 +358,8 @@ function ingest() {
|
|
|
358
358
|
|
|
359
359
|
// CLI 타입 감지
|
|
360
360
|
let cli = "codex";
|
|
361
|
-
if (pane.title.includes("
|
|
362
|
-
cli = "
|
|
361
|
+
if (pane.title.includes("antigravity") || pane.title.includes("🔵"))
|
|
362
|
+
cli = "antigravity";
|
|
363
363
|
else if (pane.title.includes("claude") || pane.title.includes("🟠"))
|
|
364
364
|
cli = "claude";
|
|
365
365
|
ws.title = pane.title;
|