@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,62 +0,0 @@
|
|
|
1
|
-
// cli-codex.mjs — Codex CLI adapter (Phase 0 PoC).
|
|
2
|
-
//
|
|
3
|
-
// 책임: agent 이름과 입력 파라미터를 받아 Codex 실행 계획(profile/effort/timeout/runMode)을
|
|
4
|
-
// 산출한다. 실제 spawn 은 Phase 1 에서 이관된다 (현재는 tfx-route.sh 가 수행).
|
|
5
|
-
//
|
|
6
|
-
// 진입점: tfx-route.mjs → selectAdapter("codex") → plan({...}).
|
|
7
|
-
// 출처: agent-route-policy.mjs 의 canonical Codex role policy.
|
|
8
|
-
// timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
|
|
9
|
-
|
|
10
|
-
export const id = "codex";
|
|
11
|
-
export const cliType = "codex";
|
|
12
|
-
export const command = "codex";
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
CODEX_AGENT_POLICY,
|
|
16
|
-
resolveCodexAgentPolicy,
|
|
17
|
-
resolveCodexAgentProfile,
|
|
18
|
-
resolveNestedCodexAgentProfile,
|
|
19
|
-
} from "./agent-route-policy.mjs";
|
|
20
|
-
|
|
21
|
-
// Compatibility re-exports for launchers which historically imported these
|
|
22
|
-
// helpers from this adapter. The policy module owns the semantics.
|
|
23
|
-
export { resolveCodexAgentProfile, resolveNestedCodexAgentProfile };
|
|
24
|
-
|
|
25
|
-
export function plan({
|
|
26
|
-
agent,
|
|
27
|
-
prompt = "",
|
|
28
|
-
mcpProfile = "auto",
|
|
29
|
-
timeoutSec,
|
|
30
|
-
contextFile,
|
|
31
|
-
profileOverride = "auto",
|
|
32
|
-
retryProfile = null,
|
|
33
|
-
nested = false,
|
|
34
|
-
} = {}) {
|
|
35
|
-
if (!agent) {
|
|
36
|
-
throw new Error("[cli-codex] agent required");
|
|
37
|
-
}
|
|
38
|
-
const cfg = resolveCodexAgentPolicy(agent);
|
|
39
|
-
const profile = resolveCodexAgentProfile(agent, {
|
|
40
|
-
profileOverride,
|
|
41
|
-
retryProfile,
|
|
42
|
-
nested,
|
|
43
|
-
});
|
|
44
|
-
const effectiveTimeoutSec =
|
|
45
|
-
Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
|
|
46
|
-
return {
|
|
47
|
-
command,
|
|
48
|
-
subcommand: cfg.subcommand ?? "exec",
|
|
49
|
-
profile,
|
|
50
|
-
effort: profile,
|
|
51
|
-
timeoutMs: effectiveTimeoutSec * 1000,
|
|
52
|
-
runMode: cfg.runMode,
|
|
53
|
-
opusOversight: cfg.opusOversight,
|
|
54
|
-
mcpProfile: mcpProfile === "auto" ? cfg.mcpHint : mcpProfile,
|
|
55
|
-
promptLength: prompt.length,
|
|
56
|
-
contextFile: contextFile || null,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function describe() {
|
|
61
|
-
return { id, cliType, command, agents: Object.keys(CODEX_AGENT_POLICY) };
|
|
62
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// cli-gemini.mjs — legacy Gemini adapter alias.
|
|
2
|
-
// The public "gemini" label remains accepted, but the executable plan is AGY.
|
|
3
|
-
|
|
4
|
-
import * as agyAdapter from "./cli-agy.mjs";
|
|
5
|
-
|
|
6
|
-
export const id = "gemini";
|
|
7
|
-
export const cliType = "antigravity";
|
|
8
|
-
export const command = "agy";
|
|
9
|
-
|
|
10
|
-
export function plan({
|
|
11
|
-
agent,
|
|
12
|
-
prompt = "",
|
|
13
|
-
mcpProfile = "auto",
|
|
14
|
-
timeoutSec,
|
|
15
|
-
contextFile,
|
|
16
|
-
} = {}) {
|
|
17
|
-
if (!agent) {
|
|
18
|
-
throw new Error("[cli-gemini] agent required");
|
|
19
|
-
}
|
|
20
|
-
return agyAdapter.plan({
|
|
21
|
-
agent: agent === "gemini" ? "antigravity" : agent,
|
|
22
|
-
prompt,
|
|
23
|
-
mcpProfile,
|
|
24
|
-
timeoutSec,
|
|
25
|
-
contextFile,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function describe() {
|
|
30
|
-
return {
|
|
31
|
-
id,
|
|
32
|
-
cliType,
|
|
33
|
-
command,
|
|
34
|
-
aliases: ["gemini"],
|
|
35
|
-
agents: ["gemini", "designer", "writer", "antigravity", "agy"],
|
|
36
|
-
};
|
|
37
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/lib/dynamic-route-cli.mjs
|
|
3
|
-
//
|
|
4
|
-
// Phase 1 Wire-up 3 helper — tfx-route.sh 등 sh 경로에서 dynamic routing
|
|
5
|
-
// decision 을 받아 단일 CLI 문자열 (또는 JSON) 으로 출력한다.
|
|
6
|
-
//
|
|
7
|
-
// 사용:
|
|
8
|
-
// node scripts/lib/dynamic-route-cli.mjs --agent-hint codex
|
|
9
|
-
// → "codex"
|
|
10
|
-
// node scripts/lib/dynamic-route-cli.mjs --agent-hint codex --json
|
|
11
|
-
// → {"scenario":"fallback","mode":"codex-default","shards":[{"cli":"codex",...}]}
|
|
12
|
-
// node scripts/lib/dynamic-route-cli.mjs --agent-hint codex --field scenario
|
|
13
|
-
// → "fallback"
|
|
14
|
-
//
|
|
15
|
-
// 정책:
|
|
16
|
-
// - opt-in env TRIFLUX_DYNAMIC_ROUTING=1|true 미설정 시 즉시 silent exit 0
|
|
17
|
-
// (override 없음 = stdout empty, sh caller 가 graceful no-op).
|
|
18
|
-
// - error 발생 시 stderr 에 한 줄 + exit 0 (sh caller 가 fallback path 진행).
|
|
19
|
-
|
|
20
|
-
import { createDynamicRouter } from "@triflux/core/hub/dynamic-routing-engine.mjs";
|
|
21
|
-
|
|
22
|
-
function parseArgs(argv) {
|
|
23
|
-
const args = {
|
|
24
|
-
taskId: `tfx-route-${process.pid}-${Date.now()}`,
|
|
25
|
-
agentHint: "codex",
|
|
26
|
-
teamSize: 1,
|
|
27
|
-
json: false,
|
|
28
|
-
field: null,
|
|
29
|
-
};
|
|
30
|
-
for (let i = 2; i < argv.length; i++) {
|
|
31
|
-
const v = argv[i];
|
|
32
|
-
switch (v) {
|
|
33
|
-
case "--task-id":
|
|
34
|
-
args.taskId = argv[++i] || args.taskId;
|
|
35
|
-
break;
|
|
36
|
-
case "--agent-hint":
|
|
37
|
-
args.agentHint = argv[++i] || args.agentHint;
|
|
38
|
-
break;
|
|
39
|
-
case "--team-size":
|
|
40
|
-
args.teamSize = Number(argv[++i]) || args.teamSize;
|
|
41
|
-
break;
|
|
42
|
-
case "--json":
|
|
43
|
-
args.json = true;
|
|
44
|
-
break;
|
|
45
|
-
case "--field":
|
|
46
|
-
args.field = argv[++i] || null;
|
|
47
|
-
break;
|
|
48
|
-
case "--help":
|
|
49
|
-
case "-h":
|
|
50
|
-
process.stdout.write(
|
|
51
|
-
"Usage: dynamic-route-cli.mjs [--task-id ID] [--agent-hint CLI] [--team-size N] [--json|--field FIELD]\n",
|
|
52
|
-
);
|
|
53
|
-
process.exit(0);
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return args;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function main() {
|
|
61
|
-
const args = parseArgs(process.argv);
|
|
62
|
-
|
|
63
|
-
// opt-in env 미설정 시 silent no-op (sh caller graceful path)
|
|
64
|
-
const flag = process.env.TRIFLUX_DYNAMIC_ROUTING;
|
|
65
|
-
if (flag !== "1" && flag !== "true") return;
|
|
66
|
-
|
|
67
|
-
let decision;
|
|
68
|
-
try {
|
|
69
|
-
const router = createDynamicRouter();
|
|
70
|
-
decision = await router.routeRequest({
|
|
71
|
-
task_id: args.taskId,
|
|
72
|
-
team_size: args.teamSize,
|
|
73
|
-
agent_hint: args.agentHint,
|
|
74
|
-
});
|
|
75
|
-
} catch (err) {
|
|
76
|
-
process.stderr.write(
|
|
77
|
-
`[dynamic-route-cli] error: ${err?.message || String(err)}\n`,
|
|
78
|
-
);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (args.json) {
|
|
83
|
-
process.stdout.write(JSON.stringify(decision));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (args.field) {
|
|
88
|
-
const value = decision?.[args.field];
|
|
89
|
-
if (value !== undefined && value !== null) {
|
|
90
|
-
process.stdout.write(String(value));
|
|
91
|
-
}
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 기본 — shards[0].cli 만 stdout 으로 (sh caller 가 즉시 비교)
|
|
96
|
-
const cli = decision?.shards?.[0]?.cli;
|
|
97
|
-
if (cli) {
|
|
98
|
-
process.stdout.write(String(cli));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
main().catch((err) => {
|
|
103
|
-
process.stderr.write(
|
|
104
|
-
`[dynamic-route-cli] fatal: ${err?.message || String(err)}\n`,
|
|
105
|
-
);
|
|
106
|
-
process.exit(0); // sh caller 가 정상 fallback 하도록 always 0
|
|
107
|
-
});
|
package/scripts/lib/env.mjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function buildPreflightEnv(env = process.env) {
|
|
2
|
-
return {
|
|
3
|
-
...env,
|
|
4
|
-
GIT_TERMINAL_PROMPT: env.GIT_TERMINAL_PROMPT ?? "0",
|
|
5
|
-
GIT_ASKPASS: env.GIT_ASKPASS ?? "false",
|
|
6
|
-
npm_config_yes: env.npm_config_yes ?? "true",
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function shouldWarnGhAuth(env = process.env, checks = {}) {
|
|
11
|
-
if (!checks.ghExists) return false;
|
|
12
|
-
if (env.GH_TOKEN || env.GITHUB_TOKEN) return false;
|
|
13
|
-
return checks.ghAuthenticated === false;
|
|
14
|
-
}
|
package/scripts/lib/hub.mjs
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export function parseHubPort(hubUrl = "") {
|
|
4
|
-
try {
|
|
5
|
-
const parsed = new URL(hubUrl);
|
|
6
|
-
return parsed.port || "27888";
|
|
7
|
-
} catch {
|
|
8
|
-
return "27888";
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function restartHub({
|
|
13
|
-
hubUrl = process.env.TFX_HUB_URL || "http://127.0.0.1:27888",
|
|
14
|
-
hubServer,
|
|
15
|
-
nodeBin = process.execPath,
|
|
16
|
-
spawnFn = spawn,
|
|
17
|
-
fetchFn = globalThis.fetch,
|
|
18
|
-
sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
19
|
-
attempts = 8,
|
|
20
|
-
} = {}) {
|
|
21
|
-
if (!hubServer) return { ok: false, error: "hub_server_missing" };
|
|
22
|
-
const port = parseHubPort(hubUrl);
|
|
23
|
-
const child = spawnFn(nodeBin, [hubServer], {
|
|
24
|
-
env: { ...process.env, TFX_HUB_PORT: port },
|
|
25
|
-
stdio: "ignore",
|
|
26
|
-
detached: true,
|
|
27
|
-
});
|
|
28
|
-
child.unref?.();
|
|
29
|
-
|
|
30
|
-
for (let i = 0; i < attempts; i += 1) {
|
|
31
|
-
await sleepMs(500);
|
|
32
|
-
try {
|
|
33
|
-
const response = await fetchFn(`${hubUrl.replace(/\/$/, "")}/status`);
|
|
34
|
-
if (response?.ok) return { ok: true, pid: child.pid };
|
|
35
|
-
} catch {
|
|
36
|
-
// Keep polling until attempts are exhausted.
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return { ok: false, pid: child.pid };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function withHubRestart({ action, restart }) {
|
|
43
|
-
const first = await action();
|
|
44
|
-
if (first?.ok) return first;
|
|
45
|
-
const restarted = await restart();
|
|
46
|
-
if (!restarted?.ok) return first;
|
|
47
|
-
return action();
|
|
48
|
-
}
|
package/scripts/lib/pid.mjs
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
let treeKill = null;
|
|
6
|
-
try {
|
|
7
|
-
treeKill = require("tree-kill");
|
|
8
|
-
} catch {
|
|
9
|
-
treeKill = null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function defaultIsAlive(pid) {
|
|
13
|
-
try {
|
|
14
|
-
process.kill(pid, 0);
|
|
15
|
-
return true;
|
|
16
|
-
} catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function defaultKillTree(pid) {
|
|
22
|
-
return new Promise((resolve) => {
|
|
23
|
-
if (treeKill) {
|
|
24
|
-
treeKill(pid, "SIGTERM", () => resolve());
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
process.kill(pid, "SIGTERM");
|
|
29
|
-
} catch {
|
|
30
|
-
// Process already exited.
|
|
31
|
-
}
|
|
32
|
-
resolve();
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class PidTracker {
|
|
37
|
-
constructor(path, options = {}) {
|
|
38
|
-
this.path = path;
|
|
39
|
-
this.isAlive = options.isAlive ?? defaultIsAlive;
|
|
40
|
-
this.killTree = options.killTree ?? defaultKillTree;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
track(pid) {
|
|
44
|
-
if (!pid) return;
|
|
45
|
-
appendFileSync(this.path, `${pid}\n`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
list() {
|
|
49
|
-
if (!existsSync(this.path)) return [];
|
|
50
|
-
return readFileSync(this.path, "utf8")
|
|
51
|
-
.split(/\r?\n/)
|
|
52
|
-
.map((line) => Number(line.trim()))
|
|
53
|
-
.filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async cleanup() {
|
|
57
|
-
for (const pid of this.list()) {
|
|
58
|
-
if (!this.isAlive(pid)) continue;
|
|
59
|
-
await this.killTree(pid);
|
|
60
|
-
}
|
|
61
|
-
rmSync(this.path, { force: true });
|
|
62
|
-
}
|
|
63
|
-
}
|
package/scripts/lib/quota.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export const QUOTA_PATTERNS = [
|
|
2
|
-
/usage limit exceeded/i,
|
|
3
|
-
/rate limit exceeded/i,
|
|
4
|
-
/rate limit reached/i,
|
|
5
|
-
/try again at/i,
|
|
6
|
-
/purchase more credits/i,
|
|
7
|
-
/quota exceeded/i,
|
|
8
|
-
/RESOURCE_EXHAUSTED/i,
|
|
9
|
-
/rateLimitExceeded/i,
|
|
10
|
-
/Too Many Requests/i,
|
|
11
|
-
/rate_limit_error/i,
|
|
12
|
-
/overloaded_error/i,
|
|
13
|
-
/insufficient_quota/i,
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
export class QuotaStreamBuffer {
|
|
17
|
-
constructor({ maxBytes = 65536 } = {}) {
|
|
18
|
-
this.maxBytes = maxBytes;
|
|
19
|
-
this.text = "";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
push(chunk) {
|
|
23
|
-
this.text += Buffer.isBuffer(chunk)
|
|
24
|
-
? chunk.toString("utf8")
|
|
25
|
-
: String(chunk);
|
|
26
|
-
if (this.text.length > this.maxBytes) {
|
|
27
|
-
this.text = this.text.slice(-this.maxBytes);
|
|
28
|
-
}
|
|
29
|
-
return detectQuotaText(this.text);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function detectQuotaText(text = "") {
|
|
34
|
-
return QUOTA_PATTERNS.some((pattern) => pattern.test(text));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function getRerouteTarget(cliType) {
|
|
38
|
-
switch (cliType) {
|
|
39
|
-
case "codex":
|
|
40
|
-
case "gemini":
|
|
41
|
-
return "antigravity";
|
|
42
|
-
case "antigravity":
|
|
43
|
-
return "codex";
|
|
44
|
-
default:
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
access,
|
|
3
|
-
mkdir,
|
|
4
|
-
open,
|
|
5
|
-
readdir,
|
|
6
|
-
readFile,
|
|
7
|
-
rename,
|
|
8
|
-
rm,
|
|
9
|
-
writeFile,
|
|
10
|
-
} from "node:fs/promises";
|
|
11
|
-
import { basename, join } from "node:path";
|
|
12
|
-
|
|
13
|
-
const DEFAULT_STATE_DIR = join(process.cwd(), ".tfx", "state");
|
|
14
|
-
const STOP_HOOKS = new Map();
|
|
15
|
-
|
|
16
|
-
function stateFilePath(stateDir, skillName) {
|
|
17
|
-
return join(stateDir, `${skillName}-active.json`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function assertValidSkillName(skillName) {
|
|
21
|
-
if (basename(skillName) !== skillName) {
|
|
22
|
-
throw new Error(`Invalid skill name: ${skillName}`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function assertValidOnStop(onStop) {
|
|
27
|
-
if (onStop !== undefined && typeof onStop !== "function") {
|
|
28
|
-
throw new TypeError("onStop must be a function");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function readStateFile(filePath) {
|
|
33
|
-
try {
|
|
34
|
-
const raw = await readFile(filePath, "utf8");
|
|
35
|
-
return JSON.parse(raw);
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function rememberStopHook(filePath, onStop) {
|
|
42
|
-
if (typeof onStop === "function") {
|
|
43
|
-
STOP_HOOKS.set(filePath, onStop);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
STOP_HOOKS.delete(filePath);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function logStopHookWarning(message, error) {
|
|
50
|
-
if (error) {
|
|
51
|
-
console.warn(message, error);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
console.warn(message);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function runStopHook(filePath, state, onStop) {
|
|
58
|
-
if (!state?.hasStopHook) {
|
|
59
|
-
STOP_HOOKS.delete(filePath);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const hook = onStop ?? STOP_HOOKS.get(filePath);
|
|
64
|
-
STOP_HOOKS.delete(filePath);
|
|
65
|
-
|
|
66
|
-
if (typeof hook !== "function") {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
await hook({
|
|
72
|
-
skillName: state.skillName,
|
|
73
|
-
filePath,
|
|
74
|
-
state,
|
|
75
|
-
});
|
|
76
|
-
} catch (error) {
|
|
77
|
-
logStopHookWarning(
|
|
78
|
-
`Failed to run stop-hook for skill: ${state.skillName}`,
|
|
79
|
-
error,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Activate a skill by writing its state file.
|
|
86
|
-
* Throws if the skill is already active.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} skillName
|
|
89
|
-
* @param {{ stateDir?: string, onStop?: (() => Promise<void> | void) }} options
|
|
90
|
-
*/
|
|
91
|
-
export async function activateSkill(
|
|
92
|
-
skillName,
|
|
93
|
-
{ stateDir = DEFAULT_STATE_DIR, onStop } = {},
|
|
94
|
-
) {
|
|
95
|
-
assertValidSkillName(skillName);
|
|
96
|
-
assertValidOnStop(onStop);
|
|
97
|
-
|
|
98
|
-
await mkdir(stateDir, { recursive: true });
|
|
99
|
-
|
|
100
|
-
const filePath = stateFilePath(stateDir, skillName);
|
|
101
|
-
const lockPath = `${filePath}.lock`;
|
|
102
|
-
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
103
|
-
let lockHandle;
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
lockHandle = await open(lockPath, "wx");
|
|
107
|
-
try {
|
|
108
|
-
await access(filePath);
|
|
109
|
-
throw new Error(`Skill already active: ${skillName}`);
|
|
110
|
-
} catch (error) {
|
|
111
|
-
if (error?.message === `Skill already active: ${skillName}`) {
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
114
|
-
if (error?.code !== "ENOENT") {
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const state = {
|
|
120
|
-
skillName,
|
|
121
|
-
pid: process.pid,
|
|
122
|
-
activatedAt: Date.now(),
|
|
123
|
-
hasStopHook: typeof onStop === "function",
|
|
124
|
-
};
|
|
125
|
-
await writeFile(tmpPath, JSON.stringify(state), "utf8");
|
|
126
|
-
await rename(tmpPath, filePath);
|
|
127
|
-
rememberStopHook(filePath, onStop);
|
|
128
|
-
} finally {
|
|
129
|
-
await rm(tmpPath, { force: true }).catch(() => {});
|
|
130
|
-
if (lockHandle) {
|
|
131
|
-
await lockHandle.close().catch(() => {});
|
|
132
|
-
}
|
|
133
|
-
await rm(lockPath, { force: true }).catch(() => {});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Deactivate a skill by removing its state file.
|
|
139
|
-
* Does not throw if the file does not exist.
|
|
140
|
-
*
|
|
141
|
-
* @param {string} skillName
|
|
142
|
-
* @param {{ stateDir?: string, onStop?: (() => Promise<void> | void) }} options
|
|
143
|
-
*/
|
|
144
|
-
export async function deactivateSkill(
|
|
145
|
-
skillName,
|
|
146
|
-
{ stateDir = DEFAULT_STATE_DIR, onStop } = {},
|
|
147
|
-
) {
|
|
148
|
-
assertValidOnStop(onStop);
|
|
149
|
-
|
|
150
|
-
const filePath = stateFilePath(stateDir, skillName);
|
|
151
|
-
const state = await readStateFile(filePath);
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
await runStopHook(filePath, state, onStop);
|
|
155
|
-
} finally {
|
|
156
|
-
STOP_HOOKS.delete(filePath);
|
|
157
|
-
await rm(filePath, { force: true }).catch(() => {});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Return all currently active skills by scanning *-active.json files.
|
|
163
|
-
*
|
|
164
|
-
* @param {{ stateDir?: string }} options
|
|
165
|
-
* @returns {Promise<Array<{ skillName: string, pid: number, activatedAt: number, hasStopHook?: boolean }>>}
|
|
166
|
-
*/
|
|
167
|
-
export async function getActiveSkills({ stateDir = DEFAULT_STATE_DIR } = {}) {
|
|
168
|
-
let entries;
|
|
169
|
-
try {
|
|
170
|
-
entries = await readdir(stateDir);
|
|
171
|
-
} catch {
|
|
172
|
-
return [];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const results = [];
|
|
176
|
-
for (const entry of entries) {
|
|
177
|
-
if (!entry.endsWith("-active.json")) continue;
|
|
178
|
-
const state = await readStateFile(join(stateDir, entry));
|
|
179
|
-
if (state) {
|
|
180
|
-
results.push(state);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return results;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Remove state files for skills whose processes are no longer alive.
|
|
188
|
-
*
|
|
189
|
-
* @param {{ stateDir?: string }} options
|
|
190
|
-
* @returns {Promise<string[]>} list of pruned skill names
|
|
191
|
-
*/
|
|
192
|
-
export async function pruneOrphanSkillStates({
|
|
193
|
-
stateDir = DEFAULT_STATE_DIR,
|
|
194
|
-
} = {}) {
|
|
195
|
-
const active = await getActiveSkills({ stateDir });
|
|
196
|
-
const pruned = [];
|
|
197
|
-
|
|
198
|
-
for (const state of active) {
|
|
199
|
-
let alive = true;
|
|
200
|
-
try {
|
|
201
|
-
process.kill(state.pid, 0);
|
|
202
|
-
} catch {
|
|
203
|
-
alive = false;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (!alive) {
|
|
207
|
-
const filePath = stateFilePath(stateDir, state.skillName);
|
|
208
|
-
STOP_HOOKS.delete(filePath);
|
|
209
|
-
if (state.hasStopHook) {
|
|
210
|
-
logStopHookWarning(
|
|
211
|
-
`Skipping stop-hook for orphaned skill state: ${state.skillName}`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
await rm(filePath, { force: true }).catch(() => {});
|
|
215
|
-
pruned.push(state.skillName);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return pruned;
|
|
220
|
-
}
|
package/scripts/lib/team.mjs
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
function teamContext(env) {
|
|
5
|
-
const team = env.TFX_TEAM_NAME || "";
|
|
6
|
-
const taskId = env.TFX_TEAM_TASK_ID || "";
|
|
7
|
-
if (!team || !taskId) return null;
|
|
8
|
-
return {
|
|
9
|
-
team,
|
|
10
|
-
taskId,
|
|
11
|
-
agent: env.TFX_TEAM_AGENT_NAME || "agent",
|
|
12
|
-
lead: env.TFX_TEAM_LEAD_NAME || "team-lead",
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function claimTask({ env = process.env, bridge } = {}) {
|
|
17
|
-
const ctx = teamContext(env);
|
|
18
|
-
if (!ctx) return { skipped: true };
|
|
19
|
-
const response = await bridge.claim({
|
|
20
|
-
team: ctx.team,
|
|
21
|
-
taskId: ctx.taskId,
|
|
22
|
-
owner: ctx.agent,
|
|
23
|
-
status: "in_progress",
|
|
24
|
-
});
|
|
25
|
-
if (response?.ok) return { claimed: true };
|
|
26
|
-
const code = response?.error?.code;
|
|
27
|
-
const before = response?.error?.details?.task_before ?? {};
|
|
28
|
-
if (
|
|
29
|
-
code === "CLAIM_CONFLICT" &&
|
|
30
|
-
before.owner === ctx.agent &&
|
|
31
|
-
before.status === "in_progress"
|
|
32
|
-
) {
|
|
33
|
-
return { claimed: true, alreadyClaimed: true };
|
|
34
|
-
}
|
|
35
|
-
if (code === "CLAIM_CONFLICT") {
|
|
36
|
-
return { claimed: false, conflict: true, owner: before.owner ?? null };
|
|
37
|
-
}
|
|
38
|
-
return { claimed: false, error: response?.error ?? null };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function completeTask({
|
|
42
|
-
env = process.env,
|
|
43
|
-
bridge,
|
|
44
|
-
result = "success",
|
|
45
|
-
summary = "작업 완료",
|
|
46
|
-
now = () => new Date().toISOString(),
|
|
47
|
-
} = {}) {
|
|
48
|
-
const ctx = teamContext(env);
|
|
49
|
-
if (!ctx) return { skipped: true };
|
|
50
|
-
const trimmed = String(summary).slice(0, 4096);
|
|
51
|
-
const response = await bridge.complete({
|
|
52
|
-
team: ctx.team,
|
|
53
|
-
taskId: ctx.taskId,
|
|
54
|
-
agent: ctx.agent,
|
|
55
|
-
result,
|
|
56
|
-
summary: trimmed,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const resultDir =
|
|
60
|
-
env.TFX_RESULT_DIR ||
|
|
61
|
-
join(process.env.HOME || ".", ".claude", "tfx-results", ctx.team);
|
|
62
|
-
mkdirSync(resultDir, { recursive: true });
|
|
63
|
-
writeFileSync(
|
|
64
|
-
join(resultDir, `${ctx.taskId}.json`),
|
|
65
|
-
`${JSON.stringify({
|
|
66
|
-
taskId: ctx.taskId,
|
|
67
|
-
agent: ctx.agent,
|
|
68
|
-
team: ctx.team,
|
|
69
|
-
result,
|
|
70
|
-
summary: trimmed,
|
|
71
|
-
timestamp: now(),
|
|
72
|
-
})}\n`,
|
|
73
|
-
);
|
|
74
|
-
return { completed: Boolean(response?.ok), response };
|
|
75
|
-
}
|