@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
package/cto/status.mjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
|
|
5
4
|
import { compactHygieneCounts, projectCtoHygiene } from "./hygiene.mjs";
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
normalizeLiveSession,
|
|
7
|
+
pathLabel,
|
|
8
|
+
readSynapseSnapshot,
|
|
9
|
+
resolveLakeRootDir,
|
|
10
|
+
shortHash,
|
|
11
|
+
} from "./lake-root.mjs";
|
|
7
12
|
|
|
8
13
|
const SCHEMA_VERSION = "cto-lake.v1";
|
|
9
14
|
const SYNAPSE_TIMEOUT_MS = 1500;
|
|
@@ -20,36 +25,6 @@ function readJson(filePath) {
|
|
|
20
25
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
function firstExisting(paths) {
|
|
24
|
-
return paths.filter(Boolean).find((path) => existsSync(path)) || null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function toIsoTime(value) {
|
|
28
|
-
if (typeof value === "string" && value.trim()) return value;
|
|
29
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
30
|
-
return new Date(value).toISOString();
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Non-cryptographic djb2-style hash used only to produce stable redacted
|
|
36
|
-
// labels for local paths. Do not use this as a trust boundary or secret token.
|
|
37
|
-
function shortHash(value) {
|
|
38
|
-
const str = String(value ?? "");
|
|
39
|
-
let h = 5381;
|
|
40
|
-
for (let i = 0; i < str.length; i++) {
|
|
41
|
-
h = (h * 33) ^ str.charCodeAt(i);
|
|
42
|
-
}
|
|
43
|
-
return (h >>> 0).toString(36);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function pathLabel(value) {
|
|
47
|
-
const str = String(value ?? "").replace(/[/\\]+$/u, "");
|
|
48
|
-
if (!str) return "";
|
|
49
|
-
const segments = str.split(/[/\\]+/u);
|
|
50
|
-
return segments[segments.length - 1] || "";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
28
|
export function deriveRepoRootFromCwd(cwd) {
|
|
54
29
|
if (typeof cwd !== "string" || !cwd) return "";
|
|
55
30
|
if (cwd.includes("\\") || /^[A-Za-z]:/u.test(cwd)) return cwd;
|
|
@@ -98,54 +73,6 @@ function redactedCwdFields(cwd) {
|
|
|
98
73
|
};
|
|
99
74
|
}
|
|
100
75
|
|
|
101
|
-
function normalizeLiveSession(session) {
|
|
102
|
-
const cwd = typeof session?.cwd === "string" ? session.cwd : "";
|
|
103
|
-
return {
|
|
104
|
-
sessionId: String(session?.sessionId || ""),
|
|
105
|
-
host: typeof session?.host === "string" ? session.host : "local",
|
|
106
|
-
agent_id:
|
|
107
|
-
typeof session?.agent_id === "string"
|
|
108
|
-
? session.agent_id
|
|
109
|
-
: typeof session?.agentId === "string"
|
|
110
|
-
? session.agentId
|
|
111
|
-
: null,
|
|
112
|
-
phase:
|
|
113
|
-
typeof session?.phase === "string"
|
|
114
|
-
? session.phase
|
|
115
|
-
: typeof session?.status === "string"
|
|
116
|
-
? session.status
|
|
117
|
-
: "active",
|
|
118
|
-
started_at: toIsoTime(
|
|
119
|
-
session?.started_at ?? session?.startedAt ?? session?.lastHeartbeat,
|
|
120
|
-
),
|
|
121
|
-
...redactedCwdFields(cwd),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function normalizeActiveShard(shard) {
|
|
126
|
-
return {
|
|
127
|
-
shard_name:
|
|
128
|
-
typeof shard?.shard_name === "string"
|
|
129
|
-
? shard.shard_name
|
|
130
|
-
: typeof shard?.shardName === "string"
|
|
131
|
-
? shard.shardName
|
|
132
|
-
: typeof shard?.name === "string"
|
|
133
|
-
? shard.name
|
|
134
|
-
: typeof shard?.id === "string"
|
|
135
|
-
? shard.id
|
|
136
|
-
: null,
|
|
137
|
-
phase:
|
|
138
|
-
typeof shard?.phase === "string"
|
|
139
|
-
? shard.phase
|
|
140
|
-
: typeof shard?.status === "string"
|
|
141
|
-
? shard.status
|
|
142
|
-
: "active",
|
|
143
|
-
members: Array.isArray(shard?.members)
|
|
144
|
-
? shard.members.map((member) => String(member))
|
|
145
|
-
: [],
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
76
|
function normalizeSynapseOverlay(value) {
|
|
150
77
|
const sessions = Array.isArray(value)
|
|
151
78
|
? value
|
|
@@ -154,17 +81,15 @@ function normalizeSynapseOverlay(value) {
|
|
|
154
81
|
: Array.isArray(value?.live_sessions)
|
|
155
82
|
? value.live_sessions
|
|
156
83
|
: [];
|
|
157
|
-
const activeShards = Array.isArray(value?.active_shards)
|
|
158
|
-
? value.active_shards
|
|
159
|
-
: Array.isArray(value?.activeShards)
|
|
160
|
-
? value.activeShards
|
|
161
|
-
: [];
|
|
162
84
|
|
|
163
85
|
return {
|
|
164
86
|
live_sessions: sessions
|
|
165
|
-
.map(
|
|
87
|
+
.map((session) => ({
|
|
88
|
+
...normalizeLiveSession(session),
|
|
89
|
+
host: typeof session?.host === "string" ? session.host : "local",
|
|
90
|
+
...redactedCwdFields(session?.cwd),
|
|
91
|
+
}))
|
|
166
92
|
.filter((session) => session.sessionId),
|
|
167
|
-
active_shards: activeShards.map(normalizeActiveShard),
|
|
168
93
|
};
|
|
169
94
|
}
|
|
170
95
|
|
|
@@ -182,27 +107,9 @@ function withTimeout(promise, timeoutMs = SYNAPSE_TIMEOUT_MS) {
|
|
|
182
107
|
});
|
|
183
108
|
}
|
|
184
109
|
|
|
185
|
-
async function readSynapseWithRegistry(opts = {}) {
|
|
186
|
-
const rootDir = opts.rootDir || process.cwd();
|
|
187
|
-
const persistPath = firstExisting([
|
|
188
|
-
opts.synapsePersistPath,
|
|
189
|
-
join(rootDir, ".triflux", "synapse-registry.json"),
|
|
190
|
-
join(rootDir, ".triflux", "synapse", "registry.json"),
|
|
191
|
-
join(homedir(), ".claude", "cache", "tfx-hub", "synapse-sessions.json"),
|
|
192
|
-
join(homedir(), ".claude", "cache", "tfx-hub", "synapse-registry.json"),
|
|
193
|
-
]);
|
|
194
|
-
if (!persistPath) return { sessions: [], active_shards: [] };
|
|
195
|
-
|
|
196
|
-
const { createSynapseRegistry } = await import(
|
|
197
|
-
"../hub/team/synapse-registry.mjs"
|
|
198
|
-
);
|
|
199
|
-
const registry = createSynapseRegistry({ persistPath });
|
|
200
|
-
return { sessions: registry.getActive(), active_shards: [] };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
110
|
async function readSynapseOverlay(opts) {
|
|
204
111
|
try {
|
|
205
|
-
const reader = opts.synapseReader ||
|
|
112
|
+
const reader = opts.synapseReader || readSynapseSnapshot;
|
|
206
113
|
const raw = await withTimeout(
|
|
207
114
|
reader({
|
|
208
115
|
rootDir: opts.rootDir,
|
|
@@ -213,28 +120,10 @@ async function readSynapseOverlay(opts) {
|
|
|
213
120
|
);
|
|
214
121
|
return normalizeSynapseOverlay(raw);
|
|
215
122
|
} catch {
|
|
216
|
-
return { live_sessions: []
|
|
123
|
+
return { live_sessions: [] };
|
|
217
124
|
}
|
|
218
125
|
}
|
|
219
126
|
|
|
220
|
-
// active_shards 의 source 는 live_sessions 와 다르다: live_sessions 는 synapse
|
|
221
|
-
// overlay(실시간 세션)에서, active_shards 는 collect 가 .triflux/swarm-locks.json 을
|
|
222
|
-
// snapshot 한 current.json(sources.tfx_swarm.detail.shards)에서 온다. synapse
|
|
223
|
-
// overlay 가 shard 를 채우는 경로가 생기면 그쪽을 우선하고, 없으면 snapshot 으로
|
|
224
|
-
// fallback 한다(현재 overlay 의 active_shards 는 항상 비어 snapshot 을 쓴다).
|
|
225
|
-
function deriveActiveShards(current, overlay) {
|
|
226
|
-
// overlay(synapse)가 채운 shard 중 식별 가능한 것만 authoritative 로 본다.
|
|
227
|
-
// normalizeSynapseOverlay 가 shard_name 을 null 로 정규화한 무효 행이 snapshot
|
|
228
|
-
// fallback 을 가로채지 않도록 먼저 거른다.
|
|
229
|
-
const overlayShards = Array.isArray(overlay?.active_shards)
|
|
230
|
-
? overlay.active_shards.filter((shard) => shard?.shard_name)
|
|
231
|
-
: [];
|
|
232
|
-
if (overlayShards.length > 0) return overlayShards;
|
|
233
|
-
const shards = current?.sources?.tfx_swarm?.detail?.shards;
|
|
234
|
-
if (!Array.isArray(shards)) return [];
|
|
235
|
-
return shards.map(normalizeActiveShard).filter((shard) => shard.shard_name);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
127
|
function deriveLiveSessionGroups(liveSessions) {
|
|
239
128
|
const groups = new Map();
|
|
240
129
|
for (const session of liveSessions || []) {
|
|
@@ -265,7 +154,7 @@ function projectStatus(current, overlay) {
|
|
|
265
154
|
ledger_tail: Array.isArray(current?.ledger_tail) ? current.ledger_tail : [],
|
|
266
155
|
live_sessions: liveSessions,
|
|
267
156
|
live_session_groups: deriveLiveSessionGroups(liveSessions),
|
|
268
|
-
active_shards:
|
|
157
|
+
active_shards: [],
|
|
269
158
|
hygiene: compactHygieneCounts(projectCtoHygiene({ current, overlay })),
|
|
270
159
|
};
|
|
271
160
|
}
|
|
@@ -300,13 +189,29 @@ function formatLedgerEntry(entry) {
|
|
|
300
189
|
return `${ts} ${event}${summary}`;
|
|
301
190
|
}
|
|
302
191
|
|
|
303
|
-
function
|
|
192
|
+
function formatSnapshotAge(generatedAt, now) {
|
|
193
|
+
const ageMs = new Date(now).getTime() - Date.parse(generatedAt);
|
|
194
|
+
if (!Number.isFinite(ageMs)) return "unknown age";
|
|
195
|
+
if (ageMs < 0) return "in the future";
|
|
196
|
+
for (const [unit, ms] of [
|
|
197
|
+
["d", 86400000],
|
|
198
|
+
["h", 3600000],
|
|
199
|
+
["m", 60000],
|
|
200
|
+
["s", 1000],
|
|
201
|
+
]) {
|
|
202
|
+
if (ageMs >= ms || unit === "s")
|
|
203
|
+
return `${Math.floor(ageMs / ms)}${unit} ago`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function renderHumanStatus(status, now) {
|
|
304
208
|
const { available, total } = countAvailableSources(status.sources);
|
|
305
209
|
const activeGoals = countActiveGoals(status);
|
|
306
210
|
const recent = Array.isArray(status.ledger_tail)
|
|
307
211
|
? status.ledger_tail.slice(-2)
|
|
308
212
|
: [];
|
|
309
213
|
const lines = [
|
|
214
|
+
`generated_at: ${status.generated_at || "unknown"} (${formatSnapshotAge(status.generated_at, now)})`,
|
|
310
215
|
`repo: ${formatRepo(status.repo)}`,
|
|
311
216
|
`sources: ${available}/${total} available`,
|
|
312
217
|
`active: ${status.live_sessions.length} sessions, ${status.active_shards.length} shards, ${activeGoals} goals`,
|
|
@@ -347,7 +252,7 @@ export async function runStatus(args = [], opts = {}) {
|
|
|
347
252
|
const status = projectStatus(current, overlay);
|
|
348
253
|
|
|
349
254
|
if (jsonOut) writeJson(stdout, status);
|
|
350
|
-
else stdout.write(renderHumanStatus(status));
|
|
255
|
+
else stdout.write(renderHumanStatus(status, opts.now ?? Date.now()));
|
|
351
256
|
|
|
352
257
|
return status;
|
|
353
258
|
}
|
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/store-adapter.mjs
CHANGED
|
@@ -135,7 +135,7 @@ function attachAdaptiveRuleStore(store) {
|
|
|
135
135
|
return statements.pruneRules.run(cutoff, minConfidence).changes;
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
-
// listAdaptiveRules: decayRules
|
|
138
|
+
// listAdaptiveRules: decayRules에서 사용
|
|
139
139
|
const listStmt = store.db.prepare(
|
|
140
140
|
"SELECT * FROM adaptive_rules WHERE project_slug = ? ORDER BY confidence DESC",
|
|
141
141
|
);
|
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
|
);
|