@triflux/remote 10.50.2 → 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 +0 -73
- package/cto/current.schema.json +0 -4
- package/cto/status.mjs +2 -50
- package/hub/index.mjs +0 -8
- 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 +13 -754
- package/hub/team/orchestrator.mjs +4 -30
- package/hub/team/pane.mjs +6 -28
- package/hub/team/psmux.mjs +0 -1
- package/hub/team/remote-session.mjs +1 -2
- package/hub/team/session.mjs +7 -91
- package/hub/team/swarm-intent.mjs +0 -198
- 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/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 +11 -59
- package/scripts/lib/skill-template.mjs +0 -245
- 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/scripts/lib/dynamic-route-cli.mjs +0 -107
package/cto/brief.mjs
CHANGED
|
@@ -27,13 +27,6 @@ function formatGoal(goal) {
|
|
|
27
27
|
return `- ${id} ${status}: ${title}`;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
function formatShard(shard) {
|
|
31
|
-
const id = oneLine(shard?.id || shard?.name || shard?.sessionId, "shard");
|
|
32
|
-
const status = oneLine(shard?.status || shard?.state, "unknown");
|
|
33
|
-
const detail = oneLine(shard?.summary || shard?.objective || shard?.task, "");
|
|
34
|
-
return detail ? `- ${id} ${status}: ${detail}` : `- ${id} ${status}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
30
|
function formatEvent(event) {
|
|
38
31
|
const ts = oneLine(event?.ts, "unknown_ts");
|
|
39
32
|
const name = oneLine(event?.event, "event");
|
|
@@ -61,9 +54,6 @@ export function renderBrief(current) {
|
|
|
61
54
|
const activeGoals = Array.isArray(summary.active_goals)
|
|
62
55
|
? summary.active_goals.slice(0, 3)
|
|
63
56
|
: [];
|
|
64
|
-
const swarmShards = Array.isArray(summary.swarm_shards)
|
|
65
|
-
? summary.swarm_shards.slice(0, 2)
|
|
66
|
-
: [];
|
|
67
57
|
const recentEvents = Array.isArray(current?.ledger_tail)
|
|
68
58
|
? current.ledger_tail.slice(-2)
|
|
69
59
|
: [];
|
|
@@ -75,8 +65,6 @@ export function renderBrief(current) {
|
|
|
75
65
|
`summary: ${oneLine(summary.repo_state, "no repo summary")}`,
|
|
76
66
|
"active_goals",
|
|
77
67
|
...(activeGoals.length > 0 ? activeGoals.map(formatGoal) : ["- none"]),
|
|
78
|
-
"swarm_shards",
|
|
79
|
-
...(swarmShards.length > 0 ? swarmShards.map(formatShard) : ["- none"]),
|
|
80
68
|
"hub_status",
|
|
81
69
|
`status: ${oneLine(hub.status, "unknown")} available: ${hub.available === true} detail: ${compactDetail(hub.detail)}`,
|
|
82
70
|
"recent_events",
|
package/cto/collect.mjs
CHANGED
|
@@ -33,12 +33,6 @@ const SOURCE_REGISTRY = [
|
|
|
33
33
|
probe: ".triflux/hub/status.json or host tfx-hub cache",
|
|
34
34
|
enabled: true,
|
|
35
35
|
},
|
|
36
|
-
{
|
|
37
|
-
id: "tfx_swarm",
|
|
38
|
-
kind: "triflux-runtime-artifact",
|
|
39
|
-
probe: ".triflux/swarm-locks.json or .triflux/swarm/*.json",
|
|
40
|
-
enabled: true,
|
|
41
|
-
},
|
|
42
36
|
{
|
|
43
37
|
id: "tfx_team",
|
|
44
38
|
kind: "triflux-runtime-artifact",
|
|
@@ -306,70 +300,6 @@ function collectUltragoal(dirPath, rootDir, collectedAt) {
|
|
|
306
300
|
}
|
|
307
301
|
}
|
|
308
302
|
|
|
309
|
-
export function collectSwarm(rootDir, collectedAt) {
|
|
310
|
-
const locksPath = join(rootDir, ".triflux", "swarm-locks.json");
|
|
311
|
-
const logsDir = join(rootDir, ".triflux", "swarm-logs");
|
|
312
|
-
if (!existsSync(locksPath) && !existsSync(logsDir)) {
|
|
313
|
-
return missingSource(
|
|
314
|
-
"no_shell_readable_artifact",
|
|
315
|
-
"no swarm locks or logs found",
|
|
316
|
-
collectedAt,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
try {
|
|
320
|
-
const detail = { locks_path: null, locks: null, shards: [], log_runs: [] };
|
|
321
|
-
if (existsSync(locksPath)) {
|
|
322
|
-
const locks = readJson(locksPath);
|
|
323
|
-
detail.locks_path = relPath(rootDir, locksPath);
|
|
324
|
-
detail.locks = safeSummary(locks);
|
|
325
|
-
if (Array.isArray(locks?.shards)) detail.shards = locks.shards;
|
|
326
|
-
else if (Array.isArray(locks)) detail.shards = locks;
|
|
327
|
-
else if (locks && typeof locks === "object") {
|
|
328
|
-
// 실제 producer(hub/team/swarm-locks.mjs persist)는
|
|
329
|
-
// { "<file>": { workerId, leaseType, sessionMeta } } 형태의 lock map 을 쓴다.
|
|
330
|
-
// workerId 가 곧 shard.name 이다 — swarm-hypervisor 가
|
|
331
|
-
// acquire(shard.name, shard.files) 로 잠그기 때문(swarm-hypervisor.mjs:753).
|
|
332
|
-
// workerId 별로 묶어 active shard row 로 환원한다. (expired lock 정밀 필터는
|
|
333
|
-
// hub snapshot() 의 책임이라, 여기서는 persist 된 보유 lock 전부를 active 로 본다.)
|
|
334
|
-
const byWorker = new Map();
|
|
335
|
-
for (const entry of Object.values(locks)) {
|
|
336
|
-
const workerId =
|
|
337
|
-
entry && typeof entry.workerId === "string" ? entry.workerId : null;
|
|
338
|
-
if (!workerId) continue;
|
|
339
|
-
let row = byWorker.get(workerId);
|
|
340
|
-
if (!row) {
|
|
341
|
-
row = { shard_name: workerId, phase: "active", members: [] };
|
|
342
|
-
byWorker.set(workerId, row);
|
|
343
|
-
}
|
|
344
|
-
const host = entry?.sessionMeta?.host;
|
|
345
|
-
if (typeof host === "string" && host && !row.members.includes(host)) {
|
|
346
|
-
row.members.push(host);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
detail.shards = [...byWorker.values()];
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (existsSync(logsDir)) {
|
|
353
|
-
detail.log_runs = readdirSync(logsDir, { withFileTypes: true })
|
|
354
|
-
.filter((entry) => entry.isDirectory() && entry.name.startsWith("run-"))
|
|
355
|
-
.map((entry) => {
|
|
356
|
-
const path = join(logsDir, entry.name);
|
|
357
|
-
return { id: entry.name, mtime: statSync(path).mtime.toISOString() };
|
|
358
|
-
})
|
|
359
|
-
.sort((a, b) => b.mtime.localeCompare(a.mtime))
|
|
360
|
-
.slice(0, 5);
|
|
361
|
-
}
|
|
362
|
-
return sourceState(true, "ok", detail, collectedAt);
|
|
363
|
-
} catch (error) {
|
|
364
|
-
return sourceState(
|
|
365
|
-
true,
|
|
366
|
-
"read_error",
|
|
367
|
-
error?.message || "swarm read failed",
|
|
368
|
-
collectedAt,
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
303
|
function collectHandoffs(rootDir, collectedAt) {
|
|
374
304
|
const dirPath = join(rootDir, ".omx", "handoffs");
|
|
375
305
|
const files = listDirFiles(dirPath, rootDir, 10);
|
|
@@ -392,7 +322,6 @@ function buildSummary(current) {
|
|
|
392
322
|
const repo = current.repo;
|
|
393
323
|
const omxGoals = current.sources.ultragoal_omx.detail?.active_goals || [];
|
|
394
324
|
const omcGoals = current.sources.ultragoal_omc.detail?.active_goals || [];
|
|
395
|
-
const swarmShards = current.sources.tfx_swarm.detail?.shards || [];
|
|
396
325
|
const availableSources = Object.values(current.sources).filter(
|
|
397
326
|
(source) => source.available,
|
|
398
327
|
).length;
|
|
@@ -400,7 +329,6 @@ function buildSummary(current) {
|
|
|
400
329
|
return {
|
|
401
330
|
repo_state: `branch ${repo.branch || "unknown"} at ${repo.head || "unknown"} is ${repo.dirty ? "dirty" : "clean"}`,
|
|
402
331
|
active_goals: [...omxGoals, ...omcGoals].slice(0, 5),
|
|
403
|
-
swarm_shards: Array.isArray(swarmShards) ? swarmShards.slice(0, 5) : [],
|
|
404
332
|
hub_status: current.sources.tfx_hub.status,
|
|
405
333
|
available_sources: availableSources,
|
|
406
334
|
missing_sources: SOURCE_IDS.length - availableSources,
|
|
@@ -581,7 +509,6 @@ function collectSources(rootDir, collectedAt, execFileSyncFn, opts = {}) {
|
|
|
581
509
|
rootDir,
|
|
582
510
|
collectedAt,
|
|
583
511
|
),
|
|
584
|
-
tfx_swarm: collectSwarm(rootDir, collectedAt),
|
|
585
512
|
tfx_team: collectJsonArtifact(
|
|
586
513
|
"tfx_team",
|
|
587
514
|
[
|
package/cto/current.schema.json
CHANGED
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
"required": [
|
|
50
50
|
"git",
|
|
51
51
|
"tfx_hub",
|
|
52
|
-
"tfx_swarm",
|
|
53
52
|
"tfx_team",
|
|
54
53
|
"tfx_synapse",
|
|
55
54
|
"ultragoal_omx",
|
|
@@ -63,9 +62,6 @@
|
|
|
63
62
|
"tfx_hub": {
|
|
64
63
|
"$ref": "#/$defs/sourceState"
|
|
65
64
|
},
|
|
66
|
-
"tfx_swarm": {
|
|
67
|
-
"$ref": "#/$defs/sourceState"
|
|
68
|
-
},
|
|
69
65
|
"tfx_team": {
|
|
70
66
|
"$ref": "#/$defs/sourceState"
|
|
71
67
|
},
|
package/cto/status.mjs
CHANGED
|
@@ -73,30 +73,6 @@ function redactedCwdFields(cwd) {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function normalizeActiveShard(shard) {
|
|
77
|
-
return {
|
|
78
|
-
shard_name:
|
|
79
|
-
typeof shard?.shard_name === "string"
|
|
80
|
-
? shard.shard_name
|
|
81
|
-
: typeof shard?.shardName === "string"
|
|
82
|
-
? shard.shardName
|
|
83
|
-
: typeof shard?.name === "string"
|
|
84
|
-
? shard.name
|
|
85
|
-
: typeof shard?.id === "string"
|
|
86
|
-
? shard.id
|
|
87
|
-
: null,
|
|
88
|
-
phase:
|
|
89
|
-
typeof shard?.phase === "string"
|
|
90
|
-
? shard.phase
|
|
91
|
-
: typeof shard?.status === "string"
|
|
92
|
-
? shard.status
|
|
93
|
-
: "active",
|
|
94
|
-
members: Array.isArray(shard?.members)
|
|
95
|
-
? shard.members.map((member) => String(member))
|
|
96
|
-
: [],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
76
|
function normalizeSynapseOverlay(value) {
|
|
101
77
|
const sessions = Array.isArray(value)
|
|
102
78
|
? value
|
|
@@ -105,11 +81,6 @@ function normalizeSynapseOverlay(value) {
|
|
|
105
81
|
: Array.isArray(value?.live_sessions)
|
|
106
82
|
? value.live_sessions
|
|
107
83
|
: [];
|
|
108
|
-
const activeShards = Array.isArray(value?.active_shards)
|
|
109
|
-
? value.active_shards
|
|
110
|
-
: Array.isArray(value?.activeShards)
|
|
111
|
-
? value.activeShards
|
|
112
|
-
: [];
|
|
113
84
|
|
|
114
85
|
return {
|
|
115
86
|
live_sessions: sessions
|
|
@@ -119,7 +90,6 @@ function normalizeSynapseOverlay(value) {
|
|
|
119
90
|
...redactedCwdFields(session?.cwd),
|
|
120
91
|
}))
|
|
121
92
|
.filter((session) => session.sessionId),
|
|
122
|
-
active_shards: activeShards.map(normalizeActiveShard),
|
|
123
93
|
};
|
|
124
94
|
}
|
|
125
95
|
|
|
@@ -150,28 +120,10 @@ async function readSynapseOverlay(opts) {
|
|
|
150
120
|
);
|
|
151
121
|
return normalizeSynapseOverlay(raw);
|
|
152
122
|
} catch {
|
|
153
|
-
return { live_sessions: []
|
|
123
|
+
return { live_sessions: [] };
|
|
154
124
|
}
|
|
155
125
|
}
|
|
156
126
|
|
|
157
|
-
// active_shards 의 source 는 live_sessions 와 다르다: live_sessions 는 synapse
|
|
158
|
-
// overlay(실시간 세션)에서, active_shards 는 collect 가 .triflux/swarm-locks.json 을
|
|
159
|
-
// snapshot 한 current.json(sources.tfx_swarm.detail.shards)에서 온다. synapse
|
|
160
|
-
// overlay 가 shard 를 채우는 경로가 생기면 그쪽을 우선하고, 없으면 snapshot 으로
|
|
161
|
-
// fallback 한다(현재 overlay 의 active_shards 는 항상 비어 snapshot 을 쓴다).
|
|
162
|
-
function deriveActiveShards(current, overlay) {
|
|
163
|
-
// overlay(synapse)가 채운 shard 중 식별 가능한 것만 authoritative 로 본다.
|
|
164
|
-
// normalizeSynapseOverlay 가 shard_name 을 null 로 정규화한 무효 행이 snapshot
|
|
165
|
-
// fallback 을 가로채지 않도록 먼저 거른다.
|
|
166
|
-
const overlayShards = Array.isArray(overlay?.active_shards)
|
|
167
|
-
? overlay.active_shards.filter((shard) => shard?.shard_name)
|
|
168
|
-
: [];
|
|
169
|
-
if (overlayShards.length > 0) return overlayShards;
|
|
170
|
-
const shards = current?.sources?.tfx_swarm?.detail?.shards;
|
|
171
|
-
if (!Array.isArray(shards)) return [];
|
|
172
|
-
return shards.map(normalizeActiveShard).filter((shard) => shard.shard_name);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
127
|
function deriveLiveSessionGroups(liveSessions) {
|
|
176
128
|
const groups = new Map();
|
|
177
129
|
for (const session of liveSessions || []) {
|
|
@@ -202,7 +154,7 @@ function projectStatus(current, overlay) {
|
|
|
202
154
|
ledger_tail: Array.isArray(current?.ledger_tail) ? current.ledger_tail : [],
|
|
203
155
|
live_sessions: liveSessions,
|
|
204
156
|
live_session_groups: deriveLiveSessionGroups(liveSessions),
|
|
205
|
-
active_shards:
|
|
157
|
+
active_shards: [],
|
|
206
158
|
hygiene: compactHygieneCounts(projectCtoHygiene({ current, overlay })),
|
|
207
159
|
};
|
|
208
160
|
}
|
package/hub/index.mjs
CHANGED
|
@@ -11,11 +11,3 @@ export { createPipeServer, getPipePath } from './pipe.mjs';
|
|
|
11
11
|
// Team
|
|
12
12
|
export { orchestrate, decomposeTask, buildLeadPrompt, buildPrompt } from './team/orchestrator.mjs';
|
|
13
13
|
export { createPsmuxSession, startCapture } from './team/psmux.mjs';
|
|
14
|
-
export { createConductor } from './team/conductor.mjs';
|
|
15
|
-
|
|
16
|
-
// Swarm
|
|
17
|
-
export { createSwarmLocks } from './team/swarm-locks.mjs';
|
|
18
|
-
export { parseShards, buildFileLeaseMap, buildMcpManifest, computeMergeOrder, planSwarm } from './team/swarm-planner.mjs';
|
|
19
|
-
export { createSwarmHypervisor, SWARM_STATES } from './team/swarm-hypervisor.mjs';
|
|
20
|
-
export { reconcile, buildRedundantIds, shouldRunRedundant } from './team/swarm-reconciler.mjs';
|
|
21
|
-
export { ensureWorktree, prepareIntegrationBranch, rebaseShardOntoIntegration, pruneWorktree, fetchRemoteShard } from './team/worktree-lifecycle.mjs';
|
package/hub/team/agent-map.json
CHANGED
package/hub/team/ansi.mjs
CHANGED
|
@@ -21,9 +21,6 @@ export function moveTo(row, col) {
|
|
|
21
21
|
export function moveUp(n = 1) {
|
|
22
22
|
return `${ESC}[${n}A`;
|
|
23
23
|
}
|
|
24
|
-
export function moveDown(n = 1) {
|
|
25
|
-
return `${ESC}[${n}B`;
|
|
26
|
-
}
|
|
27
24
|
|
|
28
25
|
// ── 줄 제어 ──
|
|
29
26
|
export const clearLine = `${ESC}[2K`;
|
|
@@ -47,7 +44,7 @@ export const FG = {
|
|
|
47
44
|
gray: `${ESC}[90m`,
|
|
48
45
|
// triflux 브랜드
|
|
49
46
|
codex: `${ESC}[38;2;16;163;127m`, // #10a37f codex green
|
|
50
|
-
|
|
47
|
+
antigravity: `${ESC}[38;5;39m`, // blue
|
|
51
48
|
claude: `${ESC}[38;2;232;112;64m`, // orange
|
|
52
49
|
triflux: `${ESC}[38;5;214m`, // amber
|
|
53
50
|
accent: `${ESC}[38;5;75m`, // light blue (Catppuccin blue)
|
|
@@ -439,40 +436,9 @@ export function progressBar(percent, width = 20, time) {
|
|
|
439
436
|
return `${fillText}${emptyText}`;
|
|
440
437
|
}
|
|
441
438
|
|
|
442
|
-
// ── 애니메이션 진행률 바 (shimmer sweep) ──
|
|
443
|
-
export function animatedProgressBar(percent, width = 20, tick = 0) {
|
|
444
|
-
const ratio = Math.max(0, Math.min(100, percent)) / 100;
|
|
445
|
-
const filled = Math.round(ratio * width);
|
|
446
|
-
const empty = width - filled;
|
|
447
|
-
if (filled === 0 || percent >= 100) return progressBar(percent, width);
|
|
448
|
-
const baseClr = percent >= 50 ? MOCHA.partial : MOCHA.fail;
|
|
449
|
-
const pos = tick % (filled + 3);
|
|
450
|
-
let bar = "";
|
|
451
|
-
for (let i = 0; i < filled; i++) {
|
|
452
|
-
const d = Math.abs(i - pos);
|
|
453
|
-
if (d === 0) bar += `${ESC}[97m█`;
|
|
454
|
-
else if (d === 1) bar += `${baseClr}▓`;
|
|
455
|
-
else bar += `${baseClr}█`;
|
|
456
|
-
}
|
|
457
|
-
return `${bar}${MOCHA.border}${"░".repeat(empty)}${RESET}`;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// ── 상태 아이콘 ──
|
|
461
439
|
export const STATUS_ICON = {
|
|
462
440
|
running: `${MOCHA.partial}⏳${RESET}`,
|
|
463
441
|
completed: `${MOCHA.ok}✓${RESET}`,
|
|
464
442
|
failed: `${MOCHA.fail}✗${RESET}`,
|
|
465
443
|
pending: `${FG.gray}⏸${RESET}`,
|
|
466
444
|
};
|
|
467
|
-
|
|
468
|
-
export const CLI_ICON = {
|
|
469
|
-
codex: `${FG.codex}⚪${RESET}`,
|
|
470
|
-
gemini: `${FG.gemini}🔵${RESET}`,
|
|
471
|
-
claude: `${FG.claude}🟠${RESET}`,
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
// ── 로딩 도트 (braille spinner) ──
|
|
475
|
-
const BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
476
|
-
export function loadingDots(tick = 0, clr = MOCHA.thinking) {
|
|
477
|
-
return `${clr}${BRAILLE_FRAMES[tick % BRAILLE_FRAMES.length]}${RESET}`;
|
|
478
|
-
}
|
package/hub/team/backend.mjs
CHANGED
|
@@ -1,153 +1,17 @@
|
|
|
1
|
-
// hub/team/backend.mjs — CLI 백엔드 추상화 레이어
|
|
2
|
-
// 각 CLI(codex/antigravity/claude)의 명령 빌드 로직을 클래스로 캡슐화한다.
|
|
3
|
-
// v7.2.2
|
|
4
|
-
import { writeFileSync } from "node:fs";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
|
|
7
|
-
import { buildExecArgs } from "@triflux/core/hub/codex-adapter.mjs";
|
|
8
1
|
import { IS_WINDOWS } from "@triflux/core/hub/platform.mjs";
|
|
2
|
+
import { powershellSingleQuote, shellQuote } from "./terminal-opener.mjs";
|
|
9
3
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
prompt,
|
|
16
|
-
resultFile,
|
|
17
|
-
{ isWindows } = {},
|
|
18
|
-
) {
|
|
19
|
-
// Persist prompt to a sibling file so the shell never interpolates raw
|
|
20
|
-
// prompt content. Without this the Unix branch let `$()`, backticks, `\\`,
|
|
21
|
-
// and `"` reach the shell verbatim (P1: shell injection).
|
|
22
|
-
const promptFile = `${resultFile}.prompt`;
|
|
23
|
-
writeFileSync(promptFile, prompt);
|
|
24
|
-
// agy 1.1.27 부터 --print 는 값(프롬프트)이 필수이고 stdin 프롬프트는 거부된다.
|
|
25
|
-
// 파일 내용을 셸이 재해석하지 않는 형태(따옴표 안 명령 치환 / Get-Content 식)로 값에 넣는다.
|
|
26
|
-
if (isWindows) {
|
|
27
|
-
return `agy --dangerously-skip-permissions --print (Get-Content -Raw '${promptFile}') > '${resultFile}' 2>'${resultFile}.err'`;
|
|
28
|
-
}
|
|
29
|
-
return `agy --dangerously-skip-permissions --print "$(cat '${promptFile}')" > '${resultFile}' 2>'${resultFile}.err'`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const _require = createRequire(import.meta.url);
|
|
33
|
-
|
|
34
|
-
// ── 백엔드 클래스 ──────────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
export class CodexBackend {
|
|
37
|
-
name() {
|
|
38
|
-
return "codex";
|
|
39
|
-
}
|
|
40
|
-
command() {
|
|
41
|
-
return "codex";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @param {string} prompt — 프롬프트 (프롬프트 파일 경로가 아닌 PowerShell 표현식)
|
|
46
|
-
* @param {string} resultFile — 결과 저장 경로
|
|
47
|
-
* @param {object} [opts]
|
|
48
|
-
* @returns {string} PowerShell 명령 (cls 제외)
|
|
49
|
-
*/
|
|
50
|
-
buildArgs(prompt, resultFile, opts = {}) {
|
|
51
|
-
return buildExecArgs({ prompt, resultFile, ...opts });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
env() {
|
|
55
|
-
return {};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export class GeminiBackend {
|
|
60
|
-
name() {
|
|
61
|
-
return "gemini";
|
|
62
|
-
}
|
|
63
|
-
command() {
|
|
64
|
-
return "agy";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
buildArgs(prompt, resultFile, opts = {}) {
|
|
68
|
-
return buildGeminiCommand(prompt, resultFile, { isWindows: IS_WINDOWS });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
env() {
|
|
72
|
-
return {};
|
|
4
|
+
class ClaudeBackend {
|
|
5
|
+
buildArgs(prompt, resultFile, { isWindows = IS_WINDOWS } = {}) {
|
|
6
|
+
const quote = isWindows ? powershellSingleQuote : shellQuote;
|
|
7
|
+
const args = ["--print", prompt, "--output-format", "text"];
|
|
8
|
+
return `claude ${args.map(quote).join(" ")} > ${quote(resultFile)} 2>&1`;
|
|
73
9
|
}
|
|
74
10
|
}
|
|
75
11
|
|
|
76
|
-
|
|
77
|
-
name() {
|
|
78
|
-
return "claude";
|
|
79
|
-
}
|
|
80
|
-
command() {
|
|
81
|
-
return "claude";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
buildArgs(prompt, resultFile, opts = {}) {
|
|
85
|
-
return `claude --print ${prompt} --output-format text > '${resultFile}' 2>&1`;
|
|
86
|
-
}
|
|
12
|
+
const claude = new ClaudeBackend();
|
|
87
13
|
|
|
88
|
-
env() {
|
|
89
|
-
return {};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export class AntigravityBackend {
|
|
94
|
-
name() {
|
|
95
|
-
return "antigravity";
|
|
96
|
-
}
|
|
97
|
-
command() {
|
|
98
|
-
return "agy";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
buildArgs(prompt, resultFile, opts = {}) {
|
|
102
|
-
return buildAntigravityCommand(prompt, resultFile, {
|
|
103
|
-
isWindows: IS_WINDOWS,
|
|
104
|
-
...opts,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
env() {
|
|
109
|
-
return {};
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── 레지스트리 ─────────────────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
/** @type {Map<string, CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend>} */
|
|
116
|
-
const backends = new Map([
|
|
117
|
-
["codex", new CodexBackend()],
|
|
118
|
-
["gemini", new GeminiBackend()],
|
|
119
|
-
["claude", new ClaudeBackend()],
|
|
120
|
-
["antigravity", new AntigravityBackend()],
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 백엔드 이름으로 조회한다.
|
|
125
|
-
* @param {string} name — "codex" | "gemini" | "claude" | "antigravity"
|
|
126
|
-
* @returns {CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend}
|
|
127
|
-
* @throws {Error} 등록되지 않은 이름
|
|
128
|
-
*/
|
|
129
14
|
export function getBackend(name) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return b;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* 에이전트명 또는 CLI명을 Backend로 해석한다.
|
|
137
|
-
* agent-map.json을 통해 에이전트명 → CLI명으로 변환 후 레지스트리에서 조회한다.
|
|
138
|
-
* @param {string} agentOrCli — "executor", "codex", "designer" 등
|
|
139
|
-
* @returns {CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend}
|
|
140
|
-
*/
|
|
141
|
-
export function getBackendForAgent(agentOrCli) {
|
|
142
|
-
const agentMap = _require("./agent-map.json");
|
|
143
|
-
const cliName = agentMap[agentOrCli] || agentOrCli;
|
|
144
|
-
return getBackend(cliName);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 등록된 모든 백엔드를 반환한다.
|
|
149
|
-
* @returns {Array<CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend>}
|
|
150
|
-
*/
|
|
151
|
-
export function listBackends() {
|
|
152
|
-
return Array.from(backends.values());
|
|
15
|
+
if (name !== "claude") throw new Error(`지원하지 않는 CLI: ${name}`);
|
|
16
|
+
return claude;
|
|
153
17
|
}
|
|
@@ -6,11 +6,7 @@ import {
|
|
|
6
6
|
launchAttachInWindowsTerminal,
|
|
7
7
|
wantsWtAttachFallback,
|
|
8
8
|
} from "../services/attach-fallback.mjs";
|
|
9
|
-
import {
|
|
10
|
-
isNativeMode,
|
|
11
|
-
isTeamAlive,
|
|
12
|
-
isWtMode,
|
|
13
|
-
} from "../services/runtime-mode.mjs";
|
|
9
|
+
import { isTeamAlive } from "../services/runtime-mode.mjs";
|
|
14
10
|
import { loadTeamState } from "../services/state-store.mjs";
|
|
15
11
|
|
|
16
12
|
export async function teamAttach(args = []) {
|
|
@@ -19,18 +15,6 @@ export async function teamAttach(args = []) {
|
|
|
19
15
|
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
20
16
|
return;
|
|
21
17
|
}
|
|
22
|
-
if (isNativeMode(state)) {
|
|
23
|
-
console.log(
|
|
24
|
-
`\n ${DIM}in-process 모드는 별도 attach가 없습니다.${RESET}\n ${DIM}상태 확인: tfx multi status${RESET}\n`,
|
|
25
|
-
);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (isWtMode(state)) {
|
|
29
|
-
console.log(
|
|
30
|
-
`\n ${DIM}wt 모드는 attach 개념이 없습니다 (Windows Terminal pane가 독립 실행됨).${RESET}\n ${DIM}재실행/정리는: tfx multi stop${RESET}\n`,
|
|
31
|
-
);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
18
|
|
|
35
19
|
try {
|
|
36
20
|
attachSession(state.sessionName);
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { injectPrompt, sendKeys } from "../../pane.mjs";
|
|
2
|
-
import { DIM, RESET, WHITE
|
|
3
|
-
import { ok
|
|
4
|
-
import { publishLeadControl } from "../services/hub-client.mjs";
|
|
2
|
+
import { DIM, RESET, WHITE } from "../../shared.mjs";
|
|
3
|
+
import { ok } from "../render.mjs";
|
|
5
4
|
import { resolveMember } from "../services/member-selector.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
isNativeMode,
|
|
9
|
-
isTeamAlive,
|
|
10
|
-
isWtMode,
|
|
11
|
-
} from "../services/runtime-mode.mjs";
|
|
5
|
+
import { isTeamAlive } from "../services/runtime-mode.mjs";
|
|
12
6
|
import { loadTeamState } from "../services/state-store.mjs";
|
|
13
7
|
|
|
14
8
|
export async function teamControl(args = []) {
|
|
15
9
|
const state = loadTeamState();
|
|
10
|
+
if (state?.teammateMode === "headless") {
|
|
11
|
+
throw new Error("headless 실행에는 지원하지 않는다");
|
|
12
|
+
}
|
|
16
13
|
if (!state || !isTeamAlive(state)) {
|
|
17
14
|
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
18
15
|
return;
|
|
@@ -30,35 +27,12 @@ export async function teamControl(args = []) {
|
|
|
30
27
|
);
|
|
31
28
|
return;
|
|
32
29
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let directOk = false;
|
|
41
|
-
if (isNativeMode(state)) {
|
|
42
|
-
directOk = !!(
|
|
43
|
-
await nativeRequest(state, "/control", {
|
|
44
|
-
member: member.name,
|
|
45
|
-
command,
|
|
46
|
-
reason,
|
|
47
|
-
})
|
|
48
|
-
)?.ok;
|
|
49
|
-
} else {
|
|
50
|
-
injectPrompt(
|
|
51
|
-
member.pane,
|
|
52
|
-
`[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`,
|
|
53
|
-
);
|
|
54
|
-
if (command === "interrupt") sendKeys(member.pane, "C-c");
|
|
55
|
-
directOk = true;
|
|
56
|
-
}
|
|
30
|
+
injectPrompt(
|
|
31
|
+
member.pane,
|
|
32
|
+
`[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`,
|
|
33
|
+
);
|
|
34
|
+
if (command === "interrupt") sendKeys(member.pane, "C-c");
|
|
57
35
|
|
|
58
|
-
|
|
59
|
-
if (directOk && published)
|
|
60
|
-
ok(`${member.name} 제어 전송 (${command}, direct + hub)`);
|
|
61
|
-
else if (directOk) ok(`${member.name} 제어 전송 (${command}, direct only)`);
|
|
62
|
-
else warn(`${member.name} 제어 전송 실패 (${command})`);
|
|
36
|
+
ok(`${member.name} 제어 전송 (${command})`);
|
|
63
37
|
console.log("");
|
|
64
38
|
}
|
|
@@ -2,18 +2,10 @@ import {
|
|
|
2
2
|
capturePaneOutput,
|
|
3
3
|
detectMultiplexer,
|
|
4
4
|
getSessionAttachedCount,
|
|
5
|
-
hasWindowsTerminal,
|
|
6
|
-
hasWindowsTerminalSession,
|
|
7
5
|
listSessions,
|
|
8
6
|
} from "../../session.mjs";
|
|
9
7
|
import { AMBER, BOLD, DIM, RESET } from "../../shared.mjs";
|
|
10
|
-
import {
|
|
11
|
-
import { getHubInfo, nativeGetStatus } from "../services/hub-client.mjs";
|
|
12
|
-
import {
|
|
13
|
-
isNativeMode,
|
|
14
|
-
isTeamAlive,
|
|
15
|
-
isWtMode,
|
|
16
|
-
} from "../services/runtime-mode.mjs";
|
|
8
|
+
import { isTeamAlive } from "../services/runtime-mode.mjs";
|
|
17
9
|
import { loadTeamState, TEAM_PROFILE } from "../services/state-store.mjs";
|
|
18
10
|
|
|
19
11
|
export async function teamDebug(args = []) {
|
|
@@ -23,7 +15,6 @@ export async function teamDebug(args = []) {
|
|
|
23
15
|
flagIndex === -1
|
|
24
16
|
? 20
|
|
25
17
|
: Math.max(3, parseInt(args[flagIndex + 1] || "20", 10) || 20);
|
|
26
|
-
const hub = await getHubInfo();
|
|
27
18
|
|
|
28
19
|
console.log(`\n ${AMBER}${BOLD}⬡ Team Debug${RESET}\n`);
|
|
29
20
|
console.log(` platform: ${process.platform}`);
|
|
@@ -32,8 +23,6 @@ export async function teamDebug(args = []) {
|
|
|
32
23
|
` tty: stdout=${!!process.stdout.isTTY}, stdin=${!!process.stdin.isTTY}`,
|
|
33
24
|
);
|
|
34
25
|
console.log(` mux: ${detectMultiplexer() || "none"}`);
|
|
35
|
-
console.log(` hub-pid: ${hub ? `${hub.pid}` : "-"}`);
|
|
36
|
-
console.log(` hub-url: ${hub?.url || "-"}`);
|
|
37
26
|
const sessions = listSessions();
|
|
38
27
|
console.log(` sessions: ${sessions.length ? sessions.join(", ") : "-"}`);
|
|
39
28
|
|
|
@@ -53,31 +42,6 @@ export async function teamDebug(args = []) {
|
|
|
53
42
|
` attached: ${getSessionAttachedCount(state.sessionName) ?? "-"}`,
|
|
54
43
|
);
|
|
55
44
|
|
|
56
|
-
if (isWtMode(state)) {
|
|
57
|
-
console.log(`\n ${BOLD}wt-session${RESET}`);
|
|
58
|
-
console.log(` window: ${state?.wt?.windowId ?? 0}`);
|
|
59
|
-
console.log(` layout: ${state?.wt?.layout || state?.layout || "-"}`);
|
|
60
|
-
console.log(
|
|
61
|
-
` panes: ${state?.wt?.paneCount ?? (state.members || []).length}`,
|
|
62
|
-
);
|
|
63
|
-
console.log(` wt.exe: ${hasWindowsTerminal() ? "yes" : "no"}`);
|
|
64
|
-
console.log(` WT_SESSION:${hasWindowsTerminalSession() ? "yes" : "no"}`);
|
|
65
|
-
console.log("");
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (isNativeMode(state)) {
|
|
70
|
-
console.log(`\n ${BOLD}native-members${RESET}`);
|
|
71
|
-
const members = (await nativeGetStatus(state))?.data?.members || [];
|
|
72
|
-
if (!members.length) console.log(` ${DIM}(no data)${RESET}`);
|
|
73
|
-
for (const member of members)
|
|
74
|
-
console.log(
|
|
75
|
-
` - ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`,
|
|
76
|
-
);
|
|
77
|
-
console.log("");
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
45
|
console.log(
|
|
82
46
|
`\n ${BOLD}pane-tail${RESET} ${DIM}(last ${lines} lines)${RESET}`,
|
|
83
47
|
);
|