@triflux/remote 10.50.1 → 10.50.2

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.
@@ -1,170 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
3
- import { dirname } from "node:path";
4
-
5
- const ACTIONABLE_COUNT_KEYS = Object.freeze([
6
- "active_tasks",
7
- "stale_sessions",
8
- "orphan_worktrees",
9
- "superseded_checkpoints",
10
- "unknown_owner",
11
- ]);
12
-
13
- function stableValue(value) {
14
- if (Array.isArray(value)) return value.map(stableValue);
15
- if (value && typeof value === "object") {
16
- return Object.fromEntries(
17
- Object.keys(value)
18
- .sort()
19
- .map((key) => [key, stableValue(value[key])]),
20
- );
21
- }
22
- return value;
23
- }
24
-
25
- function stableJson(value) {
26
- return JSON.stringify(stableValue(value));
27
- }
28
-
29
- function normalizeCount(value) {
30
- return Number.isFinite(Number(value)) ? Number(value) : 0;
31
- }
32
-
33
- function actionableCounts(projection = {}) {
34
- const counts = projection?.counts || {};
35
- return Object.fromEntries(
36
- ACTIONABLE_COUNT_KEYS.map((key) => [key, normalizeCount(counts[key])]),
37
- );
38
- }
39
-
40
- function normalizeRows(rows) {
41
- return (Array.isArray(rows) ? rows : [])
42
- .map((row) => ({
43
- kind: String(row?.kind || ""),
44
- id: String(row?.id || ""),
45
- status: String(row?.status || ""),
46
- action: row?.action == null ? "" : String(row.action),
47
- owner: row?.owner == null ? "" : String(row.owner),
48
- }))
49
- .sort((a, b) =>
50
- [a.kind, a.id, a.status, a.action, a.owner]
51
- .join("\u0000")
52
- .localeCompare(
53
- [b.kind, b.id, b.status, b.action, b.owner].join("\u0000"),
54
- ),
55
- );
56
- }
57
-
58
- export function ctoHygieneStateHash(projection = {}) {
59
- const canonical = {
60
- counts: actionableCounts(projection),
61
- rows: normalizeRows(projection.rows),
62
- };
63
- return createHash("sha256").update(stableJson(canonical)).digest("hex");
64
- }
65
-
66
- function plural(count, singular, pluralWord = `${singular}s`) {
67
- return `${count} ${count === 1 ? singular : pluralWord}`;
68
- }
69
-
70
- function summarizeCounts(counts) {
71
- const parts = [];
72
- if (counts.active_tasks)
73
- parts.push(plural(counts.active_tasks, "active task"));
74
- if (counts.stale_sessions)
75
- parts.push(plural(counts.stale_sessions, "stale session"));
76
- if (counts.orphan_worktrees)
77
- parts.push(plural(counts.orphan_worktrees, "orphan worktree"));
78
- if (counts.superseded_checkpoints)
79
- parts.push(plural(counts.superseded_checkpoints, "superseded checkpoint"));
80
- if (counts.unknown_owner)
81
- parts.push(plural(counts.unknown_owner, "unknown owner"));
82
- return parts;
83
- }
84
-
85
- export function buildCtoHygieneNotification(projection = {}, opts = {}) {
86
- const counts = actionableCounts(projection);
87
- const actionable = ACTIONABLE_COUNT_KEYS.some((key) => counts[key] > 0);
88
- const hash = ctoHygieneStateHash(projection);
89
- if (!actionable) {
90
- return { actionable, hash, event: null, counts };
91
- }
92
-
93
- const summary = `CTO hygiene needs action: ${summarizeCounts(counts).join(", ")}`;
94
- return {
95
- actionable,
96
- hash,
97
- counts,
98
- event: {
99
- type: "ctoHygiene",
100
- sessionId: opts.sessionId || "cto-hygiene",
101
- summary,
102
- timestamp:
103
- typeof opts.now === "function"
104
- ? opts.now()
105
- : (opts.timestamp ?? new Date().toISOString()),
106
- },
107
- };
108
- }
109
-
110
- async function readState(stateFile) {
111
- if (!stateFile) return null;
112
- try {
113
- return JSON.parse(await readFile(stateFile, "utf8"));
114
- } catch {
115
- return null;
116
- }
117
- }
118
-
119
- async function writeState(stateFile, state) {
120
- if (!stateFile) return;
121
- await mkdir(dirname(stateFile), { recursive: true });
122
- await writeFile(stateFile, `${JSON.stringify(state, null, 2)}\n`, "utf8");
123
- }
124
-
125
- export async function notifyCtoHygieneOnce(projection = {}, opts = {}) {
126
- const notification = buildCtoHygieneNotification(projection, opts);
127
- const previous = await readState(opts.stateFile);
128
- const unchanged = previous?.hash === notification.hash;
129
- const checkedAt =
130
- typeof opts.now === "function" ? opts.now() : new Date().toISOString();
131
-
132
- if (!notification.actionable) {
133
- await writeState(opts.stateFile, {
134
- hash: notification.hash,
135
- actionable: false,
136
- checkedAt,
137
- });
138
- return {
139
- actionable: false,
140
- notified: false,
141
- reason: "not-actionable",
142
- hash: notification.hash,
143
- };
144
- }
145
-
146
- if (unchanged) {
147
- return {
148
- actionable: true,
149
- notified: false,
150
- reason: "unchanged-state",
151
- hash: notification.hash,
152
- };
153
- }
154
-
155
- const notifierResult = await opts.notifier.notify(notification.event);
156
- await writeState(opts.stateFile, {
157
- hash: notification.hash,
158
- actionable: true,
159
- notifiedAt: notification.event.timestamp,
160
- });
161
-
162
- return {
163
- actionable: true,
164
- notified: true,
165
- reason: "changed-actionable-state",
166
- hash: notification.hash,
167
- event: notification.event,
168
- notify: notifierResult,
169
- };
170
- }
@@ -1,217 +0,0 @@
1
- // hub/workers/gemini-worker.mjs
2
- // Deprecated compatibility surface. Direct Gemini CLI execution is removed;
3
- // old imports now route through Antigravity.
4
-
5
- import { existsSync } from "node:fs";
6
- import { extname } from "node:path";
7
-
8
- import { AntigravityRouteWorker } from "./antigravity-route-worker.mjs";
9
- import { withRetry } from "./worker-utils.mjs";
10
-
11
- function toStringList(value) {
12
- if (!Array.isArray(value)) return [];
13
- return value.map((item) => String(item ?? "").trim()).filter(Boolean);
14
- }
15
-
16
- /**
17
- * Windows에서 cmd.exe에 전달할 인자를 안전하게 quoting한다.
18
- */
19
- function quoteWindowsCmdArg(value) {
20
- const raw = String(value ?? "").replace(/[\r\n]/g, " ");
21
- if (raw.length === 0) return '""';
22
- if (!/[\s"()^&|<>%!]/.test(raw)) return raw;
23
- const escaped = raw
24
- .replace(/%/g, "%%")
25
- .replace(/(\\*)"/g, '$1$1\\"')
26
- .replace(/(\\+)$/g, "$1$1");
27
- return `"${escaped}"`;
28
- }
29
-
30
- /**
31
- * Windows npm shim(.cmd) spawn 문제를 해결한다.
32
- */
33
- function buildSpawnSpec(command, args) {
34
- if (process.platform !== "win32") {
35
- return { command, args, shell: false };
36
- }
37
-
38
- let resolved = command;
39
- if (!extname(resolved)) {
40
- for (const ext of [".exe", ".cmd", ".bat"]) {
41
- if (existsSync(resolved + ext)) {
42
- resolved = resolved + ext;
43
- break;
44
- }
45
- }
46
- }
47
-
48
- if (/\.(cmd|bat)$/i.test(resolved)) {
49
- const line = [resolved, ...args].map(quoteWindowsCmdArg).join(" ");
50
- return {
51
- command: "cmd.exe",
52
- args: ["/d", "/s", "/v:off", "/c", line],
53
- shell: false,
54
- };
55
- }
56
-
57
- return { command: resolved, args, shell: false };
58
- }
59
-
60
- function createWorkerError(message, details = {}) {
61
- const error = new Error(message);
62
- Object.assign(error, details);
63
- return error;
64
- }
65
-
66
- function normalizeRetryOptions(retryOptions) {
67
- if (!retryOptions || typeof retryOptions !== "object") {
68
- return Object.freeze({});
69
- }
70
- return Object.freeze({ ...retryOptions });
71
- }
72
-
73
- function isAntigravityRetryable(error) {
74
- return (
75
- error?.code === "WORKER_EXIT" &&
76
- error?.result?.exitCode !== 0 &&
77
- error?.result?.exitCode !== 2
78
- );
79
- }
80
-
81
- function detectAntigravityCategory(error) {
82
- const combined =
83
- `${error?.message || ""}\n${error?.stderr || ""}`.toLowerCase();
84
-
85
- if (
86
- /(unauthorized|forbidden|auth|login|token|credential|apikey|api key)/.test(
87
- combined,
88
- )
89
- ) {
90
- return "auth";
91
- }
92
- if (
93
- error?.result?.exitCode === 2 ||
94
- /unknown option|invalid option|config|route script/.test(combined)
95
- ) {
96
- return "config";
97
- }
98
-
99
- return "transient";
100
- }
101
-
102
- function buildAntigravityErrorInfo(error, attempts) {
103
- const category = detectAntigravityCategory(error);
104
- const retryable = isAntigravityRetryable(error);
105
- let recovery =
106
- "Retry the Antigravity route worker after correcting the issue.";
107
-
108
- if (category === "auth") {
109
- recovery = "Refresh the Antigravity authentication state and retry.";
110
- } else if (category === "config") {
111
- recovery = "Check the Antigravity route worker configuration.";
112
- }
113
-
114
- return Object.freeze({
115
- code: error?.code || "ANTIGRAVITY_EXECUTION_ERROR",
116
- retryable,
117
- attempts,
118
- category,
119
- recovery,
120
- });
121
- }
122
-
123
- export class GeminiWorker extends AntigravityRouteWorker {
124
- type = "antigravity";
125
-
126
- constructor(options = {}) {
127
- const { command, commandArgs, args, model, approvalMode, yolo, ...rest } =
128
- options;
129
- void commandArgs;
130
- void args;
131
- void model;
132
- void approvalMode;
133
- void yolo;
134
- super(rest);
135
- this.command = command || "agy";
136
- this.retryOptions = normalizeRetryOptions(options.retryOptions);
137
- this.lastRun = null;
138
- }
139
-
140
- getStatus() {
141
- return {
142
- type: "antigravity",
143
- state: this.children.size > 0 ? "running" : "idle",
144
- pid: null,
145
- last_run_at_ms: this.lastRun?.finishedAtMs || null,
146
- last_exit_code: this.lastRun?.exitCode ?? null,
147
- };
148
- }
149
-
150
- async run(prompt, options = {}) {
151
- const startedAtMs = Date.now();
152
- const result = await super.run(prompt, options);
153
- const normalized = {
154
- type: "antigravity",
155
- command: this.command,
156
- args: toStringList(options.extraArgs),
157
- response: result.response || result.output || "",
158
- output: result.output || result.response || "",
159
- stderr: result.stderr || "",
160
- exitCode: result.exitCode ?? 1,
161
- exitSignal: null,
162
- timedOut: result.exitCode === 124,
163
- startedAtMs,
164
- finishedAtMs: Date.now(),
165
- raw: result.raw || result,
166
- };
167
- this.lastRun = normalized;
168
-
169
- if (normalized.exitCode !== 0) {
170
- throw createWorkerError(
171
- `Antigravity route worker exited with code ${normalized.exitCode}`,
172
- {
173
- code: "WORKER_EXIT",
174
- result: normalized,
175
- stderr: normalized.stderr,
176
- },
177
- );
178
- }
179
-
180
- return normalized;
181
- }
182
-
183
- async execute(prompt, options = {}) {
184
- let attempts = 0;
185
-
186
- try {
187
- const result = await withRetry(
188
- async () => {
189
- attempts += 1;
190
- return this.run(prompt, options);
191
- },
192
- {
193
- ...this.retryOptions,
194
- shouldRetry: (error) => isAntigravityRetryable(error),
195
- },
196
- );
197
-
198
- return {
199
- output: result.response,
200
- exitCode: 0,
201
- sessionKey: options.sessionKey || null,
202
- raw: result,
203
- };
204
- } catch (error) {
205
- return {
206
- output: error.stderr || error.message || "Antigravity worker failed",
207
- exitCode: error.code === "ETIMEDOUT" ? 124 : 1,
208
- sessionKey: options.sessionKey || null,
209
- error: buildAntigravityErrorInfo(error, attempts || 1),
210
- raw: error.result || null,
211
- };
212
- }
213
- }
214
- }
215
-
216
- /** @visibleForTesting - 테스트 전용 export. 외부 소비 금지. */
217
- export { buildSpawnSpec, quoteWindowsCmdArg };
@@ -1,27 +0,0 @@
1
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- export function agentJsonPath({ tmpDir, pid = process.pid }) {
5
- return join(tmpDir, `tfx-agent-${pid}.json`);
6
- }
7
-
8
- export function registerAgent({
9
- tmpDir,
10
- pid = process.pid,
11
- cli = "",
12
- agent = "",
13
- started = Math.floor(Date.now() / 1000),
14
- }) {
15
- mkdirSync(tmpDir, { recursive: true });
16
- const file = agentJsonPath({ tmpDir, pid });
17
- writeFileSync(
18
- file,
19
- `${JSON.stringify({ pid, cli, agent, started })}\n`,
20
- "utf8",
21
- );
22
- return file;
23
- }
24
-
25
- export function removeAgent({ tmpDir, pid = process.pid }) {
26
- rmSync(agentJsonPath({ tmpDir, pid }), { force: true });
27
- }
@@ -1,174 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { randomUUID } from "node:crypto";
3
- import {
4
- existsSync,
5
- mkdirSync,
6
- readFileSync,
7
- statSync,
8
- writeFileSync,
9
- } from "node:fs";
10
- import { join } from "node:path";
11
-
12
- function jobDir(jobsDir, jobId) {
13
- return join(jobsDir, jobId);
14
- }
15
-
16
- function readState(dir) {
17
- const path = join(dir, "state.json");
18
- if (!existsSync(path)) return null;
19
- return JSON.parse(readFileSync(path, "utf8"));
20
- }
21
-
22
- function writeState(dir, state) {
23
- writeFileSync(join(dir, "state.json"), `${JSON.stringify(state, null, 2)}\n`);
24
- }
25
-
26
- function fileSize(path) {
27
- try {
28
- return statSync(path).size;
29
- } catch {
30
- return 0;
31
- }
32
- }
33
-
34
- function isAlive(pid) {
35
- if (!pid || pid === "starting") return false;
36
- try {
37
- process.kill(Number(pid), 0);
38
- return true;
39
- } catch {
40
- return false;
41
- }
42
- }
43
-
44
- export function createJob({
45
- jobsDir,
46
- command,
47
- args = [],
48
- env = process.env,
49
- cwd = process.cwd(),
50
- timeoutMs = 0,
51
- }) {
52
- mkdirSync(jobsDir, { recursive: true });
53
- const id = randomUUID();
54
- const dir = jobDir(jobsDir, id);
55
- mkdirSync(dir, { recursive: true });
56
- const startedAt = Date.now();
57
- const stdoutPath = join(dir, "result.log");
58
- const stderrPath = join(dir, "stderr.log");
59
- writeState(dir, {
60
- id,
61
- state: "starting",
62
- startedAt,
63
- command,
64
- args,
65
- timeoutMs,
66
- });
67
- writeFileSync(join(dir, "pid"), "starting");
68
- writeFileSync(join(dir, "start_time"), String(Math.floor(startedAt / 1000)));
69
-
70
- const child = spawn(command, args, {
71
- cwd,
72
- env,
73
- detached: true,
74
- stdio: ["ignore", "pipe", "pipe"],
75
- });
76
- child.unref?.();
77
- writeFileSync(join(dir, "pid"), String(child.pid));
78
- writeState(dir, {
79
- id,
80
- state: "running",
81
- pid: child.pid,
82
- startedAt,
83
- command,
84
- args,
85
- timeoutMs,
86
- });
87
- child.stdout?.on("data", (chunk) => {
88
- writeFileSync(stdoutPath, chunk, { flag: "a" });
89
- });
90
- child.stderr?.on("data", (chunk) => {
91
- writeFileSync(stderrPath, chunk, { flag: "a" });
92
- });
93
- let timer = null;
94
- let timedOut = false;
95
- if (timeoutMs > 0) {
96
- timer = setTimeout(() => {
97
- timedOut = true;
98
- try {
99
- process.kill(child.pid, "SIGTERM");
100
- } catch {
101
- // Process already exited.
102
- }
103
- }, timeoutMs);
104
- timer.unref?.();
105
- }
106
- child.on("exit", (code) => {
107
- if (timer) clearTimeout(timer);
108
- const exitCode = timedOut ? 124 : (code ?? 1);
109
- writeFileSync(join(dir, "exit_code"), String(exitCode));
110
- writeFileSync(join(dir, "done"), "");
111
- writeState(dir, {
112
- id,
113
- state: exitCode === 0 ? "done" : exitCode === 124 ? "timeout" : "failed",
114
- pid: child.pid,
115
- startedAt,
116
- finishedAt: Date.now(),
117
- exitCode,
118
- command,
119
- args,
120
- timeoutMs,
121
- });
122
- });
123
-
124
- return { id, dir, pid: child.pid };
125
- }
126
-
127
- export function getJobStatus(jobId, { jobsDir }) {
128
- const dir = jobDir(jobsDir, jobId);
129
- if (!existsSync(dir))
130
- return { state: "error", text: "error: job not found", exitCode: 1 };
131
- const state = readState(dir);
132
- if (state?.state === "done") return { ...state, text: "done" };
133
- if (state?.state === "timeout") return { ...state, text: "timeout" };
134
- if (state?.state === "failed") return { ...state, text: "failed" };
135
- const pidText = existsSync(join(dir, "pid"))
136
- ? readFileSync(join(dir, "pid"), "utf8").trim()
137
- : "";
138
- if (pidText === "starting")
139
- return { ...(state ?? {}), state: "starting", text: "starting" };
140
- if (isAlive(pidText)) {
141
- const start =
142
- Number(readFileSync(join(dir, "start_time"), "utf8")) ||
143
- Math.floor(Date.now() / 1000);
144
- const elapsed = Math.max(0, Math.floor(Date.now() / 1000) - start);
145
- const bytes = fileSize(join(dir, "result.log"));
146
- return {
147
- ...(state ?? {}),
148
- state: "running",
149
- text: `running elapsed=${elapsed}s output=${bytes}B`,
150
- };
151
- }
152
- return { ...(state ?? {}), state: "failed", text: "failed" };
153
- }
154
-
155
- export function getJobResult(jobId, { jobsDir }) {
156
- const dir = jobDir(jobsDir, jobId);
157
- if (!existsSync(dir)) throw new Error("job not found");
158
- const status = getJobStatus(jobId, { jobsDir });
159
- if (!["done", "timeout", "failed"].includes(status.state)) {
160
- throw new Error("job still running");
161
- }
162
- const stdout = existsSync(join(dir, "result.log"))
163
- ? readFileSync(join(dir, "result.log"), "utf8")
164
- : "";
165
- const stderr = existsSync(join(dir, "stderr.log"))
166
- ? readFileSync(join(dir, "stderr.log"), "utf8")
167
- : "";
168
- return {
169
- state: status.state,
170
- exitCode: status.exitCode ?? 1,
171
- stdout: stdout || stderr,
172
- stderr,
173
- };
174
- }
@@ -1,85 +0,0 @@
1
- // cli-agy.mjs — Antigravity (agy) CLI adapter (Phase 0 PoC).
2
- //
3
- // 책임: agy 1.1.27 부터 --print 는 값(프롬프트)이 필수이고 stdin 프롬프트는 거부되므로
4
- // plan() 은 프롬프트를 args 의 --print 값으로 넣고 stdinMode="argv" 를 돌려준다.
5
- // --print 뒤에 다른 플래그가 오면 그 플래그를 프롬프트로 삼키므로 항상 마지막에 둔다.
6
- //
7
- // 출처: scripts/tfx-route.sh route_agent() L1052-L1059, run_codex_exec() L2036-L2046.
8
- // .claude/rules/tfx-update-logic.md Antigravity CLI 1.0.0 정밀 sanity matrix.
9
- // timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
10
-
11
- import {
12
- resolveGeminiModel,
13
- resolveGeminiProfileForPurpose,
14
- } from "./gemini-profiles.mjs";
15
-
16
- export const id = "agy";
17
- export const cliType = "antigravity";
18
- export const command = "agy";
19
-
20
- // #310: agent-map.json normalizes upstream callers to antigravity,
21
- // but this adapter also accepts the direct `agy` route for compatibility.
22
- const AGENT_PROFILES = {
23
- antigravity: {
24
- profile: "agy_v1",
25
- timeoutSec: 900,
26
- runMode: "bg",
27
- opusOversight: false,
28
- mcpHint: "docs",
29
- },
30
- agy: {
31
- profile: "agy_v1",
32
- timeoutSec: 900,
33
- runMode: "bg",
34
- opusOversight: false,
35
- mcpHint: "docs",
36
- },
37
- };
38
-
39
- const DEFAULT_PROFILE = AGENT_PROFILES.antigravity;
40
-
41
- export function plan({
42
- agent,
43
- prompt = "",
44
- mcpProfile = "auto",
45
- timeoutSec,
46
- contextFile,
47
- geminiProfilesPath,
48
- } = {}) {
49
- if (!agent) {
50
- throw new Error("[cli-agy] agent required");
51
- }
52
- const cfg = AGENT_PROFILES[agent] ?? DEFAULT_PROFILE;
53
- const effectiveTimeoutSec =
54
- Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
55
- return {
56
- command,
57
- profile: cfg.profile,
58
- effort: cfg.profile,
59
- // effort 는 역할별 SSOT(gemini-profiles.mjs): designer → High, writer → Medium ...
60
- geminiProfile: resolveGeminiProfileForPurpose(agent),
61
- model: resolveGeminiModel(resolveGeminiProfileForPurpose(agent), {
62
- profilesPath: geminiProfilesPath,
63
- }),
64
- args: [
65
- "--dangerously-skip-permissions",
66
- "--model",
67
- resolveGeminiModel(resolveGeminiProfileForPurpose(agent), {
68
- profilesPath: geminiProfilesPath,
69
- }),
70
- "--print",
71
- prompt,
72
- ],
73
- stdinMode: "argv",
74
- timeoutMs: effectiveTimeoutSec * 1000,
75
- runMode: cfg.runMode,
76
- opusOversight: cfg.opusOversight,
77
- mcpProfile: mcpProfile === "auto" ? cfg.mcpHint : mcpProfile,
78
- promptLength: prompt.length,
79
- contextFile: contextFile || null,
80
- };
81
- }
82
-
83
- export function describe() {
84
- return { id, cliType, command, agents: Object.keys(AGENT_PROFILES) };
85
- }