@synkro-sh/cli 1.10.6 → 1.10.9
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/dist/bootstrap.js +1850 -680
- package/dist/bootstrap.js.map +1 -1
- package/package.json +3 -1
package/dist/bootstrap.js
CHANGED
|
@@ -81,10 +81,10 @@ import { createHash } from "crypto";
|
|
|
81
81
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
82
82
|
import { homedir as homedir2, hostname, platform } from "os";
|
|
83
83
|
import { join as join2 } from "path";
|
|
84
|
-
function readConfigValue(
|
|
85
|
-
if (process.env[
|
|
84
|
+
function readConfigValue(key2) {
|
|
85
|
+
if (process.env[key2]) return String(process.env[key2]).toLowerCase();
|
|
86
86
|
try {
|
|
87
|
-
const re =
|
|
87
|
+
const re = key2 === "SYNKRO_GRADING_MODE" ? /^SYNKRO_GRADING_MODE=['"]?([^'"\n]*)/m : /^SYNKRO_STORAGE_MODE=['"]?([^'"\n]*)/m;
|
|
88
88
|
const m = readFileSync2(CONFIG_PATH, "utf-8").match(re);
|
|
89
89
|
return m ? m[1].toLowerCase() : "";
|
|
90
90
|
} catch {
|
|
@@ -147,7 +147,7 @@ function getIdentity() {
|
|
|
147
147
|
if (cached2) return cached2;
|
|
148
148
|
let cliVersion2 = "0.0.0";
|
|
149
149
|
try {
|
|
150
|
-
cliVersion2 = "1.10.
|
|
150
|
+
cliVersion2 = "1.10.9";
|
|
151
151
|
} catch {
|
|
152
152
|
}
|
|
153
153
|
const creds = loadCredentialsIdentity();
|
|
@@ -789,9 +789,9 @@ function sanitize(raw, maxLen = 256) {
|
|
|
789
789
|
function shellQuoteSingle(value) {
|
|
790
790
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
791
791
|
}
|
|
792
|
-
function writeConfigEnvFlag(
|
|
792
|
+
function writeConfigEnvFlag(key2, value) {
|
|
793
793
|
const safe = sanitize(value, 8);
|
|
794
|
-
const line = `${
|
|
794
|
+
const line = `${key2}=${shellQuoteSingle(safe)}`;
|
|
795
795
|
let content = "";
|
|
796
796
|
if (existsSync5(CONFIG_PATH2)) {
|
|
797
797
|
try {
|
|
@@ -800,7 +800,7 @@ function writeConfigEnvFlag(key, value) {
|
|
|
800
800
|
content = "";
|
|
801
801
|
}
|
|
802
802
|
}
|
|
803
|
-
const re = new RegExp(`^${
|
|
803
|
+
const re = new RegExp(`^${key2}=.*$`, "m");
|
|
804
804
|
if (re.test(content)) {
|
|
805
805
|
content = content.replace(re, line);
|
|
806
806
|
} else {
|
|
@@ -1231,15 +1231,15 @@ async function exportEvents(path) {
|
|
|
1231
1231
|
writeFileSync4(path, "", "utf-8");
|
|
1232
1232
|
return;
|
|
1233
1233
|
}
|
|
1234
|
-
const
|
|
1234
|
+
const lines2 = [];
|
|
1235
1235
|
try {
|
|
1236
1236
|
const rows = await sql`SELECT * FROM telemetry_events ORDER BY occurred_at ASC`;
|
|
1237
1237
|
for (const row2 of rows) {
|
|
1238
|
-
|
|
1238
|
+
lines2.push(JSON.stringify(row2));
|
|
1239
1239
|
}
|
|
1240
1240
|
} catch {
|
|
1241
1241
|
}
|
|
1242
|
-
writeFileSync4(path,
|
|
1242
|
+
writeFileSync4(path, lines2.join("\n") + (lines2.length > 0 ? "\n" : ""), "utf-8");
|
|
1243
1243
|
}
|
|
1244
1244
|
function pendingFileSize() {
|
|
1245
1245
|
try {
|
|
@@ -1856,21 +1856,21 @@ function validateHooksPath(path) {
|
|
|
1856
1856
|
return resolved;
|
|
1857
1857
|
}
|
|
1858
1858
|
function readHooksFile(rawPath) {
|
|
1859
|
-
const
|
|
1859
|
+
const safePath2 = validateHooksPath(rawPath);
|
|
1860
1860
|
try {
|
|
1861
|
-
const raw = readFileSync10(
|
|
1861
|
+
const raw = readFileSync10(safePath2, "utf-8");
|
|
1862
1862
|
return JSON.parse(raw);
|
|
1863
1863
|
} catch (err) {
|
|
1864
1864
|
if (err?.code === "ENOENT") return { version: 1, hooks: {} };
|
|
1865
|
-
throw new Error(`Failed to parse ${
|
|
1865
|
+
throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
|
|
1866
1866
|
}
|
|
1867
1867
|
}
|
|
1868
1868
|
function writeHooksFileAtomic(rawPath, data) {
|
|
1869
|
-
const
|
|
1870
|
-
mkdirSync5(dirname2(
|
|
1871
|
-
const tmpPath = `${
|
|
1869
|
+
const safePath2 = validateHooksPath(rawPath);
|
|
1870
|
+
mkdirSync5(dirname2(safePath2), { recursive: true });
|
|
1871
|
+
const tmpPath = `${safePath2}.synkro.tmp`;
|
|
1872
1872
|
writeFileSync7(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
1873
|
-
renameSync4(tmpPath,
|
|
1873
|
+
renameSync4(tmpPath, safePath2);
|
|
1874
1874
|
}
|
|
1875
1875
|
function isSynkroEntry2(entry) {
|
|
1876
1876
|
if (entry?.[SYNKRO_MARKER2]) return true;
|
|
@@ -2158,20 +2158,20 @@ function validateHooksPath2(path) {
|
|
|
2158
2158
|
return resolved;
|
|
2159
2159
|
}
|
|
2160
2160
|
function readHooksFile2(rawPath) {
|
|
2161
|
-
const
|
|
2162
|
-
if (!existsSync12(
|
|
2161
|
+
const safePath2 = validateHooksPath2(rawPath);
|
|
2162
|
+
if (!existsSync12(safePath2)) return { hooks: {} };
|
|
2163
2163
|
try {
|
|
2164
|
-
return JSON.parse(readFileSync11(
|
|
2164
|
+
return JSON.parse(readFileSync11(safePath2, "utf-8"));
|
|
2165
2165
|
} catch (err) {
|
|
2166
|
-
throw new Error(`Failed to parse ${
|
|
2166
|
+
throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
|
|
2167
2167
|
}
|
|
2168
2168
|
}
|
|
2169
2169
|
function writeHooksFileAtomic2(rawPath, data) {
|
|
2170
|
-
const
|
|
2171
|
-
mkdirSync6(dirname3(
|
|
2172
|
-
const tmpPath = `${
|
|
2170
|
+
const safePath2 = validateHooksPath2(rawPath);
|
|
2171
|
+
mkdirSync6(dirname3(safePath2), { recursive: true });
|
|
2172
|
+
const tmpPath = `${safePath2}.synkro.tmp`;
|
|
2173
2173
|
writeFileSync8(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
2174
|
-
renameSync5(tmpPath,
|
|
2174
|
+
renameSync5(tmpPath, safePath2);
|
|
2175
2175
|
}
|
|
2176
2176
|
function isSynkroEntry3(entry) {
|
|
2177
2177
|
if (entry?.[SYNKRO_MARKER3]) return true;
|
|
@@ -2351,7 +2351,7 @@ function buildCodexHookTrustEdits(hooks) {
|
|
|
2351
2351
|
return [...edits.values()];
|
|
2352
2352
|
}
|
|
2353
2353
|
function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTrust = false) {
|
|
2354
|
-
return new Promise((
|
|
2354
|
+
return new Promise((resolve9) => {
|
|
2355
2355
|
let settled = false;
|
|
2356
2356
|
let stdout = "";
|
|
2357
2357
|
let pending = "";
|
|
@@ -2369,7 +2369,7 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
|
|
|
2369
2369
|
child.kill();
|
|
2370
2370
|
} catch {
|
|
2371
2371
|
}
|
|
2372
|
-
|
|
2372
|
+
resolve9(summary);
|
|
2373
2373
|
};
|
|
2374
2374
|
const timer = setTimeout(() => finish(null), 1e4);
|
|
2375
2375
|
try {
|
|
@@ -2384,9 +2384,9 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
|
|
|
2384
2384
|
child.stdout.on("data", (chunk) => {
|
|
2385
2385
|
stdout += chunk;
|
|
2386
2386
|
pending += chunk;
|
|
2387
|
-
const
|
|
2388
|
-
pending =
|
|
2389
|
-
for (const line of
|
|
2387
|
+
const lines2 = pending.split("\n");
|
|
2388
|
+
pending = lines2.pop() || "";
|
|
2389
|
+
for (const line of lines2) {
|
|
2390
2390
|
let message;
|
|
2391
2391
|
try {
|
|
2392
2392
|
message = JSON.parse(line);
|
|
@@ -2620,11 +2620,11 @@ function writeCodexTomlAtomic(path, content) {
|
|
|
2620
2620
|
renameSync6(tmpPath, path);
|
|
2621
2621
|
}
|
|
2622
2622
|
function removeCodexManagedBlock(content, path) {
|
|
2623
|
-
const
|
|
2623
|
+
const lines2 = content.split("\n");
|
|
2624
2624
|
const out = [];
|
|
2625
2625
|
let removed = false;
|
|
2626
2626
|
let inside = false;
|
|
2627
|
-
for (const line of
|
|
2627
|
+
for (const line of lines2) {
|
|
2628
2628
|
if (!inside && line.trim() === CODEX_MCP_BEGIN) {
|
|
2629
2629
|
inside = true;
|
|
2630
2630
|
removed = true;
|
|
@@ -2652,8 +2652,8 @@ function findCodexMcpSection(content) {
|
|
|
2652
2652
|
body: content.slice(match.index, next?.index ?? content.length)
|
|
2653
2653
|
};
|
|
2654
2654
|
}
|
|
2655
|
-
function readTomlString(block,
|
|
2656
|
-
const match = block.match(new RegExp(`^\\s*${
|
|
2655
|
+
function readTomlString(block, key2) {
|
|
2656
|
+
const match = block.match(new RegExp(`^\\s*${key2}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")\\s*$`, "m"));
|
|
2657
2657
|
if (!match) return null;
|
|
2658
2658
|
try {
|
|
2659
2659
|
return JSON.parse(match[1]);
|
|
@@ -5181,7 +5181,7 @@ function createCallbackServer() {
|
|
|
5181
5181
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
5182
5182
|
"Vary": "Origin"
|
|
5183
5183
|
};
|
|
5184
|
-
return new Promise((
|
|
5184
|
+
return new Promise((resolve9, reject) => {
|
|
5185
5185
|
const server = createServer((req, res) => {
|
|
5186
5186
|
if (req.method === "OPTIONS") {
|
|
5187
5187
|
const origin = req.headers.origin;
|
|
@@ -5270,7 +5270,7 @@ function createCallbackServer() {
|
|
|
5270
5270
|
res.end(JSON.stringify({ ok: true }));
|
|
5271
5271
|
setTimeout(() => {
|
|
5272
5272
|
server.close();
|
|
5273
|
-
|
|
5273
|
+
resolve9(authData);
|
|
5274
5274
|
}, 200);
|
|
5275
5275
|
});
|
|
5276
5276
|
req.on("error", (e) => {
|
|
@@ -5606,7 +5606,7 @@ function detectSubdirRepos() {
|
|
|
5606
5606
|
}
|
|
5607
5607
|
}
|
|
5608
5608
|
function ask(rl, question) {
|
|
5609
|
-
return new Promise((
|
|
5609
|
+
return new Promise((resolve9) => rl.question(question, resolve9));
|
|
5610
5610
|
}
|
|
5611
5611
|
async function linkRepo(repo, linkedNames) {
|
|
5612
5612
|
try {
|
|
@@ -5845,7 +5845,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
5845
5845
|
writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
|
|
5846
5846
|
const runnerPath = join13(sessionDir, "run.sh");
|
|
5847
5847
|
writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
|
|
5848
|
-
await new Promise((
|
|
5848
|
+
await new Promise((resolve9) => {
|
|
5849
5849
|
const child = spawn3("bash", [runnerPath], {
|
|
5850
5850
|
stdio: "inherit",
|
|
5851
5851
|
env: { ...process.env, SYNKRO_CAPTURE_URL: CAPTURE_URL, SYNKRO_SCAN_URL: SCAN_URL, SYNKRO_SCAN_TURN_URL: SCAN_TURN_URL, SYNKRO_DLP_POLICY_URL: DLP_POLICY_URL, SYNKRO_TURN_VERDICTS_URL: TURN_VERDICTS_URL, SYNKRO_TURN_VERDICT_URL: TURN_VERDICT_URL, SYNKRO_MCP_EVENT_URL: MCP_EVENT_URL, SYNKRO_TAP_TOKEN: token, SYNKRO_TAP_TOKEN_FILE: JWT_PATH, SYNKRO_CD_BACKFILL: opts.backfill ? "1" : "" }
|
|
@@ -5861,7 +5861,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
5861
5861
|
child.on("exit", () => {
|
|
5862
5862
|
process.off("SIGINT", forward);
|
|
5863
5863
|
process.off("SIGTERM", forward);
|
|
5864
|
-
|
|
5864
|
+
resolve9();
|
|
5865
5865
|
});
|
|
5866
5866
|
});
|
|
5867
5867
|
}
|
|
@@ -7034,8 +7034,8 @@ function cursorApiKeyConfigured() {
|
|
|
7034
7034
|
return false;
|
|
7035
7035
|
}
|
|
7036
7036
|
}
|
|
7037
|
-
function writeCursorApiKey(
|
|
7038
|
-
const trimmed =
|
|
7037
|
+
function writeCursorApiKey(key2) {
|
|
7038
|
+
const trimmed = key2.trim();
|
|
7039
7039
|
if (!trimmed) return;
|
|
7040
7040
|
mkdirSync11(CURSOR_CREDS_DIR, { recursive: true });
|
|
7041
7041
|
chmodSync2(CURSOR_CREDS_DIR, 448);
|
|
@@ -7043,15 +7043,15 @@ function writeCursorApiKey(key) {
|
|
|
7043
7043
|
chmodSync2(CURSOR_API_KEY_FILE, 384);
|
|
7044
7044
|
}
|
|
7045
7045
|
async function validateCursorApiKey() {
|
|
7046
|
-
let
|
|
7046
|
+
let key2;
|
|
7047
7047
|
try {
|
|
7048
|
-
|
|
7048
|
+
key2 = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
|
|
7049
7049
|
} catch {
|
|
7050
7050
|
return null;
|
|
7051
7051
|
}
|
|
7052
|
-
if (!
|
|
7052
|
+
if (!key2) return null;
|
|
7053
7053
|
try {
|
|
7054
|
-
const auth = Buffer.from(`${
|
|
7054
|
+
const auth = Buffer.from(`${key2}:`).toString("base64");
|
|
7055
7055
|
const r = await fetch("https://api.cursor.com/v1/me", {
|
|
7056
7056
|
headers: { Authorization: `Basic ${auth}` },
|
|
7057
7057
|
signal: AbortSignal.timeout(8e3)
|
|
@@ -7391,15 +7391,15 @@ function parseSynkroToml(raw) {
|
|
|
7391
7391
|
}
|
|
7392
7392
|
const eq = line.indexOf("=");
|
|
7393
7393
|
if (eq === -1) continue;
|
|
7394
|
-
const
|
|
7394
|
+
const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
|
|
7395
7395
|
let valRaw = line.slice(eq + 1).trim();
|
|
7396
7396
|
if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
|
|
7397
7397
|
const h = valRaw.indexOf("#");
|
|
7398
7398
|
if (h !== -1) valRaw = valRaw.slice(0, h).trim();
|
|
7399
7399
|
}
|
|
7400
7400
|
const value = parseTomlValue(valRaw);
|
|
7401
|
-
if (section) result[section][
|
|
7402
|
-
else result[
|
|
7401
|
+
if (section) result[section][key2] = value;
|
|
7402
|
+
else result[key2] = value;
|
|
7403
7403
|
}
|
|
7404
7404
|
return result;
|
|
7405
7405
|
}
|
|
@@ -7676,7 +7676,7 @@ async function dockerInstall(opts = {}) {
|
|
|
7676
7676
|
"SYNKRO_CODEX_MODEL",
|
|
7677
7677
|
"SYNKRO_CONDUCTOR_MODEL",
|
|
7678
7678
|
"SYNKRO_ROUTE_MODEL"
|
|
7679
|
-
].flatMap((
|
|
7679
|
+
].flatMap((key2) => process.env[key2] ? ["-e", `${key2}=${process.env[key2]}`] : []),
|
|
7680
7680
|
// Fix-poll kill switch. Default ON in the image; a benchmark/headless run
|
|
7681
7681
|
// (e.g. sec-code-bench) sets SYNKRO_FIX_POLL=0 so ask-mode violations skip the
|
|
7682
7682
|
// interactive AskUserQuestion poll and fall through to generate-the-fix. Only
|
|
@@ -7934,7 +7934,7 @@ var init_dockerInstall = __esm({
|
|
|
7934
7934
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
7935
7935
|
CONTAINER_NAME = resolveContainerName();
|
|
7936
7936
|
defaultImageVersion = () => {
|
|
7937
|
-
if (true) return "1.10.
|
|
7937
|
+
if (true) return "1.10.9";
|
|
7938
7938
|
try {
|
|
7939
7939
|
const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
|
|
7940
7940
|
if (pkg.version) return pkg.version;
|
|
@@ -7966,7 +7966,7 @@ function captureClaudeSetupToken() {
|
|
|
7966
7966
|
const bin = "script";
|
|
7967
7967
|
const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
|
|
7968
7968
|
const OAUTH_HINT = 'The browser approval did not return a token. This usually means claude.ai rejected the OAuth request (its "Authorization failed \u2014 Unsupported media type" page), most often because a browser extension (Grammarly, ad/script blockers, AI-assistant toolbars) stripped the request headers, or you approved on the wrong Claude account. Fix: retry in a clean incognito window with extensions disabled, and approve on the Claude account you want the cloud workers to use.';
|
|
7969
|
-
return new Promise((
|
|
7969
|
+
return new Promise((resolve9, reject) => {
|
|
7970
7970
|
const proc = nodeSpawn(bin, args2, {
|
|
7971
7971
|
stdio: "inherit",
|
|
7972
7972
|
env: { ...process.env, FORCE_COLOR: "3", COLORTERM: "truecolor", TERM: "xterm-256color" }
|
|
@@ -8033,7 +8033,7 @@ function captureClaudeSetupToken() {
|
|
|
8033
8033
|
reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));
|
|
8034
8034
|
return;
|
|
8035
8035
|
}
|
|
8036
|
-
|
|
8036
|
+
resolve9(token);
|
|
8037
8037
|
});
|
|
8038
8038
|
});
|
|
8039
8039
|
}
|
|
@@ -8059,13 +8059,13 @@ function findCodexBinary() {
|
|
|
8059
8059
|
function runCodexLogin(codexBin, codexHome) {
|
|
8060
8060
|
mkdirSync13(codexHome, { recursive: true, mode: 448 });
|
|
8061
8061
|
writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
|
|
8062
|
-
return new Promise((
|
|
8062
|
+
return new Promise((resolve9, reject) => {
|
|
8063
8063
|
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
8064
8064
|
stdio: "inherit",
|
|
8065
8065
|
env: { ...process.env, CODEX_HOME: codexHome }
|
|
8066
8066
|
});
|
|
8067
8067
|
proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
|
|
8068
|
-
proc.on("close", (code) => code === 0 ?
|
|
8068
|
+
proc.on("close", (code) => code === 0 ? resolve9() : reject(new Error(`codex login exited with code ${code}`)));
|
|
8069
8069
|
});
|
|
8070
8070
|
}
|
|
8071
8071
|
async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
@@ -8477,8 +8477,8 @@ function addUsage(a, b) {
|
|
|
8477
8477
|
}
|
|
8478
8478
|
function parseCodexTranscriptUsage(transcript, options = {}) {
|
|
8479
8479
|
if (!transcript) return null;
|
|
8480
|
-
const
|
|
8481
|
-
const hasCanonicalAssistant =
|
|
8480
|
+
const lines2 = transcript.split("\n");
|
|
8481
|
+
const hasCanonicalAssistant = lines2.some((line) => {
|
|
8482
8482
|
try {
|
|
8483
8483
|
const entry = JSON.parse(line);
|
|
8484
8484
|
return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
|
|
@@ -8492,8 +8492,8 @@ function parseCodexTranscriptUsage(transcript, options = {}) {
|
|
|
8492
8492
|
let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
|
|
8493
8493
|
let latest = null;
|
|
8494
8494
|
let sawSnapshot = false;
|
|
8495
|
-
for (let i = 0; i <
|
|
8496
|
-
const line =
|
|
8495
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
8496
|
+
const line = lines2[i].trim();
|
|
8497
8497
|
if (!line) continue;
|
|
8498
8498
|
let entry;
|
|
8499
8499
|
try {
|
|
@@ -8675,8 +8675,8 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
|
|
|
8675
8675
|
const entryModel = typeof message.model === "string" && message.model ? message.model : "unknown";
|
|
8676
8676
|
if (entryModel !== "<synthetic>") model = entryModel;
|
|
8677
8677
|
const day = isoDay(entry.timestamp, fallbackDay);
|
|
8678
|
-
const
|
|
8679
|
-
const row2 = rollups.get(
|
|
8678
|
+
const key2 = `${day}\0${entryModel}`;
|
|
8679
|
+
const row2 = rollups.get(key2) ?? {
|
|
8680
8680
|
day,
|
|
8681
8681
|
model: entryModel,
|
|
8682
8682
|
turns: 0,
|
|
@@ -8690,7 +8690,7 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
|
|
|
8690
8690
|
row2.output_tokens += counts.output_tokens;
|
|
8691
8691
|
row2.cache_creation_input_tokens += counts.cache_creation_input_tokens;
|
|
8692
8692
|
row2.cache_read_input_tokens += counts.cache_read_input_tokens;
|
|
8693
|
-
rollups.set(
|
|
8693
|
+
rollups.set(key2, row2);
|
|
8694
8694
|
turns += 1;
|
|
8695
8695
|
usage2.input_tokens += counts.input_tokens;
|
|
8696
8696
|
usage2.output_tokens += counts.output_tokens;
|
|
@@ -8767,20 +8767,20 @@ async function promptAgentSelection(detected) {
|
|
|
8767
8767
|
detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
|
|
8768
8768
|
console.log(` ${detected.length + 1}. Both / all (default)`);
|
|
8769
8769
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8770
|
-
const ask3 = () => new Promise((
|
|
8770
|
+
const ask3 = () => new Promise((resolve9) => {
|
|
8771
8771
|
rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
|
|
8772
8772
|
const t = answer.trim().toLowerCase();
|
|
8773
8773
|
if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
|
|
8774
8774
|
rl.close();
|
|
8775
|
-
return
|
|
8775
|
+
return resolve9(detected);
|
|
8776
8776
|
}
|
|
8777
8777
|
const n = parseInt(t, 10);
|
|
8778
8778
|
if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
|
|
8779
8779
|
rl.close();
|
|
8780
|
-
return
|
|
8780
|
+
return resolve9([detected[n - 1]]);
|
|
8781
8781
|
}
|
|
8782
8782
|
console.log("Invalid choice. Try again.");
|
|
8783
|
-
|
|
8783
|
+
resolve9(ask3());
|
|
8784
8784
|
});
|
|
8785
8785
|
});
|
|
8786
8786
|
return ask3();
|
|
@@ -8803,17 +8803,17 @@ async function promptCursorApiKey(opts) {
|
|
|
8803
8803
|
return;
|
|
8804
8804
|
}
|
|
8805
8805
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8806
|
-
const
|
|
8806
|
+
const key2 = await new Promise((resolve9) => {
|
|
8807
8807
|
rl.question(
|
|
8808
8808
|
"Cursor grading needs a Cursor API key (cursor.com \u2192 Settings \u2192 API Keys).\nPaste it now, or press Enter to skip (Cursor workers stay idle until set): ",
|
|
8809
8809
|
(answer) => {
|
|
8810
8810
|
rl.close();
|
|
8811
|
-
|
|
8811
|
+
resolve9(answer.trim());
|
|
8812
8812
|
}
|
|
8813
8813
|
);
|
|
8814
8814
|
});
|
|
8815
|
-
if (
|
|
8816
|
-
writeCursorApiKey2(
|
|
8815
|
+
if (key2) {
|
|
8816
|
+
writeCursorApiKey2(key2);
|
|
8817
8817
|
console.log(" \u2713 Cursor API key saved.");
|
|
8818
8818
|
} else {
|
|
8819
8819
|
console.log(" \u26A0 Skipped \u2014 Cursor workers will be idle. Re-run install or pass --cursor-api-key=\u2026 later.");
|
|
@@ -8823,7 +8823,7 @@ async function promptDeployLocation(current = "local") {
|
|
|
8823
8823
|
if (!process.stdin.isTTY) return current;
|
|
8824
8824
|
const other = current === "cloud" ? "local" : "cloud";
|
|
8825
8825
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8826
|
-
return new Promise((
|
|
8826
|
+
return new Promise((resolve9) => {
|
|
8827
8827
|
rl.question(
|
|
8828
8828
|
`Where should Synkro run?
|
|
8829
8829
|
local \u2014 a grading container on this machine (Docker)
|
|
@@ -8832,7 +8832,7 @@ Each worker uses the account credentials you authorize. Choose [${current}] / ${
|
|
|
8832
8832
|
(answer) => {
|
|
8833
8833
|
rl.close();
|
|
8834
8834
|
const a = answer.trim().toLowerCase();
|
|
8835
|
-
|
|
8835
|
+
resolve9(a === "cloud" ? "cloud" : a === "local" ? "local" : current);
|
|
8836
8836
|
}
|
|
8837
8837
|
);
|
|
8838
8838
|
});
|
|
@@ -8986,7 +8986,7 @@ function writeConfigEnv(opts) {
|
|
|
8986
8986
|
const safeTier = sanitizeConfigValue(opts.tier ?? "pro", 32);
|
|
8987
8987
|
const safeInference = sanitizeConfigValue(opts.inference ?? "fast", 16);
|
|
8988
8988
|
const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? "", 1024);
|
|
8989
|
-
const
|
|
8989
|
+
const lines2 = [
|
|
8990
8990
|
"# Synkro CLI config (managed by synkro install)",
|
|
8991
8991
|
"# JWT auth \u2014 the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime",
|
|
8992
8992
|
"# and send Authorization: Bearer <access_token> on every gateway call.",
|
|
@@ -8994,27 +8994,27 @@ function writeConfigEnv(opts) {
|
|
|
8994
8994
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
8995
8995
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
8996
8996
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
8997
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.
|
|
8997
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.9")}`
|
|
8998
8998
|
];
|
|
8999
|
-
if (safeSynkroBin)
|
|
9000
|
-
if (safeUserId)
|
|
9001
|
-
if (safeOrgId)
|
|
9002
|
-
if (safeEmail)
|
|
8999
|
+
if (safeSynkroBin) lines2.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
9000
|
+
if (safeUserId) lines2.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
9001
|
+
if (safeOrgId) lines2.push(`SYNKRO_ORG_ID=${shellQuoteSingle2(safeOrgId)}`);
|
|
9002
|
+
if (safeEmail) lines2.push(`SYNKRO_EMAIL=${shellQuoteSingle2(safeEmail)}`);
|
|
9003
9003
|
if (opts.transcriptConsent !== void 0) {
|
|
9004
|
-
|
|
9004
|
+
lines2.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle2(opts.transcriptConsent ? "yes" : "no")}`);
|
|
9005
9005
|
}
|
|
9006
|
-
if (opts.transcriptConsentCC !== void 0)
|
|
9007
|
-
if (opts.transcriptConsentCursor !== void 0)
|
|
9008
|
-
if (opts.transcriptConsentCodex !== void 0)
|
|
9009
|
-
|
|
9006
|
+
if (opts.transcriptConsentCC !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CC=${shellQuoteSingle2(opts.transcriptConsentCC ? "yes" : "no")}`);
|
|
9007
|
+
if (opts.transcriptConsentCursor !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CURSOR=${shellQuoteSingle2(opts.transcriptConsentCursor ? "yes" : "no")}`);
|
|
9008
|
+
if (opts.transcriptConsentCodex !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CODEX=${shellQuoteSingle2(opts.transcriptConsentCodex ? "yes" : "no")}`);
|
|
9009
|
+
lines2.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle2(opts.localInference ? "yes" : "no")}`);
|
|
9010
9010
|
const safeMode = sanitizeConfigValue(opts.deploymentMode ?? "docker", 16);
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
writeFileSync17(CONFIG_PATH4,
|
|
9011
|
+
lines2.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle2(safeMode)}`);
|
|
9012
|
+
lines2.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
|
|
9013
|
+
lines2.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
|
|
9014
|
+
lines2.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
|
|
9015
|
+
lines2.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
|
|
9016
|
+
lines2.push("");
|
|
9017
|
+
writeFileSync17(CONFIG_PATH4, lines2.join("\n"), "utf-8");
|
|
9018
9018
|
chmodSync5(CONFIG_PATH4, 384);
|
|
9019
9019
|
}
|
|
9020
9020
|
function persistedTranscriptConsent(source) {
|
|
@@ -9741,7 +9741,7 @@ async function installCommand(opts = {}) {
|
|
|
9741
9741
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
9742
9742
|
emit("install", {
|
|
9743
9743
|
phase: "started",
|
|
9744
|
-
cli_version_to: "1.10.
|
|
9744
|
+
cli_version_to: "1.10.9",
|
|
9745
9745
|
agents_detected: agents.map((a) => a.kind),
|
|
9746
9746
|
with_github: false,
|
|
9747
9747
|
with_local_cc: false,
|
|
@@ -10303,15 +10303,15 @@ function parseSynkroToml2(raw) {
|
|
|
10303
10303
|
}
|
|
10304
10304
|
const eq = line.indexOf("=");
|
|
10305
10305
|
if (eq === -1) continue;
|
|
10306
|
-
const
|
|
10306
|
+
const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
|
|
10307
10307
|
let valRaw = line.slice(eq + 1).trim();
|
|
10308
10308
|
if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
|
|
10309
10309
|
const h = valRaw.indexOf("#");
|
|
10310
10310
|
if (h !== -1) valRaw = valRaw.slice(0, h).trim();
|
|
10311
10311
|
}
|
|
10312
10312
|
const value = parseTomlValue2(valRaw);
|
|
10313
|
-
if (section) result[section][
|
|
10314
|
-
else result[
|
|
10313
|
+
if (section) result[section][key2] = value;
|
|
10314
|
+
else result[key2] = value;
|
|
10315
10315
|
}
|
|
10316
10316
|
return result;
|
|
10317
10317
|
}
|
|
@@ -10592,8 +10592,8 @@ async function syncSkillFiles() {
|
|
|
10592
10592
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
10593
10593
|
return null;
|
|
10594
10594
|
}
|
|
10595
|
-
const
|
|
10596
|
-
console.log(` \u2192 read ${source} (${
|
|
10595
|
+
const lines2 = content.split("\n").length;
|
|
10596
|
+
console.log(` \u2192 read ${source} (${lines2} lines, ${(content.length / 1024).toFixed(1)} KB)`);
|
|
10597
10597
|
return { source, content };
|
|
10598
10598
|
}).filter(Boolean);
|
|
10599
10599
|
if (tasks.length === 0) return;
|
|
@@ -10809,8 +10809,8 @@ function ensureReachabilityGitHook() {
|
|
|
10809
10809
|
}
|
|
10810
10810
|
return "updated";
|
|
10811
10811
|
}
|
|
10812
|
-
const
|
|
10813
|
-
writeFileSync17(hookPath, cur +
|
|
10812
|
+
const sep4 = cur.endsWith("\n") ? "" : "\n";
|
|
10813
|
+
writeFileSync17(hookPath, cur + sep4 + "\n" + block + "\n");
|
|
10814
10814
|
try {
|
|
10815
10815
|
chmodSync5(hookPath, 493);
|
|
10816
10816
|
} catch {
|
|
@@ -10872,10 +10872,10 @@ function extractSessionInsights(projectsDir) {
|
|
|
10872
10872
|
const filePath = join19(projectsDir, file);
|
|
10873
10873
|
try {
|
|
10874
10874
|
const content = readFileSync21(filePath, "utf-8");
|
|
10875
|
-
const
|
|
10876
|
-
for (let i = 0; i <
|
|
10875
|
+
const lines2 = content.split("\n").filter(Boolean);
|
|
10876
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
10877
10877
|
try {
|
|
10878
|
-
const entry = JSON.parse(
|
|
10878
|
+
const entry = JSON.parse(lines2[i]);
|
|
10879
10879
|
if (entry.type === "user" && typeof entry.message?.content === "string" && entry.message.content.startsWith("This session is being continued")) {
|
|
10880
10880
|
insights.push({
|
|
10881
10881
|
session_id: sessionId,
|
|
@@ -10888,9 +10888,9 @@ function extractSessionInsights(projectsDir) {
|
|
|
10888
10888
|
}
|
|
10889
10889
|
}
|
|
10890
10890
|
const userMessages = [];
|
|
10891
|
-
for (let i =
|
|
10891
|
+
for (let i = lines2.length - 1; i >= 0 && userMessages.length < 20; i--) {
|
|
10892
10892
|
try {
|
|
10893
|
-
const entry = JSON.parse(
|
|
10893
|
+
const entry = JSON.parse(lines2[i]);
|
|
10894
10894
|
if (entry.type === "user") {
|
|
10895
10895
|
const text = typeof entry.message?.content === "string" ? entry.message.content : Array.isArray(entry.message?.content) ? entry.message.content.map((b) => b.text ?? b).filter((t) => typeof t === "string").join(" ") : null;
|
|
10896
10896
|
if (text && text.length > 10 && text.length < 2e3 && !text.startsWith("This session is being continued")) {
|
|
@@ -10949,13 +10949,13 @@ function extractTextContent(content) {
|
|
|
10949
10949
|
function getCodexTranscriptFiles(repo) {
|
|
10950
10950
|
const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
|
|
10951
10951
|
if (!existsSync22(sessionsDir)) return [];
|
|
10952
|
-
let
|
|
10952
|
+
let relative3 = [];
|
|
10953
10953
|
try {
|
|
10954
|
-
|
|
10954
|
+
relative3 = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
|
|
10955
10955
|
} catch {
|
|
10956
10956
|
return [];
|
|
10957
10957
|
}
|
|
10958
|
-
return
|
|
10958
|
+
return relative3.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
|
|
10959
10959
|
try {
|
|
10960
10960
|
const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
|
|
10961
10961
|
const meta = JSON.parse(first);
|
|
@@ -10972,11 +10972,11 @@ function isJsonSyntaxError(error) {
|
|
|
10972
10972
|
}
|
|
10973
10973
|
function parseCodexTranscriptFile(filePath) {
|
|
10974
10974
|
const transcript = readFileSync21(filePath, "utf-8");
|
|
10975
|
-
const
|
|
10975
|
+
const lines2 = transcript.split("\n");
|
|
10976
10976
|
const transcriptUsage = parseCodexTranscriptUsage(transcript);
|
|
10977
10977
|
let sessionId = "";
|
|
10978
10978
|
let model = "";
|
|
10979
|
-
for (const line of
|
|
10979
|
+
for (const line of lines2) {
|
|
10980
10980
|
try {
|
|
10981
10981
|
const entry = JSON.parse(line);
|
|
10982
10982
|
if (entry.type === "session_meta") {
|
|
@@ -11060,11 +11060,11 @@ function isSafeConvId(id) {
|
|
|
11060
11060
|
}
|
|
11061
11061
|
function parseCursorTranscriptFile(filePath) {
|
|
11062
11062
|
const content = readFileSync21(filePath, "utf-8");
|
|
11063
|
-
const
|
|
11063
|
+
const lines2 = content.split("\n").filter(Boolean);
|
|
11064
11064
|
const messages = [];
|
|
11065
|
-
for (let i = 0; i <
|
|
11065
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
11066
11066
|
try {
|
|
11067
|
-
const entry = JSON.parse(
|
|
11067
|
+
const entry = JSON.parse(lines2[i]);
|
|
11068
11068
|
const role = entry.role || entry.message?.role;
|
|
11069
11069
|
if (role !== "user" && role !== "assistant") continue;
|
|
11070
11070
|
const text = extractTextContent(entry.message?.content ?? entry.content);
|
|
@@ -11124,11 +11124,11 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
11124
11124
|
}
|
|
11125
11125
|
function parseTranscriptFile(filePath) {
|
|
11126
11126
|
const content = readFileSync21(filePath, "utf-8");
|
|
11127
|
-
const
|
|
11127
|
+
const lines2 = content.split("\n").filter(Boolean);
|
|
11128
11128
|
const messages = [];
|
|
11129
|
-
for (let i = 0; i <
|
|
11129
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
11130
11130
|
try {
|
|
11131
|
-
const entry = JSON.parse(
|
|
11131
|
+
const entry = JSON.parse(lines2[i]);
|
|
11132
11132
|
if (entry.type !== "user" && entry.type !== "assistant") continue;
|
|
11133
11133
|
const msg = {
|
|
11134
11134
|
message_index: i,
|
|
@@ -12030,10 +12030,10 @@ function confirmPurge() {
|
|
|
12030
12030
|
return Promise.resolve(false);
|
|
12031
12031
|
}
|
|
12032
12032
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
12033
|
-
return new Promise((
|
|
12033
|
+
return new Promise((resolve9) => {
|
|
12034
12034
|
rl.question(" Type 'yes' to wipe everything (anything else cancels): ", (answer) => {
|
|
12035
12035
|
rl.close();
|
|
12036
|
-
|
|
12036
|
+
resolve9(answer.trim().toLowerCase() === "yes");
|
|
12037
12037
|
});
|
|
12038
12038
|
});
|
|
12039
12039
|
}
|
|
@@ -12194,8 +12194,8 @@ function readRecentTurns(n = 20) {
|
|
|
12194
12194
|
const size = statSync3(TURN_LOG_PATH).size;
|
|
12195
12195
|
if (size === 0) return [];
|
|
12196
12196
|
const text = readFileSync23(TURN_LOG_PATH, "utf-8");
|
|
12197
|
-
const
|
|
12198
|
-
const lastN =
|
|
12197
|
+
const lines2 = text.split("\n").filter(Boolean);
|
|
12198
|
+
const lastN = lines2.slice(-n).reverse();
|
|
12199
12199
|
return lastN.map((line) => {
|
|
12200
12200
|
try {
|
|
12201
12201
|
return JSON.parse(line);
|
|
@@ -12286,7 +12286,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12286
12286
|
const port = opts.port ?? CHANNEL_PORT;
|
|
12287
12287
|
const startedAt = Date.now();
|
|
12288
12288
|
try {
|
|
12289
|
-
const result = await new Promise((
|
|
12289
|
+
const result = await new Promise((resolve9, reject) => {
|
|
12290
12290
|
const req = httpRequest({
|
|
12291
12291
|
host: CHANNEL_HOST,
|
|
12292
12292
|
port,
|
|
@@ -12312,7 +12312,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12312
12312
|
reject(new LocalCCError(parsed.error));
|
|
12313
12313
|
return;
|
|
12314
12314
|
}
|
|
12315
|
-
|
|
12315
|
+
resolve9(String(parsed.result ?? ""));
|
|
12316
12316
|
} catch (err) {
|
|
12317
12317
|
reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
|
|
12318
12318
|
}
|
|
@@ -12338,14 +12338,14 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12338
12338
|
}
|
|
12339
12339
|
}
|
|
12340
12340
|
function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
|
|
12341
|
-
return new Promise((
|
|
12341
|
+
return new Promise((resolve9) => {
|
|
12342
12342
|
const sock = connect(port, CHANNEL_HOST);
|
|
12343
12343
|
const done = (ok) => {
|
|
12344
12344
|
try {
|
|
12345
12345
|
sock.destroy();
|
|
12346
12346
|
} catch {
|
|
12347
12347
|
}
|
|
12348
|
-
|
|
12348
|
+
resolve9(ok);
|
|
12349
12349
|
};
|
|
12350
12350
|
sock.once("connect", () => done(true));
|
|
12351
12351
|
sock.once("error", () => done(false));
|
|
@@ -12377,10 +12377,10 @@ __export(grade_exports, {
|
|
|
12377
12377
|
gradeCommand: () => gradeCommand
|
|
12378
12378
|
});
|
|
12379
12379
|
async function readStdin() {
|
|
12380
|
-
return new Promise((
|
|
12380
|
+
return new Promise((resolve9, reject) => {
|
|
12381
12381
|
const chunks = [];
|
|
12382
12382
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
12383
|
-
process.stdin.on("end", () =>
|
|
12383
|
+
process.stdin.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
|
|
12384
12384
|
process.stdin.on("error", reject);
|
|
12385
12385
|
});
|
|
12386
12386
|
}
|
|
@@ -12467,9 +12467,9 @@ async function fetchOrgRules(gatewayUrl, apiKey) {
|
|
|
12467
12467
|
function applyLiteralMatchNegative(rules, file) {
|
|
12468
12468
|
if (!file.patch) return [];
|
|
12469
12469
|
const findings = [];
|
|
12470
|
-
const
|
|
12470
|
+
const lines2 = file.patch.split("\n");
|
|
12471
12471
|
let currentNewLine = 0;
|
|
12472
|
-
for (const line of
|
|
12472
|
+
for (const line of lines2) {
|
|
12473
12473
|
if (line.startsWith("@@")) {
|
|
12474
12474
|
const m = line.match(/\+(\d+)(?:,\d+)?/);
|
|
12475
12475
|
if (m) currentNewLine = parseInt(m[1], 10);
|
|
@@ -12597,12 +12597,12 @@ async function fetchScanContext(gatewayUrl, apiKey, repo, prNumber, sha) {
|
|
|
12597
12597
|
}
|
|
12598
12598
|
function getFileDiffWithLines(file) {
|
|
12599
12599
|
if (!file.patch) return { hunks: "", newFileLineMap: /* @__PURE__ */ new Map() };
|
|
12600
|
-
const
|
|
12600
|
+
const lines2 = file.patch.split("\n");
|
|
12601
12601
|
const annotated = [];
|
|
12602
12602
|
const lineMap = /* @__PURE__ */ new Map();
|
|
12603
12603
|
let currentNewLine = 0;
|
|
12604
12604
|
let patchIndex = 0;
|
|
12605
|
-
for (const line of
|
|
12605
|
+
for (const line of lines2) {
|
|
12606
12606
|
patchIndex++;
|
|
12607
12607
|
if (line.startsWith("@@")) {
|
|
12608
12608
|
const match = line.match(/\+(\d+)(?:,\d+)?/);
|
|
@@ -12634,7 +12634,7 @@ function spawnClaudeJudge(file, claudeToken, promptHeader) {
|
|
|
12634
12634
|
Diff:
|
|
12635
12635
|
${hunks}`;
|
|
12636
12636
|
const fullPrompt = promptHeader + userMessage;
|
|
12637
|
-
return new Promise((
|
|
12637
|
+
return new Promise((resolve9) => {
|
|
12638
12638
|
const t0 = Date.now();
|
|
12639
12639
|
const proc = spawn6(
|
|
12640
12640
|
"claude",
|
|
@@ -12662,7 +12662,7 @@ ${hunks}`;
|
|
|
12662
12662
|
const latencyMs = Date.now() - t0;
|
|
12663
12663
|
if (code !== 0) {
|
|
12664
12664
|
console.warn(` claude exited ${code}: ${(stderr || stdout).slice(0, 500)}`);
|
|
12665
|
-
|
|
12665
|
+
resolve9({ findings: [], latencyMs });
|
|
12666
12666
|
return;
|
|
12667
12667
|
}
|
|
12668
12668
|
try {
|
|
@@ -12681,10 +12681,10 @@ ${hunks}`;
|
|
|
12681
12681
|
description: f.description,
|
|
12682
12682
|
fix: f.fix
|
|
12683
12683
|
}));
|
|
12684
|
-
|
|
12684
|
+
resolve9({ findings, latencyMs });
|
|
12685
12685
|
} catch (parseErr) {
|
|
12686
12686
|
console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
|
|
12687
|
-
|
|
12687
|
+
resolve9({ findings: [], latencyMs });
|
|
12688
12688
|
}
|
|
12689
12689
|
});
|
|
12690
12690
|
});
|
|
@@ -12733,7 +12733,7 @@ ${JSON.stringify(findings, null, 2)}
|
|
|
12733
12733
|
`;
|
|
12734
12734
|
}
|
|
12735
12735
|
function spawnOpusConsolidator(findings, claudeToken) {
|
|
12736
|
-
return new Promise((
|
|
12736
|
+
return new Promise((resolve9) => {
|
|
12737
12737
|
const prompt = buildConsolidationPrompt(findings);
|
|
12738
12738
|
const proc = spawn6(
|
|
12739
12739
|
"claude",
|
|
@@ -12760,7 +12760,7 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12760
12760
|
proc.on("close", (code) => {
|
|
12761
12761
|
if (code !== 0) {
|
|
12762
12762
|
console.warn(` opus consolidation exited ${code}: ${(stderr || stdout).slice(0, 300)}`);
|
|
12763
|
-
|
|
12763
|
+
resolve9(fallbackReview(findings));
|
|
12764
12764
|
return;
|
|
12765
12765
|
}
|
|
12766
12766
|
try {
|
|
@@ -12781,10 +12781,10 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12781
12781
|
const order = ["low", "medium", "high", "critical"];
|
|
12782
12782
|
return order.indexOf(f.severity) > order.indexOf(max) ? f.severity : max;
|
|
12783
12783
|
}, "low");
|
|
12784
|
-
|
|
12784
|
+
resolve9({ summary: review.summary || "", comments, severity: maxSeverity });
|
|
12785
12785
|
} catch {
|
|
12786
12786
|
console.warn(` failed to parse opus response, using fallback`);
|
|
12787
|
-
|
|
12787
|
+
resolve9(fallbackReview(findings));
|
|
12788
12788
|
}
|
|
12789
12789
|
});
|
|
12790
12790
|
});
|
|
@@ -12792,15 +12792,15 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12792
12792
|
function fallbackReview(findings) {
|
|
12793
12793
|
const grouped = /* @__PURE__ */ new Map();
|
|
12794
12794
|
for (const f of findings) {
|
|
12795
|
-
const
|
|
12796
|
-
if (!grouped.has(
|
|
12797
|
-
grouped.get(
|
|
12795
|
+
const key2 = `${f.file}::${f.category}`;
|
|
12796
|
+
if (!grouped.has(key2)) grouped.set(key2, []);
|
|
12797
|
+
grouped.get(key2).push(f);
|
|
12798
12798
|
}
|
|
12799
12799
|
const comments = [];
|
|
12800
12800
|
for (const [, group] of grouped) {
|
|
12801
12801
|
const first = group[0];
|
|
12802
|
-
const
|
|
12803
|
-
const linesStr =
|
|
12802
|
+
const lines2 = group.map((f) => f.line);
|
|
12803
|
+
const linesStr = lines2.length > 1 ? `Lines ${lines2.join(", ")}` : `Line ${lines2[0]}`;
|
|
12804
12804
|
const severityEmoji = first.severity === "critical" ? "\u{1F534}" : first.severity === "high" ? "\u{1F7E0}" : first.severity === "medium" ? "\u{1F7E1}" : "\u{1F535}";
|
|
12805
12805
|
comments.push({
|
|
12806
12806
|
path: first.file,
|
|
@@ -13254,10 +13254,10 @@ function stopTask(channel = CHANNEL_PRIMARY) {
|
|
|
13254
13254
|
t = findTask(channel);
|
|
13255
13255
|
}
|
|
13256
13256
|
}
|
|
13257
|
-
function tailLogs(
|
|
13257
|
+
function tailLogs(lines2 = 80, channel = CHANNEL_PRIMARY) {
|
|
13258
13258
|
const t = findTask(channel);
|
|
13259
13259
|
if (!t) return `(no ${channel.taskLabel} task)`;
|
|
13260
|
-
const r = spawnSync9("pueue", ["log", "--lines", String(
|
|
13260
|
+
const r = spawnSync9("pueue", ["log", "--lines", String(lines2), String(t.id)], { encoding: "utf-8" });
|
|
13261
13261
|
return r.stdout || r.stderr || "(no output)";
|
|
13262
13262
|
}
|
|
13263
13263
|
function ensureRunning(opts = {}) {
|
|
@@ -13267,14 +13267,14 @@ function ensureRunning(opts = {}) {
|
|
|
13267
13267
|
return startTask(opts);
|
|
13268
13268
|
}
|
|
13269
13269
|
function probePort(host, port, timeoutMs = 500) {
|
|
13270
|
-
return new Promise((
|
|
13270
|
+
return new Promise((resolve9) => {
|
|
13271
13271
|
const sock = connect2(port, host);
|
|
13272
13272
|
const done = (ok) => {
|
|
13273
13273
|
try {
|
|
13274
13274
|
sock.destroy();
|
|
13275
13275
|
} catch {
|
|
13276
13276
|
}
|
|
13277
|
-
|
|
13277
|
+
resolve9(ok);
|
|
13278
13278
|
};
|
|
13279
13279
|
sock.once("connect", () => done(true));
|
|
13280
13280
|
sock.once("error", () => done(false));
|
|
@@ -13824,7 +13824,7 @@ function cmdLogs(rest) {
|
|
|
13824
13824
|
if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
|
|
13825
13825
|
return;
|
|
13826
13826
|
}
|
|
13827
|
-
return new Promise((
|
|
13827
|
+
return new Promise((resolve9) => {
|
|
13828
13828
|
console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
|
|
13829
13829
|
const stop = followTurns((t) => {
|
|
13830
13830
|
console.log(" " + formatTurn(t, raw));
|
|
@@ -13832,7 +13832,7 @@ function cmdLogs(rest) {
|
|
|
13832
13832
|
const onSigint = () => {
|
|
13833
13833
|
stop();
|
|
13834
13834
|
process.removeListener("SIGINT", onSigint);
|
|
13835
|
-
|
|
13835
|
+
resolve9();
|
|
13836
13836
|
};
|
|
13837
13837
|
process.on("SIGINT", onSigint);
|
|
13838
13838
|
});
|
|
@@ -14050,7 +14050,7 @@ function extractToolResultText(content, e) {
|
|
|
14050
14050
|
function parseSession(file, seenStableIds) {
|
|
14051
14051
|
const { filePath, sessionId, parentSessionId } = file;
|
|
14052
14052
|
const transcript = readFileSync27(filePath, "utf-8");
|
|
14053
|
-
const
|
|
14053
|
+
const lines2 = transcript.split("\n").filter(Boolean);
|
|
14054
14054
|
const transcriptUsage = parseClaudeTranscriptUsage(
|
|
14055
14055
|
transcript,
|
|
14056
14056
|
statSync4(filePath).mtime.toISOString().slice(0, 10),
|
|
@@ -14062,10 +14062,10 @@ function parseSession(file, seenStableIds) {
|
|
|
14062
14062
|
const messages = [];
|
|
14063
14063
|
const actions = [];
|
|
14064
14064
|
let step = 0;
|
|
14065
|
-
for (let i = 0; i <
|
|
14065
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
14066
14066
|
let e;
|
|
14067
14067
|
try {
|
|
14068
|
-
e = JSON.parse(
|
|
14068
|
+
e = JSON.parse(lines2[i]);
|
|
14069
14069
|
} catch {
|
|
14070
14070
|
continue;
|
|
14071
14071
|
}
|
|
@@ -14121,9 +14121,9 @@ function parseSession(file, seenStableIds) {
|
|
|
14121
14121
|
}
|
|
14122
14122
|
function ask2(q) {
|
|
14123
14123
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
14124
|
-
return new Promise((
|
|
14124
|
+
return new Promise((resolve9) => rl.question(q, (a) => {
|
|
14125
14125
|
rl.close();
|
|
14126
|
-
|
|
14126
|
+
resolve9(/^y(es)?$/i.test(a.trim()));
|
|
14127
14127
|
}));
|
|
14128
14128
|
}
|
|
14129
14129
|
async function importCommand() {
|
|
@@ -14261,8 +14261,8 @@ function computeDigest(canonical2) {
|
|
|
14261
14261
|
}
|
|
14262
14262
|
function verifySignature(digest, signatureB64, publicKeyPem) {
|
|
14263
14263
|
try {
|
|
14264
|
-
const
|
|
14265
|
-
return crypto.verify(null, Buffer.from(digest, "utf8"),
|
|
14264
|
+
const key2 = crypto.createPublicKey(publicKeyPem);
|
|
14265
|
+
return crypto.verify(null, Buffer.from(digest, "utf8"), key2, Buffer.from(signatureB64, "base64"));
|
|
14266
14266
|
} catch {
|
|
14267
14267
|
return false;
|
|
14268
14268
|
}
|
|
@@ -14650,14 +14650,23 @@ function buildSpawnAgent(opts) {
|
|
|
14650
14650
|
// and its own history scrolls — without it only the last screen is
|
|
14651
14651
|
// reachable once the session is nested inside the layout.
|
|
14652
14652
|
["tmux", "set-option", "-t", session, "mouse", "on"],
|
|
14653
|
+
...buildClipboardBindings(session),
|
|
14653
14654
|
["tmux", "set-option", "-t", session, "-q", "@synkro_harness", opts.harness],
|
|
14654
14655
|
["tmux", "set-option", "-t", session, "-q", "@synkro_space", opts.space],
|
|
14655
14656
|
["tmux", "set-option", "-t", session, "-q", "@synkro_backend", opts.backend],
|
|
14657
|
+
["tmux", "set-option", "-t", session, "-q", "@synkro_mode", opts.mode || "native"],
|
|
14656
14658
|
// Keep the pane visible after exit so the sidebar can render 'done'
|
|
14657
14659
|
// instead of the agent silently vanishing.
|
|
14658
14660
|
["tmux", "set-option", "-t", session, "remain-on-exit", "on"]
|
|
14659
14661
|
];
|
|
14660
14662
|
}
|
|
14663
|
+
function buildClipboardBindings(session) {
|
|
14664
|
+
return [
|
|
14665
|
+
["tmux", "set-option", "-t", session, "set-clipboard", "on"],
|
|
14666
|
+
["tmux", "bind-key", "-T", "copy-mode", "MouseDragEnd1Pane", "send-keys", "-X", "copy-pipe-and-cancel", SYSTEM_CLIPBOARD],
|
|
14667
|
+
["tmux", "bind-key", "-T", "copy-mode-vi", "MouseDragEnd1Pane", "send-keys", "-X", "copy-pipe-and-cancel", SYSTEM_CLIPBOARD]
|
|
14668
|
+
];
|
|
14669
|
+
}
|
|
14661
14670
|
function buildListAgents() {
|
|
14662
14671
|
return ["tmux", "list-sessions", "-F", ["#{session_name}", "#{?pane_dead,dead,alive}", "#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}", "#{@synkro_pueue}", "#{pane_current_path}"].join(FIELD_SEP)];
|
|
14663
14672
|
}
|
|
@@ -14667,7 +14676,7 @@ function buildAgentSnapshot() {
|
|
|
14667
14676
|
return ["sh", "-c", script];
|
|
14668
14677
|
}
|
|
14669
14678
|
function parseAgentSnapshot(output) {
|
|
14670
|
-
const
|
|
14679
|
+
const lines2 = String(output || "").split("\n");
|
|
14671
14680
|
const captures = /* @__PURE__ */ new Map();
|
|
14672
14681
|
const listLines = [];
|
|
14673
14682
|
let current = null;
|
|
@@ -14675,7 +14684,7 @@ function parseAgentSnapshot(output) {
|
|
|
14675
14684
|
const flush2 = () => {
|
|
14676
14685
|
if (current) captures.set(current, chunk.join("\n"));
|
|
14677
14686
|
};
|
|
14678
|
-
for (const line of
|
|
14687
|
+
for (const line of lines2) {
|
|
14679
14688
|
if (line.startsWith("===")) {
|
|
14680
14689
|
flush2();
|
|
14681
14690
|
current = line.slice(3).trim();
|
|
@@ -14701,6 +14710,9 @@ function buildSendText(session, text) {
|
|
|
14701
14710
|
function buildEnableMouse(session) {
|
|
14702
14711
|
return ["tmux", "set-option", "-t", session, "mouse", "on"];
|
|
14703
14712
|
}
|
|
14713
|
+
function buildKeepPaneAfterExit(pane) {
|
|
14714
|
+
return ["tmux", "set-option", "-p", "-t", pane, "remain-on-exit", "on"];
|
|
14715
|
+
}
|
|
14704
14716
|
function buildInterrupt(session) {
|
|
14705
14717
|
return ["tmux", "send-keys", "-t", session, "Escape"];
|
|
14706
14718
|
}
|
|
@@ -14767,7 +14779,7 @@ function parseWorktrees(porcelain) {
|
|
|
14767
14779
|
}
|
|
14768
14780
|
return rows;
|
|
14769
14781
|
}
|
|
14770
|
-
var execFileAsync, AGENT_PREFIX, UI_SESSION, CONTAINER_USER, FIELD_SEP;
|
|
14782
|
+
var execFileAsync, AGENT_PREFIX, UI_SESSION, CONTAINER_USER, SYSTEM_CLIPBOARD, FIELD_SEP;
|
|
14771
14783
|
var init_tmux = __esm({
|
|
14772
14784
|
"cli/ui/tmux.ts"() {
|
|
14773
14785
|
"use strict";
|
|
@@ -14775,6 +14787,7 @@ var init_tmux = __esm({
|
|
|
14775
14787
|
AGENT_PREFIX = "synkro-agent-";
|
|
14776
14788
|
UI_SESSION = "synkro-ui";
|
|
14777
14789
|
CONTAINER_USER = "synkro";
|
|
14790
|
+
SYSTEM_CLIPBOARD = "if command -v pbcopy >/dev/null 2>&1; then pbcopy; elif command -v wl-copy >/dev/null 2>&1; then wl-copy; elif command -v xclip >/dev/null 2>&1; then xclip -selection clipboard; else cat >/dev/null; fi";
|
|
14778
14791
|
FIELD_SEP = "|";
|
|
14779
14792
|
}
|
|
14780
14793
|
});
|
|
@@ -14907,6 +14920,10 @@ function visibleAgents(all, space, filter, grouped) {
|
|
|
14907
14920
|
if (grouped || filter === "all" || !space) return all;
|
|
14908
14921
|
return all.filter((agent) => agent.repo === space.path);
|
|
14909
14922
|
}
|
|
14923
|
+
function nextLiveAgent(all, preferredRepo = "") {
|
|
14924
|
+
const live = all.filter((agent) => agent.status !== "offline" && agent.status !== "done");
|
|
14925
|
+
return live.find((agent) => Boolean(preferredRepo) && agent.repo === preferredRepo) || live[0];
|
|
14926
|
+
}
|
|
14910
14927
|
async function discoverAgents(runner, backend, memory) {
|
|
14911
14928
|
const snapshot = await run(runner, buildAgentSnapshot());
|
|
14912
14929
|
const { list, captures } = parseAgentSnapshot(snapshot.ok ? snapshot.stdout : "");
|
|
@@ -14946,12 +14963,12 @@ async function discoverAgents(runner, backend, memory) {
|
|
|
14946
14963
|
}
|
|
14947
14964
|
function offlineAgents(live, records) {
|
|
14948
14965
|
const alive = new Set(live.map((agent) => agent.session));
|
|
14949
|
-
return records.filter((
|
|
14950
|
-
name:
|
|
14951
|
-
session:
|
|
14952
|
-
harness:
|
|
14953
|
-
space:
|
|
14954
|
-
backend:
|
|
14966
|
+
return records.filter((record2) => !alive.has(record2.session)).map((record2) => ({
|
|
14967
|
+
name: record2.name,
|
|
14968
|
+
session: record2.session,
|
|
14969
|
+
harness: record2.harness,
|
|
14970
|
+
space: record2.space,
|
|
14971
|
+
backend: record2.backend,
|
|
14955
14972
|
status: "offline"
|
|
14956
14973
|
}));
|
|
14957
14974
|
}
|
|
@@ -14988,8 +15005,8 @@ function saveRecords(records) {
|
|
|
14988
15005
|
} catch {
|
|
14989
15006
|
}
|
|
14990
15007
|
}
|
|
14991
|
-
function recordSession(
|
|
14992
|
-
saveRecords([...loadRecords().filter((row2) => row2.session !==
|
|
15008
|
+
function recordSession(record2) {
|
|
15009
|
+
saveRecords([...loadRecords().filter((row2) => row2.session !== record2.session), record2]);
|
|
14993
15010
|
}
|
|
14994
15011
|
function forgetSession(session) {
|
|
14995
15012
|
saveRecords(loadRecords().filter((row2) => row2.session !== session));
|
|
@@ -15024,11 +15041,11 @@ function lastAgentFor(space) {
|
|
|
15024
15041
|
return loadLastAgents()[String(space || "").replace(/\/+$/, "")] || "";
|
|
15025
15042
|
}
|
|
15026
15043
|
function rememberLastAgent(space, session) {
|
|
15027
|
-
const
|
|
15028
|
-
if (!
|
|
15044
|
+
const key2 = String(space || "").replace(/\/+$/, "");
|
|
15045
|
+
if (!key2 || !session) return;
|
|
15029
15046
|
try {
|
|
15030
15047
|
mkdirSync21(dirname10(LAST_AGENT_FILE), { recursive: true });
|
|
15031
|
-
writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [
|
|
15048
|
+
writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [key2]: session }, null, 2));
|
|
15032
15049
|
} catch {
|
|
15033
15050
|
}
|
|
15034
15051
|
}
|
|
@@ -15067,9 +15084,17 @@ var init_manifest = __esm({
|
|
|
15067
15084
|
});
|
|
15068
15085
|
|
|
15069
15086
|
// cli/ui/launch.ts
|
|
15070
|
-
import { mkdirSync as mkdirSync22, writeFileSync as writeFileSync25 } from "fs";
|
|
15087
|
+
import { mkdirSync as mkdirSync22, statSync as statSync5, writeFileSync as writeFileSync25 } from "fs";
|
|
15071
15088
|
import { homedir as homedir34 } from "os";
|
|
15072
15089
|
import { join as join34 } from "path";
|
|
15090
|
+
function buildStamp(bootPath) {
|
|
15091
|
+
try {
|
|
15092
|
+
const stat = statSync5(bootPath);
|
|
15093
|
+
return String(stat.size) + ":" + String(Math.floor(stat.mtimeMs));
|
|
15094
|
+
} catch {
|
|
15095
|
+
return bootPath;
|
|
15096
|
+
}
|
|
15097
|
+
}
|
|
15073
15098
|
function sidebarColumns(totalColumns) {
|
|
15074
15099
|
return String(Math.max(20, Math.min(34, Math.round(totalColumns * 0.24))));
|
|
15075
15100
|
}
|
|
@@ -15141,6 +15166,7 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
|
|
|
15141
15166
|
const tabPopup = "display-popup -E -w 60 -h 18 -S fg=colour111 " + shellQuote3(tabLauncher);
|
|
15142
15167
|
const dispatch = 'if-shell -F "#{==:#{mouse_status_range},tabmenu}" "' + tabPopup + '" "select-window -t="';
|
|
15143
15168
|
const style = [
|
|
15169
|
+
["set-option", "-t", UI_SESSION, "-q", "@synkro_build", buildStamp(bootPath)],
|
|
15144
15170
|
// Mouse: drag the pane border to resize the sidebar, click a pane to
|
|
15145
15171
|
// focus it, click rows/chips. Without this the fixed split reads as
|
|
15146
15172
|
// "blocked in".
|
|
@@ -15172,6 +15198,39 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
|
|
|
15172
15198
|
["bind-key", "-n", "M-s", "select-pane", "-L"]
|
|
15173
15199
|
];
|
|
15174
15200
|
for (const argv of style) await run(HOST, ["tmux", ...argv]);
|
|
15201
|
+
for (const argv of buildClipboardBindings(UI_SESSION)) await run(HOST, argv);
|
|
15202
|
+
}
|
|
15203
|
+
function parseUiPanes(output) {
|
|
15204
|
+
return String(output || "").split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 4).map(([window, pane, width, ...command]) => ({
|
|
15205
|
+
window,
|
|
15206
|
+
pane,
|
|
15207
|
+
width: Number(width) || 0,
|
|
15208
|
+
command: command.join("|")
|
|
15209
|
+
})).filter((row2) => Boolean(row2.window && row2.pane));
|
|
15210
|
+
}
|
|
15211
|
+
async function refreshUiShell(bootPath, repoCwd) {
|
|
15212
|
+
const expected = buildStamp(bootPath);
|
|
15213
|
+
const current = await run(HOST, ["tmux", "show-options", "-t", UI_SESSION, "-v", "@synkro_build"]);
|
|
15214
|
+
if (current.ok && current.stdout.trim() === expected) return;
|
|
15215
|
+
const listed = await run(HOST, [
|
|
15216
|
+
"tmux",
|
|
15217
|
+
"list-panes",
|
|
15218
|
+
"-s",
|
|
15219
|
+
"-t",
|
|
15220
|
+
UI_SESSION,
|
|
15221
|
+
"-F",
|
|
15222
|
+
"#{window_id}|#{pane_id}|#{pane_width}|#{pane_start_command}"
|
|
15223
|
+
]);
|
|
15224
|
+
if (!listed.ok) return;
|
|
15225
|
+
const panes = parseUiPanes(listed.stdout);
|
|
15226
|
+
const sidebars = panes.filter((row2) => row2.command.includes("--sidebar"));
|
|
15227
|
+
if (sidebars.length === 0) return;
|
|
15228
|
+
await styleOuterSession(bootPath, repoCwd, sidebars[0].width || 30);
|
|
15229
|
+
for (const sidebar of sidebars) {
|
|
15230
|
+
const center = panes.find((row2) => row2.window === sidebar.window && row2.pane !== sidebar.pane);
|
|
15231
|
+
if (!center) continue;
|
|
15232
|
+
await run(HOST, ["tmux", "respawn-pane", "-k", "-t", sidebar.pane, sidebarCommand(bootPath, center.pane, repoCwd)]);
|
|
15233
|
+
}
|
|
15175
15234
|
}
|
|
15176
15235
|
async function buildTab(bootPath, repoCwd, spec) {
|
|
15177
15236
|
await run(HOST, ["tmux", "set-option", "-g", "history-limit", "50000"]);
|
|
@@ -15214,6 +15273,7 @@ async function buildTab(bootPath, repoCwd, spec) {
|
|
|
15214
15273
|
const sidebarPane = split.stdout.trim();
|
|
15215
15274
|
const panes = await run(HOST, ["tmux", "list-panes", "-t", windowTarget, "-F", "#{pane_id}"]);
|
|
15216
15275
|
const centerPane = panes.stdout.split("\n").map((line) => line.trim()).filter(Boolean).find((id) => id !== sidebarPane) || "";
|
|
15276
|
+
await run(HOST, buildKeepPaneAfterExit(centerPane));
|
|
15217
15277
|
if (spec.agentSession) {
|
|
15218
15278
|
await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_agent", spec.agentSession]);
|
|
15219
15279
|
}
|
|
@@ -15264,6 +15324,8 @@ async function launchUi(bootPath, repoCwd) {
|
|
|
15264
15324
|
rememberSpace(repoCwd);
|
|
15265
15325
|
if (!await uiSessionExists()) {
|
|
15266
15326
|
await buildTab(bootPath, repoCwd, { cwd: repoCwd, center: makeTerminalCommand(bootPath), focus: "sidebar" });
|
|
15327
|
+
} else {
|
|
15328
|
+
await refreshUiShell(bootPath, repoCwd);
|
|
15267
15329
|
}
|
|
15268
15330
|
await pruneCollapsedClients();
|
|
15269
15331
|
return runInherit(process.env.TMUX ? ["tmux", "switch-client", "-t", UI_SESSION] : ["tmux", "attach-session", "-t", UI_SESSION]);
|
|
@@ -15320,10 +15382,10 @@ function row(selected, width, content) {
|
|
|
15320
15382
|
return STYLE.select + body.split(STYLE.reset).join(STYLE.reset + STYLE.select) + STYLE.reset;
|
|
15321
15383
|
}
|
|
15322
15384
|
function stripForPad(text, width) {
|
|
15323
|
-
let
|
|
15385
|
+
let visible2 = 0;
|
|
15324
15386
|
let out = "";
|
|
15325
15387
|
let index = 0;
|
|
15326
|
-
while (index < text.length &&
|
|
15388
|
+
while (index < text.length && visible2 < width) {
|
|
15327
15389
|
if (text.startsWith(ESC, index)) {
|
|
15328
15390
|
const end = text.indexOf("m", index);
|
|
15329
15391
|
if (end === -1) break;
|
|
@@ -15332,13 +15394,13 @@ function stripForPad(text, width) {
|
|
|
15332
15394
|
} else {
|
|
15333
15395
|
out += text[index];
|
|
15334
15396
|
index += 1;
|
|
15335
|
-
|
|
15397
|
+
visible2 += 1;
|
|
15336
15398
|
}
|
|
15337
15399
|
}
|
|
15338
|
-
return out + " ".repeat(Math.max(0, width -
|
|
15400
|
+
return out + " ".repeat(Math.max(0, width - visible2));
|
|
15339
15401
|
}
|
|
15340
15402
|
function visibleLength(text) {
|
|
15341
|
-
let
|
|
15403
|
+
let visible2 = 0;
|
|
15342
15404
|
let index = 0;
|
|
15343
15405
|
while (index < text.length) {
|
|
15344
15406
|
if (text.startsWith(ESC, index)) {
|
|
@@ -15346,11 +15408,11 @@ function visibleLength(text) {
|
|
|
15346
15408
|
if (end === -1) break;
|
|
15347
15409
|
index = end + 1;
|
|
15348
15410
|
} else {
|
|
15349
|
-
|
|
15411
|
+
visible2 += 1;
|
|
15350
15412
|
index += 1;
|
|
15351
15413
|
}
|
|
15352
15414
|
}
|
|
15353
|
-
return
|
|
15415
|
+
return visible2;
|
|
15354
15416
|
}
|
|
15355
15417
|
function splitRow(width, left, right) {
|
|
15356
15418
|
const gap = Math.max(1, width - 2 - visibleLength(left) - visibleLength(right));
|
|
@@ -15362,10 +15424,10 @@ function windowAround(count, selected, capacity) {
|
|
|
15362
15424
|
return { start, end: start + capacity };
|
|
15363
15425
|
}
|
|
15364
15426
|
function renderCollapsed(state, width, height) {
|
|
15365
|
-
const
|
|
15427
|
+
const lines2 = [];
|
|
15366
15428
|
const targets = [];
|
|
15367
15429
|
const push2 = (text, target = null) => {
|
|
15368
|
-
|
|
15430
|
+
lines2.push(stripForPad(" " + text, width));
|
|
15369
15431
|
targets.push(target);
|
|
15370
15432
|
};
|
|
15371
15433
|
push2("");
|
|
@@ -15382,16 +15444,16 @@ function renderCollapsed(state, width, height) {
|
|
|
15382
15444
|
});
|
|
15383
15445
|
push2("");
|
|
15384
15446
|
push2(STYLE.dim + "+" + STYLE.reset, { kind: "new" });
|
|
15385
|
-
while (
|
|
15447
|
+
while (lines2.length < height - 1) push2("");
|
|
15386
15448
|
push2(STYLE.dim + "\u203A\u203A" + STYLE.reset, { kind: "collapse" });
|
|
15387
|
-
return { lines:
|
|
15449
|
+
return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
|
|
15388
15450
|
}
|
|
15389
15451
|
function renderLayout(state, width = 30, height = 40) {
|
|
15390
15452
|
if (state.collapsed) return renderCollapsed(state, width, height);
|
|
15391
|
-
const
|
|
15453
|
+
const lines2 = [];
|
|
15392
15454
|
const targets = [];
|
|
15393
15455
|
const push2 = (line, target = null) => {
|
|
15394
|
-
|
|
15456
|
+
lines2.push(line);
|
|
15395
15457
|
targets.push(target);
|
|
15396
15458
|
};
|
|
15397
15459
|
const chrome = 8;
|
|
@@ -15413,7 +15475,7 @@ function renderLayout(state, width = 30, height = 40) {
|
|
|
15413
15475
|
push2(row(selected, width, " " + STYLE.branch + clip(space.branch, width - 6 - (space.track ? space.track.length + 1 : 0)) + STYLE.reset + drift), target);
|
|
15414
15476
|
});
|
|
15415
15477
|
if (state.spaces.length === 0) push2(row(false, width, STYLE.dim + "no spaces found" + STYLE.reset));
|
|
15416
|
-
while (
|
|
15478
|
+
while (lines2.length < 3 + spacesCapacity * 2) push2(pad("", width));
|
|
15417
15479
|
push2("");
|
|
15418
15480
|
push2(splitRow(width, "new", "menu"), { kind: "new" });
|
|
15419
15481
|
push2(STYLE.dim + "\u2500".repeat(Math.max(0, width)) + STYLE.reset);
|
|
@@ -15458,13 +15520,13 @@ function renderLayout(state, width = 30, height = 40) {
|
|
|
15458
15520
|
push2("");
|
|
15459
15521
|
push2(row(false, width, STYLE.blocked + "\u26D4 needs consent" + STYLE.reset));
|
|
15460
15522
|
for (const action of actionsForAsk(selectedAgent.ask)) {
|
|
15461
|
-
const
|
|
15462
|
-
push2(row(false, width, STYLE.dim + " " +
|
|
15523
|
+
const key2 = action === "track" ? "g" : action === "skip" ? "s" : "y";
|
|
15524
|
+
push2(row(false, width, STYLE.dim + " " + key2 + " \u2014 " + action + STYLE.reset));
|
|
15463
15525
|
}
|
|
15464
15526
|
}
|
|
15465
|
-
while (
|
|
15527
|
+
while (lines2.length < height - 1) push2(pad("", width));
|
|
15466
15528
|
push2(stripForPad(pad("", width - 3) + STYLE.dim + "\u2039\u2039 " + STYLE.reset, width), { kind: "collapse" });
|
|
15467
|
-
return { lines:
|
|
15529
|
+
return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
|
|
15468
15530
|
}
|
|
15469
15531
|
function nextSelection(state, spaces, agents, delta) {
|
|
15470
15532
|
const flat = state.section === "spaces" ? state.spaceIndex : spaces + state.agentIndex;
|
|
@@ -15593,6 +15655,9 @@ async function detectContainerBackend() {
|
|
|
15593
15655
|
}
|
|
15594
15656
|
return { runner: { kind: "host" }, backend: "host", note: "runtime: host (container unavailable)" };
|
|
15595
15657
|
}
|
|
15658
|
+
function harnessCommand(request) {
|
|
15659
|
+
return request.resume ? resumeCommand(request.harness) : HARNESS_COMMANDS[request.harness] || "claude";
|
|
15660
|
+
}
|
|
15596
15661
|
async function provisionContainerWorkspace(runner, slug) {
|
|
15597
15662
|
const dir = CONTAINER_WORK + "/ui-" + slug;
|
|
15598
15663
|
await run(runner, [
|
|
@@ -15610,8 +15675,8 @@ async function spawnAgent(info, request) {
|
|
|
15610
15675
|
if (request.backend === "container") {
|
|
15611
15676
|
cwd = request.cwd.startsWith(CONTAINER_WORK) ? request.cwd : await provisionContainerWorkspace(runner, slug);
|
|
15612
15677
|
}
|
|
15613
|
-
const command = request.
|
|
15614
|
-
for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend })) {
|
|
15678
|
+
const command = request.command || harnessCommand(request);
|
|
15679
|
+
for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend, mode: request.mode })) {
|
|
15615
15680
|
const result = await run(runner, argv);
|
|
15616
15681
|
if (!result.ok && argv[1] === "new-session") {
|
|
15617
15682
|
return { ok: false, session, error: result.stderr.trim() || "tmux new-session failed" };
|
|
@@ -15625,7 +15690,8 @@ async function spawnAgent(info, request) {
|
|
|
15625
15690
|
harness: request.harness,
|
|
15626
15691
|
space: cwd,
|
|
15627
15692
|
spaceName: request.spaceName,
|
|
15628
|
-
backend: request.backend
|
|
15693
|
+
backend: request.backend,
|
|
15694
|
+
mode: request.mode || "native"
|
|
15629
15695
|
});
|
|
15630
15696
|
return { ok: true, session };
|
|
15631
15697
|
}
|
|
@@ -15901,6 +15967,24 @@ async function runSidebar() {
|
|
|
15901
15967
|
const agent = state.agents[state.agentIndex];
|
|
15902
15968
|
if (agent) await showAgent(agent);
|
|
15903
15969
|
}
|
|
15970
|
+
async function showWorkspaceTerminal(cwd) {
|
|
15971
|
+
if (!centerPane || !cwd) return;
|
|
15972
|
+
const command = makeTerminalCommand(bootPath);
|
|
15973
|
+
const argv = ["tmux", "respawn-pane", "-k", "-t", centerPane, "-c", cwd];
|
|
15974
|
+
if (command) argv.push(command);
|
|
15975
|
+
const respawned = await run(host, argv);
|
|
15976
|
+
if (!respawned.ok) {
|
|
15977
|
+
state.message = "could not restore workspace terminal";
|
|
15978
|
+
return;
|
|
15979
|
+
}
|
|
15980
|
+
const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
|
|
15981
|
+
await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-u", "@synkro_agent"]);
|
|
15982
|
+
await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_cwd", cwd]);
|
|
15983
|
+
await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_kind", "terminal"]);
|
|
15984
|
+
await run(host, ["tmux", "rename-window", "-t", centerPane, tabTitle("terminal", spaceName)]);
|
|
15985
|
+
state.viewing = "";
|
|
15986
|
+
await snapshotTabs();
|
|
15987
|
+
}
|
|
15904
15988
|
async function showAgent(agent) {
|
|
15905
15989
|
if (!centerPane) return;
|
|
15906
15990
|
if (agent.status === "offline") {
|
|
@@ -15919,6 +16003,9 @@ async function runSidebar() {
|
|
|
15919
16003
|
return;
|
|
15920
16004
|
}
|
|
15921
16005
|
await run(runnerFor(agent.backend), buildEnableMouse(agent.session));
|
|
16006
|
+
for (const argv of buildClipboardBindings(agent.session)) {
|
|
16007
|
+
await run(runnerFor(agent.backend), argv);
|
|
16008
|
+
}
|
|
15922
16009
|
const command = buildCenterAttachCommand(runnerFor(agent.backend), agent.session);
|
|
15923
16010
|
await run(host, ["tmux", "respawn-pane", "-k", "-t", centerPane, command]);
|
|
15924
16011
|
const where = agent.space ? agent.space.split("/").filter(Boolean).pop() || "" : "";
|
|
@@ -15931,7 +16018,15 @@ async function runSidebar() {
|
|
|
15931
16018
|
async function confirmKillAgent() {
|
|
15932
16019
|
const agent = state.agents[state.agentIndex];
|
|
15933
16020
|
if (!agent) return;
|
|
16021
|
+
const wasViewing = state.viewing === agent.session;
|
|
15934
16022
|
await openDialog("kill", [agent.backend, agent.session]);
|
|
16023
|
+
await refresh();
|
|
16024
|
+
if (allAgents.some((candidate) => candidate.session === agent.session && candidate.status !== "offline")) return;
|
|
16025
|
+
state.message = "closed " + agent.name;
|
|
16026
|
+
if (!wasViewing) return;
|
|
16027
|
+
await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
|
|
16028
|
+
const successor = nextLiveAgent(allAgents, agent.repo || "");
|
|
16029
|
+
if (successor) await showAgent(successor);
|
|
15935
16030
|
}
|
|
15936
16031
|
async function consent(action) {
|
|
15937
16032
|
const agent = state.agents[state.agentIndex];
|
|
@@ -15954,6 +16049,7 @@ async function runSidebar() {
|
|
|
15954
16049
|
const remembered = space ? lastAgentFor(space.path) : "";
|
|
15955
16050
|
const agent = allAgents.find((row2) => row2.session === remembered && row2.status !== "offline");
|
|
15956
16051
|
if (agent) await showAgent(agent);
|
|
16052
|
+
else if (space) await showWorkspaceTerminal(space.path);
|
|
15957
16053
|
} else if (target.kind === "agent") {
|
|
15958
16054
|
state.section = "agents";
|
|
15959
16055
|
state.agentIndex = target.index;
|
|
@@ -15995,45 +16091,45 @@ async function runSidebar() {
|
|
|
15995
16091
|
}
|
|
15996
16092
|
await activate(target);
|
|
15997
16093
|
}
|
|
15998
|
-
async function handleKey(
|
|
15999
|
-
if (
|
|
16000
|
-
else if (
|
|
16001
|
-
else if (
|
|
16002
|
-
else if (
|
|
16094
|
+
async function handleKey(key2) {
|
|
16095
|
+
if (key2 === " ") state.section = state.section === "spaces" ? "agents" : "spaces";
|
|
16096
|
+
else if (key2 === "j" || key2 === CSI + "B") moveSelection(1);
|
|
16097
|
+
else if (key2 === "k" || key2 === CSI + "A") moveSelection(-1);
|
|
16098
|
+
else if (key2 === "\r") {
|
|
16003
16099
|
if (state.section === "agents") await attachSelected();
|
|
16004
16100
|
else await openDialog("new-tab");
|
|
16005
|
-
} else if (
|
|
16101
|
+
} else if (key2 === "n" || key2 === "T") {
|
|
16006
16102
|
worldChanged = true;
|
|
16007
16103
|
await openDialog("new-tab");
|
|
16008
|
-
} else if (
|
|
16009
|
-
else if (
|
|
16010
|
-
else if (
|
|
16104
|
+
} else if (key2 === "m") await mainMenu();
|
|
16105
|
+
else if (key2 === "O") void openDialog("new-workspace");
|
|
16106
|
+
else if (key2 === "C") {
|
|
16011
16107
|
worldChanged = true;
|
|
16012
16108
|
closeSelectedWorkspace();
|
|
16013
|
-
} else if (
|
|
16109
|
+
} else if (key2 === "r") {
|
|
16014
16110
|
worldChanged = true;
|
|
16015
16111
|
await restoreSelected();
|
|
16016
|
-
} else if (
|
|
16112
|
+
} else if (key2 === "d") {
|
|
16017
16113
|
const client = await attachedClient();
|
|
16018
16114
|
await run(host, client ? ["tmux", "detach-client", "-t", client] : ["tmux", "detach-client"]);
|
|
16019
|
-
} else if (
|
|
16020
|
-
else if (
|
|
16115
|
+
} else if (key2 === "K") await keybindsMenu();
|
|
16116
|
+
else if (key2 === "G") {
|
|
16021
16117
|
state.grouped = !state.grouped;
|
|
16022
16118
|
applyFilter();
|
|
16023
|
-
} else if (
|
|
16119
|
+
} else if (key2 === "a") {
|
|
16024
16120
|
state.filter = state.filter === "space" ? "all" : "space";
|
|
16025
16121
|
applyFilter();
|
|
16026
|
-
} else if (
|
|
16027
|
-
else if (
|
|
16122
|
+
} else if (key2 === "<" || key2 === ">" || key2 === "," || key2 === ".") await toggleCollapsed();
|
|
16123
|
+
else if (key2 === "x") {
|
|
16028
16124
|
worldChanged = true;
|
|
16029
16125
|
await confirmKillAgent();
|
|
16030
|
-
} else if (
|
|
16126
|
+
} else if (key2 === "i") {
|
|
16031
16127
|
const agent = state.agents[state.agentIndex];
|
|
16032
16128
|
if (agent) await run(runnerFor(agent.backend), buildInterrupt(agent.session));
|
|
16033
|
-
} else if (
|
|
16034
|
-
else if (
|
|
16035
|
-
else if (
|
|
16036
|
-
else if (
|
|
16129
|
+
} else if (key2 === "g") await consent("track");
|
|
16130
|
+
else if (key2 === "s") await consent("skip");
|
|
16131
|
+
else if (key2 === "y") await consent("stay");
|
|
16132
|
+
else if (key2 === "q" || key2 === KEY_CTRL_C) {
|
|
16037
16133
|
await snapshotTabs();
|
|
16038
16134
|
releaseAwake();
|
|
16039
16135
|
await run(host, ["tmux", "kill-session", "-t", outerSession]);
|
|
@@ -16155,6 +16251,9 @@ var init_repos = __esm({
|
|
|
16155
16251
|
|
|
16156
16252
|
// cli/ui/tabs.ts
|
|
16157
16253
|
import { execSync as execSync7 } from "child_process";
|
|
16254
|
+
function embeddedSessionCommand(bootPath, harness, cwd) {
|
|
16255
|
+
return ["node", bootPath, "ui", "--run", harness, cwd].map(shellQuote3).join(" ");
|
|
16256
|
+
}
|
|
16158
16257
|
function repoRoot() {
|
|
16159
16258
|
try {
|
|
16160
16259
|
return execSync7("git rev-parse --show-toplevel", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim() || process.cwd();
|
|
@@ -16175,12 +16274,25 @@ async function createTab(bootPath, kind, spacePath) {
|
|
|
16175
16274
|
});
|
|
16176
16275
|
return;
|
|
16177
16276
|
}
|
|
16178
|
-
if (kind === "cursor-synkro") {
|
|
16277
|
+
if (kind === "cursor-synkro" || kind === "codex-synkro") {
|
|
16278
|
+
const harness2 = kind === "codex-synkro" ? "codex" : "cursor";
|
|
16279
|
+
const command = embeddedSessionCommand(bootPath, harness2, spacePath);
|
|
16280
|
+
const spawned2 = await spawnAgent({ runner: HOST2, backend: "host", note: "runtime: host" }, {
|
|
16281
|
+
name: space + "-" + harness2 + "-synkro-" + String(process.pid % 1e4),
|
|
16282
|
+
harness: harness2,
|
|
16283
|
+
spaceName: space,
|
|
16284
|
+
cwd: spacePath,
|
|
16285
|
+
backend: "host",
|
|
16286
|
+
command,
|
|
16287
|
+
mode: "embedded"
|
|
16288
|
+
});
|
|
16289
|
+
if (!spawned2.ok) return;
|
|
16179
16290
|
await buildTab(bootPath, repo, {
|
|
16180
16291
|
cwd: spacePath,
|
|
16181
|
-
center:
|
|
16182
|
-
title: tabTitle(
|
|
16183
|
-
kind
|
|
16292
|
+
center: buildCenterAttachCommand(HOST2, spawned2.session),
|
|
16293
|
+
title: tabTitle(harness2, space),
|
|
16294
|
+
kind,
|
|
16295
|
+
agentSession: spawned2.session,
|
|
16184
16296
|
focus: "center"
|
|
16185
16297
|
});
|
|
16186
16298
|
return;
|
|
@@ -16195,12 +16307,13 @@ async function createTab(bootPath, kind, spacePath) {
|
|
|
16195
16307
|
});
|
|
16196
16308
|
return;
|
|
16197
16309
|
}
|
|
16310
|
+
if (!["claude", "codex", "cursor"].includes(kind)) return;
|
|
16198
16311
|
const info = await detectContainerBackend();
|
|
16199
16312
|
const spaceName = space;
|
|
16200
16313
|
const stamp = String(process.pid % 1e4);
|
|
16201
|
-
const harness =
|
|
16314
|
+
const harness = kind;
|
|
16202
16315
|
const reachable = info.backend === "container" && (await run(info.runner, ["test", "-d", spacePath])).ok;
|
|
16203
|
-
const backend = reachable ? "container" : "host";
|
|
16316
|
+
const backend = harness === "codex" ? "host" : reachable ? "container" : "host";
|
|
16204
16317
|
const spawned = await spawnAgent(info, {
|
|
16205
16318
|
name: spaceName + "-" + harness + "-" + stamp,
|
|
16206
16319
|
harness,
|
|
@@ -16227,25 +16340,26 @@ async function openAgentTab(bootPath, session) {
|
|
|
16227
16340
|
"-p",
|
|
16228
16341
|
"-t",
|
|
16229
16342
|
session,
|
|
16230
|
-
["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}"].join("|")
|
|
16343
|
+
["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}", "#{@synkro_mode}"].join("|")
|
|
16231
16344
|
]);
|
|
16232
|
-
const [harness, space, backend] = (meta.stdout.trim() || "
|
|
16345
|
+
const [harness, space, backend, mode] = (meta.stdout.trim() || "|||").split("|");
|
|
16233
16346
|
const containerHosted = backend === "container";
|
|
16234
|
-
const runner = containerHosted ? info.runner : HOST2;
|
|
16347
|
+
const runner = mode === "embedded" ? HOST2 : containerHosted ? info.runner : HOST2;
|
|
16348
|
+
const spaceName = (space || "").split("/").filter(Boolean).pop() || "space";
|
|
16235
16349
|
const alive = (await run(runner, ["tmux", "has-session", "-t", session])).ok;
|
|
16236
16350
|
if (!alive) return;
|
|
16237
|
-
const spaceName = (space || "").split("/").filter(Boolean).pop() || "space";
|
|
16238
16351
|
await buildTab(bootPath, repoRoot(), {
|
|
16239
16352
|
cwd: space || repoRoot(),
|
|
16240
16353
|
center: buildCenterAttachCommand(runner, session),
|
|
16241
16354
|
title: tabTitle(harness || "claude", spaceName),
|
|
16242
|
-
kind: harness || "claude",
|
|
16355
|
+
kind: mode === "embedded" ? (harness || "cursor") + "-synkro" : harness || "claude",
|
|
16243
16356
|
agentSession: session,
|
|
16244
16357
|
focus: "center"
|
|
16245
16358
|
});
|
|
16246
16359
|
}
|
|
16247
16360
|
async function restoreTabs(bootPath, repoCwd) {
|
|
16248
16361
|
const tabs = loadTabs();
|
|
16362
|
+
const records = loadRecords();
|
|
16249
16363
|
if (tabs.length === 0) return false;
|
|
16250
16364
|
const info = await detectContainerBackend();
|
|
16251
16365
|
const hostSessions = (await run(HOST2, ["tmux", "list-sessions", "-F", "#{session_name}"])).stdout;
|
|
@@ -16255,31 +16369,42 @@ async function restoreTabs(bootPath, repoCwd) {
|
|
|
16255
16369
|
for (const tab of tabs) {
|
|
16256
16370
|
const space = tab.cwd.split("/").filter(Boolean).pop() || "space";
|
|
16257
16371
|
const containerHosted = info.backend === "container" && (await run(info.runner, ["test", "-d", tab.cwd])).ok;
|
|
16258
|
-
const
|
|
16259
|
-
|
|
16260
|
-
|
|
16372
|
+
const embedded = tab.kind === "cursor-synkro" || tab.kind === "codex-synkro";
|
|
16373
|
+
const harness = embedded ? tab.kind.replace("-synkro", "") : ["claude", "codex", "cursor"].includes(tab.kind) ? tab.kind : "";
|
|
16374
|
+
const sessionRunner = embedded || harness === "codex" ? HOST2 : containerHosted ? info.runner : HOST2;
|
|
16375
|
+
const sessionAlive = Boolean(tab.agentSession && alive.has(tab.agentSession));
|
|
16376
|
+
const record2 = records.find((row2) => row2.session === tab.agentSession);
|
|
16377
|
+
if (tab.agentSession && sessionAlive) {
|
|
16378
|
+
await run(sessionRunner, buildEnableMouse(tab.agentSession));
|
|
16379
|
+
for (const argv of buildClipboardBindings(tab.agentSession)) {
|
|
16380
|
+
await run(sessionRunner, argv);
|
|
16381
|
+
}
|
|
16261
16382
|
await buildTab(bootPath, repoCwd, {
|
|
16262
16383
|
cwd: tab.cwd,
|
|
16263
|
-
center: buildCenterAttachCommand(
|
|
16384
|
+
center: buildCenterAttachCommand(sessionRunner, tab.agentSession),
|
|
16264
16385
|
title: tab.title,
|
|
16265
16386
|
kind: tab.kind,
|
|
16266
16387
|
agentSession: tab.agentSession,
|
|
16267
16388
|
focus: "center"
|
|
16268
16389
|
});
|
|
16269
16390
|
} else if (harness) {
|
|
16391
|
+
const command = embedded ? embeddedSessionCommand(bootPath, harness, tab.cwd) : void 0;
|
|
16270
16392
|
const spawned = await spawnAgent(info, {
|
|
16271
|
-
name: space + "-" + harness + "-" + String(process.pid % 1e4),
|
|
16393
|
+
name: record2?.name || space + "-" + harness + "-" + String(process.pid % 1e4),
|
|
16272
16394
|
harness,
|
|
16273
16395
|
spaceName: space,
|
|
16274
16396
|
cwd: tab.cwd,
|
|
16275
|
-
backend: containerHosted ? "container" : "host",
|
|
16276
|
-
resume:
|
|
16397
|
+
backend: embedded || harness === "codex" ? "host" : containerHosted ? "container" : "host",
|
|
16398
|
+
resume: !embedded,
|
|
16399
|
+
command,
|
|
16400
|
+
mode: embedded ? "embedded" : "native"
|
|
16277
16401
|
});
|
|
16402
|
+
const restoredInContainer = !embedded && harness !== "codex" && containerHosted;
|
|
16278
16403
|
await buildTab(bootPath, repoCwd, spawned.ok ? {
|
|
16279
16404
|
cwd: tab.cwd,
|
|
16280
|
-
center: buildCenterAttachCommand(
|
|
16405
|
+
center: buildCenterAttachCommand(restoredInContainer ? info.runner : HOST2, spawned.session),
|
|
16281
16406
|
title: tabTitle(harness, space),
|
|
16282
|
-
kind: harness,
|
|
16407
|
+
kind: embedded ? tab.kind : harness,
|
|
16283
16408
|
agentSession: spawned.session,
|
|
16284
16409
|
focus: "center"
|
|
16285
16410
|
} : { cwd: tab.cwd, center: makeTerminalCommand(bootPath), title: tabTitle("terminal", space), kind: "terminal", focus: "center" });
|
|
@@ -16311,11 +16436,20 @@ var init_tabs = __esm({
|
|
|
16311
16436
|
// cli/ui/dialog.ts
|
|
16312
16437
|
import { existsSync as existsSync37 } from "fs";
|
|
16313
16438
|
import { homedir as homedir37 } from "os";
|
|
16439
|
+
function providerChoices(harnesses) {
|
|
16440
|
+
return [
|
|
16441
|
+
{ value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
|
|
16442
|
+
...harnesses.map((harness) => ({
|
|
16443
|
+
value: harness === "cursor" || harness === "codex" ? harness + "-synkro" : harness,
|
|
16444
|
+
label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
|
|
16445
|
+
}))
|
|
16446
|
+
];
|
|
16447
|
+
}
|
|
16314
16448
|
function write(text) {
|
|
16315
16449
|
process.stdout.write(text);
|
|
16316
16450
|
}
|
|
16317
16451
|
function visibleLength2(text) {
|
|
16318
|
-
let
|
|
16452
|
+
let visible2 = 0;
|
|
16319
16453
|
let index = 0;
|
|
16320
16454
|
while (index < text.length) {
|
|
16321
16455
|
if (text.startsWith(CSI2, index)) {
|
|
@@ -16323,11 +16457,11 @@ function visibleLength2(text) {
|
|
|
16323
16457
|
if (end === -1) break;
|
|
16324
16458
|
index = end + 1;
|
|
16325
16459
|
} else {
|
|
16326
|
-
|
|
16460
|
+
visible2 += 1;
|
|
16327
16461
|
index += 1;
|
|
16328
16462
|
}
|
|
16329
16463
|
}
|
|
16330
|
-
return
|
|
16464
|
+
return visible2;
|
|
16331
16465
|
}
|
|
16332
16466
|
function clipPath(text, max) {
|
|
16333
16467
|
const value = String(text || "");
|
|
@@ -16370,34 +16504,34 @@ async function pick(opts) {
|
|
|
16370
16504
|
const perItem = opts.choices.some((choice) => choice.detail) ? 2 : 1;
|
|
16371
16505
|
const visibleItems = Math.max(1, Math.floor(Math.max(2, height - 5) / perItem));
|
|
16372
16506
|
const firstRow = 4;
|
|
16373
|
-
return new Promise((
|
|
16507
|
+
return new Promise((resolve9) => {
|
|
16374
16508
|
const view = () => opts.choices.filter((choice) => matches(choice, filter));
|
|
16375
16509
|
const draw = () => {
|
|
16376
16510
|
const shown = view();
|
|
16377
16511
|
if (selected >= shown.length) selected = Math.max(0, shown.length - 1);
|
|
16378
16512
|
if (selected < top) top = selected;
|
|
16379
16513
|
if (selected >= top + visibleItems) top = selected - visibleItems + 1;
|
|
16380
|
-
const
|
|
16381
|
-
|
|
16514
|
+
const lines2 = [];
|
|
16515
|
+
lines2.push(" " + S.title + opts.title + S.reset);
|
|
16382
16516
|
const left = filter && !opts.menu ? " " + S.accent + "/ " + S.reset + filter + "\u258C" : " " + S.dim + opts.hint + S.reset;
|
|
16383
16517
|
const right = opts.menu ? "" : S.dim + String(shown.length) + (shown.length === 1 ? " match" : " matches") + S.reset;
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
if (shown.length === 0)
|
|
16518
|
+
lines2.push(padRow(left, Math.max(0, width - visibleLength2(right) - 1)) + right);
|
|
16519
|
+
lines2.push("");
|
|
16520
|
+
if (shown.length === 0) lines2.push(" " + S.dim + (opts.emptyNote || "nothing matches") + S.reset);
|
|
16387
16521
|
shown.slice(top, top + visibleItems).forEach((choice, offset) => {
|
|
16388
16522
|
const index = top + offset;
|
|
16389
16523
|
const isSelected = index === selected;
|
|
16390
16524
|
const marker = isSelected ? S.accent + "\u203A" + S.reset + " " : " ";
|
|
16391
16525
|
const note = choice.note ? S.dim + choice.note + S.reset : "";
|
|
16392
16526
|
const head = " " + marker + S.bold + clip2(choice.label, width - 6 - visibleLength2(note)) + S.reset;
|
|
16393
|
-
|
|
16527
|
+
lines2.push(selectable(padRow(head, Math.max(0, width - visibleLength2(note) - 1)) + note, width, isSelected));
|
|
16394
16528
|
if (perItem === 2) {
|
|
16395
|
-
|
|
16529
|
+
lines2.push(selectable(" " + S.muted + clipPath(choice.detail || "", width - 6) + S.reset, width, isSelected));
|
|
16396
16530
|
}
|
|
16397
16531
|
});
|
|
16398
|
-
while (
|
|
16399
|
-
|
|
16400
|
-
write(CSI2 + "H" +
|
|
16532
|
+
while (lines2.length < height - 1) lines2.push("");
|
|
16533
|
+
lines2.push(" " + S.dim + opts.footer + S.reset);
|
|
16534
|
+
write(CSI2 + "H" + lines2.slice(0, height).map((line) => padRow(line, width)).join("\n"));
|
|
16401
16535
|
};
|
|
16402
16536
|
const rowOfItem = (index) => firstRow + (index - top) * perItem;
|
|
16403
16537
|
const MOUSE = /\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
@@ -16418,7 +16552,7 @@ async function pick(opts) {
|
|
|
16418
16552
|
const hit = shown.findIndex((_, index) => y >= rowOfItem(index) && y < rowOfItem(index) + perItem);
|
|
16419
16553
|
if (hit >= 0) {
|
|
16420
16554
|
process.stdin.off("data", onData);
|
|
16421
|
-
|
|
16555
|
+
resolve9(shown[hit].value);
|
|
16422
16556
|
return;
|
|
16423
16557
|
}
|
|
16424
16558
|
}
|
|
@@ -16426,13 +16560,13 @@ async function pick(opts) {
|
|
|
16426
16560
|
if (!sawMouse) {
|
|
16427
16561
|
if (input === KEY_ESC && !input.includes("[<") || input === KEY_CTRL_C2) {
|
|
16428
16562
|
process.stdin.off("data", onData);
|
|
16429
|
-
|
|
16563
|
+
resolve9(null);
|
|
16430
16564
|
return;
|
|
16431
16565
|
}
|
|
16432
16566
|
if (input === "\r") {
|
|
16433
16567
|
if (shown.length === 0) return;
|
|
16434
16568
|
process.stdin.off("data", onData);
|
|
16435
|
-
|
|
16569
|
+
resolve9(shown[selected].value);
|
|
16436
16570
|
return;
|
|
16437
16571
|
}
|
|
16438
16572
|
const down = input === CSI2 + "B" || opts.menu && (input === "j" || input === CSI2 + "C");
|
|
@@ -16456,9 +16590,9 @@ async function readLine(opts) {
|
|
|
16456
16590
|
let value = "";
|
|
16457
16591
|
let error = "";
|
|
16458
16592
|
const width = Math.max(20, Number(process.stdout.columns || 80));
|
|
16459
|
-
return new Promise((
|
|
16593
|
+
return new Promise((resolve9) => {
|
|
16460
16594
|
const draw = () => {
|
|
16461
|
-
const
|
|
16595
|
+
const lines2 = [
|
|
16462
16596
|
" " + S.title + opts.title + S.reset,
|
|
16463
16597
|
"",
|
|
16464
16598
|
" " + S.dim + opts.label + S.reset,
|
|
@@ -16469,13 +16603,13 @@ async function readLine(opts) {
|
|
|
16469
16603
|
"",
|
|
16470
16604
|
" " + S.dim + opts.footer + S.reset
|
|
16471
16605
|
];
|
|
16472
|
-
write(CSI2 + "2J" + CSI2 + "H" +
|
|
16606
|
+
write(CSI2 + "2J" + CSI2 + "H" + lines2.map((line) => padRow(line, width)).join("\n"));
|
|
16473
16607
|
};
|
|
16474
16608
|
const onData = (chunk) => {
|
|
16475
16609
|
const input = chunk.toString("utf8");
|
|
16476
16610
|
if (input === KEY_ESC || input === KEY_CTRL_C2) {
|
|
16477
16611
|
process.stdin.off("data", onData);
|
|
16478
|
-
|
|
16612
|
+
resolve9(null);
|
|
16479
16613
|
return;
|
|
16480
16614
|
}
|
|
16481
16615
|
if (input === "\r") {
|
|
@@ -16487,7 +16621,7 @@ async function readLine(opts) {
|
|
|
16487
16621
|
return;
|
|
16488
16622
|
}
|
|
16489
16623
|
process.stdin.off("data", onData);
|
|
16490
|
-
|
|
16624
|
+
resolve9(value.trim());
|
|
16491
16625
|
})();
|
|
16492
16626
|
return;
|
|
16493
16627
|
}
|
|
@@ -16747,16 +16881,7 @@ async function runDialog(kind, repoCwd, argA = "", argB = "") {
|
|
|
16747
16881
|
process.exit(0);
|
|
16748
16882
|
}
|
|
16749
16883
|
const harnesses = await detectHarnesses();
|
|
16750
|
-
const sessions =
|
|
16751
|
-
{ value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
|
|
16752
|
-
...harnesses.map((harness) => ({
|
|
16753
|
-
value: harness,
|
|
16754
|
-
label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
|
|
16755
|
-
}))
|
|
16756
|
-
];
|
|
16757
|
-
if (harnesses.includes("cursor")) {
|
|
16758
|
-
sessions.push({ value: "cursor-synkro", label: TAB_GLYPHS.cursor + " Cursor in Synkro UX" });
|
|
16759
|
-
}
|
|
16884
|
+
const sessions = providerChoices(harnesses);
|
|
16760
16885
|
for (; ; ) {
|
|
16761
16886
|
const session = await pick({
|
|
16762
16887
|
menu: true,
|
|
@@ -16815,21 +16940,386 @@ var init_dialog = __esm({
|
|
|
16815
16940
|
}
|
|
16816
16941
|
});
|
|
16817
16942
|
|
|
16943
|
+
// cli/harness/render.ts
|
|
16944
|
+
function spinnerFrame(tick) {
|
|
16945
|
+
return SPINNER[Math.abs(tick) % SPINNER.length];
|
|
16946
|
+
}
|
|
16947
|
+
function terminalText(text) {
|
|
16948
|
+
return String(text).replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
|
16949
|
+
}
|
|
16950
|
+
function clip3(text, max) {
|
|
16951
|
+
const value = cleanOutput(text).replace(/\s+/g, " ").trim();
|
|
16952
|
+
if (max <= 1) return value;
|
|
16953
|
+
return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
|
|
16954
|
+
}
|
|
16955
|
+
function cleanOutput(text) {
|
|
16956
|
+
return String(text || "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
16957
|
+
}
|
|
16958
|
+
function seconds(ms) {
|
|
16959
|
+
const total = Math.max(0, Math.round(ms / 1e3));
|
|
16960
|
+
if (total < 60) return total + "s";
|
|
16961
|
+
return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
|
|
16962
|
+
}
|
|
16963
|
+
function blockNextStep(reason) {
|
|
16964
|
+
if (/tracking (?:decision|is required)|task tracking|skip tracking|\bconductor\b/i.test(reason)) {
|
|
16965
|
+
return "Next: reply \u201Ctrack it\u201D to create a task, or \u201Cskip tracking\u201D to continue untracked.";
|
|
16966
|
+
}
|
|
16967
|
+
if (/\bCWE-\d+\b|\bCVE-\d{4}-\d+\b/i.test(reason)) {
|
|
16968
|
+
return "Next: revise the flagged code, then retry. Do not bypass the rule.";
|
|
16969
|
+
}
|
|
16970
|
+
return "Next: resolve the rule above, then retry.";
|
|
16971
|
+
}
|
|
16972
|
+
function statusLine(opts) {
|
|
16973
|
+
const hint = opts.hint ? " \xB7 " + opts.hint : "";
|
|
16974
|
+
const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
|
|
16975
|
+
const width = Math.max(20, (opts.width || 100) - 4);
|
|
16976
|
+
return S2.think + " " + spinnerFrame(opts.tick) + " " + clip3(body, width) + S2.reset;
|
|
16977
|
+
}
|
|
16978
|
+
function renderEvent(event, width = 100) {
|
|
16979
|
+
const body = Math.max(30, width - 4);
|
|
16980
|
+
switch (event.type) {
|
|
16981
|
+
case "session-start":
|
|
16982
|
+
return [];
|
|
16983
|
+
case "user-message":
|
|
16984
|
+
return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset];
|
|
16985
|
+
// Live state, not transcript. The runner promotes these to the status line.
|
|
16986
|
+
case "thinking":
|
|
16987
|
+
return [];
|
|
16988
|
+
case "assistant-message": {
|
|
16989
|
+
const lines2 = wrap(event.text, body - 2);
|
|
16990
|
+
return [
|
|
16991
|
+
"",
|
|
16992
|
+
...lines2.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset)
|
|
16993
|
+
];
|
|
16994
|
+
}
|
|
16995
|
+
case "tool-start": {
|
|
16996
|
+
const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
|
|
16997
|
+
return [
|
|
16998
|
+
"",
|
|
16999
|
+
S2.panel + S2.tool + " " + BULLET + " " + label + S2.reset + S2.panel + S2.secondary + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
|
|
17000
|
+
];
|
|
17001
|
+
}
|
|
17002
|
+
case "tool-end": {
|
|
17003
|
+
if (event.blocked) {
|
|
17004
|
+
return [
|
|
17005
|
+
S2.panel + S2.blocked + " \u29C9 Blocked" + S2.reset + S2.panel + S2.secondary + " " + clip3(terminalText(event.target), body - 21) + S2.reset,
|
|
17006
|
+
...wrap(terminalText(event.reason), body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset),
|
|
17007
|
+
...wrap(blockNextStep(event.reason), body - 6).map((line) => S2.secondary + " " + line + S2.reset)
|
|
17008
|
+
];
|
|
17009
|
+
}
|
|
17010
|
+
const timing = event.durationMs == null ? "" : " \xB7 " + seconds(event.durationMs);
|
|
17011
|
+
const detail = event.ok ? event.output.trim() ? clip3(terminalText(event.output), body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
|
|
17012
|
+
const tint = event.ok ? S2.ok : S2.blocked;
|
|
17013
|
+
const changes = (event.fileChanges || []).flatMap((change) => wrap(change.path, body - 20).map((line, index) => S2.panel + S2.secondary + (index === 0 ? " " : " ") + line + (index === 0 ? " " + S2.ok + "+" + change.additions + S2.secondary + " " + S2.blocked + "-" + change.deletions : "") + S2.reset));
|
|
17014
|
+
return [
|
|
17015
|
+
S2.panel + tint + " " + ELBOW + " " + S2.reset + S2.panel + S2.secondary + detail + timing + S2.reset,
|
|
17016
|
+
...changes
|
|
17017
|
+
];
|
|
17018
|
+
}
|
|
17019
|
+
// A clean finish needs no announcement: the prompt returning IS the signal.
|
|
17020
|
+
case "turn-end":
|
|
17021
|
+
return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
|
|
17022
|
+
case "notice":
|
|
17023
|
+
return [S2.secondary + " " + clip3(event.text, body) + S2.reset];
|
|
17024
|
+
case "plan":
|
|
17025
|
+
return [
|
|
17026
|
+
S2.panel + S2.tool + " Plan" + S2.reset,
|
|
17027
|
+
...event.steps.map((row2) => S2.panel + (row2.status === "completed" ? S2.ok + " \u2713 " : S2.secondary + " \xB7 ") + clip3(row2.step, body - 4) + S2.reset)
|
|
17028
|
+
];
|
|
17029
|
+
case "assistant-delta":
|
|
17030
|
+
case "usage":
|
|
17031
|
+
return [];
|
|
17032
|
+
default:
|
|
17033
|
+
return [];
|
|
17034
|
+
}
|
|
17035
|
+
}
|
|
17036
|
+
function wrap(text, width) {
|
|
17037
|
+
const words = cleanOutput(text).replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
|
|
17038
|
+
if (words.length === 0) return [];
|
|
17039
|
+
const lines2 = [];
|
|
17040
|
+
let line = "";
|
|
17041
|
+
for (const word of words) {
|
|
17042
|
+
if (!line) line = word;
|
|
17043
|
+
else if ((line + " " + word).length <= width) line += " " + word;
|
|
17044
|
+
else {
|
|
17045
|
+
lines2.push(line);
|
|
17046
|
+
line = word;
|
|
17047
|
+
}
|
|
17048
|
+
}
|
|
17049
|
+
if (line) lines2.push(line);
|
|
17050
|
+
return lines2;
|
|
17051
|
+
}
|
|
17052
|
+
var ESC2, color, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
|
|
17053
|
+
var init_render2 = __esm({
|
|
17054
|
+
"cli/harness/render.ts"() {
|
|
17055
|
+
"use strict";
|
|
17056
|
+
ESC2 = "\x1B[";
|
|
17057
|
+
color = (hex, plane = 38) => {
|
|
17058
|
+
const [r, g, b] = hex.match(/../g).map((part) => Number.parseInt(part, 16));
|
|
17059
|
+
return ESC2 + plane + ";2;" + r + ";" + g + ";" + b + "m";
|
|
17060
|
+
};
|
|
17061
|
+
S2 = {
|
|
17062
|
+
reset: ESC2 + "0m",
|
|
17063
|
+
dim: ESC2 + "2m",
|
|
17064
|
+
bold: ESC2 + "1m",
|
|
17065
|
+
canvas: color("101416", 48),
|
|
17066
|
+
panel: color("171D1F", 48),
|
|
17067
|
+
composer: color("13191B", 48),
|
|
17068
|
+
text: color("C4C8C4"),
|
|
17069
|
+
secondary: color("7F8A86"),
|
|
17070
|
+
border: color("2A3334"),
|
|
17071
|
+
user: color("87A68E"),
|
|
17072
|
+
agent: color("C4C8C4"),
|
|
17073
|
+
think: color("B29A6A"),
|
|
17074
|
+
tool: color("7F9CA5"),
|
|
17075
|
+
ok: color("789B82"),
|
|
17076
|
+
blocked: color("B87474"),
|
|
17077
|
+
rule: color("B87474")
|
|
17078
|
+
};
|
|
17079
|
+
CLEAR_LINE = "\r" + ESC2 + "2K";
|
|
17080
|
+
BULLET = "\u23FA";
|
|
17081
|
+
ELBOW = "\u23BF";
|
|
17082
|
+
SPINNER = ["\xB7", "\u2722", "\u2733", "\u2217", "\u273B", "\u273D"];
|
|
17083
|
+
TOOL_LABEL = {
|
|
17084
|
+
shell: "Shell",
|
|
17085
|
+
read: "Read",
|
|
17086
|
+
edit: "Edit",
|
|
17087
|
+
write: "Write",
|
|
17088
|
+
delete: "Delete",
|
|
17089
|
+
search: "Search",
|
|
17090
|
+
list: "List",
|
|
17091
|
+
todo: "Todo",
|
|
17092
|
+
other: "Tool"
|
|
17093
|
+
};
|
|
17094
|
+
}
|
|
17095
|
+
});
|
|
17096
|
+
|
|
17097
|
+
// cli/harness/composer.ts
|
|
17098
|
+
import { execFileSync as execFileSync5, spawnSync as spawnSync12 } from "child_process";
|
|
17099
|
+
import { existsSync as existsSync38, mkdtempSync as mkdtempSync2, rmSync as rmSync7, statSync as statSync6, writeFileSync as writeFileSync27 } from "fs";
|
|
17100
|
+
import { homedir as homedir38, tmpdir } from "os";
|
|
17101
|
+
import { basename as basename3, dirname as dirname12, extname, isAbsolute as isAbsolute2, join as join37, resolve as resolve5, sep as sep3 } from "path";
|
|
17102
|
+
import { createInterface as createInterface5 } from "readline";
|
|
17103
|
+
var init_composer = __esm({
|
|
17104
|
+
"cli/harness/composer.ts"() {
|
|
17105
|
+
"use strict";
|
|
17106
|
+
init_render2();
|
|
17107
|
+
}
|
|
17108
|
+
});
|
|
17109
|
+
|
|
16818
17110
|
// cli/harness/events.ts
|
|
17111
|
+
function fixPollId(raw) {
|
|
17112
|
+
const text = String(raw || "");
|
|
17113
|
+
return FIX_POLL_MARKER.exec(text)?.[1] || LEGACY_FIX_POLL_MARKER.exec(text)?.[1] || "";
|
|
17114
|
+
}
|
|
17115
|
+
function cleanGuardText(raw) {
|
|
17116
|
+
return String(raw || "").replace(FIX_POLL_MARKER, "").replace(/\n*\s*SYNKRO FIX POLL[\s\S]*$/i, "").replace(/\n?\d{4}-\d\d-\d\dT[^\n]*\sERROR\s+codex_core::tools::router:[^\n]*/gi, "").replace(/\s*Checking command\s*$/i, "").trim();
|
|
17117
|
+
}
|
|
16819
17118
|
function blockReason(raw) {
|
|
16820
|
-
const text =
|
|
17119
|
+
const text = cleanGuardText(raw);
|
|
16821
17120
|
if (!text) return "blocked by policy";
|
|
17121
|
+
const guardAt = text.lastIndexOf("Guard:");
|
|
17122
|
+
if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
|
|
16822
17123
|
const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
|
|
16823
17124
|
if (afterTag) return afterTag[1].trim();
|
|
16824
17125
|
const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
|
|
16825
17126
|
if (afterHook) return afterHook[1].trim();
|
|
16826
17127
|
return text;
|
|
16827
17128
|
}
|
|
16828
|
-
var BLOCK_MARKER;
|
|
17129
|
+
var BLOCK_MARKER, FIX_POLL_MARKER, LEGACY_FIX_POLL_MARKER;
|
|
16829
17130
|
var init_events = __esm({
|
|
16830
17131
|
"cli/harness/events.ts"() {
|
|
16831
17132
|
"use strict";
|
|
16832
17133
|
BLOCK_MARKER = /blocked by a hook|\[synkro:/i;
|
|
17134
|
+
FIX_POLL_MARKER = /\[synkro:fix-poll\s+item_id=\\?["']?([A-Za-z0-9_-]+)\\?["']?\]/i;
|
|
17135
|
+
LEGACY_FIX_POLL_MARKER = /SYNKRO FIX POLL\s*\(item_id=([A-Za-z0-9_-]+)\)/i;
|
|
17136
|
+
}
|
|
17137
|
+
});
|
|
17138
|
+
|
|
17139
|
+
// cli/harness/changes.ts
|
|
17140
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync33, readlinkSync } from "fs";
|
|
17141
|
+
import { resolve as resolve6, relative } from "path";
|
|
17142
|
+
function lines(text) {
|
|
17143
|
+
if (!text) return [];
|
|
17144
|
+
const rows = text.replace(/\r\n/g, "\n").split("\n");
|
|
17145
|
+
if (rows[rows.length - 1] === "") rows.pop();
|
|
17146
|
+
return rows;
|
|
17147
|
+
}
|
|
17148
|
+
function lineChangeCounts(before, after) {
|
|
17149
|
+
const a = lines(before);
|
|
17150
|
+
const b = lines(after);
|
|
17151
|
+
if (a.length === 0) return { additions: b.length, deletions: 0 };
|
|
17152
|
+
if (b.length === 0) return { additions: 0, deletions: a.length };
|
|
17153
|
+
const max = a.length + b.length;
|
|
17154
|
+
let frontier = /* @__PURE__ */ new Map([[1, 0]]);
|
|
17155
|
+
for (let distance = 0; distance <= Math.min(max, MAX_DIFF_DISTANCE); distance++) {
|
|
17156
|
+
const next = /* @__PURE__ */ new Map();
|
|
17157
|
+
for (let diagonal = -distance; diagonal <= distance; diagonal += 2) {
|
|
17158
|
+
const down = frontier.get(diagonal + 1) ?? -1;
|
|
17159
|
+
const right = (frontier.get(diagonal - 1) ?? -1) + 1;
|
|
17160
|
+
let x = diagonal === -distance || diagonal !== distance && right < down ? down : right;
|
|
17161
|
+
if (x < 0) x = 0;
|
|
17162
|
+
let y = x - diagonal;
|
|
17163
|
+
while (x < a.length && y < b.length && a[x] === b[y]) {
|
|
17164
|
+
x++;
|
|
17165
|
+
y++;
|
|
17166
|
+
}
|
|
17167
|
+
next.set(diagonal, x);
|
|
17168
|
+
if (x >= a.length && y >= b.length) {
|
|
17169
|
+
return {
|
|
17170
|
+
additions: (distance + b.length - a.length) / 2,
|
|
17171
|
+
deletions: (distance + a.length - b.length) / 2
|
|
17172
|
+
};
|
|
17173
|
+
}
|
|
17174
|
+
}
|
|
17175
|
+
frontier = next;
|
|
17176
|
+
}
|
|
17177
|
+
let start = 0;
|
|
17178
|
+
while (start < a.length && start < b.length && a[start] === b[start]) start++;
|
|
17179
|
+
let aEnd = a.length;
|
|
17180
|
+
let bEnd = b.length;
|
|
17181
|
+
while (aEnd > start && bEnd > start && a[aEnd - 1] === b[bEnd - 1]) {
|
|
17182
|
+
aEnd--;
|
|
17183
|
+
bEnd--;
|
|
17184
|
+
}
|
|
17185
|
+
return { additions: bEnd - start, deletions: aEnd - start };
|
|
17186
|
+
}
|
|
17187
|
+
function unifiedDiffCounts(diff) {
|
|
17188
|
+
let additions = 0;
|
|
17189
|
+
let deletions = 0;
|
|
17190
|
+
for (const line of String(diff || "").split("\n")) {
|
|
17191
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
17192
|
+
if (line.startsWith("+")) additions++;
|
|
17193
|
+
else if (line.startsWith("-")) deletions++;
|
|
17194
|
+
}
|
|
17195
|
+
return { additions, deletions };
|
|
17196
|
+
}
|
|
17197
|
+
function safePath(cwd, file) {
|
|
17198
|
+
const absolute = resolve6(cwd, file);
|
|
17199
|
+
const rel = relative(cwd, absolute);
|
|
17200
|
+
return rel && rel !== ".." && !rel.startsWith("../") ? rel : null;
|
|
17201
|
+
}
|
|
17202
|
+
function worktreeContent(cwd, file) {
|
|
17203
|
+
const rel = safePath(cwd, file);
|
|
17204
|
+
if (!rel) return null;
|
|
17205
|
+
try {
|
|
17206
|
+
const absolute = resolve6(cwd, rel);
|
|
17207
|
+
const stat = lstatSync2(absolute);
|
|
17208
|
+
if (stat.isSymbolicLink()) return "symlink:" + readlinkSync(absolute);
|
|
17209
|
+
if (!stat.isFile() || stat.size > MAX_TEXT_BYTES) return null;
|
|
17210
|
+
const content = readFileSync33(absolute);
|
|
17211
|
+
return content.includes(0) ? null : content.toString("utf8");
|
|
17212
|
+
} catch {
|
|
17213
|
+
return null;
|
|
17214
|
+
}
|
|
17215
|
+
}
|
|
17216
|
+
function fileOperation(event) {
|
|
17217
|
+
return (event.type === "tool-start" || event.type === "tool-end") && /^(edit|write|delete)$/.test(event.kind);
|
|
17218
|
+
}
|
|
17219
|
+
function eventPaths(cwd, event) {
|
|
17220
|
+
const provided = event.type === "tool-end" ? (event.fileChanges || []).map((change) => change.path) : [];
|
|
17221
|
+
const targets = String(event.target || "").split(/,\s*/);
|
|
17222
|
+
return [...new Set([...provided, ...targets].map((file) => safePath(cwd, file)).filter((file) => Boolean(file)))];
|
|
17223
|
+
}
|
|
17224
|
+
function snapshotOperation(cwd, event) {
|
|
17225
|
+
return new Map(eventPaths(cwd, event).map((file) => [file, worktreeContent(cwd, file)]));
|
|
17226
|
+
}
|
|
17227
|
+
var MAX_TEXT_BYTES, MAX_DIFF_DISTANCE, OperationChangeTracker;
|
|
17228
|
+
var init_changes = __esm({
|
|
17229
|
+
"cli/harness/changes.ts"() {
|
|
17230
|
+
"use strict";
|
|
17231
|
+
MAX_TEXT_BYTES = 2 * 1024 * 1024;
|
|
17232
|
+
MAX_DIFF_DISTANCE = 4e3;
|
|
17233
|
+
OperationChangeTracker = class {
|
|
17234
|
+
constructor(cwd, capture) {
|
|
17235
|
+
this.cwd = cwd;
|
|
17236
|
+
this.capture = capture;
|
|
17237
|
+
}
|
|
17238
|
+
cwd;
|
|
17239
|
+
capture;
|
|
17240
|
+
starts = /* @__PURE__ */ new Map();
|
|
17241
|
+
snapshot(event) {
|
|
17242
|
+
return this.capture ? this.capture() : snapshotOperation(this.cwd, event);
|
|
17243
|
+
}
|
|
17244
|
+
observe(event) {
|
|
17245
|
+
if (event.type === "tool-start") {
|
|
17246
|
+
if (fileOperation(event)) this.starts.set(event.id, this.snapshot(event));
|
|
17247
|
+
return event;
|
|
17248
|
+
}
|
|
17249
|
+
if (event.type !== "tool-end") return event;
|
|
17250
|
+
const before = this.starts.get(event.id);
|
|
17251
|
+
this.starts.delete(event.id);
|
|
17252
|
+
if (!before) return event;
|
|
17253
|
+
const after = this.snapshot(event);
|
|
17254
|
+
const files = [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].sort();
|
|
17255
|
+
const fileChanges2 = [];
|
|
17256
|
+
for (const file of files) {
|
|
17257
|
+
const oldText = before.get(file);
|
|
17258
|
+
const newText = after.has(file) ? after.get(file) : worktreeContent(this.cwd, file);
|
|
17259
|
+
if (oldText === newText || oldText === null && newText === null) continue;
|
|
17260
|
+
const counts = lineChangeCounts(oldText || "", newText || "");
|
|
17261
|
+
fileChanges2.push({ path: file, ...counts });
|
|
17262
|
+
}
|
|
17263
|
+
const provided = event.fileChanges || [];
|
|
17264
|
+
const measured = new Map(fileChanges2.map((change) => [change.path, change]));
|
|
17265
|
+
const merged = provided.map((change) => {
|
|
17266
|
+
const actual = measured.get(change.path) || measured.get(safePath(this.cwd, change.path) || "");
|
|
17267
|
+
if (actual && change.additions === 0 && change.deletions === 0) return actual;
|
|
17268
|
+
return change;
|
|
17269
|
+
});
|
|
17270
|
+
const providedPaths = new Set(provided.flatMap((change) => [change.path, safePath(this.cwd, change.path) || ""]));
|
|
17271
|
+
for (const change of fileChanges2) if (!providedPaths.has(change.path)) merged.push(change);
|
|
17272
|
+
return { ...event, fileChanges: merged.length ? merged : fileChanges2 };
|
|
17273
|
+
}
|
|
17274
|
+
};
|
|
17275
|
+
}
|
|
17276
|
+
});
|
|
17277
|
+
|
|
17278
|
+
// cli/codexUsage.ts
|
|
17279
|
+
function record(value) {
|
|
17280
|
+
return value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17281
|
+
}
|
|
17282
|
+
function finiteNumber(value) {
|
|
17283
|
+
const n = Number(value);
|
|
17284
|
+
return Number.isFinite(n) ? n : null;
|
|
17285
|
+
}
|
|
17286
|
+
function rateWindow(value) {
|
|
17287
|
+
const row2 = record(value);
|
|
17288
|
+
if (!row2) return null;
|
|
17289
|
+
const usedPercent = finiteNumber(row2.usedPercent);
|
|
17290
|
+
if (usedPercent == null) return null;
|
|
17291
|
+
return {
|
|
17292
|
+
durationMins: finiteNumber(row2.windowDurationMins),
|
|
17293
|
+
usedPercent,
|
|
17294
|
+
resetsAt: finiteNumber(row2.resetsAt)
|
|
17295
|
+
};
|
|
17296
|
+
}
|
|
17297
|
+
function codexUsageFromRateLimitsResponse(value) {
|
|
17298
|
+
const response = record(value);
|
|
17299
|
+
if (!response) return null;
|
|
17300
|
+
const byLimitId = record(response.rateLimitsByLimitId);
|
|
17301
|
+
const snapshot = record(byLimitId?.codex) || record(response.rateLimits);
|
|
17302
|
+
if (!snapshot) return null;
|
|
17303
|
+
const primary = rateWindow(snapshot.primary);
|
|
17304
|
+
const secondary = rateWindow(snapshot.secondary);
|
|
17305
|
+
const windows = [primary, secondary].filter((w) => w != null);
|
|
17306
|
+
if (windows.length === 0) return null;
|
|
17307
|
+
const fiveHour = windows.find((w) => w.durationMins === 300) || windows.find((w) => w.durationMins != null && w.durationMins <= 360) || (primary?.durationMins == null ? primary : null);
|
|
17308
|
+
const sevenDay = windows.find((w) => w.durationMins === 10080) || windows.find((w) => w.durationMins != null && w.durationMins >= 1440) || (secondary !== fiveHour && secondary?.durationMins == null ? secondary : null);
|
|
17309
|
+
const status = typeof snapshot.rateLimitReachedType === "string" ? snapshot.rateLimitReachedType : snapshot.spendControlReached === true ? "spend_control_reached" : null;
|
|
17310
|
+
return {
|
|
17311
|
+
util5h: fiveHour ? fiveHour.usedPercent / 100 : null,
|
|
17312
|
+
util7d: sevenDay ? sevenDay.usedPercent / 100 : null,
|
|
17313
|
+
reset5h: fiveHour?.resetsAt ?? null,
|
|
17314
|
+
reset7d: sevenDay?.resetsAt ?? null,
|
|
17315
|
+
status,
|
|
17316
|
+
planType: typeof snapshot.planType === "string" ? snapshot.planType : null,
|
|
17317
|
+
limitId: typeof snapshot.limitId === "string" ? snapshot.limitId : null
|
|
17318
|
+
};
|
|
17319
|
+
}
|
|
17320
|
+
var init_codexUsage = __esm({
|
|
17321
|
+
"cli/codexUsage.ts"() {
|
|
17322
|
+
"use strict";
|
|
16833
17323
|
}
|
|
16834
17324
|
});
|
|
16835
17325
|
|
|
@@ -16845,10 +17335,10 @@ function textOf(message) {
|
|
|
16845
17335
|
}
|
|
16846
17336
|
function toolPayload(toolCall) {
|
|
16847
17337
|
if (!toolCall) return { kind: "other", body: {} };
|
|
16848
|
-
for (const [
|
|
16849
|
-
if (toolCall[
|
|
17338
|
+
for (const [key2, kind] of Object.entries(TOOL_KINDS)) {
|
|
17339
|
+
if (toolCall[key2]) return { kind, body: toolCall[key2] };
|
|
16850
17340
|
}
|
|
16851
|
-
const fallback = Object.keys(toolCall).find((
|
|
17341
|
+
const fallback = Object.keys(toolCall).find((key2) => key2.endsWith("ToolCall"));
|
|
16852
17342
|
return fallback ? { kind: "other", body: toolCall[fallback] } : { kind: "other", body: {} };
|
|
16853
17343
|
}
|
|
16854
17344
|
function targetOf(kind, body) {
|
|
@@ -16857,7 +17347,25 @@ function targetOf(kind, body) {
|
|
|
16857
17347
|
const text = typeof candidate === "string" ? candidate : JSON.stringify(candidate ?? "");
|
|
16858
17348
|
return text || body?.description || kind;
|
|
16859
17349
|
}
|
|
16860
|
-
function
|
|
17350
|
+
function completedFileChanges(body) {
|
|
17351
|
+
const success = body?.result?.success || {};
|
|
17352
|
+
const rows = Array.isArray(success.changes) ? success.changes : Array.isArray(success.files) ? success.files : [];
|
|
17353
|
+
return rows.flatMap((change) => {
|
|
17354
|
+
const path = String(change?.path || change?.filePath || change?.file_path || "");
|
|
17355
|
+
if (!path) return [];
|
|
17356
|
+
const diff = String(change?.diff || change?.patch || "");
|
|
17357
|
+
const hasAdditions = change?.additions != null || change?.linesAdded != null;
|
|
17358
|
+
const hasDeletions = change?.deletions != null || change?.linesRemoved != null;
|
|
17359
|
+
if (!diff && !hasAdditions && !hasDeletions) return [];
|
|
17360
|
+
const counted = unifiedDiffCounts(diff);
|
|
17361
|
+
return [{
|
|
17362
|
+
path,
|
|
17363
|
+
additions: hasAdditions ? Number(change?.additions ?? change?.linesAdded) : counted.additions,
|
|
17364
|
+
deletions: hasDeletions ? Number(change?.deletions ?? change?.linesRemoved) : counted.deletions
|
|
17365
|
+
}];
|
|
17366
|
+
});
|
|
17367
|
+
}
|
|
17368
|
+
function parseCursorLine(line, streamPartial = false) {
|
|
16861
17369
|
const trimmed = String(line || "").trim();
|
|
16862
17370
|
if (!trimmed) return [];
|
|
16863
17371
|
let frame;
|
|
@@ -16883,7 +17391,7 @@ function parseCursorLine(line) {
|
|
|
16883
17391
|
}
|
|
16884
17392
|
if (type === "assistant") {
|
|
16885
17393
|
const text = textOf(frame.message);
|
|
16886
|
-
return text ? [{ type: "assistant-message", text }] : [];
|
|
17394
|
+
return text ? [streamPartial ? { type: "assistant-delta", id: String(frame.model_call_id || ""), text } : { type: "assistant-message", text }] : [];
|
|
16887
17395
|
}
|
|
16888
17396
|
if (type === "thinking" && subtype === "delta" && frame.text) {
|
|
16889
17397
|
return [{ type: "thinking", text: String(frame.text) }];
|
|
@@ -16910,8 +17418,10 @@ function parseCursorLine(line) {
|
|
|
16910
17418
|
ok: Boolean(success) && Number(success?.exitCode ?? 0) === 0,
|
|
16911
17419
|
blocked,
|
|
16912
17420
|
reason: rejected ? blocked ? blockReason(rawReason) : rawReason || "rejected" : "",
|
|
17421
|
+
pollId: blocked ? fixPollId(rawReason) : "",
|
|
16913
17422
|
exitCode: success ? Number(success.exitCode ?? 0) : null,
|
|
16914
|
-
output: String(success?.stdout || success?.stderr || "")
|
|
17423
|
+
output: String(success?.stdout || success?.stderr || ""),
|
|
17424
|
+
fileChanges: completedFileChanges(body)
|
|
16915
17425
|
}];
|
|
16916
17426
|
}
|
|
16917
17427
|
return [];
|
|
@@ -16926,11 +17436,15 @@ function parseCursorLine(line) {
|
|
|
16926
17436
|
}];
|
|
16927
17437
|
}
|
|
16928
17438
|
if (type === "result") {
|
|
16929
|
-
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
|
|
17439
|
+
const result = String(frame.result || "");
|
|
17440
|
+
return [
|
|
17441
|
+
...streamPartial && result ? [{ type: "assistant-message", text: result }] : [],
|
|
17442
|
+
{
|
|
17443
|
+
type: "turn-end",
|
|
17444
|
+
ok: !frame.is_error,
|
|
17445
|
+
text: result
|
|
17446
|
+
}
|
|
17447
|
+
];
|
|
16934
17448
|
}
|
|
16935
17449
|
return [];
|
|
16936
17450
|
}
|
|
@@ -16949,12 +17463,14 @@ function feed(buffer, chunk) {
|
|
|
16949
17463
|
const rest = parts.pop() ?? "";
|
|
16950
17464
|
return { lines: parts, rest };
|
|
16951
17465
|
}
|
|
16952
|
-
function cursorArgs(prompt) {
|
|
17466
|
+
function cursorArgs(prompt, resumeId = "") {
|
|
16953
17467
|
return [
|
|
16954
17468
|
"-p",
|
|
16955
17469
|
prompt,
|
|
16956
17470
|
"--output-format",
|
|
16957
17471
|
"stream-json",
|
|
17472
|
+
"--stream-partial-output",
|
|
17473
|
+
...resumeId ? ["--resume", resumeId] : [],
|
|
16958
17474
|
// --force auto-runs tools but does NOT bypass hooks (verified live), so
|
|
16959
17475
|
// Synkro's guards still gate every call; --trust loads workspace hooks.
|
|
16960
17476
|
"--force",
|
|
@@ -16966,6 +17482,7 @@ var init_cursor = __esm({
|
|
|
16966
17482
|
"cli/harness/cursor.ts"() {
|
|
16967
17483
|
"use strict";
|
|
16968
17484
|
init_events();
|
|
17485
|
+
init_changes();
|
|
16969
17486
|
TOOL_KINDS = {
|
|
16970
17487
|
shellToolCall: "shell",
|
|
16971
17488
|
readToolCall: "read",
|
|
@@ -16981,131 +17498,9 @@ var init_cursor = __esm({
|
|
|
16981
17498
|
}
|
|
16982
17499
|
});
|
|
16983
17500
|
|
|
16984
|
-
// cli/harness/
|
|
16985
|
-
|
|
16986
|
-
|
|
16987
|
-
}
|
|
16988
|
-
function clip3(text, max) {
|
|
16989
|
-
const value = String(text || "").replace(/\s+/g, " ").trim();
|
|
16990
|
-
if (max <= 1) return value;
|
|
16991
|
-
return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
|
|
16992
|
-
}
|
|
16993
|
-
function seconds(ms) {
|
|
16994
|
-
const total = Math.max(0, Math.round(ms / 1e3));
|
|
16995
|
-
if (total < 60) return total + "s";
|
|
16996
|
-
return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
|
|
16997
|
-
}
|
|
16998
|
-
function statusLine(opts) {
|
|
16999
|
-
const hint = opts.hint ? " \xB7 " + opts.hint : "";
|
|
17000
|
-
const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
|
|
17001
|
-
const width = Math.max(20, (opts.width || 100) - 4);
|
|
17002
|
-
return S2.think + " " + spinnerFrame(opts.tick) + " " + clip3(body, width) + S2.reset;
|
|
17003
|
-
}
|
|
17004
|
-
function renderEvent(event, width = 100) {
|
|
17005
|
-
const body = Math.max(30, width - 4);
|
|
17006
|
-
switch (event.type) {
|
|
17007
|
-
// The workspace and both accounts are already in the session header, so
|
|
17008
|
-
// this line carries only what the header could not know before the harness
|
|
17009
|
-
// started: which model answered, and whether a subscription or a key paid.
|
|
17010
|
-
case "session-start":
|
|
17011
|
-
return [
|
|
17012
|
-
"",
|
|
17013
|
-
S2.dim + " " + clip3(event.model, body - 20) + (event.authSource === "login" ? " \xB7 subscription" : " \xB7 api key") + S2.reset,
|
|
17014
|
-
""
|
|
17015
|
-
];
|
|
17016
|
-
case "user-message":
|
|
17017
|
-
return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset, ""];
|
|
17018
|
-
// Live state, not transcript. The runner promotes these to the status line.
|
|
17019
|
-
case "thinking":
|
|
17020
|
-
return [];
|
|
17021
|
-
case "assistant-message": {
|
|
17022
|
-
const lines = wrap(event.text, body - 2);
|
|
17023
|
-
return [
|
|
17024
|
-
"",
|
|
17025
|
-
...lines.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset),
|
|
17026
|
-
""
|
|
17027
|
-
];
|
|
17028
|
-
}
|
|
17029
|
-
case "tool-start": {
|
|
17030
|
-
const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
|
|
17031
|
-
return [
|
|
17032
|
-
S2.tool + " " + BULLET + " " + label + S2.reset + S2.dim + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
|
|
17033
|
-
];
|
|
17034
|
-
}
|
|
17035
|
-
case "tool-end": {
|
|
17036
|
-
if (event.blocked) {
|
|
17037
|
-
return [
|
|
17038
|
-
S2.blocked + " " + BULLET + " Blocked" + S2.reset + S2.dim + " " + clip3(event.target, body - 14) + S2.reset,
|
|
17039
|
-
...wrap(event.reason, body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset)
|
|
17040
|
-
];
|
|
17041
|
-
}
|
|
17042
|
-
const detail = event.ok ? event.output.trim() ? clip3(event.output, body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
|
|
17043
|
-
const tint = event.ok ? S2.ok : S2.blocked;
|
|
17044
|
-
return [tint + " " + ELBOW + " " + S2.reset + S2.dim + detail + S2.reset];
|
|
17045
|
-
}
|
|
17046
|
-
// A clean finish needs no announcement: the prompt returning IS the signal.
|
|
17047
|
-
case "turn-end":
|
|
17048
|
-
return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
|
|
17049
|
-
case "notice":
|
|
17050
|
-
return [S2.dim + " " + clip3(event.text, body) + S2.reset];
|
|
17051
|
-
default:
|
|
17052
|
-
return [];
|
|
17053
|
-
}
|
|
17054
|
-
}
|
|
17055
|
-
function wrap(text, width) {
|
|
17056
|
-
const words = String(text || "").replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
|
|
17057
|
-
if (words.length === 0) return [];
|
|
17058
|
-
const lines = [];
|
|
17059
|
-
let line = "";
|
|
17060
|
-
for (const word of words) {
|
|
17061
|
-
if (!line) line = word;
|
|
17062
|
-
else if ((line + " " + word).length <= width) line += " " + word;
|
|
17063
|
-
else {
|
|
17064
|
-
lines.push(line);
|
|
17065
|
-
line = word;
|
|
17066
|
-
}
|
|
17067
|
-
}
|
|
17068
|
-
if (line) lines.push(line);
|
|
17069
|
-
return lines;
|
|
17070
|
-
}
|
|
17071
|
-
var ESC2, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
|
|
17072
|
-
var init_render2 = __esm({
|
|
17073
|
-
"cli/harness/render.ts"() {
|
|
17074
|
-
"use strict";
|
|
17075
|
-
ESC2 = "\x1B[";
|
|
17076
|
-
S2 = {
|
|
17077
|
-
reset: ESC2 + "0m",
|
|
17078
|
-
dim: ESC2 + "2m",
|
|
17079
|
-
bold: ESC2 + "1m",
|
|
17080
|
-
user: ESC2 + "38;5;111m",
|
|
17081
|
-
agent: ESC2 + "38;5;252m",
|
|
17082
|
-
think: ESC2 + "38;5;244m",
|
|
17083
|
-
tool: ESC2 + "38;5;180m",
|
|
17084
|
-
ok: ESC2 + "38;5;114m",
|
|
17085
|
-
blocked: ESC2 + "38;5;203m",
|
|
17086
|
-
rule: ESC2 + "38;5;211m"
|
|
17087
|
-
};
|
|
17088
|
-
CLEAR_LINE = "\r" + ESC2 + "2K";
|
|
17089
|
-
BULLET = "\u23FA";
|
|
17090
|
-
ELBOW = "\u23BF";
|
|
17091
|
-
SPINNER = ["\xB7", "\u2722", "\u2733", "\u2217", "\u273B", "\u273D"];
|
|
17092
|
-
TOOL_LABEL = {
|
|
17093
|
-
shell: "Shell",
|
|
17094
|
-
read: "Read",
|
|
17095
|
-
edit: "Edit",
|
|
17096
|
-
write: "Write",
|
|
17097
|
-
delete: "Delete",
|
|
17098
|
-
search: "Search",
|
|
17099
|
-
list: "List",
|
|
17100
|
-
todo: "Todo",
|
|
17101
|
-
other: "Tool"
|
|
17102
|
-
};
|
|
17103
|
-
}
|
|
17104
|
-
});
|
|
17105
|
-
|
|
17106
|
-
// cli/harness/run.ts
|
|
17107
|
-
import { spawn as spawn9 } from "child_process";
|
|
17108
|
-
function replayKey(event) {
|
|
17501
|
+
// cli/harness/run.ts
|
|
17502
|
+
import { spawn as spawn9 } from "child_process";
|
|
17503
|
+
function replayKey(event) {
|
|
17109
17504
|
switch (event.type) {
|
|
17110
17505
|
case "user-message":
|
|
17111
17506
|
return "u:" + event.text;
|
|
@@ -17124,6 +17519,7 @@ function createTurnSink(opts) {
|
|
|
17124
17519
|
const now = opts.now || (() => Date.now());
|
|
17125
17520
|
const showPrompt = opts.showPrompt !== false;
|
|
17126
17521
|
let blocked = 0;
|
|
17522
|
+
let sessionId = "";
|
|
17127
17523
|
let replaying = false;
|
|
17128
17524
|
const seen = /* @__PURE__ */ new Set();
|
|
17129
17525
|
let sawTurnEnd = false;
|
|
@@ -17131,6 +17527,7 @@ function createTurnSink(opts) {
|
|
|
17131
17527
|
let buffer = "";
|
|
17132
17528
|
let reconnecting = false;
|
|
17133
17529
|
let lastWasAnswer = false;
|
|
17530
|
+
const toolStartedAt = /* @__PURE__ */ new Map();
|
|
17134
17531
|
let statusText = "";
|
|
17135
17532
|
let statusKind = "progress";
|
|
17136
17533
|
let statusShown = false;
|
|
@@ -17203,6 +17600,7 @@ function createTurnSink(opts) {
|
|
|
17203
17600
|
};
|
|
17204
17601
|
const emit2 = (event) => {
|
|
17205
17602
|
events.push(event);
|
|
17603
|
+
if (event.type === "session-start" && event.sessionId) sessionId = event.sessionId;
|
|
17206
17604
|
if (event.type === "tool-end" && event.blocked) blocked += 1;
|
|
17207
17605
|
opts.onEvent?.(event);
|
|
17208
17606
|
if (event.type === "notice" && event.kind === "retry") {
|
|
@@ -17233,7 +17631,17 @@ function createTurnSink(opts) {
|
|
|
17233
17631
|
opts.write(rendered.join("\n") + "\n");
|
|
17234
17632
|
drawStatus();
|
|
17235
17633
|
};
|
|
17236
|
-
const take = (
|
|
17634
|
+
const take = (incoming) => {
|
|
17635
|
+
let event = incoming;
|
|
17636
|
+
if (event.type === "tool-start" && event.id && !toolStartedAt.has(event.id)) {
|
|
17637
|
+
toolStartedAt.set(event.id, now());
|
|
17638
|
+
} else if (event.type === "tool-end") {
|
|
17639
|
+
const started = toolStartedAt.get(event.id);
|
|
17640
|
+
if ((event.durationMs === null || event.durationMs === void 0) && started !== void 0) {
|
|
17641
|
+
event = { ...event, durationMs: Math.max(0, now() - started) };
|
|
17642
|
+
}
|
|
17643
|
+
toolStartedAt.delete(event.id);
|
|
17644
|
+
}
|
|
17237
17645
|
const isRetry = event.type === "notice" && event.kind === "retry";
|
|
17238
17646
|
if (!isRetry) disarmStall();
|
|
17239
17647
|
if (isRetry) {
|
|
@@ -17243,29 +17651,32 @@ function createTurnSink(opts) {
|
|
|
17243
17651
|
}
|
|
17244
17652
|
replaying = true;
|
|
17245
17653
|
}
|
|
17246
|
-
const
|
|
17247
|
-
if (
|
|
17248
|
-
if (replaying && seen.has(
|
|
17654
|
+
const key2 = replayKey(event);
|
|
17655
|
+
if (key2) {
|
|
17656
|
+
if (replaying && seen.has(key2)) {
|
|
17249
17657
|
lastWasAnswer = event.type === "assistant-message";
|
|
17250
17658
|
settle();
|
|
17251
17659
|
return;
|
|
17252
17660
|
}
|
|
17253
|
-
seen.add(
|
|
17661
|
+
seen.add(key2);
|
|
17254
17662
|
}
|
|
17255
|
-
if (
|
|
17663
|
+
if (key2 || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
|
|
17256
17664
|
if (event.type === "turn-end") sawTurnEnd = true;
|
|
17257
17665
|
if (event.type === "assistant-message") sawAnswer = true;
|
|
17258
17666
|
emit2(event);
|
|
17259
17667
|
};
|
|
17260
17668
|
return {
|
|
17261
17669
|
chunk(text) {
|
|
17262
|
-
const { lines, rest } = feed(buffer, text);
|
|
17670
|
+
const { lines: lines2, rest } = feed(buffer, text);
|
|
17263
17671
|
buffer = rest;
|
|
17264
|
-
for (const line of
|
|
17672
|
+
for (const line of lines2) for (const event of parseCursorLine(line, opts.streamPartial)) take(event);
|
|
17265
17673
|
},
|
|
17266
17674
|
notice(text) {
|
|
17267
17675
|
emit2({ type: "notice", text });
|
|
17268
17676
|
},
|
|
17677
|
+
event(event) {
|
|
17678
|
+
take(event);
|
|
17679
|
+
},
|
|
17269
17680
|
finish(exit, interrupted = false) {
|
|
17270
17681
|
disarmStall();
|
|
17271
17682
|
stopStatus();
|
|
@@ -17281,7 +17692,7 @@ function createTurnSink(opts) {
|
|
|
17281
17692
|
}
|
|
17282
17693
|
}
|
|
17283
17694
|
stopStatus();
|
|
17284
|
-
return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit };
|
|
17695
|
+
return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit, sessionId };
|
|
17285
17696
|
}
|
|
17286
17697
|
};
|
|
17287
17698
|
}
|
|
@@ -17297,10 +17708,11 @@ async function runCursorTurn(opts) {
|
|
|
17297
17708
|
animate: Boolean(process.stdout.isTTY),
|
|
17298
17709
|
showPrompt: opts.showPrompt,
|
|
17299
17710
|
showHeader: opts.showHeader,
|
|
17300
|
-
onGiveUp: () => stopHarness()
|
|
17711
|
+
onGiveUp: () => stopHarness(),
|
|
17712
|
+
streamPartial: true
|
|
17301
17713
|
});
|
|
17302
|
-
return new Promise((
|
|
17303
|
-
const child = spawn9("cursor-agent", cursorArgs(opts.prompt), {
|
|
17714
|
+
return new Promise((resolve9) => {
|
|
17715
|
+
const child = spawn9("cursor-agent", cursorArgs(opts.prompt, opts.resumeId), {
|
|
17304
17716
|
cwd: opts.cwd,
|
|
17305
17717
|
stdio: ["ignore", "pipe", "pipe"]
|
|
17306
17718
|
});
|
|
@@ -17328,11 +17740,11 @@ async function runCursorTurn(opts) {
|
|
|
17328
17740
|
});
|
|
17329
17741
|
child.on("close", (code) => {
|
|
17330
17742
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
17331
|
-
|
|
17743
|
+
resolve9(sink.finish(code ?? 0, interrupted));
|
|
17332
17744
|
});
|
|
17333
17745
|
child.on("error", (error) => {
|
|
17334
17746
|
sink.notice("failed to start cursor-agent: " + String(error));
|
|
17335
|
-
|
|
17747
|
+
resolve9(sink.finish(1, interrupted));
|
|
17336
17748
|
});
|
|
17337
17749
|
});
|
|
17338
17750
|
}
|
|
@@ -17346,128 +17758,884 @@ var init_run = __esm({
|
|
|
17346
17758
|
}
|
|
17347
17759
|
});
|
|
17348
17760
|
|
|
17349
|
-
// cli/harness/
|
|
17350
|
-
import {
|
|
17351
|
-
import {
|
|
17352
|
-
function
|
|
17353
|
-
|
|
17354
|
-
if (home && value === home) return "~";
|
|
17355
|
-
if (home && value.startsWith(home + "/")) return "~" + value.slice(home.length);
|
|
17356
|
-
return value;
|
|
17357
|
-
}
|
|
17358
|
-
function parseCursorAccount(output) {
|
|
17359
|
-
const match = String(output || "").match(/logged in as\s+(\S+)/i);
|
|
17360
|
-
return match ? match[1].trim() : "";
|
|
17361
|
-
}
|
|
17362
|
-
function cursorAccount() {
|
|
17761
|
+
// cli/harness/codex.ts
|
|
17762
|
+
import { spawn as spawn10 } from "child_process";
|
|
17763
|
+
import { createInterface as createInterface6 } from "readline";
|
|
17764
|
+
function stringify(value) {
|
|
17765
|
+
if (typeof value === "string") return value;
|
|
17363
17766
|
try {
|
|
17364
|
-
|
|
17365
|
-
encoding: "utf8",
|
|
17366
|
-
timeout: 5e3,
|
|
17367
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
17368
|
-
});
|
|
17369
|
-
return parseCursorAccount(out);
|
|
17767
|
+
return JSON.stringify(value);
|
|
17370
17768
|
} catch {
|
|
17371
|
-
return "";
|
|
17769
|
+
return String(value ?? "");
|
|
17770
|
+
}
|
|
17771
|
+
}
|
|
17772
|
+
function toolKind(item) {
|
|
17773
|
+
if (item.type === "commandExecution") return "shell";
|
|
17774
|
+
if (item.type === "fileChange") return "edit";
|
|
17775
|
+
if (item.type === "webSearch") return "search";
|
|
17776
|
+
return "other";
|
|
17777
|
+
}
|
|
17778
|
+
function toolTarget(item) {
|
|
17779
|
+
if (item.type === "commandExecution") return item.command || "command";
|
|
17780
|
+
if (item.type === "fileChange") return (item.changes || []).map((change) => change.path || change.filePath || "").filter(Boolean).join(", ") || "workspace files";
|
|
17781
|
+
if (item.type === "mcpToolCall") return [item.server, item.tool].filter(Boolean).join(".") || "MCP tool";
|
|
17782
|
+
if (item.type === "dynamicToolCall") return [item.namespace, item.tool].filter(Boolean).join(".") || "tool";
|
|
17783
|
+
return item.query || item.type || "tool";
|
|
17784
|
+
}
|
|
17785
|
+
function fileChanges(item) {
|
|
17786
|
+
if (item.type !== "fileChange" || !Array.isArray(item.changes)) return [];
|
|
17787
|
+
return item.changes.flatMap((change) => {
|
|
17788
|
+
const path = String(change.path || change.filePath || "");
|
|
17789
|
+
if (!path) return [];
|
|
17790
|
+
const diff = String(change.diff || change.patch || "");
|
|
17791
|
+
const hasAdditions = change.additions != null || change.linesAdded != null;
|
|
17792
|
+
const hasDeletions = change.deletions != null || change.linesRemoved != null;
|
|
17793
|
+
if (!diff && !hasAdditions && !hasDeletions) return [];
|
|
17794
|
+
const counted = unifiedDiffCounts(diff);
|
|
17795
|
+
const additions = hasAdditions ? Number(change.additions ?? change.linesAdded) : counted.additions;
|
|
17796
|
+
const deletions = hasDeletions ? Number(change.deletions ?? change.linesRemoved) : counted.deletions;
|
|
17797
|
+
return [{ path, additions, deletions }];
|
|
17798
|
+
});
|
|
17799
|
+
}
|
|
17800
|
+
function codexAccountUsageEvent(value) {
|
|
17801
|
+
const usage2 = codexUsageFromRateLimitsResponse(value);
|
|
17802
|
+
if (usage2?.util7d == null) return null;
|
|
17803
|
+
return {
|
|
17804
|
+
type: "account-usage",
|
|
17805
|
+
period: "weekly",
|
|
17806
|
+
remainingPercent: Math.max(0, Math.min(100, Math.round((1 - usage2.util7d) * 100))),
|
|
17807
|
+
resetsAt: usage2.reset7d
|
|
17808
|
+
};
|
|
17809
|
+
}
|
|
17810
|
+
function codexMessageTurnId(message) {
|
|
17811
|
+
return String(message.params?.turnId || message.params?.turn?.id || "");
|
|
17812
|
+
}
|
|
17813
|
+
function codexMessageIsTurnScoped(message) {
|
|
17814
|
+
const method = String(message.method || "");
|
|
17815
|
+
return method.startsWith("item/") || method === "hook/completed" || method === "turn/plan/updated";
|
|
17816
|
+
}
|
|
17817
|
+
function codexEvents(message) {
|
|
17818
|
+
const method = String(message.method || "");
|
|
17819
|
+
const params = message.params || {};
|
|
17820
|
+
if (method === "account/rateLimits/updated") {
|
|
17821
|
+
const event = codexAccountUsageEvent(params);
|
|
17822
|
+
return event ? [event] : [];
|
|
17823
|
+
}
|
|
17824
|
+
const item = params.item || {};
|
|
17825
|
+
if (method === "item/agentMessage/delta") return [{ type: "assistant-delta", id: params.itemId || "", text: params.delta || "" }];
|
|
17826
|
+
if (method === "item/reasoning/summaryTextDelta") return [{ type: "thinking", text: params.delta || "Thinking" }];
|
|
17827
|
+
const tools = ["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall", "webSearch"];
|
|
17828
|
+
if (method === "item/started" && tools.includes(item.type)) {
|
|
17829
|
+
return [{ type: "tool-start", id: item.id || "", kind: toolKind(item), target: toolTarget(item), description: "" }];
|
|
17830
|
+
}
|
|
17831
|
+
if (method === "item/completed" && item.type === "agentMessage") return [{ type: "assistant-message", text: item.text || "" }];
|
|
17832
|
+
if (method === "item/completed" && tools.includes(item.type)) {
|
|
17833
|
+
const raw = stringify(item.aggregatedOutput || item.error?.message || item.result?.content || item.contentItems || "");
|
|
17834
|
+
const status = String(item.status || "");
|
|
17835
|
+
const blocked = BLOCK_MARKER.test(raw) || /declined|blocked|denied/.test(status);
|
|
17836
|
+
const ok = !blocked && !/failed|error/.test(status) && (item.exitCode === null || item.exitCode === void 0 || item.exitCode === 0);
|
|
17837
|
+
return [{
|
|
17838
|
+
type: "tool-end",
|
|
17839
|
+
id: item.id || "",
|
|
17840
|
+
kind: toolKind(item),
|
|
17841
|
+
target: toolTarget(item),
|
|
17842
|
+
ok,
|
|
17843
|
+
blocked,
|
|
17844
|
+
reason: blocked ? blockReason(raw || status) : "",
|
|
17845
|
+
pollId: blocked ? fixPollId(raw) : "",
|
|
17846
|
+
exitCode: typeof item.exitCode === "number" ? item.exitCode : null,
|
|
17847
|
+
output: raw,
|
|
17848
|
+
durationMs: typeof item.durationMs === "number" ? item.durationMs : null,
|
|
17849
|
+
fileChanges: fileChanges(item)
|
|
17850
|
+
}];
|
|
17851
|
+
}
|
|
17852
|
+
if (method === "hook/completed") {
|
|
17853
|
+
const run2 = params.run || {};
|
|
17854
|
+
if (run2.status !== "blocked") return [];
|
|
17855
|
+
const reason = (run2.entries || []).map((entry) => entry.text || "").filter(Boolean).join("\n") || run2.statusMessage || "blocked by policy";
|
|
17856
|
+
return [{
|
|
17857
|
+
type: "tool-end",
|
|
17858
|
+
id: "hook:" + (run2.id || ""),
|
|
17859
|
+
kind: "other",
|
|
17860
|
+
target: run2.eventName || "Guard",
|
|
17861
|
+
ok: false,
|
|
17862
|
+
blocked: true,
|
|
17863
|
+
reason: blockReason(reason),
|
|
17864
|
+
exitCode: null,
|
|
17865
|
+
output: reason,
|
|
17866
|
+
pollId: fixPollId(reason),
|
|
17867
|
+
durationMs: typeof run2.durationMs === "number" ? run2.durationMs : null
|
|
17868
|
+
}];
|
|
17869
|
+
}
|
|
17870
|
+
if (method === "turn/plan/updated") {
|
|
17871
|
+
return [{ type: "plan", steps: (params.plan || []).map((row2) => ({ step: row2.step || "", status: row2.status || "pending" })) }];
|
|
17872
|
+
}
|
|
17873
|
+
if (method === "thread/tokenUsage/updated") {
|
|
17874
|
+
const usage2 = params.tokenUsage || {};
|
|
17875
|
+
const total = usage2.total || usage2.last || {};
|
|
17876
|
+
const used = Number(total.totalTokens ?? total.total_tokens ?? usage2.totalTokens ?? 0);
|
|
17877
|
+
const context = Number(usage2.modelContextWindow ?? usage2.model_context_window ?? 0);
|
|
17878
|
+
return [{ type: "usage", used, contextWindow: context || null }];
|
|
17372
17879
|
}
|
|
17880
|
+
if (method === "warning" || method === "error") return [{ type: "notice", text: params.message || params.error?.message || "Codex transport warning" }];
|
|
17881
|
+
return [];
|
|
17373
17882
|
}
|
|
17374
|
-
|
|
17375
|
-
|
|
17883
|
+
var CodexSession;
|
|
17884
|
+
var init_codex = __esm({
|
|
17885
|
+
"cli/harness/codex.ts"() {
|
|
17886
|
+
"use strict";
|
|
17887
|
+
init_composer();
|
|
17888
|
+
init_events();
|
|
17889
|
+
init_changes();
|
|
17890
|
+
init_codexUsage();
|
|
17891
|
+
init_run();
|
|
17892
|
+
init_render2();
|
|
17893
|
+
CodexSession = class {
|
|
17894
|
+
constructor(cwd, resumeId = "") {
|
|
17895
|
+
this.cwd = cwd;
|
|
17896
|
+
this.resumeId = resumeId;
|
|
17897
|
+
}
|
|
17898
|
+
cwd;
|
|
17899
|
+
resumeId;
|
|
17900
|
+
child = null;
|
|
17901
|
+
nextId = 1;
|
|
17902
|
+
pending = /* @__PURE__ */ new Map();
|
|
17903
|
+
onEvent = null;
|
|
17904
|
+
activeTurn = "";
|
|
17905
|
+
turnDone = null;
|
|
17906
|
+
completedTurns = /* @__PURE__ */ new Map();
|
|
17907
|
+
accountUsage = null;
|
|
17908
|
+
threadId = "";
|
|
17909
|
+
model = "";
|
|
17910
|
+
request(method, params, timeoutMs = 0) {
|
|
17911
|
+
if (!this.child) return Promise.reject(new Error("Codex app-server is not running"));
|
|
17912
|
+
const id = this.nextId++;
|
|
17913
|
+
this.child.stdin.write(JSON.stringify({ method, id, params }) + "\n");
|
|
17914
|
+
return new Promise((resolve9, reject) => {
|
|
17915
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
17916
|
+
this.pending.delete(id);
|
|
17917
|
+
reject(new Error(method + " timed out"));
|
|
17918
|
+
}, timeoutMs) : null;
|
|
17919
|
+
this.pending.set(id, {
|
|
17920
|
+
resolve: (value) => {
|
|
17921
|
+
if (timer) clearTimeout(timer);
|
|
17922
|
+
resolve9(value);
|
|
17923
|
+
},
|
|
17924
|
+
reject: (error) => {
|
|
17925
|
+
if (timer) clearTimeout(timer);
|
|
17926
|
+
reject(error);
|
|
17927
|
+
}
|
|
17928
|
+
});
|
|
17929
|
+
});
|
|
17930
|
+
}
|
|
17931
|
+
notify(method, params = {}) {
|
|
17932
|
+
this.child?.stdin.write(JSON.stringify({ method, params }) + "\n");
|
|
17933
|
+
}
|
|
17934
|
+
receive(message) {
|
|
17935
|
+
if (message.id !== void 0 && !message.method) {
|
|
17936
|
+
const pending = this.pending.get(message.id);
|
|
17937
|
+
if (!pending) return;
|
|
17938
|
+
this.pending.delete(message.id);
|
|
17939
|
+
if (message.error) pending.reject(new Error(message.error.message || stringify(message.error)));
|
|
17940
|
+
else pending.resolve(message.result);
|
|
17941
|
+
return;
|
|
17942
|
+
}
|
|
17943
|
+
if (message.id !== void 0 && message.method) {
|
|
17944
|
+
this.child?.stdin.write(JSON.stringify({ id: message.id, result: { decision: "decline" } }) + "\n");
|
|
17945
|
+
this.onEvent?.({ type: "notice", text: "Codex requested an approval the embedded policy did not permit." });
|
|
17946
|
+
return;
|
|
17947
|
+
}
|
|
17948
|
+
const messageTurnId = codexMessageTurnId(message);
|
|
17949
|
+
const staleTurnEvent = codexMessageIsTurnScoped(message) && (!this.activeTurn || messageTurnId && messageTurnId !== this.activeTurn);
|
|
17950
|
+
for (const event of codexEvents(message)) {
|
|
17951
|
+
if (event.type === "account-usage") this.accountUsage = event;
|
|
17952
|
+
if (!staleTurnEvent) this.onEvent?.(event);
|
|
17953
|
+
}
|
|
17954
|
+
if (message.method === "turn/completed" && message.params?.turn?.id === this.activeTurn) {
|
|
17955
|
+
const status = String(message.params.turn.status || "");
|
|
17956
|
+
this.completedTurns.set(message.params.turn.id, status);
|
|
17957
|
+
this.turnDone?.(status);
|
|
17958
|
+
} else if (message.method === "turn/completed" && message.params?.turn?.id) {
|
|
17959
|
+
this.completedTurns.set(message.params.turn.id, String(message.params.turn.status || ""));
|
|
17960
|
+
}
|
|
17961
|
+
}
|
|
17962
|
+
async start() {
|
|
17963
|
+
this.child = spawn10("codex", ["app-server", "--stdio"], { cwd: this.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
17964
|
+
this.child.on("error", (cause) => {
|
|
17965
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
17966
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
17967
|
+
this.pending.clear();
|
|
17968
|
+
this.turnDone?.("failed");
|
|
17969
|
+
});
|
|
17970
|
+
createInterface6({ input: this.child.stdout }).on("line", (line) => {
|
|
17971
|
+
try {
|
|
17972
|
+
this.receive(JSON.parse(line));
|
|
17973
|
+
} catch {
|
|
17974
|
+
}
|
|
17975
|
+
});
|
|
17976
|
+
this.child.stderr.on("data", (chunk) => {
|
|
17977
|
+
const message = String(chunk || "").trim();
|
|
17978
|
+
if (message) this.onEvent?.({ type: "notice", text: message });
|
|
17979
|
+
});
|
|
17980
|
+
this.child.once("exit", (code) => {
|
|
17981
|
+
const error = new Error("Codex app-server exited" + (code === null ? "" : " (" + code + ")"));
|
|
17982
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
17983
|
+
this.pending.clear();
|
|
17984
|
+
this.turnDone?.("failed");
|
|
17985
|
+
});
|
|
17986
|
+
await this.request("initialize", { clientInfo: { name: "synkro-terminal", title: "Synkro", version: "1" }, capabilities: { experimentalApi: true } });
|
|
17987
|
+
this.notify("initialized");
|
|
17988
|
+
try {
|
|
17989
|
+
const event = codexAccountUsageEvent(await this.request("account/rateLimits/read", null, 4e3));
|
|
17990
|
+
if (event) this.accountUsage = event;
|
|
17991
|
+
} catch {
|
|
17992
|
+
}
|
|
17993
|
+
let result;
|
|
17994
|
+
try {
|
|
17995
|
+
result = this.resumeId ? await this.request("thread/resume", { threadId: this.resumeId, cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write" }) : await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write", serviceName: "Synkro" });
|
|
17996
|
+
} catch (error) {
|
|
17997
|
+
if (!this.resumeId) throw error;
|
|
17998
|
+
result = await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write", serviceName: "Synkro" });
|
|
17999
|
+
}
|
|
18000
|
+
this.threadId = result.thread?.id || "";
|
|
18001
|
+
this.model = result.model || "";
|
|
18002
|
+
}
|
|
18003
|
+
async runTurn(prompt, onEvent, signal) {
|
|
18004
|
+
let blocked = 0;
|
|
18005
|
+
let turnId = "";
|
|
18006
|
+
let interrupt = null;
|
|
18007
|
+
this.onEvent = (event) => {
|
|
18008
|
+
if (event.type === "tool-end" && event.blocked) blocked++;
|
|
18009
|
+
onEvent(event);
|
|
18010
|
+
};
|
|
18011
|
+
try {
|
|
18012
|
+
onEvent({ type: "session-start", sessionId: this.threadId, model: this.model || "Codex", cwd: this.cwd, authSource: "login" });
|
|
18013
|
+
if (this.accountUsage) onEvent(this.accountUsage);
|
|
18014
|
+
onEvent({ type: "user-message", text: prompt });
|
|
18015
|
+
const response = await this.request("turn/start", { threadId: this.threadId, input: [{ type: "text", text: prompt }] });
|
|
18016
|
+
turnId = response.turn?.id || "";
|
|
18017
|
+
this.activeTurn = turnId;
|
|
18018
|
+
let aborted = false;
|
|
18019
|
+
let interruptSent = false;
|
|
18020
|
+
interrupt = () => {
|
|
18021
|
+
aborted = true;
|
|
18022
|
+
if (interruptSent || !this.activeTurn) return;
|
|
18023
|
+
interruptSent = true;
|
|
18024
|
+
void this.request("turn/interrupt", { threadId: this.threadId, turnId: this.activeTurn }).catch(() => {
|
|
18025
|
+
});
|
|
18026
|
+
};
|
|
18027
|
+
if (signal?.aborted) interrupt();
|
|
18028
|
+
else signal?.addEventListener("abort", interrupt, { once: true });
|
|
18029
|
+
const alreadyDone = this.completedTurns.get(turnId);
|
|
18030
|
+
const status = alreadyDone ?? await new Promise((resolve9) => {
|
|
18031
|
+
this.turnDone = resolve9;
|
|
18032
|
+
});
|
|
18033
|
+
this.completedTurns.delete(turnId);
|
|
18034
|
+
signal?.removeEventListener("abort", interrupt);
|
|
18035
|
+
this.activeTurn = "";
|
|
18036
|
+
this.turnDone = null;
|
|
18037
|
+
this.onEvent = null;
|
|
18038
|
+
onEvent({ type: "turn-end", ok: aborted || /completed/.test(status), text: aborted ? "interrupted" : status });
|
|
18039
|
+
return { exitCode: aborted ? 130 : /completed/.test(status) ? 0 : 1, blocked, sessionId: this.threadId };
|
|
18040
|
+
} finally {
|
|
18041
|
+
if (interrupt) signal?.removeEventListener("abort", interrupt);
|
|
18042
|
+
if (turnId) this.completedTurns.delete(turnId);
|
|
18043
|
+
this.activeTurn = "";
|
|
18044
|
+
this.turnDone = null;
|
|
18045
|
+
this.onEvent = null;
|
|
18046
|
+
}
|
|
18047
|
+
}
|
|
18048
|
+
close() {
|
|
18049
|
+
this.child?.kill("SIGTERM");
|
|
18050
|
+
this.child = null;
|
|
18051
|
+
}
|
|
18052
|
+
};
|
|
18053
|
+
}
|
|
18054
|
+
});
|
|
18055
|
+
|
|
18056
|
+
// cli/harness/screen.ts
|
|
18057
|
+
var ANSI, REVERSE, STREAM_DRAW_MS, STATUS_TICK_MS, resetDateFormat, visible, pathLabel, SynkroScreen;
|
|
18058
|
+
var init_screen = __esm({
|
|
18059
|
+
"cli/harness/screen.ts"() {
|
|
18060
|
+
"use strict";
|
|
18061
|
+
init_render2();
|
|
18062
|
+
init_changes();
|
|
18063
|
+
ANSI = /\x1b\[[0-?]*[ -\/]*[@-~]/g;
|
|
18064
|
+
REVERSE = "\x1B[7m";
|
|
18065
|
+
STREAM_DRAW_MS = 80;
|
|
18066
|
+
STATUS_TICK_MS = 240;
|
|
18067
|
+
resetDateFormat = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" });
|
|
18068
|
+
visible = (value) => value.replace(ANSI, "").length;
|
|
18069
|
+
pathLabel = (cwd) => {
|
|
18070
|
+
const parts = cwd.split("/").filter(Boolean);
|
|
18071
|
+
return parts.length > 2 ? "\u2026/" + parts.slice(-2).join("/") : cwd;
|
|
18072
|
+
};
|
|
18073
|
+
SynkroScreen = class {
|
|
18074
|
+
constructor(harness, cwd, write2 = (text) => process.stdout.write(text)) {
|
|
18075
|
+
this.harness = harness;
|
|
18076
|
+
this.cwd = cwd;
|
|
18077
|
+
this.write = write2;
|
|
18078
|
+
this.changes = new OperationChangeTracker(cwd);
|
|
18079
|
+
}
|
|
18080
|
+
harness;
|
|
18081
|
+
cwd;
|
|
18082
|
+
write;
|
|
18083
|
+
transcript = [];
|
|
18084
|
+
draft = "";
|
|
18085
|
+
input = "";
|
|
18086
|
+
selectionAnchor = null;
|
|
18087
|
+
selectionFocus = null;
|
|
18088
|
+
status = "Ready";
|
|
18089
|
+
statusSince = Date.now();
|
|
18090
|
+
tick = 0;
|
|
18091
|
+
used = 0;
|
|
18092
|
+
context = null;
|
|
18093
|
+
weeklyRemaining = null;
|
|
18094
|
+
weeklyResetsAt = null;
|
|
18095
|
+
model = "";
|
|
18096
|
+
timer = null;
|
|
18097
|
+
drawTimer = null;
|
|
18098
|
+
lastDrawAt = 0;
|
|
18099
|
+
scrollOffset = 0;
|
|
18100
|
+
changes;
|
|
18101
|
+
resize = () => this.draw();
|
|
18102
|
+
enter() {
|
|
18103
|
+
this.write("\x1B[?1049h\x1B[?25l\x1B[?1000h\x1B[?1002h\x1B[?1006h");
|
|
18104
|
+
process.stdout.on("resize", this.resize);
|
|
18105
|
+
this.timer = setInterval(() => {
|
|
18106
|
+
if (this.status === "Ready") return;
|
|
18107
|
+
this.tick++;
|
|
18108
|
+
this.queueDraw();
|
|
18109
|
+
}, STATUS_TICK_MS);
|
|
18110
|
+
this.timer.unref();
|
|
18111
|
+
this.draw();
|
|
18112
|
+
}
|
|
18113
|
+
leave() {
|
|
18114
|
+
if (this.timer) clearInterval(this.timer);
|
|
18115
|
+
if (this.drawTimer) clearTimeout(this.drawTimer);
|
|
18116
|
+
process.stdout.removeListener("resize", this.resize);
|
|
18117
|
+
this.write(S2.reset + "\x1B[?1006l\x1B[?1002l\x1B[?1000l\x1B[?25h\x1B[?1049l");
|
|
18118
|
+
}
|
|
18119
|
+
setInput(value) {
|
|
18120
|
+
this.input = cleanOutput(value);
|
|
18121
|
+
this.selectionAnchor = null;
|
|
18122
|
+
this.selectionFocus = null;
|
|
18123
|
+
this.draw();
|
|
18124
|
+
}
|
|
18125
|
+
selectionRange() {
|
|
18126
|
+
if (this.selectionAnchor === null || this.selectionFocus === null || this.selectionAnchor === this.selectionFocus) return null;
|
|
18127
|
+
return [Math.min(this.selectionAnchor, this.selectionFocus), Math.max(this.selectionAnchor, this.selectionFocus)];
|
|
18128
|
+
}
|
|
18129
|
+
clearSelection() {
|
|
18130
|
+
this.selectionAnchor = null;
|
|
18131
|
+
this.selectionFocus = null;
|
|
18132
|
+
this.draw();
|
|
18133
|
+
}
|
|
18134
|
+
scrollBy(lines2) {
|
|
18135
|
+
this.scrollOffset = Math.max(0, this.scrollOffset + lines2);
|
|
18136
|
+
this.draw();
|
|
18137
|
+
}
|
|
18138
|
+
scrollToBottom() {
|
|
18139
|
+
this.scrollOffset = 0;
|
|
18140
|
+
this.draw();
|
|
18141
|
+
}
|
|
18142
|
+
/** Select text using one-based SGR mouse coordinates from the terminal. */
|
|
18143
|
+
selectInputAt(x, y, extend, width = this.width(), height = this.height()) {
|
|
18144
|
+
const rows = this.inputRows(width);
|
|
18145
|
+
const composerLength = rows.length + 2;
|
|
18146
|
+
const viewport = Math.max(3, height - composerLength - 2);
|
|
18147
|
+
const lineIndex = y - (viewport + 2);
|
|
18148
|
+
const row2 = rows[lineIndex];
|
|
18149
|
+
if (!row2) return;
|
|
18150
|
+
const index = Math.min(row2.end, row2.start + Math.max(0, x - 5));
|
|
18151
|
+
if (!extend || this.selectionAnchor === null) this.selectionAnchor = index;
|
|
18152
|
+
this.selectionFocus = index;
|
|
18153
|
+
this.draw();
|
|
18154
|
+
}
|
|
18155
|
+
updateStatus(value) {
|
|
18156
|
+
if (this.status === value) return false;
|
|
18157
|
+
this.status = value;
|
|
18158
|
+
this.statusSince = Date.now();
|
|
18159
|
+
return true;
|
|
18160
|
+
}
|
|
18161
|
+
setStatus(value) {
|
|
18162
|
+
if (this.updateStatus(value)) this.draw();
|
|
18163
|
+
}
|
|
18164
|
+
add(event) {
|
|
18165
|
+
event = this.changes.observe(event);
|
|
18166
|
+
if (event.type === "session-start") {
|
|
18167
|
+
this.model = event.model;
|
|
18168
|
+
this.draw();
|
|
18169
|
+
return;
|
|
18170
|
+
}
|
|
18171
|
+
if (event.type === "assistant-delta") {
|
|
18172
|
+
this.draft += event.text;
|
|
18173
|
+
this.updateStatus("Writing\u2026");
|
|
18174
|
+
this.queueDraw();
|
|
18175
|
+
return;
|
|
18176
|
+
}
|
|
18177
|
+
if (event.type === "assistant-message") this.draft = "";
|
|
18178
|
+
if (event.type === "usage") {
|
|
18179
|
+
this.used = event.used;
|
|
18180
|
+
this.context = event.contextWindow;
|
|
18181
|
+
if (event.model) this.model = event.model;
|
|
18182
|
+
this.draw();
|
|
18183
|
+
return;
|
|
18184
|
+
}
|
|
18185
|
+
if (event.type === "account-usage") {
|
|
18186
|
+
this.weeklyRemaining = event.remainingPercent;
|
|
18187
|
+
this.weeklyResetsAt = event.resetsAt;
|
|
18188
|
+
this.draw();
|
|
18189
|
+
return;
|
|
18190
|
+
}
|
|
18191
|
+
if (event.type === "thinking") {
|
|
18192
|
+
if (this.updateStatus(event.text || "Thinking\u2026")) this.draw();
|
|
18193
|
+
return;
|
|
18194
|
+
}
|
|
18195
|
+
if (event.type === "tool-start") this.updateStatus("Working\u2026");
|
|
18196
|
+
if (event.type === "tool-end") this.updateStatus(event.blocked ? "Guard enforced" : "Working\u2026");
|
|
18197
|
+
if (event.type === "turn-end") this.updateStatus(event.ok ? "Ready" : "Turn ended");
|
|
18198
|
+
const rendered = renderEvent(event, this.width());
|
|
18199
|
+
if (this.scrollOffset > 0) this.scrollOffset += rendered.length;
|
|
18200
|
+
this.transcript.push(...rendered);
|
|
18201
|
+
this.draw();
|
|
18202
|
+
}
|
|
18203
|
+
width() {
|
|
18204
|
+
return Math.max(50, Number(process.stdout.columns || 100));
|
|
18205
|
+
}
|
|
18206
|
+
height() {
|
|
18207
|
+
return Math.max(16, Number(process.stdout.rows || 30));
|
|
18208
|
+
}
|
|
18209
|
+
inputRows(width) {
|
|
18210
|
+
const lineWidth = Math.max(1, Math.max(30, width - 4) - 4);
|
|
18211
|
+
if (!this.input) return [{ text: "", start: 0, end: 0 }];
|
|
18212
|
+
const rows = [];
|
|
18213
|
+
for (let start = 0; start < this.input.length; start += lineWidth) {
|
|
18214
|
+
const text = this.input.slice(start, start + lineWidth);
|
|
18215
|
+
rows.push({ text, start, end: start + text.length });
|
|
18216
|
+
}
|
|
18217
|
+
return rows.slice(-3);
|
|
18218
|
+
}
|
|
18219
|
+
selectedText(row2) {
|
|
18220
|
+
const range = this.selectionRange();
|
|
18221
|
+
if (!range || range[1] <= row2.start || range[0] >= row2.end) return S2.text + row2.text;
|
|
18222
|
+
const from = Math.max(range[0], row2.start) - row2.start;
|
|
18223
|
+
const to = Math.min(range[1], row2.end) - row2.start;
|
|
18224
|
+
return S2.text + row2.text.slice(0, from) + REVERSE + row2.text.slice(from, to) + S2.reset + S2.composer + S2.text + row2.text.slice(to);
|
|
18225
|
+
}
|
|
18226
|
+
frame(width = this.width(), height = this.height()) {
|
|
18227
|
+
const inner = Math.max(30, width - 4);
|
|
18228
|
+
const provider = this.harness === "codex" ? "Codex" : "Cursor";
|
|
18229
|
+
const draft = this.draft ? ["", ...wrap(this.draft, inner - 2).map((line, i) => S2.agent + " " + (i ? " " : "\u23FA ") + line + S2.reset)] : [];
|
|
18230
|
+
const status = this.status === "Ready" ? S2.secondary + " \xB7 Ready" + S2.reset : statusLine({ tick: this.tick, text: this.status, elapsedMs: Date.now() - this.statusSince, width });
|
|
18231
|
+
const rule = S2.border + " " + "\u2500".repeat(inner) + S2.reset;
|
|
18232
|
+
const inputRows = this.inputRows(width);
|
|
18233
|
+
const composer = [rule, ...inputRows.map((row2, i) => S2.composer + (i ? " " : S2.user + " \u276F ") + this.selectedText(row2) + (i === inputRows.length - 1 ? S2.user + " \u258C" : "") + S2.reset), rule];
|
|
18234
|
+
const usedPercent = this.context ? Math.min(100, Math.round(this.used / this.context * 100)) : null;
|
|
18235
|
+
const contextUsage = usedPercent === null ? "\u2014 context" : usedPercent + "% context";
|
|
18236
|
+
const resetDate = this.weeklyResetsAt == null ? "" : " \xB7 " + resetDateFormat.format(new Date(this.weeklyResetsAt * 1e3));
|
|
18237
|
+
const accountUsage = this.weeklyRemaining === null ? "Weekly \u2014" : "Weekly " + this.weeklyRemaining + "% left" + resetDate;
|
|
18238
|
+
const usage2 = contextUsage + " \xB7 " + accountUsage;
|
|
18239
|
+
const left = S2.secondary + " " + pathLabel(this.cwd) + S2.reset + S2.canvas;
|
|
18240
|
+
const badge = S2.panel + S2.secondary + " " + usage2 + " " + S2.reset + S2.canvas;
|
|
18241
|
+
const right = badge + S2.secondary + " \xB7 " + provider + (this.model ? " " + cleanOutput(this.model) : "") + " \xB7 \u29C9 powered by synkro " + S2.reset + S2.canvas;
|
|
18242
|
+
const footer = left + " ".repeat(Math.max(1, width - visible(left) - visible(right))) + right;
|
|
18243
|
+
const viewport = Math.max(3, height - composer.length - 2);
|
|
18244
|
+
const fullContent = [...this.transcript, ...draft, status];
|
|
18245
|
+
const maxScroll = Math.max(0, fullContent.length - viewport);
|
|
18246
|
+
const effectiveOffset = Math.min(this.scrollOffset, maxScroll);
|
|
18247
|
+
const end = fullContent.length - effectiveOffset;
|
|
18248
|
+
const content = fullContent.slice(Math.max(0, end - viewport), end);
|
|
18249
|
+
while (content.length < viewport) content.unshift("");
|
|
18250
|
+
const rows = [...content, ...composer, footer].slice(0, height);
|
|
18251
|
+
while (rows.length < height) rows.push("");
|
|
18252
|
+
return rows.map((line) => {
|
|
18253
|
+
const fill = line.startsWith(S2.panel) ? S2.panel : line.includes(S2.composer) ? S2.composer : S2.canvas;
|
|
18254
|
+
return S2.canvas + line + fill + " ".repeat(Math.max(0, width - visible(line))) + S2.reset;
|
|
18255
|
+
}).join("\n");
|
|
18256
|
+
}
|
|
18257
|
+
queueDraw() {
|
|
18258
|
+
if (this.drawTimer) return;
|
|
18259
|
+
const delay = Math.max(0, STREAM_DRAW_MS - (Date.now() - this.lastDrawAt));
|
|
18260
|
+
this.drawTimer = setTimeout(() => {
|
|
18261
|
+
this.drawTimer = null;
|
|
18262
|
+
this.draw();
|
|
18263
|
+
}, delay);
|
|
18264
|
+
this.drawTimer.unref?.();
|
|
18265
|
+
}
|
|
18266
|
+
draw() {
|
|
18267
|
+
if (this.drawTimer) {
|
|
18268
|
+
clearTimeout(this.drawTimer);
|
|
18269
|
+
this.drawTimer = null;
|
|
18270
|
+
}
|
|
18271
|
+
this.lastDrawAt = Date.now();
|
|
18272
|
+
this.write("\x1B[H" + this.frame());
|
|
18273
|
+
}
|
|
18274
|
+
};
|
|
18275
|
+
}
|
|
18276
|
+
});
|
|
18277
|
+
|
|
18278
|
+
// cli/harness/state.ts
|
|
18279
|
+
import { mkdirSync as mkdirSync24, readFileSync as readFileSync34, writeFileSync as writeFileSync28 } from "fs";
|
|
18280
|
+
import { homedir as homedir39 } from "os";
|
|
18281
|
+
import { dirname as dirname13, join as join38 } from "path";
|
|
18282
|
+
function load(file = STATE_FILE) {
|
|
17376
18283
|
try {
|
|
17377
|
-
|
|
18284
|
+
const value = JSON.parse(readFileSync34(file, "utf8"));
|
|
18285
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
17378
18286
|
} catch {
|
|
17379
|
-
return {
|
|
18287
|
+
return {};
|
|
17380
18288
|
}
|
|
17381
|
-
|
|
18289
|
+
}
|
|
18290
|
+
function loadSessionId(harness, cwd, file = STATE_FILE) {
|
|
18291
|
+
return load(file)[key(harness, cwd)] || "";
|
|
18292
|
+
}
|
|
18293
|
+
function saveSessionId(harness, cwd, sessionId, file = STATE_FILE) {
|
|
18294
|
+
if (!sessionId) return;
|
|
17382
18295
|
try {
|
|
17383
|
-
|
|
18296
|
+
mkdirSync24(dirname13(file), { recursive: true });
|
|
18297
|
+
writeFileSync28(file, JSON.stringify({ ...load(file), [key(harness, cwd)]: sessionId }, null, 2));
|
|
17384
18298
|
} catch {
|
|
17385
18299
|
}
|
|
17386
|
-
return { email, needsLogin };
|
|
17387
18300
|
}
|
|
17388
|
-
|
|
17389
|
-
|
|
18301
|
+
var STATE_FILE, key;
|
|
18302
|
+
var init_state = __esm({
|
|
18303
|
+
"cli/harness/state.ts"() {
|
|
18304
|
+
"use strict";
|
|
18305
|
+
STATE_FILE = join38(homedir39(), ".synkro", "terminal-sessions.json");
|
|
18306
|
+
key = (harness, cwd) => harness + ":" + cwd.replace(/\/+$/, "");
|
|
18307
|
+
}
|
|
18308
|
+
});
|
|
18309
|
+
|
|
18310
|
+
// cli/harness/fixPoll.ts
|
|
18311
|
+
function pollBaseUrl() {
|
|
18312
|
+
const port = String(process.env.SYNKRO_MCP_PORT || "18931");
|
|
18313
|
+
return "http://127.0.0.1:" + port + "/api/local/fix-poll";
|
|
18314
|
+
}
|
|
18315
|
+
function neutralizeTerminalControls(value) {
|
|
18316
|
+
return value.replace(/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u001B]/g, "");
|
|
18317
|
+
}
|
|
18318
|
+
async function loadFixPoll(itemId, fetchImpl = fetch) {
|
|
18319
|
+
const response = await fetchImpl(pollBaseUrl() + "?item_id=" + encodeURIComponent(itemId));
|
|
18320
|
+
if (!response.ok) return null;
|
|
18321
|
+
const body = await response.json();
|
|
18322
|
+
const candidates = Array.isArray(body.candidates) ? body.candidates.filter((candidate) => typeof candidate === "string").slice(0, 8) : [];
|
|
18323
|
+
if (!candidates.length || body.status !== "pending") return null;
|
|
17390
18324
|
return {
|
|
17391
|
-
|
|
17392
|
-
|
|
17393
|
-
|
|
18325
|
+
itemId,
|
|
18326
|
+
filePath: String(body.file_path || ""),
|
|
18327
|
+
ruleId: String(body.rule_id || ""),
|
|
18328
|
+
candidates
|
|
17394
18329
|
};
|
|
17395
18330
|
}
|
|
17396
|
-
function
|
|
17397
|
-
const
|
|
17398
|
-
|
|
17399
|
-
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
lines.push("");
|
|
17404
|
-
return lines;
|
|
18331
|
+
async function recordFixPoll(itemId, chosenIdx, fetchImpl = fetch) {
|
|
18332
|
+
const response = await fetchImpl(pollBaseUrl() + "/record", {
|
|
18333
|
+
method: "POST",
|
|
18334
|
+
headers: { "content-type": "application/json" },
|
|
18335
|
+
body: JSON.stringify({ item_id: itemId, chosen_idx: chosenIdx })
|
|
18336
|
+
});
|
|
18337
|
+
return response.ok;
|
|
17405
18338
|
}
|
|
17406
|
-
|
|
17407
|
-
|
|
18339
|
+
function fixPollLines(poll, selected, width = 100) {
|
|
18340
|
+
const max = Math.max(32, width - 10);
|
|
18341
|
+
const choices = [...poll.candidates, "None of the above"];
|
|
18342
|
+
const scope = [poll.ruleId, poll.filePath].filter(Boolean).join(" \xB7 ");
|
|
18343
|
+
const lines2 = [
|
|
18344
|
+
"",
|
|
18345
|
+
S2.bold + S2.rule + " Synkro needs your decision" + S2.reset,
|
|
18346
|
+
...scope ? [S2.dim + " " + neutralizeTerminalControls(scope) + S2.reset] : [],
|
|
18347
|
+
""
|
|
18348
|
+
];
|
|
18349
|
+
choices.forEach((choice, index) => {
|
|
18350
|
+
const prefix = index === selected ? S2.user + " \u276F " : S2.dim + " ";
|
|
18351
|
+
const text = wrap(index + 1 + ". " + neutralizeTerminalControls(choice), max);
|
|
18352
|
+
lines2.push(prefix + (text[0] || "") + S2.reset);
|
|
18353
|
+
for (const continuation of text.slice(1)) lines2.push(" " + continuation);
|
|
18354
|
+
});
|
|
18355
|
+
lines2.push("", S2.dim + " \u2191/\u2193 select \xB7 Enter confirm \xB7 1-" + choices.length + " choose" + S2.reset);
|
|
18356
|
+
return lines2;
|
|
18357
|
+
}
|
|
18358
|
+
async function pickFixPoll(poll, io = {}) {
|
|
18359
|
+
const input = io.input || process.stdin;
|
|
18360
|
+
const output = io.output || process.stdout;
|
|
18361
|
+
if (!input.isTTY || !output.isTTY) return -1;
|
|
18362
|
+
let selected = 0;
|
|
18363
|
+
let rendered = 0;
|
|
18364
|
+
const draw = () => {
|
|
18365
|
+
if (rendered) output.write("\x1B[" + rendered + "A\x1B[J");
|
|
18366
|
+
const lines2 = fixPollLines(poll, selected, Number(output.columns || 100));
|
|
18367
|
+
output.write(lines2.join("\n") + "\n");
|
|
18368
|
+
rendered = lines2.length;
|
|
18369
|
+
};
|
|
18370
|
+
const choice = await new Promise((resolve9) => {
|
|
18371
|
+
const choices = poll.candidates.length + 1;
|
|
18372
|
+
const wasRaw = Boolean(input.isRaw);
|
|
18373
|
+
const done = (index) => {
|
|
18374
|
+
input.off("data", onData);
|
|
18375
|
+
if (input.setRawMode) input.setRawMode(wasRaw);
|
|
18376
|
+
resolve9(index === poll.candidates.length ? -1 : index);
|
|
18377
|
+
};
|
|
18378
|
+
const onData = (chunk) => {
|
|
18379
|
+
const key2 = chunk.toString("utf8");
|
|
18380
|
+
if (key2 === "") return done(poll.candidates.length);
|
|
18381
|
+
if (key2 === "\x1B" || key2.toLowerCase() === "n") return done(poll.candidates.length);
|
|
18382
|
+
if (key2 === "\r" || key2 === "\n") return done(selected);
|
|
18383
|
+
if (key2 === "\x1B[A" || key2 === "k") selected = (selected - 1 + choices) % choices;
|
|
18384
|
+
else if (key2 === "\x1B[B" || key2 === "j") selected = (selected + 1) % choices;
|
|
18385
|
+
else if (/^[1-9]$/.test(key2)) {
|
|
18386
|
+
const index = Number(key2) - 1;
|
|
18387
|
+
if (index < choices) return done(index);
|
|
18388
|
+
} else return;
|
|
18389
|
+
draw();
|
|
18390
|
+
};
|
|
18391
|
+
if (input.setRawMode) input.setRawMode(true);
|
|
18392
|
+
input.resume();
|
|
18393
|
+
input.on("data", onData);
|
|
18394
|
+
draw();
|
|
18395
|
+
});
|
|
18396
|
+
return choice;
|
|
18397
|
+
}
|
|
18398
|
+
async function resolveFixPolls(events, io = {}) {
|
|
18399
|
+
const ids = Array.from(new Set(events.filter((event) => event.type === "tool-end").map((event) => event.pollId || "").filter(Boolean)));
|
|
18400
|
+
const recorded = [];
|
|
18401
|
+
for (const itemId of ids) {
|
|
18402
|
+
const poll = await loadFixPoll(itemId, io.fetchImpl).catch(() => null);
|
|
18403
|
+
if (!poll) continue;
|
|
18404
|
+
const chosen = await pickFixPoll(poll, io);
|
|
18405
|
+
const ok = await recordFixPoll(itemId, chosen, io.fetchImpl).catch(() => false);
|
|
18406
|
+
if (ok) recorded.push(chosen);
|
|
18407
|
+
else (io.output || process.stdout).write(S2.blocked + " Could not record that Synkro decision.\n" + S2.reset);
|
|
18408
|
+
}
|
|
18409
|
+
return recorded;
|
|
18410
|
+
}
|
|
18411
|
+
var init_fixPoll = __esm({
|
|
18412
|
+
"cli/harness/fixPoll.ts"() {
|
|
17408
18413
|
"use strict";
|
|
17409
|
-
init_auth();
|
|
17410
18414
|
init_render2();
|
|
17411
18415
|
}
|
|
17412
18416
|
});
|
|
17413
18417
|
|
|
17414
18418
|
// cli/harness/session.ts
|
|
17415
|
-
|
|
18419
|
+
function deleteSelectedText(value, range) {
|
|
18420
|
+
if (!range) return value.slice(0, -1);
|
|
18421
|
+
return value.slice(0, range[0]) + value.slice(range[1]);
|
|
18422
|
+
}
|
|
18423
|
+
function handleScrollInput(screen, data) {
|
|
18424
|
+
let handled = false;
|
|
18425
|
+
const mousePattern = /\x1b\[<(\d+);\d+;\d+[Mm]/g;
|
|
18426
|
+
let mouse;
|
|
18427
|
+
while ((mouse = mousePattern.exec(data)) !== null) {
|
|
18428
|
+
const button = Number(mouse[1]);
|
|
18429
|
+
if (button === 64 || button === 65) {
|
|
18430
|
+
screen.scrollBy(button === 64 ? 3 : -3);
|
|
18431
|
+
handled = true;
|
|
18432
|
+
}
|
|
18433
|
+
}
|
|
18434
|
+
if (data.includes("\x1B[5~")) {
|
|
18435
|
+
screen.scrollBy(10);
|
|
18436
|
+
handled = true;
|
|
18437
|
+
}
|
|
18438
|
+
if (data.includes("\x1B[6~")) {
|
|
18439
|
+
screen.scrollBy(-10);
|
|
18440
|
+
handled = true;
|
|
18441
|
+
}
|
|
18442
|
+
return handled;
|
|
18443
|
+
}
|
|
18444
|
+
function printEvent(event) {
|
|
18445
|
+
const lines2 = renderEvent(event, Number(process.stdout.columns || 100));
|
|
18446
|
+
if (lines2.length) process.stdout.write(lines2.join("\n") + "\n");
|
|
18447
|
+
}
|
|
17416
18448
|
async function runOnce(harness, cwd, prompt, echoPrompt = true, showHeader = true, signal) {
|
|
17417
|
-
if (harness
|
|
18449
|
+
if (!supported(harness)) {
|
|
17418
18450
|
process.stdout.write(S2.dim + " " + harness + " sessions are not embedded yet\n" + S2.reset);
|
|
17419
18451
|
return 1;
|
|
17420
18452
|
}
|
|
17421
|
-
const
|
|
17422
|
-
if (
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
);
|
|
18453
|
+
const resumeId = loadSessionId(harness, cwd);
|
|
18454
|
+
if (harness === "cursor") {
|
|
18455
|
+
const result = await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal, resumeId });
|
|
18456
|
+
saveSessionId(harness, cwd, result.sessionId || "");
|
|
18457
|
+
await resolveFixPolls(result.events);
|
|
18458
|
+
return result.exitCode;
|
|
18459
|
+
}
|
|
18460
|
+
const session = new CodexSession(cwd, resumeId);
|
|
18461
|
+
const events = [];
|
|
18462
|
+
try {
|
|
18463
|
+
await session.start();
|
|
18464
|
+
const result = await session.runTurn(prompt, (event) => {
|
|
18465
|
+
events.push(event);
|
|
18466
|
+
printEvent(event);
|
|
18467
|
+
}, signal);
|
|
18468
|
+
saveSessionId(harness, cwd, result.sessionId);
|
|
18469
|
+
await resolveFixPolls(events);
|
|
18470
|
+
return result.exitCode;
|
|
18471
|
+
} finally {
|
|
18472
|
+
session.close();
|
|
17426
18473
|
}
|
|
17427
|
-
return result.exitCode;
|
|
17428
18474
|
}
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
|
|
17439
|
-
|
|
18475
|
+
function readPrompt(screen) {
|
|
18476
|
+
return new Promise((resolve9) => {
|
|
18477
|
+
let value = "";
|
|
18478
|
+
const finish = (answer) => {
|
|
18479
|
+
process.stdin.removeListener("data", onData);
|
|
18480
|
+
screen.setInput("");
|
|
18481
|
+
resolve9(answer);
|
|
18482
|
+
};
|
|
18483
|
+
const onData = (chunk) => {
|
|
18484
|
+
const data = chunk.toString("utf8");
|
|
18485
|
+
const scrolled = handleScrollInput(screen, data);
|
|
18486
|
+
const mousePattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
18487
|
+
let mouse;
|
|
18488
|
+
let sawMouse = false;
|
|
18489
|
+
while ((mouse = mousePattern.exec(data)) !== null) {
|
|
18490
|
+
sawMouse = true;
|
|
18491
|
+
const button = Number(mouse[1]);
|
|
18492
|
+
if (button === 0 || button === 32) {
|
|
18493
|
+
screen.selectInputAt(Number(mouse[2]), Number(mouse[3]), button === 32 || mouse[4] === "m");
|
|
18494
|
+
}
|
|
18495
|
+
}
|
|
18496
|
+
if (sawMouse || scrolled) return;
|
|
18497
|
+
const selection = screen.selectionRange();
|
|
18498
|
+
if (data === "\x1B[3~") {
|
|
18499
|
+
value = deleteSelectedText(value, selection);
|
|
18500
|
+
screen.setInput(value);
|
|
18501
|
+
return;
|
|
18502
|
+
}
|
|
18503
|
+
if (data === "\x1B") {
|
|
18504
|
+
if (selection) {
|
|
18505
|
+
value = deleteSelectedText(value, selection);
|
|
18506
|
+
screen.setInput(value);
|
|
18507
|
+
} else finish(null);
|
|
18508
|
+
return;
|
|
18509
|
+
}
|
|
18510
|
+
if (data.startsWith("\x1B")) return;
|
|
18511
|
+
for (const char of data) {
|
|
18512
|
+
if (char === "" || char === "") {
|
|
18513
|
+
finish(null);
|
|
18514
|
+
return;
|
|
18515
|
+
}
|
|
18516
|
+
if (char === "\r" || char === "\n") {
|
|
18517
|
+
finish(value);
|
|
18518
|
+
return;
|
|
18519
|
+
}
|
|
18520
|
+
if (char === "\x7F" || char === "\b") value = deleteSelectedText(value, screen.selectionRange());
|
|
18521
|
+
else if (char === "") value = "";
|
|
18522
|
+
else if (char === "") value = value.replace(/\s*\S+\s*$/, "");
|
|
18523
|
+
else if (char >= " ") {
|
|
18524
|
+
const range = screen.selectionRange();
|
|
18525
|
+
value = range ? value.slice(0, range[0]) + char + value.slice(range[1]) : value + char;
|
|
18526
|
+
}
|
|
18527
|
+
screen.setInput(value);
|
|
18528
|
+
}
|
|
18529
|
+
};
|
|
18530
|
+
process.stdin.on("data", onData);
|
|
18531
|
+
screen.setInput(value);
|
|
17440
18532
|
});
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
18533
|
+
}
|
|
18534
|
+
async function cancellable(screen, run2) {
|
|
18535
|
+
const controller = new AbortController();
|
|
18536
|
+
const onData = (chunk) => {
|
|
18537
|
+
const text = chunk.toString("utf8");
|
|
18538
|
+
if (handleScrollInput(screen, text)) return;
|
|
18539
|
+
if (text.includes("") || text === "\x1B") {
|
|
18540
|
+
screen.setStatus("Interrupting\u2026");
|
|
18541
|
+
controller.abort();
|
|
17446
18542
|
}
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
18543
|
+
};
|
|
18544
|
+
process.stdin.on("data", onData);
|
|
18545
|
+
try {
|
|
18546
|
+
return await run2(controller.signal);
|
|
18547
|
+
} finally {
|
|
18548
|
+
process.stdin.removeListener("data", onData);
|
|
18549
|
+
}
|
|
18550
|
+
}
|
|
18551
|
+
async function resolveScreenFixPolls(screen, events) {
|
|
18552
|
+
if (!events.some((event) => event.type === "tool-end" && Boolean(event.pollId))) return;
|
|
18553
|
+
screen.leave();
|
|
18554
|
+
try {
|
|
18555
|
+
await resolveFixPolls(events);
|
|
18556
|
+
} finally {
|
|
18557
|
+
screen.enter();
|
|
18558
|
+
}
|
|
18559
|
+
}
|
|
18560
|
+
async function runSynkroSession(harness, cwd, prompt) {
|
|
18561
|
+
if (prompt) return runOnce(harness, cwd, prompt);
|
|
18562
|
+
if (!supported(harness)) return runOnce(harness, cwd, "");
|
|
18563
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
18564
|
+
process.stderr.write("synkro terminal requires a TTY\n");
|
|
18565
|
+
return 1;
|
|
18566
|
+
}
|
|
18567
|
+
const screen = new SynkroScreen(harness, cwd);
|
|
18568
|
+
const resumeId = loadSessionId(harness, cwd);
|
|
18569
|
+
let codex = null;
|
|
18570
|
+
process.stdin.setRawMode(true);
|
|
18571
|
+
process.stdin.resume();
|
|
18572
|
+
screen.enter();
|
|
18573
|
+
try {
|
|
18574
|
+
if (harness === "codex") {
|
|
18575
|
+
screen.setStatus("Connecting to Codex\u2026");
|
|
18576
|
+
codex = new CodexSession(cwd, resumeId);
|
|
18577
|
+
await codex.start();
|
|
18578
|
+
saveSessionId(harness, cwd, codex.threadId);
|
|
18579
|
+
screen.add({ type: "session-start", sessionId: codex.threadId, model: codex.model || "Codex", cwd, authSource: "login" });
|
|
18580
|
+
screen.setStatus("Ready");
|
|
18581
|
+
}
|
|
18582
|
+
for (; ; ) {
|
|
18583
|
+
const answer = await readPrompt(screen);
|
|
18584
|
+
if (answer === null) break;
|
|
18585
|
+
const line = answer.trim();
|
|
18586
|
+
if (!line) continue;
|
|
18587
|
+
if (line === "/exit" || line === "/quit" || line === "exit" || line === "quit") break;
|
|
18588
|
+
screen.setStatus("Working\u2026");
|
|
18589
|
+
const events = [];
|
|
18590
|
+
try {
|
|
18591
|
+
if (harness === "cursor") {
|
|
18592
|
+
const result = await cancellable(screen, (signal) => runCursorTurn({
|
|
18593
|
+
prompt: line,
|
|
18594
|
+
cwd,
|
|
18595
|
+
signal,
|
|
18596
|
+
resumeId: loadSessionId(harness, cwd),
|
|
18597
|
+
write: () => {
|
|
18598
|
+
},
|
|
18599
|
+
onEvent: (event) => {
|
|
18600
|
+
events.push(event);
|
|
18601
|
+
screen.add(event);
|
|
18602
|
+
},
|
|
18603
|
+
showHeader: false
|
|
18604
|
+
}));
|
|
18605
|
+
saveSessionId(harness, cwd, result.sessionId || "");
|
|
18606
|
+
} else {
|
|
18607
|
+
const result = await cancellable(screen, (signal) => codex.runTurn(line, (event) => {
|
|
18608
|
+
events.push(event);
|
|
18609
|
+
screen.add(event);
|
|
18610
|
+
}, signal));
|
|
18611
|
+
saveSessionId(harness, cwd, result.sessionId);
|
|
18612
|
+
}
|
|
18613
|
+
await resolveScreenFixPolls(screen, events);
|
|
18614
|
+
} catch (error) {
|
|
18615
|
+
screen.add({ type: "notice", text: error instanceof Error ? error.message : String(error) });
|
|
18616
|
+
screen.add({ type: "turn-end", ok: false, text: "" });
|
|
18617
|
+
}
|
|
18618
|
+
screen.setStatus("Ready");
|
|
18619
|
+
}
|
|
18620
|
+
return 0;
|
|
18621
|
+
} finally {
|
|
18622
|
+
codex?.close();
|
|
18623
|
+
screen.leave();
|
|
18624
|
+
process.stdin.setRawMode(false);
|
|
18625
|
+
process.stdin.pause();
|
|
17461
18626
|
}
|
|
17462
|
-
rl.close();
|
|
17463
|
-
return 0;
|
|
17464
18627
|
}
|
|
18628
|
+
var supported;
|
|
17465
18629
|
var init_session = __esm({
|
|
17466
18630
|
"cli/harness/session.ts"() {
|
|
17467
18631
|
"use strict";
|
|
18632
|
+
init_codex();
|
|
17468
18633
|
init_run();
|
|
17469
|
-
init_identity2();
|
|
17470
18634
|
init_render2();
|
|
18635
|
+
init_screen();
|
|
18636
|
+
init_state();
|
|
18637
|
+
init_fixPoll();
|
|
18638
|
+
supported = (harness) => harness === "cursor" || harness === "codex";
|
|
17471
18639
|
}
|
|
17472
18640
|
});
|
|
17473
18641
|
|
|
@@ -17489,7 +18657,7 @@ async function takeover(kind, cwd) {
|
|
|
17489
18657
|
const harness = ["claude", "codex", "cursor"].includes(kind) ? kind : "claude";
|
|
17490
18658
|
const info = await detectContainerBackend();
|
|
17491
18659
|
const reachable = info.backend === "container" ? (await run(info.runner, ["test", "-d", cwd])).ok : false;
|
|
17492
|
-
const backend = reachable ? "container" : "host";
|
|
18660
|
+
const backend = harness === "codex" ? "host" : reachable ? "container" : "host";
|
|
17493
18661
|
const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
|
|
17494
18662
|
const spawned = await spawnAgent(info, {
|
|
17495
18663
|
name: spaceName + "-" + harness + "-" + String(process.pid % 1e4),
|
|
@@ -17508,17 +18676,19 @@ async function takeover(kind, cwd) {
|
|
|
17508
18676
|
const runner = backend === "container" ? info.runner : HOST3;
|
|
17509
18677
|
return runInherit(["env", "TMUX=", ...runnerInteractiveArgs(runner, ["tmux", "attach-session", "-t", spawned.session])]);
|
|
17510
18678
|
}
|
|
17511
|
-
async function restoreSession(session) {
|
|
17512
|
-
const
|
|
17513
|
-
if (!
|
|
18679
|
+
async function restoreSession(session, bootPath) {
|
|
18680
|
+
const record2 = loadRecords().find((row2) => row2.session === session);
|
|
18681
|
+
if (!record2) return;
|
|
17514
18682
|
const info = await detectContainerBackend();
|
|
17515
18683
|
await spawnAgent(info, {
|
|
17516
|
-
name:
|
|
17517
|
-
harness:
|
|
17518
|
-
spaceName:
|
|
17519
|
-
cwd:
|
|
17520
|
-
backend:
|
|
17521
|
-
resume:
|
|
18684
|
+
name: record2.name,
|
|
18685
|
+
harness: record2.harness,
|
|
18686
|
+
spaceName: record2.spaceName,
|
|
18687
|
+
cwd: record2.space,
|
|
18688
|
+
backend: record2.mode === "embedded" ? "host" : record2.backend,
|
|
18689
|
+
resume: record2.mode !== "embedded",
|
|
18690
|
+
mode: record2.mode,
|
|
18691
|
+
command: record2.mode === "embedded" ? embeddedSessionCommand(bootPath, record2.harness, record2.space) : void 0
|
|
17522
18692
|
});
|
|
17523
18693
|
}
|
|
17524
18694
|
async function printStatus() {
|
|
@@ -17571,7 +18741,7 @@ async function uiCommand(args2) {
|
|
|
17571
18741
|
const harness = args2[runAt + 1] || "cursor";
|
|
17572
18742
|
const cwd = args2[runAt + 2] || repoRoot();
|
|
17573
18743
|
const prompt = args2.slice(runAt + 3).join(" ").trim();
|
|
17574
|
-
process.exitCode = await
|
|
18744
|
+
process.exitCode = await runSynkroSession(harness, cwd, prompt);
|
|
17575
18745
|
return;
|
|
17576
18746
|
}
|
|
17577
18747
|
const takeoverAt = args2.indexOf("--takeover");
|
|
@@ -17581,7 +18751,7 @@ async function uiCommand(args2) {
|
|
|
17581
18751
|
}
|
|
17582
18752
|
const restoreAt = args2.indexOf("--restore");
|
|
17583
18753
|
if (restoreAt !== -1) {
|
|
17584
|
-
await restoreSession(args2[restoreAt + 1] || "");
|
|
18754
|
+
await restoreSession(args2[restoreAt + 1] || "", bootPath);
|
|
17585
18755
|
return;
|
|
17586
18756
|
}
|
|
17587
18757
|
if (args2.includes("--status")) {
|
|
@@ -17659,12 +18829,12 @@ __export(linear_exports, {
|
|
|
17659
18829
|
formatLinks: () => formatLinks,
|
|
17660
18830
|
linearCommand: () => linearCommand
|
|
17661
18831
|
});
|
|
17662
|
-
import { readFileSync as
|
|
17663
|
-
import { homedir as
|
|
17664
|
-
import { join as
|
|
18832
|
+
import { readFileSync as readFileSync35 } from "fs";
|
|
18833
|
+
import { homedir as homedir40 } from "os";
|
|
18834
|
+
import { join as join39 } from "path";
|
|
17665
18835
|
function mcpJwt() {
|
|
17666
18836
|
try {
|
|
17667
|
-
return
|
|
18837
|
+
return readFileSync35(join39(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
|
|
17668
18838
|
} catch {
|
|
17669
18839
|
return "";
|
|
17670
18840
|
}
|
|
@@ -17703,7 +18873,7 @@ var SYNKRO_DIR14, PORT2, BASE;
|
|
|
17703
18873
|
var init_linear = __esm({
|
|
17704
18874
|
"cli/commands/linear.ts"() {
|
|
17705
18875
|
"use strict";
|
|
17706
|
-
SYNKRO_DIR14 =
|
|
18876
|
+
SYNKRO_DIR14 = join39(homedir40(), ".synkro");
|
|
17707
18877
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
17708
18878
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
17709
18879
|
}
|
|
@@ -17711,13 +18881,13 @@ var init_linear = __esm({
|
|
|
17711
18881
|
|
|
17712
18882
|
// cli/scanning/cveReachability.ts
|
|
17713
18883
|
import { parse } from "@babel/parser";
|
|
17714
|
-
import { readFileSync as
|
|
18884
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
17715
18885
|
function walk(node, visit) {
|
|
17716
18886
|
if (!node || typeof node.type !== "string") return;
|
|
17717
18887
|
visit(node);
|
|
17718
|
-
for (const
|
|
17719
|
-
if (
|
|
17720
|
-
const child = node[
|
|
18888
|
+
for (const key2 of Object.keys(node)) {
|
|
18889
|
+
if (key2 === "loc" || key2 === "start" || key2 === "end" || key2 === "range" || key2 === "leadingComments" || key2 === "trailingComments") continue;
|
|
18890
|
+
const child = node[key2];
|
|
17721
18891
|
if (Array.isArray(child)) {
|
|
17722
18892
|
for (const c of child) if (c && typeof c.type === "string") walk(c, visit);
|
|
17723
18893
|
} else if (child && typeof child.type === "string") walk(child, visit);
|
|
@@ -17852,10 +19022,10 @@ var init_cveReachability = __esm({
|
|
|
17852
19022
|
});
|
|
17853
19023
|
|
|
17854
19024
|
// cli/reachability/reachabilityScan.ts
|
|
17855
|
-
import { spawnSync as
|
|
17856
|
-
import { readFileSync as
|
|
17857
|
-
import { join as
|
|
17858
|
-
import { homedir as
|
|
19025
|
+
import { spawnSync as spawnSync13, execFileSync as execFileSync6 } from "child_process";
|
|
19026
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync29, existsSync as existsSync39, readdirSync as readdirSync10 } from "fs";
|
|
19027
|
+
import { join as join40 } from "path";
|
|
19028
|
+
import { homedir as homedir41 } from "os";
|
|
17859
19029
|
import { createRequire } from "module";
|
|
17860
19030
|
function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
17861
19031
|
const SKIP2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -17872,7 +19042,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
17872
19042
|
}
|
|
17873
19043
|
for (const e of ents) {
|
|
17874
19044
|
if (files.length >= maxFiles) break;
|
|
17875
|
-
const full =
|
|
19045
|
+
const full = join40(dir, e.name);
|
|
17876
19046
|
if (e.isDirectory()) {
|
|
17877
19047
|
if (!SKIP2.has(e.name) && !e.name.startsWith(".")) stack.push(full);
|
|
17878
19048
|
continue;
|
|
@@ -17880,7 +19050,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
17880
19050
|
if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
|
|
17881
19051
|
const rel = full.startsWith(repoRoot3 + "/") ? full.slice(repoRoot3.length + 1) : full;
|
|
17882
19052
|
try {
|
|
17883
|
-
const content =
|
|
19053
|
+
const content = readFileSync37(full, "utf8");
|
|
17884
19054
|
if (content.length <= maxBytes) files.push({ path: rel, content });
|
|
17885
19055
|
} catch {
|
|
17886
19056
|
}
|
|
@@ -17899,12 +19069,12 @@ function cleanVersion(spec) {
|
|
|
17899
19069
|
function gatherManifestVersions(repoRoot3) {
|
|
17900
19070
|
const out = {};
|
|
17901
19071
|
const dirs = [repoRoot3];
|
|
17902
|
-
const pkgsDir =
|
|
17903
|
-
if (
|
|
19072
|
+
const pkgsDir = join40(repoRoot3, "packages");
|
|
19073
|
+
if (existsSync39(pkgsDir)) {
|
|
17904
19074
|
try {
|
|
17905
19075
|
for (const d of readdirSync10(pkgsDir)) {
|
|
17906
|
-
const pd =
|
|
17907
|
-
if (
|
|
19076
|
+
const pd = join40(pkgsDir, d);
|
|
19077
|
+
if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
|
|
17908
19078
|
}
|
|
17909
19079
|
} catch {
|
|
17910
19080
|
}
|
|
@@ -17913,7 +19083,7 @@ function gatherManifestVersions(repoRoot3) {
|
|
|
17913
19083
|
for (const dir of dirs) {
|
|
17914
19084
|
let pkg;
|
|
17915
19085
|
try {
|
|
17916
|
-
pkg = JSON.parse(
|
|
19086
|
+
pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
|
|
17917
19087
|
} catch {
|
|
17918
19088
|
continue;
|
|
17919
19089
|
}
|
|
@@ -17933,28 +19103,28 @@ function findJelly(repoRoot3) {
|
|
|
17933
19103
|
try {
|
|
17934
19104
|
const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
|
|
17935
19105
|
const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
|
|
17936
|
-
const pkg = JSON.parse(
|
|
19106
|
+
const pkg = JSON.parse(readFileSync37(pkgJson, "utf8"));
|
|
17937
19107
|
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
|
|
17938
19108
|
if (bin) {
|
|
17939
|
-
const p =
|
|
17940
|
-
if (
|
|
19109
|
+
const p = join40(dir, bin);
|
|
19110
|
+
if (existsSync39(p)) return p;
|
|
17941
19111
|
}
|
|
17942
19112
|
} catch {
|
|
17943
19113
|
}
|
|
17944
19114
|
for (const base of [repoRoot3, process.cwd()]) {
|
|
17945
|
-
const b =
|
|
17946
|
-
if (
|
|
19115
|
+
const b = join40(base, "node_modules", ".bin", "jelly");
|
|
19116
|
+
if (existsSync39(b)) return b;
|
|
17947
19117
|
}
|
|
17948
19118
|
return null;
|
|
17949
19119
|
}
|
|
17950
19120
|
function findEntries(repoRoot3) {
|
|
17951
19121
|
const dirs = [repoRoot3];
|
|
17952
|
-
const pkgsDir =
|
|
17953
|
-
if (
|
|
19122
|
+
const pkgsDir = join40(repoRoot3, "packages");
|
|
19123
|
+
if (existsSync39(pkgsDir)) {
|
|
17954
19124
|
try {
|
|
17955
19125
|
for (const d of readdirSync10(pkgsDir)) {
|
|
17956
|
-
const pd =
|
|
17957
|
-
if (
|
|
19126
|
+
const pd = join40(pkgsDir, d);
|
|
19127
|
+
if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
|
|
17958
19128
|
}
|
|
17959
19129
|
} catch {
|
|
17960
19130
|
}
|
|
@@ -17962,12 +19132,12 @@ function findEntries(repoRoot3) {
|
|
|
17962
19132
|
const entries = [];
|
|
17963
19133
|
for (const dir of dirs) {
|
|
17964
19134
|
try {
|
|
17965
|
-
const pkg = JSON.parse(
|
|
19135
|
+
const pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
|
|
17966
19136
|
const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
|
|
17967
19137
|
for (const c of cands) {
|
|
17968
19138
|
if (typeof c !== "string") continue;
|
|
17969
|
-
const f =
|
|
17970
|
-
if (
|
|
19139
|
+
const f = join40(dir, c);
|
|
19140
|
+
if (existsSync39(f)) {
|
|
17971
19141
|
entries.push(f);
|
|
17972
19142
|
break;
|
|
17973
19143
|
}
|
|
@@ -18000,9 +19170,9 @@ function parseApiUsage(log) {
|
|
|
18000
19170
|
}
|
|
18001
19171
|
function runReachabilityScan(repoRoot3, opts = {}) {
|
|
18002
19172
|
const commit = currentCommit(repoRoot3);
|
|
18003
|
-
if (!opts.force && commit &&
|
|
19173
|
+
if (!opts.force && commit && existsSync39(REACHABILITY_PATH)) {
|
|
18004
19174
|
try {
|
|
18005
|
-
const prev = JSON.parse(
|
|
19175
|
+
const prev = JSON.parse(readFileSync37(REACHABILITY_PATH, "utf8"));
|
|
18006
19176
|
if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
|
|
18007
19177
|
} catch {
|
|
18008
19178
|
}
|
|
@@ -18053,7 +19223,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
|
|
|
18053
19223
|
if (jelly) {
|
|
18054
19224
|
const entries = findEntries(repoRoot3);
|
|
18055
19225
|
if (entries.length > 0) {
|
|
18056
|
-
const r =
|
|
19226
|
+
const r = spawnSync13(
|
|
18057
19227
|
process.execPath,
|
|
18058
19228
|
[jelly, "-b", repoRoot3, "--api-usage", ...entries],
|
|
18059
19229
|
{ encoding: "utf8", timeout: opts.timeoutMs ?? 18e4, maxBuffer: 2e8 }
|
|
@@ -18091,7 +19261,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
|
|
|
18091
19261
|
if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
|
|
18092
19262
|
const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot3) };
|
|
18093
19263
|
try {
|
|
18094
|
-
|
|
19264
|
+
writeFileSync29(REACHABILITY_PATH, JSON.stringify(file, null, 2));
|
|
18095
19265
|
} catch (e) {
|
|
18096
19266
|
return { ok: false, reason: "write failed: " + String(e.message || e) };
|
|
18097
19267
|
}
|
|
@@ -18103,7 +19273,7 @@ var init_reachabilityScan = __esm({
|
|
|
18103
19273
|
"use strict";
|
|
18104
19274
|
init_cveReachability();
|
|
18105
19275
|
require2 = createRequire(import.meta.url);
|
|
18106
|
-
REACHABILITY_PATH =
|
|
19276
|
+
REACHABILITY_PATH = join40(homedir41(), ".synkro", "reachability.json");
|
|
18107
19277
|
}
|
|
18108
19278
|
});
|
|
18109
19279
|
|
|
@@ -18112,15 +19282,15 @@ var reachabilityScan_exports = {};
|
|
|
18112
19282
|
__export(reachabilityScan_exports, {
|
|
18113
19283
|
reachabilityScanCommand: () => reachabilityScanCommand
|
|
18114
19284
|
});
|
|
18115
|
-
import { readFileSync as
|
|
18116
|
-
import { join as
|
|
18117
|
-
import { homedir as
|
|
19285
|
+
import { readFileSync as readFileSync38, existsSync as existsSync40 } from "fs";
|
|
19286
|
+
import { join as join41 } from "path";
|
|
19287
|
+
import { homedir as homedir42 } from "os";
|
|
18118
19288
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
18119
19289
|
function readConfigEnv4() {
|
|
18120
|
-
const p =
|
|
18121
|
-
if (!
|
|
19290
|
+
const p = join41(SYNKRO_DIR15, "config.env");
|
|
19291
|
+
if (!existsSync40(p)) return {};
|
|
18122
19292
|
const out = {};
|
|
18123
|
-
for (const line of
|
|
19293
|
+
for (const line of readFileSync38(p, "utf-8").split("\n")) {
|
|
18124
19294
|
const t = line.trim();
|
|
18125
19295
|
if (!t || t.startsWith("#")) continue;
|
|
18126
19296
|
const eq = t.indexOf("=");
|
|
@@ -18152,11 +19322,11 @@ async function pushToCloud(cfg, repo) {
|
|
|
18152
19322
|
while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
|
|
18153
19323
|
let jwt2 = "";
|
|
18154
19324
|
try {
|
|
18155
|
-
jwt2 =
|
|
19325
|
+
jwt2 = readFileSync38(join41(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
|
|
18156
19326
|
} catch {
|
|
18157
19327
|
}
|
|
18158
|
-
if (!jwt2 || !
|
|
18159
|
-
const body =
|
|
19328
|
+
if (!jwt2 || !existsSync40(REACHABILITY_PATH)) return;
|
|
19329
|
+
const body = readFileSync38(REACHABILITY_PATH, "utf-8");
|
|
18160
19330
|
try {
|
|
18161
19331
|
const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
|
|
18162
19332
|
method: "POST",
|
|
@@ -18188,7 +19358,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
18188
19358
|
"cli/commands/reachabilityScan.ts"() {
|
|
18189
19359
|
"use strict";
|
|
18190
19360
|
init_reachabilityScan();
|
|
18191
|
-
SYNKRO_DIR15 =
|
|
19361
|
+
SYNKRO_DIR15 = join41(homedir42(), ".synkro");
|
|
18192
19362
|
}
|
|
18193
19363
|
});
|
|
18194
19364
|
|
|
@@ -18318,13 +19488,13 @@ var config_exports = {};
|
|
|
18318
19488
|
__export(config_exports, {
|
|
18319
19489
|
configCommand: () => configCommand
|
|
18320
19490
|
});
|
|
18321
|
-
import { readFileSync as
|
|
18322
|
-
import { join as
|
|
18323
|
-
import { homedir as
|
|
19491
|
+
import { readFileSync as readFileSync39, writeFileSync as writeFileSync30, existsSync as existsSync41 } from "fs";
|
|
19492
|
+
import { join as join42 } from "path";
|
|
19493
|
+
import { homedir as homedir43 } from "os";
|
|
18324
19494
|
function readConfigEnv5() {
|
|
18325
|
-
if (!
|
|
19495
|
+
if (!existsSync41(CONFIG_PATH9)) return {};
|
|
18326
19496
|
const out = {};
|
|
18327
|
-
for (const line of
|
|
19497
|
+
for (const line of readFileSync39(CONFIG_PATH9, "utf-8").split("\n")) {
|
|
18328
19498
|
const t = line.trim();
|
|
18329
19499
|
if (!t || t.startsWith("#")) continue;
|
|
18330
19500
|
const eq = t.indexOf("=");
|
|
@@ -18332,23 +19502,23 @@ function readConfigEnv5() {
|
|
|
18332
19502
|
}
|
|
18333
19503
|
return out;
|
|
18334
19504
|
}
|
|
18335
|
-
function updateConfigValue(
|
|
18336
|
-
if (!
|
|
19505
|
+
function updateConfigValue(key2, value) {
|
|
19506
|
+
if (!existsSync41(CONFIG_PATH9)) {
|
|
18337
19507
|
console.error("No config found. Run `synkro install` first.");
|
|
18338
19508
|
process.exit(1);
|
|
18339
19509
|
}
|
|
18340
|
-
const
|
|
18341
|
-
const pattern = new RegExp(`^${
|
|
19510
|
+
const lines2 = readFileSync39(CONFIG_PATH9, "utf-8").split("\n");
|
|
19511
|
+
const pattern = new RegExp(`^${key2}=`);
|
|
18342
19512
|
let found = false;
|
|
18343
|
-
const updated =
|
|
19513
|
+
const updated = lines2.map((line) => {
|
|
18344
19514
|
if (pattern.test(line.trim())) {
|
|
18345
19515
|
found = true;
|
|
18346
|
-
return `${
|
|
19516
|
+
return `${key2}='${value}'`;
|
|
18347
19517
|
}
|
|
18348
19518
|
return line;
|
|
18349
19519
|
});
|
|
18350
|
-
if (!found) updated.splice(updated.length - 1, 0, `${
|
|
18351
|
-
|
|
19520
|
+
if (!found) updated.splice(updated.length - 1, 0, `${key2}='${value}'`);
|
|
19521
|
+
writeFileSync30(CONFIG_PATH9, updated.join("\n"), "utf-8");
|
|
18352
19522
|
}
|
|
18353
19523
|
function resolveInferenceMode(cfg) {
|
|
18354
19524
|
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
@@ -18506,8 +19676,8 @@ var init_config = __esm({
|
|
|
18506
19676
|
"use strict";
|
|
18507
19677
|
init_stub();
|
|
18508
19678
|
init_optout();
|
|
18509
|
-
SYNKRO_DIR16 =
|
|
18510
|
-
CONFIG_PATH9 =
|
|
19679
|
+
SYNKRO_DIR16 = join42(homedir43(), ".synkro");
|
|
19680
|
+
CONFIG_PATH9 = join42(SYNKRO_DIR16, "config.env");
|
|
18511
19681
|
}
|
|
18512
19682
|
});
|
|
18513
19683
|
|
|
@@ -18516,7 +19686,7 @@ var telemetry_exports2 = {};
|
|
|
18516
19686
|
__export(telemetry_exports2, {
|
|
18517
19687
|
telemetryCommand: () => telemetryCommand
|
|
18518
19688
|
});
|
|
18519
|
-
import { createInterface as
|
|
19689
|
+
import { createInterface as createInterface7 } from "readline";
|
|
18520
19690
|
function parseFlag(args2, name) {
|
|
18521
19691
|
const prefix = `--${name}=`;
|
|
18522
19692
|
for (const a of args2) if (a.startsWith(prefix)) return a.slice(prefix.length);
|
|
@@ -18597,12 +19767,12 @@ async function runExport(args2) {
|
|
|
18597
19767
|
}
|
|
18598
19768
|
function confirmYesNo(question) {
|
|
18599
19769
|
if (!process.stdin.isTTY) return Promise.resolve(false);
|
|
18600
|
-
return new Promise((
|
|
18601
|
-
const rl =
|
|
19770
|
+
return new Promise((resolve9) => {
|
|
19771
|
+
const rl = createInterface7({ input: process.stdin, output: process.stdout });
|
|
18602
19772
|
rl.question(`${question} (y/N): `, (answer) => {
|
|
18603
19773
|
rl.close();
|
|
18604
19774
|
const t = answer.trim().toLowerCase();
|
|
18605
|
-
|
|
19775
|
+
resolve9(t === "y" || t === "yes");
|
|
18606
19776
|
});
|
|
18607
19777
|
});
|
|
18608
19778
|
}
|
|
@@ -18697,11 +19867,11 @@ Usage:
|
|
|
18697
19867
|
|
|
18698
19868
|
// cli/inventory/identity.ts
|
|
18699
19869
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
18700
|
-
import { existsSync as
|
|
18701
|
-
import { homedir as
|
|
18702
|
-
import { dirname as
|
|
19870
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync25, readFileSync as readFileSync40, renameSync as renameSync9, writeFileSync as writeFileSync31 } from "fs";
|
|
19871
|
+
import { homedir as homedir44 } from "os";
|
|
19872
|
+
import { dirname as dirname14, join as join43 } from "path";
|
|
18703
19873
|
function operationalIdentityPath() {
|
|
18704
|
-
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH ||
|
|
19874
|
+
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join43(homedir44(), ".synkro", "installation.json");
|
|
18705
19875
|
}
|
|
18706
19876
|
function validIdentity(value) {
|
|
18707
19877
|
if (!value || typeof value !== "object") return false;
|
|
@@ -18709,17 +19879,17 @@ function validIdentity(value) {
|
|
|
18709
19879
|
return typeof row2.installation_id === "string" && UUID_RE.test(row2.installation_id) && typeof row2.created_at === "string" && Number.isFinite(Date.parse(row2.created_at));
|
|
18710
19880
|
}
|
|
18711
19881
|
function writeIdentity(path, identity) {
|
|
18712
|
-
|
|
19882
|
+
mkdirSync25(dirname14(path), { recursive: true, mode: 448 });
|
|
18713
19883
|
const temp = `${path}.${process.pid}.${randomUUID5()}.tmp`;
|
|
18714
|
-
|
|
19884
|
+
writeFileSync31(temp, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
18715
19885
|
renameSync9(temp, path);
|
|
18716
19886
|
}
|
|
18717
19887
|
function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
18718
19888
|
const prior = cached4.get(path);
|
|
18719
19889
|
if (prior) return prior;
|
|
18720
|
-
if (
|
|
19890
|
+
if (existsSync42(path)) {
|
|
18721
19891
|
try {
|
|
18722
|
-
const parsed = JSON.parse(
|
|
19892
|
+
const parsed = JSON.parse(readFileSync40(path, "utf8"));
|
|
18723
19893
|
if (validIdentity(parsed)) {
|
|
18724
19894
|
cached4.set(path, parsed);
|
|
18725
19895
|
return parsed;
|
|
@@ -18733,7 +19903,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
|
18733
19903
|
return identity;
|
|
18734
19904
|
}
|
|
18735
19905
|
var UUID_RE, cached4;
|
|
18736
|
-
var
|
|
19906
|
+
var init_identity2 = __esm({
|
|
18737
19907
|
"cli/inventory/identity.ts"() {
|
|
18738
19908
|
"use strict";
|
|
18739
19909
|
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -18744,13 +19914,13 @@ var init_identity3 = __esm({
|
|
|
18744
19914
|
// cli/inventory/collector.ts
|
|
18745
19915
|
import { createHash as createHash5 } from "crypto";
|
|
18746
19916
|
import {
|
|
18747
|
-
existsSync as
|
|
18748
|
-
readFileSync as
|
|
19917
|
+
existsSync as existsSync43,
|
|
19918
|
+
readFileSync as readFileSync41,
|
|
18749
19919
|
readdirSync as readdirSync11,
|
|
18750
|
-
statSync as
|
|
19920
|
+
statSync as statSync7
|
|
18751
19921
|
} from "fs";
|
|
18752
|
-
import { arch, homedir as
|
|
18753
|
-
import { basename as
|
|
19922
|
+
import { arch, homedir as homedir45, hostname as hostname2, platform as platform6, release as release2 } from "os";
|
|
19923
|
+
import { basename as basename4, join as join44, relative as relative2, resolve as resolve7 } from "path";
|
|
18754
19924
|
import { fileURLToPath } from "url";
|
|
18755
19925
|
function sha256(value) {
|
|
18756
19926
|
return createHash5("sha256").update(value).digest("hex");
|
|
@@ -18760,15 +19930,15 @@ function pseudonymousHostnameHash(installationId, host) {
|
|
|
18760
19930
|
}
|
|
18761
19931
|
function cliVersion() {
|
|
18762
19932
|
try {
|
|
18763
|
-
return "1.10.
|
|
19933
|
+
return "1.10.9";
|
|
18764
19934
|
} catch {
|
|
18765
19935
|
return "0.0.0";
|
|
18766
19936
|
}
|
|
18767
19937
|
}
|
|
18768
19938
|
function readJson(path) {
|
|
18769
19939
|
try {
|
|
18770
|
-
if (!
|
|
18771
|
-
const parsed = JSON.parse(
|
|
19940
|
+
if (!existsSync43(path)) return null;
|
|
19941
|
+
const parsed = JSON.parse(readFileSync41(path, "utf8"));
|
|
18772
19942
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
18773
19943
|
} catch {
|
|
18774
19944
|
return null;
|
|
@@ -18776,8 +19946,8 @@ function readJson(path) {
|
|
|
18776
19946
|
}
|
|
18777
19947
|
function readText(path) {
|
|
18778
19948
|
try {
|
|
18779
|
-
if (!
|
|
18780
|
-
return
|
|
19949
|
+
if (!existsSync43(path)) return "";
|
|
19950
|
+
return readFileSync41(path, "utf8");
|
|
18781
19951
|
} catch {
|
|
18782
19952
|
return "";
|
|
18783
19953
|
}
|
|
@@ -18797,7 +19967,7 @@ function canonical(raw) {
|
|
|
18797
19967
|
}
|
|
18798
19968
|
function safePackageName(command, args2) {
|
|
18799
19969
|
if (typeof command !== "string" || !command.trim()) return void 0;
|
|
18800
|
-
const runner =
|
|
19970
|
+
const runner = basename4(command.trim()).replace(/\.exe$/i, "");
|
|
18801
19971
|
if (Array.isArray(args2) && ["npx", "bunx", "uvx"].includes(runner)) {
|
|
18802
19972
|
const pkg = args2.find((arg) => typeof arg === "string" && !arg.startsWith("-"));
|
|
18803
19973
|
if (typeof pkg === "string") {
|
|
@@ -18805,7 +19975,7 @@ function safePackageName(command, args2) {
|
|
|
18805
19975
|
if (/^(?:@[a-z0-9_.-]+\/)?[a-z0-9_.-]+(?:@[a-z0-9_.+~-]+)?$/i.test(candidate)) {
|
|
18806
19976
|
return candidate;
|
|
18807
19977
|
}
|
|
18808
|
-
return
|
|
19978
|
+
return basename4(candidate);
|
|
18809
19979
|
}
|
|
18810
19980
|
}
|
|
18811
19981
|
return runner;
|
|
@@ -18863,16 +20033,16 @@ function mcpArtifactsFromJson(harness, config, configScope = "user") {
|
|
|
18863
20033
|
}
|
|
18864
20034
|
function claudeDesktopConfigCandidates(home, targetPlatform) {
|
|
18865
20035
|
if (targetPlatform === "darwin") {
|
|
18866
|
-
return [
|
|
20036
|
+
return [join44(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
|
|
18867
20037
|
}
|
|
18868
20038
|
if (targetPlatform === "linux") {
|
|
18869
20039
|
return [
|
|
18870
|
-
|
|
18871
|
-
|
|
20040
|
+
join44(home, ".config", "Claude", "claude_desktop_config.json"),
|
|
20041
|
+
join44(home, ".config", "claude", "claude_desktop_config.json")
|
|
18872
20042
|
];
|
|
18873
20043
|
}
|
|
18874
20044
|
if (targetPlatform === "win32" && process.env.APPDATA) {
|
|
18875
|
-
return [
|
|
20045
|
+
return [join44(process.env.APPDATA, "Claude", "claude_desktop_config.json")];
|
|
18876
20046
|
}
|
|
18877
20047
|
return [];
|
|
18878
20048
|
}
|
|
@@ -18880,7 +20050,7 @@ function claudeManagedMcpConfigCandidates(targetPlatform) {
|
|
|
18880
20050
|
if (targetPlatform === "darwin") return ["/Library/Application Support/ClaudeCode/managed-mcp.json"];
|
|
18881
20051
|
if (targetPlatform === "linux") return ["/etc/claude-code/managed-mcp.json"];
|
|
18882
20052
|
if (targetPlatform === "win32" && process.env.ProgramFiles) {
|
|
18883
|
-
return [
|
|
20053
|
+
return [join44(process.env.ProgramFiles, "ClaudeCode", "managed-mcp.json")];
|
|
18884
20054
|
}
|
|
18885
20055
|
return [];
|
|
18886
20056
|
}
|
|
@@ -18888,8 +20058,8 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
|
|
|
18888
20058
|
const roots = /* @__PURE__ */ new Set();
|
|
18889
20059
|
const add = (value) => {
|
|
18890
20060
|
if (typeof value !== "string" || !value.trim()) return;
|
|
18891
|
-
const path =
|
|
18892
|
-
if (
|
|
20061
|
+
const path = resolve7(value);
|
|
20062
|
+
if (existsSync43(path)) roots.add(path);
|
|
18893
20063
|
};
|
|
18894
20064
|
add(currentDirectory);
|
|
18895
20065
|
for (const path of explicit) add(path);
|
|
@@ -18900,17 +20070,17 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
|
|
|
18900
20070
|
return [...roots];
|
|
18901
20071
|
}
|
|
18902
20072
|
function cursorWorkspaceStorageCandidates(home, targetPlatform) {
|
|
18903
|
-
if (targetPlatform === "darwin") return [
|
|
18904
|
-
if (targetPlatform === "linux") return [
|
|
20073
|
+
if (targetPlatform === "darwin") return [join44(home, "Library", "Application Support", "Cursor", "User", "workspaceStorage")];
|
|
20074
|
+
if (targetPlatform === "linux") return [join44(home, ".config", "Cursor", "User", "workspaceStorage")];
|
|
18905
20075
|
if (targetPlatform === "win32" && process.env.APPDATA) {
|
|
18906
|
-
return [
|
|
20076
|
+
return [join44(process.env.APPDATA, "Cursor", "User", "workspaceStorage")];
|
|
18907
20077
|
}
|
|
18908
20078
|
return [];
|
|
18909
20079
|
}
|
|
18910
20080
|
function cursorWorkspaceRoots(home, targetPlatform) {
|
|
18911
20081
|
const roots = /* @__PURE__ */ new Set();
|
|
18912
20082
|
for (const storage of cursorWorkspaceStorageCandidates(home, targetPlatform)) {
|
|
18913
|
-
if (!
|
|
20083
|
+
if (!existsSync43(storage)) continue;
|
|
18914
20084
|
let entries = [];
|
|
18915
20085
|
try {
|
|
18916
20086
|
entries = readdirSync11(storage, { withFileTypes: true });
|
|
@@ -18919,12 +20089,12 @@ function cursorWorkspaceRoots(home, targetPlatform) {
|
|
|
18919
20089
|
}
|
|
18920
20090
|
for (const entry of entries) {
|
|
18921
20091
|
if (!entry.isDirectory() || entry.isSymbolicLink?.()) continue;
|
|
18922
|
-
const state = readJson(
|
|
20092
|
+
const state = readJson(join44(storage, entry.name, "workspace.json"));
|
|
18923
20093
|
const raw = state?.folder;
|
|
18924
20094
|
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
18925
20095
|
try {
|
|
18926
20096
|
const path = raw.startsWith("file:") ? fileURLToPath(raw) : raw;
|
|
18927
|
-
if (
|
|
20097
|
+
if (existsSync43(path)) roots.add(resolve7(path));
|
|
18928
20098
|
} catch {
|
|
18929
20099
|
}
|
|
18930
20100
|
}
|
|
@@ -18936,8 +20106,8 @@ function codexMcpArtifacts(content) {
|
|
|
18936
20106
|
const sections = [...content.matchAll(/^\s*\[\s*([^\]]+)\s*\]\s*$/gm)];
|
|
18937
20107
|
for (let index = 0; index < sections.length && artifacts.length < 1e3; index++) {
|
|
18938
20108
|
const section = sections[index];
|
|
18939
|
-
const
|
|
18940
|
-
const root =
|
|
20109
|
+
const key2 = section[1].trim();
|
|
20110
|
+
const root = key2.match(/^mcp_servers\s*\.\s*(?:"((?:[^"\\]|\\.)+)"|'([^']+)'|([A-Za-z0-9_-]+))$/);
|
|
18941
20111
|
if (!root) continue;
|
|
18942
20112
|
let name = root[1] || root[2] || root[3];
|
|
18943
20113
|
if (root[1]) {
|
|
@@ -18950,8 +20120,8 @@ function codexMcpArtifacts(content) {
|
|
|
18950
20120
|
const start = (section.index ?? 0) + section[0].length;
|
|
18951
20121
|
const end = sections[index + 1]?.index ?? content.length;
|
|
18952
20122
|
const block = content.slice(start, end);
|
|
18953
|
-
const stringValue = (
|
|
18954
|
-
const found = block.match(new RegExp(`^\\s*${
|
|
20123
|
+
const stringValue = (key3) => {
|
|
20124
|
+
const found = block.match(new RegExp(`^\\s*${key3}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")`, "m"));
|
|
18955
20125
|
if (!found) return void 0;
|
|
18956
20126
|
try {
|
|
18957
20127
|
return JSON.parse(found[1]);
|
|
@@ -18983,9 +20153,9 @@ function flattenHookEntries(value) {
|
|
|
18983
20153
|
const out = [];
|
|
18984
20154
|
for (const entry of value) {
|
|
18985
20155
|
if (!entry || typeof entry !== "object") continue;
|
|
18986
|
-
const
|
|
18987
|
-
if (typeof
|
|
18988
|
-
if (Array.isArray(
|
|
20156
|
+
const record2 = entry;
|
|
20157
|
+
if (typeof record2.command === "string") out.push(record2);
|
|
20158
|
+
if (Array.isArray(record2.hooks)) out.push(...flattenHookEntries(record2.hooks));
|
|
18989
20159
|
}
|
|
18990
20160
|
return out;
|
|
18991
20161
|
}
|
|
@@ -19000,10 +20170,10 @@ function hookArtifacts(harness, config) {
|
|
|
19000
20170
|
harness,
|
|
19001
20171
|
type: "hook",
|
|
19002
20172
|
canonical_id: `${canonical(event)}:${commandHash.slice(0, 20)}`,
|
|
19003
|
-
display_name: `${event} \xB7 ${
|
|
20173
|
+
display_name: `${event} \xB7 ${basename4(String(entry.command).split(/\s+/)[0] || "hook")}`,
|
|
19004
20174
|
enabled: entry.enabled !== false,
|
|
19005
20175
|
config_scope: "user",
|
|
19006
|
-
package_name:
|
|
20176
|
+
package_name: basename4(String(entry.command).split(/\s+/)[0] || "") || void 0,
|
|
19007
20177
|
config_hash: commandHash,
|
|
19008
20178
|
metadata: { managed, events: [event] }
|
|
19009
20179
|
});
|
|
@@ -19014,11 +20184,11 @@ function hookArtifacts(harness, config) {
|
|
|
19014
20184
|
function parseFrontmatter(content) {
|
|
19015
20185
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
19016
20186
|
if (!match) return {};
|
|
19017
|
-
const value = (
|
|
20187
|
+
const value = (key2) => match[1].match(new RegExp(`^${key2}:\\s*["']?([^"'\\n]+)`, "m"))?.[1]?.trim();
|
|
19018
20188
|
return { name: value("name"), version: value("version") };
|
|
19019
20189
|
}
|
|
19020
20190
|
function skillArtifacts(harness, root) {
|
|
19021
|
-
if (!
|
|
20191
|
+
if (!existsSync43(root)) return [];
|
|
19022
20192
|
const manifests = [];
|
|
19023
20193
|
const visit = (dir) => {
|
|
19024
20194
|
let entries;
|
|
@@ -19029,7 +20199,7 @@ function skillArtifacts(harness, root) {
|
|
|
19029
20199
|
}
|
|
19030
20200
|
for (const entry of entries) {
|
|
19031
20201
|
if (entry.isSymbolicLink?.()) continue;
|
|
19032
|
-
const path =
|
|
20202
|
+
const path = join44(dir, entry.name);
|
|
19033
20203
|
if (entry.isFile() && entry.name === "SKILL.md") manifests.push(path);
|
|
19034
20204
|
else if (entry.isDirectory()) visit(path);
|
|
19035
20205
|
}
|
|
@@ -19038,8 +20208,8 @@ function skillArtifacts(harness, root) {
|
|
|
19038
20208
|
return manifests.map((path) => {
|
|
19039
20209
|
const content = readText(path);
|
|
19040
20210
|
const frontmatter = parseFrontmatter(content);
|
|
19041
|
-
const rel =
|
|
19042
|
-
const name = frontmatter.name ||
|
|
20211
|
+
const rel = relative2(root, path).replaceAll("\\", "/");
|
|
20212
|
+
const name = frontmatter.name || basename4(join44(path, "..")) || "skill";
|
|
19043
20213
|
return {
|
|
19044
20214
|
harness,
|
|
19045
20215
|
type: "skill",
|
|
@@ -19054,7 +20224,7 @@ function skillArtifacts(harness, root) {
|
|
|
19054
20224
|
});
|
|
19055
20225
|
}
|
|
19056
20226
|
function cursorExtensionArtifacts(root) {
|
|
19057
|
-
if (!
|
|
20227
|
+
if (!existsSync43(root)) return [];
|
|
19058
20228
|
let dirs = [];
|
|
19059
20229
|
try {
|
|
19060
20230
|
dirs = readdirSync11(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.isSymbolicLink());
|
|
@@ -19063,7 +20233,7 @@ function cursorExtensionArtifacts(root) {
|
|
|
19063
20233
|
}
|
|
19064
20234
|
const artifacts = [];
|
|
19065
20235
|
for (const dir of dirs) {
|
|
19066
|
-
const pkg = readJson(
|
|
20236
|
+
const pkg = readJson(join44(root, dir.name, "package.json"));
|
|
19067
20237
|
if (!pkg) continue;
|
|
19068
20238
|
const publisher = typeof pkg.publisher === "string" ? pkg.publisher : void 0;
|
|
19069
20239
|
const name = typeof pkg.name === "string" ? pkg.name : dir.name;
|
|
@@ -19083,21 +20253,21 @@ function cursorExtensionArtifacts(root) {
|
|
|
19083
20253
|
return artifacts;
|
|
19084
20254
|
}
|
|
19085
20255
|
function deploymentMode2(home) {
|
|
19086
|
-
const raw = readText(
|
|
19087
|
-
const value = (
|
|
20256
|
+
const raw = readText(join44(home, ".synkro", "config.env"));
|
|
20257
|
+
const value = (key2) => raw.match(new RegExp(`^${key2}=['"]?([^'"\\n]*)`, "m"))?.[1]?.toLowerCase();
|
|
19088
20258
|
if (value("SYNKRO_GRADING_MODE") === "byok") return "byok";
|
|
19089
20259
|
if (value("SYNKRO_STORAGE_MODE") === "cloud") return "cloud";
|
|
19090
20260
|
return "local";
|
|
19091
20261
|
}
|
|
19092
20262
|
function telemetryHealth(home) {
|
|
19093
|
-
const meta = readJson(
|
|
20263
|
+
const meta = readJson(join44(home, ".synkro", "telemetry-meta.json"));
|
|
19094
20264
|
const health = {};
|
|
19095
20265
|
if (meta?.last_flush_ok_at && Number.isFinite(Date.parse(meta.last_flush_ok_at))) health.telemetry_last_flush_at = meta.last_flush_ok_at;
|
|
19096
20266
|
if (meta?.last_flush_error) health.telemetry_last_error = "flush_failed";
|
|
19097
|
-
const queue =
|
|
20267
|
+
const queue = join44(home, ".synkro", "telemetry-pending.jsonl");
|
|
19098
20268
|
try {
|
|
19099
|
-
const size =
|
|
19100
|
-
health.telemetry_backlog = size <= 5 * 1024 * 1024 ?
|
|
20269
|
+
const size = statSync7(queue).size;
|
|
20270
|
+
health.telemetry_backlog = size <= 5 * 1024 * 1024 ? readFileSync41(queue, "utf8").split("\n").filter(Boolean).length : Math.ceil(size / 1024);
|
|
19101
20271
|
} catch {
|
|
19102
20272
|
}
|
|
19103
20273
|
return health;
|
|
@@ -19138,7 +20308,7 @@ function harnessSnapshot(agent) {
|
|
|
19138
20308
|
}
|
|
19139
20309
|
const config = readJson(agent.settingsPath);
|
|
19140
20310
|
const coverage = inspectCodexHooks(agent.settingsPath);
|
|
19141
|
-
const toml = readText(
|
|
20311
|
+
const toml = readText(join44(agent.configDir, "config.toml"));
|
|
19142
20312
|
const permission = toml.match(/^\s*approval_policy\s*=\s*["']([^"']+)/m)?.[1];
|
|
19143
20313
|
return {
|
|
19144
20314
|
row: {
|
|
@@ -19155,11 +20325,11 @@ function harnessSnapshot(agent) {
|
|
|
19155
20325
|
};
|
|
19156
20326
|
}
|
|
19157
20327
|
function collectOperationalInventory(options = {}) {
|
|
19158
|
-
const home = options.homeDir ??
|
|
20328
|
+
const home = options.homeDir ?? homedir45();
|
|
19159
20329
|
const detected = options.detectedAgents ?? detectAgents();
|
|
19160
20330
|
const identity = getOperationalInstallationIdentity(options.identityPath);
|
|
19161
20331
|
const targetPlatform = options.platformName ?? platform6();
|
|
19162
|
-
const codexHome = options.homeDir ?
|
|
20332
|
+
const codexHome = options.homeDir ? join44(home, ".codex") : process.env.CODEX_HOME || join44(home, ".codex");
|
|
19163
20333
|
const harnesses = [];
|
|
19164
20334
|
const artifacts = [];
|
|
19165
20335
|
for (const agent of detected) {
|
|
@@ -19167,7 +20337,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
19167
20337
|
harnesses.push(row2);
|
|
19168
20338
|
artifacts.push(...hookArtifacts(row2.harness, config));
|
|
19169
20339
|
}
|
|
19170
|
-
const claudeJson = readJson(
|
|
20340
|
+
const claudeJson = readJson(join44(home, ".claude.json"));
|
|
19171
20341
|
artifacts.push(...mcpArtifactsFromJson("claude_code", claudeJson));
|
|
19172
20342
|
if (claudeJson?.projects && typeof claudeJson.projects === "object") {
|
|
19173
20343
|
for (const [projectPath, project] of Object.entries(claudeJson.projects)) {
|
|
@@ -19175,8 +20345,8 @@ function collectOperationalInventory(options = {}) {
|
|
|
19175
20345
|
artifacts.push(...mcpArtifactsFromJson("claude_code", project, `local:${sha256(projectPath).slice(0, 16)}`));
|
|
19176
20346
|
}
|
|
19177
20347
|
}
|
|
19178
|
-
artifacts.push(...mcpArtifactsFromJson("cursor", readJson(
|
|
19179
|
-
artifacts.push(...codexMcpArtifacts(readText(
|
|
20348
|
+
artifacts.push(...mcpArtifactsFromJson("cursor", readJson(join44(home, ".cursor", "mcp.json"))));
|
|
20349
|
+
artifacts.push(...codexMcpArtifacts(readText(join44(codexHome, "config.toml"))));
|
|
19180
20350
|
const projectRoots = discoveredProjectRoots(
|
|
19181
20351
|
claudeJson,
|
|
19182
20352
|
options.currentDirectory ?? process.cwd(),
|
|
@@ -19187,11 +20357,11 @@ function collectOperationalInventory(options = {}) {
|
|
|
19187
20357
|
const scopeHash = sha256(projectRoot).slice(0, 16);
|
|
19188
20358
|
artifacts.push(...mcpArtifactsFromJson(
|
|
19189
20359
|
"claude_code",
|
|
19190
|
-
readJson(
|
|
20360
|
+
readJson(join44(projectRoot, ".mcp.json")),
|
|
19191
20361
|
`project:${scopeHash}`
|
|
19192
20362
|
));
|
|
19193
|
-
const cursorProjectConfig =
|
|
19194
|
-
if (
|
|
20363
|
+
const cursorProjectConfig = join44(projectRoot, ".cursor", "mcp.json");
|
|
20364
|
+
if (resolve7(cursorProjectConfig) !== resolve7(join44(home, ".cursor", "mcp.json"))) {
|
|
19195
20365
|
artifacts.push(...mcpArtifactsFromJson(
|
|
19196
20366
|
"cursor",
|
|
19197
20367
|
readJson(cursorProjectConfig),
|
|
@@ -19202,7 +20372,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
19202
20372
|
for (const managedPath of claudeManagedMcpConfigCandidates(targetPlatform)) {
|
|
19203
20373
|
artifacts.push(...mcpArtifactsFromJson("claude_code", readJson(managedPath), "managed"));
|
|
19204
20374
|
}
|
|
19205
|
-
const desktopConfigPath = claudeDesktopConfigCandidates(home, targetPlatform).find((path) =>
|
|
20375
|
+
const desktopConfigPath = claudeDesktopConfigCandidates(home, targetPlatform).find((path) => existsSync43(path));
|
|
19206
20376
|
if (desktopConfigPath) {
|
|
19207
20377
|
const desktopConfig = readJson(desktopConfigPath);
|
|
19208
20378
|
harnesses.push({
|
|
@@ -19213,7 +20383,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
19213
20383
|
});
|
|
19214
20384
|
artifacts.push(...mcpArtifactsFromJson("claude_desktop", desktopConfig));
|
|
19215
20385
|
}
|
|
19216
|
-
const claudeSettings = readJson(
|
|
20386
|
+
const claudeSettings = readJson(join44(home, ".claude", "settings.json"));
|
|
19217
20387
|
if (claudeSettings?.enabledPlugins && typeof claudeSettings.enabledPlugins === "object") {
|
|
19218
20388
|
for (const [name, enabled] of Object.entries(claudeSettings.enabledPlugins)) {
|
|
19219
20389
|
artifacts.push({
|
|
@@ -19227,14 +20397,14 @@ function collectOperationalInventory(options = {}) {
|
|
|
19227
20397
|
});
|
|
19228
20398
|
}
|
|
19229
20399
|
}
|
|
19230
|
-
artifacts.push(...skillArtifacts("claude_code",
|
|
19231
|
-
artifacts.push(...skillArtifacts("cursor",
|
|
19232
|
-
artifacts.push(...skillArtifacts("codex",
|
|
19233
|
-
artifacts.push(...cursorExtensionArtifacts(
|
|
20400
|
+
artifacts.push(...skillArtifacts("claude_code", join44(home, ".claude", "skills")));
|
|
20401
|
+
artifacts.push(...skillArtifacts("cursor", join44(home, ".cursor", "skills")));
|
|
20402
|
+
artifacts.push(...skillArtifacts("codex", join44(codexHome, "skills")));
|
|
20403
|
+
artifacts.push(...cursorExtensionArtifacts(join44(home, ".cursor", "extensions")));
|
|
19234
20404
|
const uniqueArtifacts = /* @__PURE__ */ new Map();
|
|
19235
20405
|
for (const artifact of artifacts) {
|
|
19236
|
-
const
|
|
19237
|
-
uniqueArtifacts.set(
|
|
20406
|
+
const key2 = `${artifact.harness || "global"}:${artifact.type}:${artifact.canonical_id}`;
|
|
20407
|
+
uniqueArtifacts.set(key2, artifact);
|
|
19238
20408
|
}
|
|
19239
20409
|
const codingHarnesses = harnesses.filter((row2) => row2.harness === "claude_code" || row2.harness === "cursor" || row2.harness === "codex");
|
|
19240
20410
|
const health = telemetryHealth(home) ?? {};
|
|
@@ -19267,7 +20437,7 @@ var init_collector = __esm({
|
|
|
19267
20437
|
init_ccHookConfig();
|
|
19268
20438
|
init_cursorHookConfig();
|
|
19269
20439
|
init_codexHookConfig();
|
|
19270
|
-
|
|
20440
|
+
init_identity2();
|
|
19271
20441
|
}
|
|
19272
20442
|
});
|
|
19273
20443
|
|
|
@@ -19283,22 +20453,22 @@ __export(sync_exports2, {
|
|
|
19283
20453
|
syncOperationalInventoryDetached: () => syncOperationalInventoryDetached
|
|
19284
20454
|
});
|
|
19285
20455
|
import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
|
|
19286
|
-
import { spawn as
|
|
20456
|
+
import { spawn as spawn11 } from "child_process";
|
|
19287
20457
|
import {
|
|
19288
|
-
existsSync as
|
|
19289
|
-
mkdirSync as
|
|
19290
|
-
readFileSync as
|
|
20458
|
+
existsSync as existsSync44,
|
|
20459
|
+
mkdirSync as mkdirSync26,
|
|
20460
|
+
readFileSync as readFileSync42,
|
|
19291
20461
|
renameSync as renameSync10,
|
|
19292
|
-
writeFileSync as
|
|
20462
|
+
writeFileSync as writeFileSync32
|
|
19293
20463
|
} from "fs";
|
|
19294
|
-
import { homedir as
|
|
19295
|
-
import { dirname as
|
|
20464
|
+
import { homedir as homedir46 } from "os";
|
|
20465
|
+
import { dirname as dirname15, join as join45 } from "path";
|
|
19296
20466
|
function syncStatePath() {
|
|
19297
|
-
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH ||
|
|
20467
|
+
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join45(homedir46(), ".synkro", "inventory-sync.json");
|
|
19298
20468
|
}
|
|
19299
20469
|
function readState(path = syncStatePath()) {
|
|
19300
20470
|
try {
|
|
19301
|
-
const parsed = JSON.parse(
|
|
20471
|
+
const parsed = JSON.parse(readFileSync42(path, "utf8"));
|
|
19302
20472
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
19303
20473
|
} catch {
|
|
19304
20474
|
return {};
|
|
@@ -19306,9 +20476,9 @@ function readState(path = syncStatePath()) {
|
|
|
19306
20476
|
}
|
|
19307
20477
|
function writeState(state, path = syncStatePath()) {
|
|
19308
20478
|
try {
|
|
19309
|
-
|
|
20479
|
+
mkdirSync26(dirname15(path), { recursive: true, mode: 448 });
|
|
19310
20480
|
const temp = `${path}.${process.pid}.tmp`;
|
|
19311
|
-
|
|
20481
|
+
writeFileSync32(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
19312
20482
|
renameSync10(temp, path);
|
|
19313
20483
|
} catch {
|
|
19314
20484
|
}
|
|
@@ -19321,18 +20491,18 @@ function shouldSyncInventory(state, now = Date.now(), target) {
|
|
|
19321
20491
|
return !Number.isFinite(lastAttempt) || lastAttempt <= 0 || now - lastAttempt >= FAILURE_RETRY_MS;
|
|
19322
20492
|
}
|
|
19323
20493
|
function readConfig() {
|
|
19324
|
-
const path =
|
|
20494
|
+
const path = join45(homedir46(), ".synkro", "config.env");
|
|
19325
20495
|
const out = {};
|
|
19326
20496
|
try {
|
|
19327
|
-
for (const rawLine of
|
|
20497
|
+
for (const rawLine of readFileSync42(path, "utf8").split("\n")) {
|
|
19328
20498
|
const line = rawLine.trim();
|
|
19329
20499
|
if (!line || line.startsWith("#")) continue;
|
|
19330
20500
|
const index = line.indexOf("=");
|
|
19331
20501
|
if (index <= 0) continue;
|
|
19332
|
-
const
|
|
20502
|
+
const key2 = line.slice(0, index).trim();
|
|
19333
20503
|
let value = line.slice(index + 1).trim();
|
|
19334
20504
|
if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
|
|
19335
|
-
out[
|
|
20505
|
+
out[key2] = value;
|
|
19336
20506
|
}
|
|
19337
20507
|
} catch {
|
|
19338
20508
|
}
|
|
@@ -19363,7 +20533,7 @@ function resolveInventoryGateway(raw) {
|
|
|
19363
20533
|
}
|
|
19364
20534
|
async function loadToken() {
|
|
19365
20535
|
try {
|
|
19366
|
-
const durable =
|
|
20536
|
+
const durable = readFileSync42(join45(homedir46(), ".synkro", ".mcp-jwt"), "utf8").trim();
|
|
19367
20537
|
if (durable) return durable;
|
|
19368
20538
|
} catch {
|
|
19369
20539
|
}
|
|
@@ -19379,7 +20549,7 @@ async function loadToken() {
|
|
|
19379
20549
|
function stable(value) {
|
|
19380
20550
|
if (Array.isArray(value)) return value.map(stable);
|
|
19381
20551
|
if (!value || typeof value !== "object") return value;
|
|
19382
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([
|
|
20552
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key2, child]) => [key2, stable(child)]));
|
|
19383
20553
|
}
|
|
19384
20554
|
function inventorySnapshotChunks(snapshot, maxBytes = INVENTORY_CHUNK_BYTES) {
|
|
19385
20555
|
const { collected_at: _heartbeat, ...material } = snapshot;
|
|
@@ -19481,8 +20651,8 @@ function syncOperationalInventoryDetached() {
|
|
|
19481
20651
|
writeState({ ...state, last_attempt_at: (/* @__PURE__ */ new Date()).toISOString(), last_target: target }, path);
|
|
19482
20652
|
try {
|
|
19483
20653
|
const script = process.argv[1];
|
|
19484
|
-
if (!script || !
|
|
19485
|
-
const child =
|
|
20654
|
+
if (!script || !existsSync44(script)) return;
|
|
20655
|
+
const child = spawn11(process.execPath, [script, "inventory-sync", "--detached"], {
|
|
19486
20656
|
detached: true,
|
|
19487
20657
|
stdio: "ignore",
|
|
19488
20658
|
env: { ...process.env, SYNKRO_INVENTORY_DETACHED: "1" }
|
|
@@ -19505,23 +20675,23 @@ var init_sync2 = __esm({
|
|
|
19505
20675
|
});
|
|
19506
20676
|
|
|
19507
20677
|
// cli/bootstrap.js
|
|
19508
|
-
import { readFileSync as
|
|
19509
|
-
import { resolve as
|
|
20678
|
+
import { readFileSync as readFileSync43, existsSync as existsSync45 } from "fs";
|
|
20679
|
+
import { resolve as resolve8 } from "path";
|
|
19510
20680
|
process.title = "synkro";
|
|
19511
20681
|
var envCandidates = [
|
|
19512
|
-
|
|
20682
|
+
resolve8(process.env.HOME ?? "", ".synkro", "config.env")
|
|
19513
20683
|
];
|
|
19514
20684
|
for (const envPath of envCandidates) {
|
|
19515
|
-
if (!
|
|
19516
|
-
const envContent =
|
|
20685
|
+
if (!existsSync45(envPath)) continue;
|
|
20686
|
+
const envContent = readFileSync43(envPath, "utf-8");
|
|
19517
20687
|
for (const line of envContent.split("\n")) {
|
|
19518
20688
|
const trimmed = line.trim();
|
|
19519
20689
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
19520
20690
|
const eqIndex = trimmed.indexOf("=");
|
|
19521
20691
|
if (eqIndex <= 0) continue;
|
|
19522
|
-
const
|
|
20692
|
+
const key2 = trimmed.slice(0, eqIndex).trim();
|
|
19523
20693
|
const value = trimmed.slice(eqIndex + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
19524
|
-
if (!process.env[
|
|
20694
|
+
if (!process.env[key2] && !value.startsWith("op://")) process.env[key2] = value;
|
|
19525
20695
|
}
|
|
19526
20696
|
}
|
|
19527
20697
|
var args = process.argv.slice(2);
|
|
@@ -19530,7 +20700,7 @@ var subArgs = args.slice(1);
|
|
|
19530
20700
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
19531
20701
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
19532
20702
|
function printVersion() {
|
|
19533
|
-
console.log("1.10.
|
|
20703
|
+
console.log("1.10.9");
|
|
19534
20704
|
}
|
|
19535
20705
|
function printHelp2() {
|
|
19536
20706
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|