@synkro-sh/cli 1.10.8 → 1.10.10
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 +1778 -1202
- package/dist/bootstrap.js.map +1 -1
- package/package.json +18 -19
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.10";
|
|
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 {
|
|
@@ -1645,10 +1645,9 @@ function installCCHooks(settingsPath, config) {
|
|
|
1645
1645
|
{
|
|
1646
1646
|
type: "command",
|
|
1647
1647
|
command: config.cvePrecheckScriptPath,
|
|
1648
|
-
//
|
|
1649
|
-
//
|
|
1650
|
-
|
|
1651
|
-
timeout: 15
|
|
1648
|
+
// Only a confirmed vulnerable runtime dependency reaches VEX triage. Codex can
|
|
1649
|
+
// take 13-17s for a valid answer, so match the bounded 25s gate plus its margin.
|
|
1650
|
+
timeout: 30
|
|
1652
1651
|
},
|
|
1653
1652
|
// skill-judge — its OWN system-message block, parallel with edit-precheck.
|
|
1654
1653
|
...config.skillJudgeScriptPath ? [{
|
|
@@ -1856,21 +1855,21 @@ function validateHooksPath(path) {
|
|
|
1856
1855
|
return resolved;
|
|
1857
1856
|
}
|
|
1858
1857
|
function readHooksFile(rawPath) {
|
|
1859
|
-
const
|
|
1858
|
+
const safePath2 = validateHooksPath(rawPath);
|
|
1860
1859
|
try {
|
|
1861
|
-
const raw = readFileSync10(
|
|
1860
|
+
const raw = readFileSync10(safePath2, "utf-8");
|
|
1862
1861
|
return JSON.parse(raw);
|
|
1863
1862
|
} catch (err) {
|
|
1864
1863
|
if (err?.code === "ENOENT") return { version: 1, hooks: {} };
|
|
1865
|
-
throw new Error(`Failed to parse ${
|
|
1864
|
+
throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
|
|
1866
1865
|
}
|
|
1867
1866
|
}
|
|
1868
1867
|
function writeHooksFileAtomic(rawPath, data) {
|
|
1869
|
-
const
|
|
1870
|
-
mkdirSync5(dirname2(
|
|
1871
|
-
const tmpPath = `${
|
|
1868
|
+
const safePath2 = validateHooksPath(rawPath);
|
|
1869
|
+
mkdirSync5(dirname2(safePath2), { recursive: true });
|
|
1870
|
+
const tmpPath = `${safePath2}.synkro.tmp`;
|
|
1872
1871
|
writeFileSync7(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
1873
|
-
renameSync4(tmpPath,
|
|
1872
|
+
renameSync4(tmpPath, safePath2);
|
|
1874
1873
|
}
|
|
1875
1874
|
function isSynkroEntry2(entry) {
|
|
1876
1875
|
if (entry?.[SYNKRO_MARKER2]) return true;
|
|
@@ -1972,7 +1971,7 @@ function installCursorHooks(hooksJsonPath, config) {
|
|
|
1972
1971
|
matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch"
|
|
1973
1972
|
});
|
|
1974
1973
|
pushCcHook(h, "preToolUse", config.cvePrecheckScriptPath, {
|
|
1975
|
-
timeout:
|
|
1974
|
+
timeout: 30,
|
|
1976
1975
|
matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch"
|
|
1977
1976
|
});
|
|
1978
1977
|
pushCcHook(h, "preToolUse", config.agentJudgeScriptPath, {
|
|
@@ -2158,20 +2157,20 @@ function validateHooksPath2(path) {
|
|
|
2158
2157
|
return resolved;
|
|
2159
2158
|
}
|
|
2160
2159
|
function readHooksFile2(rawPath) {
|
|
2161
|
-
const
|
|
2162
|
-
if (!existsSync12(
|
|
2160
|
+
const safePath2 = validateHooksPath2(rawPath);
|
|
2161
|
+
if (!existsSync12(safePath2)) return { hooks: {} };
|
|
2163
2162
|
try {
|
|
2164
|
-
return JSON.parse(readFileSync11(
|
|
2163
|
+
return JSON.parse(readFileSync11(safePath2, "utf-8"));
|
|
2165
2164
|
} catch (err) {
|
|
2166
|
-
throw new Error(`Failed to parse ${
|
|
2165
|
+
throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
|
|
2167
2166
|
}
|
|
2168
2167
|
}
|
|
2169
2168
|
function writeHooksFileAtomic2(rawPath, data) {
|
|
2170
|
-
const
|
|
2171
|
-
mkdirSync6(dirname3(
|
|
2172
|
-
const tmpPath = `${
|
|
2169
|
+
const safePath2 = validateHooksPath2(rawPath);
|
|
2170
|
+
mkdirSync6(dirname3(safePath2), { recursive: true });
|
|
2171
|
+
const tmpPath = `${safePath2}.synkro.tmp`;
|
|
2173
2172
|
writeFileSync8(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
2174
|
-
renameSync5(tmpPath,
|
|
2173
|
+
renameSync5(tmpPath, safePath2);
|
|
2175
2174
|
}
|
|
2176
2175
|
function isSynkroEntry3(entry) {
|
|
2177
2176
|
if (entry?.[SYNKRO_MARKER3]) return true;
|
|
@@ -2204,7 +2203,7 @@ function installCodexHooks(hooksJsonPath, config) {
|
|
|
2204
2203
|
push(h, "PreToolUse", [
|
|
2205
2204
|
cmd(config.editPrecheckScriptPath, 50, "Reviewing edit"),
|
|
2206
2205
|
cmd(config.cwePrecheckScriptPath, 50),
|
|
2207
|
-
cmd(config.cvePrecheckScriptPath,
|
|
2206
|
+
cmd(config.cvePrecheckScriptPath, 30),
|
|
2208
2207
|
...config.skillJudgeScriptPath ? [cmd(config.skillJudgeScriptPath, 50)] : []
|
|
2209
2208
|
], M_EDIT);
|
|
2210
2209
|
push(h, "PostToolUse", [
|
|
@@ -2351,7 +2350,7 @@ function buildCodexHookTrustEdits(hooks) {
|
|
|
2351
2350
|
return [...edits.values()];
|
|
2352
2351
|
}
|
|
2353
2352
|
function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTrust = false) {
|
|
2354
|
-
return new Promise((
|
|
2353
|
+
return new Promise((resolve9) => {
|
|
2355
2354
|
let settled = false;
|
|
2356
2355
|
let stdout = "";
|
|
2357
2356
|
let pending = "";
|
|
@@ -2369,7 +2368,7 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
|
|
|
2369
2368
|
child.kill();
|
|
2370
2369
|
} catch {
|
|
2371
2370
|
}
|
|
2372
|
-
|
|
2371
|
+
resolve9(summary);
|
|
2373
2372
|
};
|
|
2374
2373
|
const timer = setTimeout(() => finish(null), 1e4);
|
|
2375
2374
|
try {
|
|
@@ -2384,9 +2383,9 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
|
|
|
2384
2383
|
child.stdout.on("data", (chunk) => {
|
|
2385
2384
|
stdout += chunk;
|
|
2386
2385
|
pending += chunk;
|
|
2387
|
-
const
|
|
2388
|
-
pending =
|
|
2389
|
-
for (const line of
|
|
2386
|
+
const lines2 = pending.split("\n");
|
|
2387
|
+
pending = lines2.pop() || "";
|
|
2388
|
+
for (const line of lines2) {
|
|
2390
2389
|
let message;
|
|
2391
2390
|
try {
|
|
2392
2391
|
message = JSON.parse(line);
|
|
@@ -2620,11 +2619,11 @@ function writeCodexTomlAtomic(path, content) {
|
|
|
2620
2619
|
renameSync6(tmpPath, path);
|
|
2621
2620
|
}
|
|
2622
2621
|
function removeCodexManagedBlock(content, path) {
|
|
2623
|
-
const
|
|
2622
|
+
const lines2 = content.split("\n");
|
|
2624
2623
|
const out = [];
|
|
2625
2624
|
let removed = false;
|
|
2626
2625
|
let inside = false;
|
|
2627
|
-
for (const line of
|
|
2626
|
+
for (const line of lines2) {
|
|
2628
2627
|
if (!inside && line.trim() === CODEX_MCP_BEGIN) {
|
|
2629
2628
|
inside = true;
|
|
2630
2629
|
removed = true;
|
|
@@ -2652,8 +2651,8 @@ function findCodexMcpSection(content) {
|
|
|
2652
2651
|
body: content.slice(match.index, next?.index ?? content.length)
|
|
2653
2652
|
};
|
|
2654
2653
|
}
|
|
2655
|
-
function readTomlString(block,
|
|
2656
|
-
const match = block.match(new RegExp(`^\\s*${
|
|
2654
|
+
function readTomlString(block, key2) {
|
|
2655
|
+
const match = block.match(new RegExp(`^\\s*${key2}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")\\s*$`, "m"));
|
|
2657
2656
|
if (!match) return null;
|
|
2658
2657
|
try {
|
|
2659
2658
|
return JSON.parse(match[1]);
|
|
@@ -5181,7 +5180,7 @@ function createCallbackServer() {
|
|
|
5181
5180
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
5182
5181
|
"Vary": "Origin"
|
|
5183
5182
|
};
|
|
5184
|
-
return new Promise((
|
|
5183
|
+
return new Promise((resolve9, reject) => {
|
|
5185
5184
|
const server = createServer((req, res) => {
|
|
5186
5185
|
if (req.method === "OPTIONS") {
|
|
5187
5186
|
const origin = req.headers.origin;
|
|
@@ -5270,7 +5269,7 @@ function createCallbackServer() {
|
|
|
5270
5269
|
res.end(JSON.stringify({ ok: true }));
|
|
5271
5270
|
setTimeout(() => {
|
|
5272
5271
|
server.close();
|
|
5273
|
-
|
|
5272
|
+
resolve9(authData);
|
|
5274
5273
|
}, 200);
|
|
5275
5274
|
});
|
|
5276
5275
|
req.on("error", (e) => {
|
|
@@ -5606,7 +5605,7 @@ function detectSubdirRepos() {
|
|
|
5606
5605
|
}
|
|
5607
5606
|
}
|
|
5608
5607
|
function ask(rl, question) {
|
|
5609
|
-
return new Promise((
|
|
5608
|
+
return new Promise((resolve9) => rl.question(question, resolve9));
|
|
5610
5609
|
}
|
|
5611
5610
|
async function linkRepo(repo, linkedNames) {
|
|
5612
5611
|
try {
|
|
@@ -5845,7 +5844,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
5845
5844
|
writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
|
|
5846
5845
|
const runnerPath = join13(sessionDir, "run.sh");
|
|
5847
5846
|
writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
|
|
5848
|
-
await new Promise((
|
|
5847
|
+
await new Promise((resolve9) => {
|
|
5849
5848
|
const child = spawn3("bash", [runnerPath], {
|
|
5850
5849
|
stdio: "inherit",
|
|
5851
5850
|
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 +5860,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
5861
5860
|
child.on("exit", () => {
|
|
5862
5861
|
process.off("SIGINT", forward);
|
|
5863
5862
|
process.off("SIGTERM", forward);
|
|
5864
|
-
|
|
5863
|
+
resolve9();
|
|
5865
5864
|
});
|
|
5866
5865
|
});
|
|
5867
5866
|
}
|
|
@@ -7034,8 +7033,8 @@ function cursorApiKeyConfigured() {
|
|
|
7034
7033
|
return false;
|
|
7035
7034
|
}
|
|
7036
7035
|
}
|
|
7037
|
-
function writeCursorApiKey(
|
|
7038
|
-
const trimmed =
|
|
7036
|
+
function writeCursorApiKey(key2) {
|
|
7037
|
+
const trimmed = key2.trim();
|
|
7039
7038
|
if (!trimmed) return;
|
|
7040
7039
|
mkdirSync11(CURSOR_CREDS_DIR, { recursive: true });
|
|
7041
7040
|
chmodSync2(CURSOR_CREDS_DIR, 448);
|
|
@@ -7043,15 +7042,15 @@ function writeCursorApiKey(key) {
|
|
|
7043
7042
|
chmodSync2(CURSOR_API_KEY_FILE, 384);
|
|
7044
7043
|
}
|
|
7045
7044
|
async function validateCursorApiKey() {
|
|
7046
|
-
let
|
|
7045
|
+
let key2;
|
|
7047
7046
|
try {
|
|
7048
|
-
|
|
7047
|
+
key2 = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
|
|
7049
7048
|
} catch {
|
|
7050
7049
|
return null;
|
|
7051
7050
|
}
|
|
7052
|
-
if (!
|
|
7051
|
+
if (!key2) return null;
|
|
7053
7052
|
try {
|
|
7054
|
-
const auth = Buffer.from(`${
|
|
7053
|
+
const auth = Buffer.from(`${key2}:`).toString("base64");
|
|
7055
7054
|
const r = await fetch("https://api.cursor.com/v1/me", {
|
|
7056
7055
|
headers: { Authorization: `Basic ${auth}` },
|
|
7057
7056
|
signal: AbortSignal.timeout(8e3)
|
|
@@ -7391,15 +7390,15 @@ function parseSynkroToml(raw) {
|
|
|
7391
7390
|
}
|
|
7392
7391
|
const eq = line.indexOf("=");
|
|
7393
7392
|
if (eq === -1) continue;
|
|
7394
|
-
const
|
|
7393
|
+
const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
|
|
7395
7394
|
let valRaw = line.slice(eq + 1).trim();
|
|
7396
7395
|
if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
|
|
7397
7396
|
const h = valRaw.indexOf("#");
|
|
7398
7397
|
if (h !== -1) valRaw = valRaw.slice(0, h).trim();
|
|
7399
7398
|
}
|
|
7400
7399
|
const value = parseTomlValue(valRaw);
|
|
7401
|
-
if (section) result[section][
|
|
7402
|
-
else result[
|
|
7400
|
+
if (section) result[section][key2] = value;
|
|
7401
|
+
else result[key2] = value;
|
|
7403
7402
|
}
|
|
7404
7403
|
return result;
|
|
7405
7404
|
}
|
|
@@ -7676,7 +7675,7 @@ async function dockerInstall(opts = {}) {
|
|
|
7676
7675
|
"SYNKRO_CODEX_MODEL",
|
|
7677
7676
|
"SYNKRO_CONDUCTOR_MODEL",
|
|
7678
7677
|
"SYNKRO_ROUTE_MODEL"
|
|
7679
|
-
].flatMap((
|
|
7678
|
+
].flatMap((key2) => process.env[key2] ? ["-e", `${key2}=${process.env[key2]}`] : []),
|
|
7680
7679
|
// Fix-poll kill switch. Default ON in the image; a benchmark/headless run
|
|
7681
7680
|
// (e.g. sec-code-bench) sets SYNKRO_FIX_POLL=0 so ask-mode violations skip the
|
|
7682
7681
|
// interactive AskUserQuestion poll and fall through to generate-the-fix. Only
|
|
@@ -7934,7 +7933,7 @@ var init_dockerInstall = __esm({
|
|
|
7934
7933
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
7935
7934
|
CONTAINER_NAME = resolveContainerName();
|
|
7936
7935
|
defaultImageVersion = () => {
|
|
7937
|
-
if (true) return "1.10.
|
|
7936
|
+
if (true) return "1.10.10";
|
|
7938
7937
|
try {
|
|
7939
7938
|
const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
|
|
7940
7939
|
if (pkg.version) return pkg.version;
|
|
@@ -7966,7 +7965,7 @@ function captureClaudeSetupToken() {
|
|
|
7966
7965
|
const bin = "script";
|
|
7967
7966
|
const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
|
|
7968
7967
|
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((
|
|
7968
|
+
return new Promise((resolve9, reject) => {
|
|
7970
7969
|
const proc = nodeSpawn(bin, args2, {
|
|
7971
7970
|
stdio: "inherit",
|
|
7972
7971
|
env: { ...process.env, FORCE_COLOR: "3", COLORTERM: "truecolor", TERM: "xterm-256color" }
|
|
@@ -8033,7 +8032,7 @@ function captureClaudeSetupToken() {
|
|
|
8033
8032
|
reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));
|
|
8034
8033
|
return;
|
|
8035
8034
|
}
|
|
8036
|
-
|
|
8035
|
+
resolve9(token);
|
|
8037
8036
|
});
|
|
8038
8037
|
});
|
|
8039
8038
|
}
|
|
@@ -8059,13 +8058,13 @@ function findCodexBinary() {
|
|
|
8059
8058
|
function runCodexLogin(codexBin, codexHome) {
|
|
8060
8059
|
mkdirSync13(codexHome, { recursive: true, mode: 448 });
|
|
8061
8060
|
writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
|
|
8062
|
-
return new Promise((
|
|
8061
|
+
return new Promise((resolve9, reject) => {
|
|
8063
8062
|
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
8064
8063
|
stdio: "inherit",
|
|
8065
8064
|
env: { ...process.env, CODEX_HOME: codexHome }
|
|
8066
8065
|
});
|
|
8067
8066
|
proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
|
|
8068
|
-
proc.on("close", (code) => code === 0 ?
|
|
8067
|
+
proc.on("close", (code) => code === 0 ? resolve9() : reject(new Error(`codex login exited with code ${code}`)));
|
|
8069
8068
|
});
|
|
8070
8069
|
}
|
|
8071
8070
|
async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
@@ -8477,8 +8476,8 @@ function addUsage(a, b) {
|
|
|
8477
8476
|
}
|
|
8478
8477
|
function parseCodexTranscriptUsage(transcript, options = {}) {
|
|
8479
8478
|
if (!transcript) return null;
|
|
8480
|
-
const
|
|
8481
|
-
const hasCanonicalAssistant =
|
|
8479
|
+
const lines2 = transcript.split("\n");
|
|
8480
|
+
const hasCanonicalAssistant = lines2.some((line) => {
|
|
8482
8481
|
try {
|
|
8483
8482
|
const entry = JSON.parse(line);
|
|
8484
8483
|
return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
|
|
@@ -8492,8 +8491,8 @@ function parseCodexTranscriptUsage(transcript, options = {}) {
|
|
|
8492
8491
|
let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
|
|
8493
8492
|
let latest = null;
|
|
8494
8493
|
let sawSnapshot = false;
|
|
8495
|
-
for (let i = 0; i <
|
|
8496
|
-
const line =
|
|
8494
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
8495
|
+
const line = lines2[i].trim();
|
|
8497
8496
|
if (!line) continue;
|
|
8498
8497
|
let entry;
|
|
8499
8498
|
try {
|
|
@@ -8675,8 +8674,8 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
|
|
|
8675
8674
|
const entryModel = typeof message.model === "string" && message.model ? message.model : "unknown";
|
|
8676
8675
|
if (entryModel !== "<synthetic>") model = entryModel;
|
|
8677
8676
|
const day = isoDay(entry.timestamp, fallbackDay);
|
|
8678
|
-
const
|
|
8679
|
-
const row2 = rollups.get(
|
|
8677
|
+
const key2 = `${day}\0${entryModel}`;
|
|
8678
|
+
const row2 = rollups.get(key2) ?? {
|
|
8680
8679
|
day,
|
|
8681
8680
|
model: entryModel,
|
|
8682
8681
|
turns: 0,
|
|
@@ -8690,7 +8689,7 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
|
|
|
8690
8689
|
row2.output_tokens += counts.output_tokens;
|
|
8691
8690
|
row2.cache_creation_input_tokens += counts.cache_creation_input_tokens;
|
|
8692
8691
|
row2.cache_read_input_tokens += counts.cache_read_input_tokens;
|
|
8693
|
-
rollups.set(
|
|
8692
|
+
rollups.set(key2, row2);
|
|
8694
8693
|
turns += 1;
|
|
8695
8694
|
usage2.input_tokens += counts.input_tokens;
|
|
8696
8695
|
usage2.output_tokens += counts.output_tokens;
|
|
@@ -8767,20 +8766,20 @@ async function promptAgentSelection(detected) {
|
|
|
8767
8766
|
detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
|
|
8768
8767
|
console.log(` ${detected.length + 1}. Both / all (default)`);
|
|
8769
8768
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8770
|
-
const ask3 = () => new Promise((
|
|
8769
|
+
const ask3 = () => new Promise((resolve9) => {
|
|
8771
8770
|
rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
|
|
8772
8771
|
const t = answer.trim().toLowerCase();
|
|
8773
8772
|
if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
|
|
8774
8773
|
rl.close();
|
|
8775
|
-
return
|
|
8774
|
+
return resolve9(detected);
|
|
8776
8775
|
}
|
|
8777
8776
|
const n = parseInt(t, 10);
|
|
8778
8777
|
if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
|
|
8779
8778
|
rl.close();
|
|
8780
|
-
return
|
|
8779
|
+
return resolve9([detected[n - 1]]);
|
|
8781
8780
|
}
|
|
8782
8781
|
console.log("Invalid choice. Try again.");
|
|
8783
|
-
|
|
8782
|
+
resolve9(ask3());
|
|
8784
8783
|
});
|
|
8785
8784
|
});
|
|
8786
8785
|
return ask3();
|
|
@@ -8803,17 +8802,17 @@ async function promptCursorApiKey(opts) {
|
|
|
8803
8802
|
return;
|
|
8804
8803
|
}
|
|
8805
8804
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8806
|
-
const
|
|
8805
|
+
const key2 = await new Promise((resolve9) => {
|
|
8807
8806
|
rl.question(
|
|
8808
8807
|
"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
8808
|
(answer) => {
|
|
8810
8809
|
rl.close();
|
|
8811
|
-
|
|
8810
|
+
resolve9(answer.trim());
|
|
8812
8811
|
}
|
|
8813
8812
|
);
|
|
8814
8813
|
});
|
|
8815
|
-
if (
|
|
8816
|
-
writeCursorApiKey2(
|
|
8814
|
+
if (key2) {
|
|
8815
|
+
writeCursorApiKey2(key2);
|
|
8817
8816
|
console.log(" \u2713 Cursor API key saved.");
|
|
8818
8817
|
} else {
|
|
8819
8818
|
console.log(" \u26A0 Skipped \u2014 Cursor workers will be idle. Re-run install or pass --cursor-api-key=\u2026 later.");
|
|
@@ -8823,7 +8822,7 @@ async function promptDeployLocation(current = "local") {
|
|
|
8823
8822
|
if (!process.stdin.isTTY) return current;
|
|
8824
8823
|
const other = current === "cloud" ? "local" : "cloud";
|
|
8825
8824
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8826
|
-
return new Promise((
|
|
8825
|
+
return new Promise((resolve9) => {
|
|
8827
8826
|
rl.question(
|
|
8828
8827
|
`Where should Synkro run?
|
|
8829
8828
|
local \u2014 a grading container on this machine (Docker)
|
|
@@ -8832,7 +8831,7 @@ Each worker uses the account credentials you authorize. Choose [${current}] / ${
|
|
|
8832
8831
|
(answer) => {
|
|
8833
8832
|
rl.close();
|
|
8834
8833
|
const a = answer.trim().toLowerCase();
|
|
8835
|
-
|
|
8834
|
+
resolve9(a === "cloud" ? "cloud" : a === "local" ? "local" : current);
|
|
8836
8835
|
}
|
|
8837
8836
|
);
|
|
8838
8837
|
});
|
|
@@ -8986,7 +8985,7 @@ function writeConfigEnv(opts) {
|
|
|
8986
8985
|
const safeTier = sanitizeConfigValue(opts.tier ?? "pro", 32);
|
|
8987
8986
|
const safeInference = sanitizeConfigValue(opts.inference ?? "fast", 16);
|
|
8988
8987
|
const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? "", 1024);
|
|
8989
|
-
const
|
|
8988
|
+
const lines2 = [
|
|
8990
8989
|
"# Synkro CLI config (managed by synkro install)",
|
|
8991
8990
|
"# JWT auth \u2014 the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime",
|
|
8992
8991
|
"# and send Authorization: Bearer <access_token> on every gateway call.",
|
|
@@ -8994,27 +8993,27 @@ function writeConfigEnv(opts) {
|
|
|
8994
8993
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
8995
8994
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
8996
8995
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
8997
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.
|
|
8996
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.10")}`
|
|
8998
8997
|
];
|
|
8999
|
-
if (safeSynkroBin)
|
|
9000
|
-
if (safeUserId)
|
|
9001
|
-
if (safeOrgId)
|
|
9002
|
-
if (safeEmail)
|
|
8998
|
+
if (safeSynkroBin) lines2.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
8999
|
+
if (safeUserId) lines2.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
9000
|
+
if (safeOrgId) lines2.push(`SYNKRO_ORG_ID=${shellQuoteSingle2(safeOrgId)}`);
|
|
9001
|
+
if (safeEmail) lines2.push(`SYNKRO_EMAIL=${shellQuoteSingle2(safeEmail)}`);
|
|
9003
9002
|
if (opts.transcriptConsent !== void 0) {
|
|
9004
|
-
|
|
9003
|
+
lines2.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle2(opts.transcriptConsent ? "yes" : "no")}`);
|
|
9005
9004
|
}
|
|
9006
|
-
if (opts.transcriptConsentCC !== void 0)
|
|
9007
|
-
if (opts.transcriptConsentCursor !== void 0)
|
|
9008
|
-
if (opts.transcriptConsentCodex !== void 0)
|
|
9009
|
-
|
|
9005
|
+
if (opts.transcriptConsentCC !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CC=${shellQuoteSingle2(opts.transcriptConsentCC ? "yes" : "no")}`);
|
|
9006
|
+
if (opts.transcriptConsentCursor !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CURSOR=${shellQuoteSingle2(opts.transcriptConsentCursor ? "yes" : "no")}`);
|
|
9007
|
+
if (opts.transcriptConsentCodex !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CODEX=${shellQuoteSingle2(opts.transcriptConsentCodex ? "yes" : "no")}`);
|
|
9008
|
+
lines2.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle2(opts.localInference ? "yes" : "no")}`);
|
|
9010
9009
|
const safeMode = sanitizeConfigValue(opts.deploymentMode ?? "docker", 16);
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
writeFileSync17(CONFIG_PATH4,
|
|
9010
|
+
lines2.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle2(safeMode)}`);
|
|
9011
|
+
lines2.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
|
|
9012
|
+
lines2.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
|
|
9013
|
+
lines2.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
|
|
9014
|
+
lines2.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
|
|
9015
|
+
lines2.push("");
|
|
9016
|
+
writeFileSync17(CONFIG_PATH4, lines2.join("\n"), "utf-8");
|
|
9018
9017
|
chmodSync5(CONFIG_PATH4, 384);
|
|
9019
9018
|
}
|
|
9020
9019
|
function persistedTranscriptConsent(source) {
|
|
@@ -9741,7 +9740,7 @@ async function installCommand(opts = {}) {
|
|
|
9741
9740
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
9742
9741
|
emit("install", {
|
|
9743
9742
|
phase: "started",
|
|
9744
|
-
cli_version_to: "1.10.
|
|
9743
|
+
cli_version_to: "1.10.10",
|
|
9745
9744
|
agents_detected: agents.map((a) => a.kind),
|
|
9746
9745
|
with_github: false,
|
|
9747
9746
|
with_local_cc: false,
|
|
@@ -10303,15 +10302,15 @@ function parseSynkroToml2(raw) {
|
|
|
10303
10302
|
}
|
|
10304
10303
|
const eq = line.indexOf("=");
|
|
10305
10304
|
if (eq === -1) continue;
|
|
10306
|
-
const
|
|
10305
|
+
const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
|
|
10307
10306
|
let valRaw = line.slice(eq + 1).trim();
|
|
10308
10307
|
if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
|
|
10309
10308
|
const h = valRaw.indexOf("#");
|
|
10310
10309
|
if (h !== -1) valRaw = valRaw.slice(0, h).trim();
|
|
10311
10310
|
}
|
|
10312
10311
|
const value = parseTomlValue2(valRaw);
|
|
10313
|
-
if (section) result[section][
|
|
10314
|
-
else result[
|
|
10312
|
+
if (section) result[section][key2] = value;
|
|
10313
|
+
else result[key2] = value;
|
|
10315
10314
|
}
|
|
10316
10315
|
return result;
|
|
10317
10316
|
}
|
|
@@ -10592,8 +10591,8 @@ async function syncSkillFiles() {
|
|
|
10592
10591
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
10593
10592
|
return null;
|
|
10594
10593
|
}
|
|
10595
|
-
const
|
|
10596
|
-
console.log(` \u2192 read ${source} (${
|
|
10594
|
+
const lines2 = content.split("\n").length;
|
|
10595
|
+
console.log(` \u2192 read ${source} (${lines2} lines, ${(content.length / 1024).toFixed(1)} KB)`);
|
|
10597
10596
|
return { source, content };
|
|
10598
10597
|
}).filter(Boolean);
|
|
10599
10598
|
if (tasks.length === 0) return;
|
|
@@ -10872,10 +10871,10 @@ function extractSessionInsights(projectsDir) {
|
|
|
10872
10871
|
const filePath = join19(projectsDir, file);
|
|
10873
10872
|
try {
|
|
10874
10873
|
const content = readFileSync21(filePath, "utf-8");
|
|
10875
|
-
const
|
|
10876
|
-
for (let i = 0; i <
|
|
10874
|
+
const lines2 = content.split("\n").filter(Boolean);
|
|
10875
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
10877
10876
|
try {
|
|
10878
|
-
const entry = JSON.parse(
|
|
10877
|
+
const entry = JSON.parse(lines2[i]);
|
|
10879
10878
|
if (entry.type === "user" && typeof entry.message?.content === "string" && entry.message.content.startsWith("This session is being continued")) {
|
|
10880
10879
|
insights.push({
|
|
10881
10880
|
session_id: sessionId,
|
|
@@ -10888,9 +10887,9 @@ function extractSessionInsights(projectsDir) {
|
|
|
10888
10887
|
}
|
|
10889
10888
|
}
|
|
10890
10889
|
const userMessages = [];
|
|
10891
|
-
for (let i =
|
|
10890
|
+
for (let i = lines2.length - 1; i >= 0 && userMessages.length < 20; i--) {
|
|
10892
10891
|
try {
|
|
10893
|
-
const entry = JSON.parse(
|
|
10892
|
+
const entry = JSON.parse(lines2[i]);
|
|
10894
10893
|
if (entry.type === "user") {
|
|
10895
10894
|
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
10895
|
if (text && text.length > 10 && text.length < 2e3 && !text.startsWith("This session is being continued")) {
|
|
@@ -10949,13 +10948,13 @@ function extractTextContent(content) {
|
|
|
10949
10948
|
function getCodexTranscriptFiles(repo) {
|
|
10950
10949
|
const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
|
|
10951
10950
|
if (!existsSync22(sessionsDir)) return [];
|
|
10952
|
-
let
|
|
10951
|
+
let relative3 = [];
|
|
10953
10952
|
try {
|
|
10954
|
-
|
|
10953
|
+
relative3 = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
|
|
10955
10954
|
} catch {
|
|
10956
10955
|
return [];
|
|
10957
10956
|
}
|
|
10958
|
-
return
|
|
10957
|
+
return relative3.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
|
|
10959
10958
|
try {
|
|
10960
10959
|
const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
|
|
10961
10960
|
const meta = JSON.parse(first);
|
|
@@ -10972,11 +10971,11 @@ function isJsonSyntaxError(error) {
|
|
|
10972
10971
|
}
|
|
10973
10972
|
function parseCodexTranscriptFile(filePath) {
|
|
10974
10973
|
const transcript = readFileSync21(filePath, "utf-8");
|
|
10975
|
-
const
|
|
10974
|
+
const lines2 = transcript.split("\n");
|
|
10976
10975
|
const transcriptUsage = parseCodexTranscriptUsage(transcript);
|
|
10977
10976
|
let sessionId = "";
|
|
10978
10977
|
let model = "";
|
|
10979
|
-
for (const line of
|
|
10978
|
+
for (const line of lines2) {
|
|
10980
10979
|
try {
|
|
10981
10980
|
const entry = JSON.parse(line);
|
|
10982
10981
|
if (entry.type === "session_meta") {
|
|
@@ -11060,11 +11059,11 @@ function isSafeConvId(id) {
|
|
|
11060
11059
|
}
|
|
11061
11060
|
function parseCursorTranscriptFile(filePath) {
|
|
11062
11061
|
const content = readFileSync21(filePath, "utf-8");
|
|
11063
|
-
const
|
|
11062
|
+
const lines2 = content.split("\n").filter(Boolean);
|
|
11064
11063
|
const messages = [];
|
|
11065
|
-
for (let i = 0; i <
|
|
11064
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
11066
11065
|
try {
|
|
11067
|
-
const entry = JSON.parse(
|
|
11066
|
+
const entry = JSON.parse(lines2[i]);
|
|
11068
11067
|
const role = entry.role || entry.message?.role;
|
|
11069
11068
|
if (role !== "user" && role !== "assistant") continue;
|
|
11070
11069
|
const text = extractTextContent(entry.message?.content ?? entry.content);
|
|
@@ -11124,11 +11123,11 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
11124
11123
|
}
|
|
11125
11124
|
function parseTranscriptFile(filePath) {
|
|
11126
11125
|
const content = readFileSync21(filePath, "utf-8");
|
|
11127
|
-
const
|
|
11126
|
+
const lines2 = content.split("\n").filter(Boolean);
|
|
11128
11127
|
const messages = [];
|
|
11129
|
-
for (let i = 0; i <
|
|
11128
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
11130
11129
|
try {
|
|
11131
|
-
const entry = JSON.parse(
|
|
11130
|
+
const entry = JSON.parse(lines2[i]);
|
|
11132
11131
|
if (entry.type !== "user" && entry.type !== "assistant") continue;
|
|
11133
11132
|
const msg = {
|
|
11134
11133
|
message_index: i,
|
|
@@ -12030,10 +12029,10 @@ function confirmPurge() {
|
|
|
12030
12029
|
return Promise.resolve(false);
|
|
12031
12030
|
}
|
|
12032
12031
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
12033
|
-
return new Promise((
|
|
12032
|
+
return new Promise((resolve9) => {
|
|
12034
12033
|
rl.question(" Type 'yes' to wipe everything (anything else cancels): ", (answer) => {
|
|
12035
12034
|
rl.close();
|
|
12036
|
-
|
|
12035
|
+
resolve9(answer.trim().toLowerCase() === "yes");
|
|
12037
12036
|
});
|
|
12038
12037
|
});
|
|
12039
12038
|
}
|
|
@@ -12194,8 +12193,8 @@ function readRecentTurns(n = 20) {
|
|
|
12194
12193
|
const size = statSync3(TURN_LOG_PATH).size;
|
|
12195
12194
|
if (size === 0) return [];
|
|
12196
12195
|
const text = readFileSync23(TURN_LOG_PATH, "utf-8");
|
|
12197
|
-
const
|
|
12198
|
-
const lastN =
|
|
12196
|
+
const lines2 = text.split("\n").filter(Boolean);
|
|
12197
|
+
const lastN = lines2.slice(-n).reverse();
|
|
12199
12198
|
return lastN.map((line) => {
|
|
12200
12199
|
try {
|
|
12201
12200
|
return JSON.parse(line);
|
|
@@ -12286,7 +12285,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12286
12285
|
const port = opts.port ?? CHANNEL_PORT;
|
|
12287
12286
|
const startedAt = Date.now();
|
|
12288
12287
|
try {
|
|
12289
|
-
const result = await new Promise((
|
|
12288
|
+
const result = await new Promise((resolve9, reject) => {
|
|
12290
12289
|
const req = httpRequest({
|
|
12291
12290
|
host: CHANNEL_HOST,
|
|
12292
12291
|
port,
|
|
@@ -12312,7 +12311,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12312
12311
|
reject(new LocalCCError(parsed.error));
|
|
12313
12312
|
return;
|
|
12314
12313
|
}
|
|
12315
|
-
|
|
12314
|
+
resolve9(String(parsed.result ?? ""));
|
|
12316
12315
|
} catch (err) {
|
|
12317
12316
|
reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
|
|
12318
12317
|
}
|
|
@@ -12338,14 +12337,14 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12338
12337
|
}
|
|
12339
12338
|
}
|
|
12340
12339
|
function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
|
|
12341
|
-
return new Promise((
|
|
12340
|
+
return new Promise((resolve9) => {
|
|
12342
12341
|
const sock = connect(port, CHANNEL_HOST);
|
|
12343
12342
|
const done = (ok) => {
|
|
12344
12343
|
try {
|
|
12345
12344
|
sock.destroy();
|
|
12346
12345
|
} catch {
|
|
12347
12346
|
}
|
|
12348
|
-
|
|
12347
|
+
resolve9(ok);
|
|
12349
12348
|
};
|
|
12350
12349
|
sock.once("connect", () => done(true));
|
|
12351
12350
|
sock.once("error", () => done(false));
|
|
@@ -12377,10 +12376,10 @@ __export(grade_exports, {
|
|
|
12377
12376
|
gradeCommand: () => gradeCommand
|
|
12378
12377
|
});
|
|
12379
12378
|
async function readStdin() {
|
|
12380
|
-
return new Promise((
|
|
12379
|
+
return new Promise((resolve9, reject) => {
|
|
12381
12380
|
const chunks = [];
|
|
12382
12381
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
12383
|
-
process.stdin.on("end", () =>
|
|
12382
|
+
process.stdin.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
|
|
12384
12383
|
process.stdin.on("error", reject);
|
|
12385
12384
|
});
|
|
12386
12385
|
}
|
|
@@ -12467,9 +12466,9 @@ async function fetchOrgRules(gatewayUrl, apiKey) {
|
|
|
12467
12466
|
function applyLiteralMatchNegative(rules, file) {
|
|
12468
12467
|
if (!file.patch) return [];
|
|
12469
12468
|
const findings = [];
|
|
12470
|
-
const
|
|
12469
|
+
const lines2 = file.patch.split("\n");
|
|
12471
12470
|
let currentNewLine = 0;
|
|
12472
|
-
for (const line of
|
|
12471
|
+
for (const line of lines2) {
|
|
12473
12472
|
if (line.startsWith("@@")) {
|
|
12474
12473
|
const m = line.match(/\+(\d+)(?:,\d+)?/);
|
|
12475
12474
|
if (m) currentNewLine = parseInt(m[1], 10);
|
|
@@ -12597,12 +12596,12 @@ async function fetchScanContext(gatewayUrl, apiKey, repo, prNumber, sha) {
|
|
|
12597
12596
|
}
|
|
12598
12597
|
function getFileDiffWithLines(file) {
|
|
12599
12598
|
if (!file.patch) return { hunks: "", newFileLineMap: /* @__PURE__ */ new Map() };
|
|
12600
|
-
const
|
|
12599
|
+
const lines2 = file.patch.split("\n");
|
|
12601
12600
|
const annotated = [];
|
|
12602
12601
|
const lineMap = /* @__PURE__ */ new Map();
|
|
12603
12602
|
let currentNewLine = 0;
|
|
12604
12603
|
let patchIndex = 0;
|
|
12605
|
-
for (const line of
|
|
12604
|
+
for (const line of lines2) {
|
|
12606
12605
|
patchIndex++;
|
|
12607
12606
|
if (line.startsWith("@@")) {
|
|
12608
12607
|
const match = line.match(/\+(\d+)(?:,\d+)?/);
|
|
@@ -12634,7 +12633,7 @@ function spawnClaudeJudge(file, claudeToken, promptHeader) {
|
|
|
12634
12633
|
Diff:
|
|
12635
12634
|
${hunks}`;
|
|
12636
12635
|
const fullPrompt = promptHeader + userMessage;
|
|
12637
|
-
return new Promise((
|
|
12636
|
+
return new Promise((resolve9) => {
|
|
12638
12637
|
const t0 = Date.now();
|
|
12639
12638
|
const proc = spawn6(
|
|
12640
12639
|
"claude",
|
|
@@ -12662,7 +12661,7 @@ ${hunks}`;
|
|
|
12662
12661
|
const latencyMs = Date.now() - t0;
|
|
12663
12662
|
if (code !== 0) {
|
|
12664
12663
|
console.warn(` claude exited ${code}: ${(stderr || stdout).slice(0, 500)}`);
|
|
12665
|
-
|
|
12664
|
+
resolve9({ findings: [], latencyMs });
|
|
12666
12665
|
return;
|
|
12667
12666
|
}
|
|
12668
12667
|
try {
|
|
@@ -12681,10 +12680,10 @@ ${hunks}`;
|
|
|
12681
12680
|
description: f.description,
|
|
12682
12681
|
fix: f.fix
|
|
12683
12682
|
}));
|
|
12684
|
-
|
|
12683
|
+
resolve9({ findings, latencyMs });
|
|
12685
12684
|
} catch (parseErr) {
|
|
12686
12685
|
console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
|
|
12687
|
-
|
|
12686
|
+
resolve9({ findings: [], latencyMs });
|
|
12688
12687
|
}
|
|
12689
12688
|
});
|
|
12690
12689
|
});
|
|
@@ -12733,7 +12732,7 @@ ${JSON.stringify(findings, null, 2)}
|
|
|
12733
12732
|
`;
|
|
12734
12733
|
}
|
|
12735
12734
|
function spawnOpusConsolidator(findings, claudeToken) {
|
|
12736
|
-
return new Promise((
|
|
12735
|
+
return new Promise((resolve9) => {
|
|
12737
12736
|
const prompt = buildConsolidationPrompt(findings);
|
|
12738
12737
|
const proc = spawn6(
|
|
12739
12738
|
"claude",
|
|
@@ -12760,7 +12759,7 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12760
12759
|
proc.on("close", (code) => {
|
|
12761
12760
|
if (code !== 0) {
|
|
12762
12761
|
console.warn(` opus consolidation exited ${code}: ${(stderr || stdout).slice(0, 300)}`);
|
|
12763
|
-
|
|
12762
|
+
resolve9(fallbackReview(findings));
|
|
12764
12763
|
return;
|
|
12765
12764
|
}
|
|
12766
12765
|
try {
|
|
@@ -12781,10 +12780,10 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12781
12780
|
const order = ["low", "medium", "high", "critical"];
|
|
12782
12781
|
return order.indexOf(f.severity) > order.indexOf(max) ? f.severity : max;
|
|
12783
12782
|
}, "low");
|
|
12784
|
-
|
|
12783
|
+
resolve9({ summary: review.summary || "", comments, severity: maxSeverity });
|
|
12785
12784
|
} catch {
|
|
12786
12785
|
console.warn(` failed to parse opus response, using fallback`);
|
|
12787
|
-
|
|
12786
|
+
resolve9(fallbackReview(findings));
|
|
12788
12787
|
}
|
|
12789
12788
|
});
|
|
12790
12789
|
});
|
|
@@ -12792,15 +12791,15 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12792
12791
|
function fallbackReview(findings) {
|
|
12793
12792
|
const grouped = /* @__PURE__ */ new Map();
|
|
12794
12793
|
for (const f of findings) {
|
|
12795
|
-
const
|
|
12796
|
-
if (!grouped.has(
|
|
12797
|
-
grouped.get(
|
|
12794
|
+
const key2 = `${f.file}::${f.category}`;
|
|
12795
|
+
if (!grouped.has(key2)) grouped.set(key2, []);
|
|
12796
|
+
grouped.get(key2).push(f);
|
|
12798
12797
|
}
|
|
12799
12798
|
const comments = [];
|
|
12800
12799
|
for (const [, group] of grouped) {
|
|
12801
12800
|
const first = group[0];
|
|
12802
|
-
const
|
|
12803
|
-
const linesStr =
|
|
12801
|
+
const lines2 = group.map((f) => f.line);
|
|
12802
|
+
const linesStr = lines2.length > 1 ? `Lines ${lines2.join(", ")}` : `Line ${lines2[0]}`;
|
|
12804
12803
|
const severityEmoji = first.severity === "critical" ? "\u{1F534}" : first.severity === "high" ? "\u{1F7E0}" : first.severity === "medium" ? "\u{1F7E1}" : "\u{1F535}";
|
|
12805
12804
|
comments.push({
|
|
12806
12805
|
path: first.file,
|
|
@@ -13254,10 +13253,10 @@ function stopTask(channel = CHANNEL_PRIMARY) {
|
|
|
13254
13253
|
t = findTask(channel);
|
|
13255
13254
|
}
|
|
13256
13255
|
}
|
|
13257
|
-
function tailLogs(
|
|
13256
|
+
function tailLogs(lines2 = 80, channel = CHANNEL_PRIMARY) {
|
|
13258
13257
|
const t = findTask(channel);
|
|
13259
13258
|
if (!t) return `(no ${channel.taskLabel} task)`;
|
|
13260
|
-
const r = spawnSync9("pueue", ["log", "--lines", String(
|
|
13259
|
+
const r = spawnSync9("pueue", ["log", "--lines", String(lines2), String(t.id)], { encoding: "utf-8" });
|
|
13261
13260
|
return r.stdout || r.stderr || "(no output)";
|
|
13262
13261
|
}
|
|
13263
13262
|
function ensureRunning(opts = {}) {
|
|
@@ -13267,14 +13266,14 @@ function ensureRunning(opts = {}) {
|
|
|
13267
13266
|
return startTask(opts);
|
|
13268
13267
|
}
|
|
13269
13268
|
function probePort(host, port, timeoutMs = 500) {
|
|
13270
|
-
return new Promise((
|
|
13269
|
+
return new Promise((resolve9) => {
|
|
13271
13270
|
const sock = connect2(port, host);
|
|
13272
13271
|
const done = (ok) => {
|
|
13273
13272
|
try {
|
|
13274
13273
|
sock.destroy();
|
|
13275
13274
|
} catch {
|
|
13276
13275
|
}
|
|
13277
|
-
|
|
13276
|
+
resolve9(ok);
|
|
13278
13277
|
};
|
|
13279
13278
|
sock.once("connect", () => done(true));
|
|
13280
13279
|
sock.once("error", () => done(false));
|
|
@@ -13824,7 +13823,7 @@ function cmdLogs(rest) {
|
|
|
13824
13823
|
if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
|
|
13825
13824
|
return;
|
|
13826
13825
|
}
|
|
13827
|
-
return new Promise((
|
|
13826
|
+
return new Promise((resolve9) => {
|
|
13828
13827
|
console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
|
|
13829
13828
|
const stop = followTurns((t) => {
|
|
13830
13829
|
console.log(" " + formatTurn(t, raw));
|
|
@@ -13832,7 +13831,7 @@ function cmdLogs(rest) {
|
|
|
13832
13831
|
const onSigint = () => {
|
|
13833
13832
|
stop();
|
|
13834
13833
|
process.removeListener("SIGINT", onSigint);
|
|
13835
|
-
|
|
13834
|
+
resolve9();
|
|
13836
13835
|
};
|
|
13837
13836
|
process.on("SIGINT", onSigint);
|
|
13838
13837
|
});
|
|
@@ -14050,7 +14049,7 @@ function extractToolResultText(content, e) {
|
|
|
14050
14049
|
function parseSession(file, seenStableIds) {
|
|
14051
14050
|
const { filePath, sessionId, parentSessionId } = file;
|
|
14052
14051
|
const transcript = readFileSync27(filePath, "utf-8");
|
|
14053
|
-
const
|
|
14052
|
+
const lines2 = transcript.split("\n").filter(Boolean);
|
|
14054
14053
|
const transcriptUsage = parseClaudeTranscriptUsage(
|
|
14055
14054
|
transcript,
|
|
14056
14055
|
statSync4(filePath).mtime.toISOString().slice(0, 10),
|
|
@@ -14062,10 +14061,10 @@ function parseSession(file, seenStableIds) {
|
|
|
14062
14061
|
const messages = [];
|
|
14063
14062
|
const actions = [];
|
|
14064
14063
|
let step = 0;
|
|
14065
|
-
for (let i = 0; i <
|
|
14064
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
14066
14065
|
let e;
|
|
14067
14066
|
try {
|
|
14068
|
-
e = JSON.parse(
|
|
14067
|
+
e = JSON.parse(lines2[i]);
|
|
14069
14068
|
} catch {
|
|
14070
14069
|
continue;
|
|
14071
14070
|
}
|
|
@@ -14121,9 +14120,9 @@ function parseSession(file, seenStableIds) {
|
|
|
14121
14120
|
}
|
|
14122
14121
|
function ask2(q) {
|
|
14123
14122
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
14124
|
-
return new Promise((
|
|
14123
|
+
return new Promise((resolve9) => rl.question(q, (a) => {
|
|
14125
14124
|
rl.close();
|
|
14126
|
-
|
|
14125
|
+
resolve9(/^y(es)?$/i.test(a.trim()));
|
|
14127
14126
|
}));
|
|
14128
14127
|
}
|
|
14129
14128
|
async function importCommand() {
|
|
@@ -14261,8 +14260,8 @@ function computeDigest(canonical2) {
|
|
|
14261
14260
|
}
|
|
14262
14261
|
function verifySignature(digest, signatureB64, publicKeyPem) {
|
|
14263
14262
|
try {
|
|
14264
|
-
const
|
|
14265
|
-
return crypto.verify(null, Buffer.from(digest, "utf8"),
|
|
14263
|
+
const key2 = crypto.createPublicKey(publicKeyPem);
|
|
14264
|
+
return crypto.verify(null, Buffer.from(digest, "utf8"), key2, Buffer.from(signatureB64, "base64"));
|
|
14266
14265
|
} catch {
|
|
14267
14266
|
return false;
|
|
14268
14267
|
}
|
|
@@ -14654,6 +14653,7 @@ function buildSpawnAgent(opts) {
|
|
|
14654
14653
|
["tmux", "set-option", "-t", session, "-q", "@synkro_harness", opts.harness],
|
|
14655
14654
|
["tmux", "set-option", "-t", session, "-q", "@synkro_space", opts.space],
|
|
14656
14655
|
["tmux", "set-option", "-t", session, "-q", "@synkro_backend", opts.backend],
|
|
14656
|
+
["tmux", "set-option", "-t", session, "-q", "@synkro_mode", opts.mode || "native"],
|
|
14657
14657
|
// Keep the pane visible after exit so the sidebar can render 'done'
|
|
14658
14658
|
// instead of the agent silently vanishing.
|
|
14659
14659
|
["tmux", "set-option", "-t", session, "remain-on-exit", "on"]
|
|
@@ -14675,7 +14675,7 @@ function buildAgentSnapshot() {
|
|
|
14675
14675
|
return ["sh", "-c", script];
|
|
14676
14676
|
}
|
|
14677
14677
|
function parseAgentSnapshot(output) {
|
|
14678
|
-
const
|
|
14678
|
+
const lines2 = String(output || "").split("\n");
|
|
14679
14679
|
const captures = /* @__PURE__ */ new Map();
|
|
14680
14680
|
const listLines = [];
|
|
14681
14681
|
let current = null;
|
|
@@ -14683,7 +14683,7 @@ function parseAgentSnapshot(output) {
|
|
|
14683
14683
|
const flush2 = () => {
|
|
14684
14684
|
if (current) captures.set(current, chunk.join("\n"));
|
|
14685
14685
|
};
|
|
14686
|
-
for (const line of
|
|
14686
|
+
for (const line of lines2) {
|
|
14687
14687
|
if (line.startsWith("===")) {
|
|
14688
14688
|
flush2();
|
|
14689
14689
|
current = line.slice(3).trim();
|
|
@@ -14962,12 +14962,12 @@ async function discoverAgents(runner, backend, memory) {
|
|
|
14962
14962
|
}
|
|
14963
14963
|
function offlineAgents(live, records) {
|
|
14964
14964
|
const alive = new Set(live.map((agent) => agent.session));
|
|
14965
|
-
return records.filter((
|
|
14966
|
-
name:
|
|
14967
|
-
session:
|
|
14968
|
-
harness:
|
|
14969
|
-
space:
|
|
14970
|
-
backend:
|
|
14965
|
+
return records.filter((record2) => !alive.has(record2.session)).map((record2) => ({
|
|
14966
|
+
name: record2.name,
|
|
14967
|
+
session: record2.session,
|
|
14968
|
+
harness: record2.harness,
|
|
14969
|
+
space: record2.space,
|
|
14970
|
+
backend: record2.backend,
|
|
14971
14971
|
status: "offline"
|
|
14972
14972
|
}));
|
|
14973
14973
|
}
|
|
@@ -15004,8 +15004,8 @@ function saveRecords(records) {
|
|
|
15004
15004
|
} catch {
|
|
15005
15005
|
}
|
|
15006
15006
|
}
|
|
15007
|
-
function recordSession(
|
|
15008
|
-
saveRecords([...loadRecords().filter((row2) => row2.session !==
|
|
15007
|
+
function recordSession(record2) {
|
|
15008
|
+
saveRecords([...loadRecords().filter((row2) => row2.session !== record2.session), record2]);
|
|
15009
15009
|
}
|
|
15010
15010
|
function forgetSession(session) {
|
|
15011
15011
|
saveRecords(loadRecords().filter((row2) => row2.session !== session));
|
|
@@ -15040,11 +15040,11 @@ function lastAgentFor(space) {
|
|
|
15040
15040
|
return loadLastAgents()[String(space || "").replace(/\/+$/, "")] || "";
|
|
15041
15041
|
}
|
|
15042
15042
|
function rememberLastAgent(space, session) {
|
|
15043
|
-
const
|
|
15044
|
-
if (!
|
|
15043
|
+
const key2 = String(space || "").replace(/\/+$/, "");
|
|
15044
|
+
if (!key2 || !session) return;
|
|
15045
15045
|
try {
|
|
15046
15046
|
mkdirSync21(dirname10(LAST_AGENT_FILE), { recursive: true });
|
|
15047
|
-
writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [
|
|
15047
|
+
writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [key2]: session }, null, 2));
|
|
15048
15048
|
} catch {
|
|
15049
15049
|
}
|
|
15050
15050
|
}
|
|
@@ -15083,9 +15083,17 @@ var init_manifest = __esm({
|
|
|
15083
15083
|
});
|
|
15084
15084
|
|
|
15085
15085
|
// cli/ui/launch.ts
|
|
15086
|
-
import { mkdirSync as mkdirSync22, writeFileSync as writeFileSync25 } from "fs";
|
|
15086
|
+
import { mkdirSync as mkdirSync22, statSync as statSync5, writeFileSync as writeFileSync25 } from "fs";
|
|
15087
15087
|
import { homedir as homedir34 } from "os";
|
|
15088
15088
|
import { join as join34 } from "path";
|
|
15089
|
+
function buildStamp(bootPath) {
|
|
15090
|
+
try {
|
|
15091
|
+
const stat = statSync5(bootPath);
|
|
15092
|
+
return String(stat.size) + ":" + String(Math.floor(stat.mtimeMs));
|
|
15093
|
+
} catch {
|
|
15094
|
+
return bootPath;
|
|
15095
|
+
}
|
|
15096
|
+
}
|
|
15089
15097
|
function sidebarColumns(totalColumns) {
|
|
15090
15098
|
return String(Math.max(20, Math.min(34, Math.round(totalColumns * 0.24))));
|
|
15091
15099
|
}
|
|
@@ -15187,8 +15195,151 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
|
|
|
15187
15195
|
// Alt+s always returns focus to the sidebar (the leftmost pane).
|
|
15188
15196
|
["bind-key", "-n", "M-s", "select-pane", "-L"]
|
|
15189
15197
|
];
|
|
15190
|
-
for (const argv of style)
|
|
15191
|
-
|
|
15198
|
+
for (const argv of style) {
|
|
15199
|
+
if (!(await run(HOST, ["tmux", ...argv])).ok) return false;
|
|
15200
|
+
}
|
|
15201
|
+
for (const argv of buildClipboardBindings(UI_SESSION)) {
|
|
15202
|
+
if (!(await run(HOST, argv)).ok) return false;
|
|
15203
|
+
}
|
|
15204
|
+
return true;
|
|
15205
|
+
}
|
|
15206
|
+
function parseUiPanes(output) {
|
|
15207
|
+
return String(output || "").split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 7).map(([window, pane, width, dead, remain, cwd, ...command]) => ({
|
|
15208
|
+
window,
|
|
15209
|
+
pane,
|
|
15210
|
+
width: Number(width) || 0,
|
|
15211
|
+
dead: dead === "1",
|
|
15212
|
+
remain: remain === "on",
|
|
15213
|
+
cwd,
|
|
15214
|
+
command: command.join("|")
|
|
15215
|
+
})).filter((row2) => Boolean(row2.window && row2.pane));
|
|
15216
|
+
}
|
|
15217
|
+
async function ensureUiCenterPane(options) {
|
|
15218
|
+
const listed = await run(HOST, [
|
|
15219
|
+
"tmux",
|
|
15220
|
+
"list-panes",
|
|
15221
|
+
"-t",
|
|
15222
|
+
options.sidebarPane,
|
|
15223
|
+
"-F",
|
|
15224
|
+
"#{pane_id}|#{pane_dead}|#{remain-on-exit}|#{pane_start_command}"
|
|
15225
|
+
]);
|
|
15226
|
+
if (!listed.ok) return { ok: false, pane: "", created: false, recovered: false };
|
|
15227
|
+
const rows = listed.stdout.split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 4).map(([pane, dead, remain, ...command]) => ({
|
|
15228
|
+
pane,
|
|
15229
|
+
dead: dead === "1",
|
|
15230
|
+
remain: remain === "on",
|
|
15231
|
+
command: command.join("|")
|
|
15232
|
+
}));
|
|
15233
|
+
let center = rows.find((row2) => row2.pane !== options.sidebarPane);
|
|
15234
|
+
let created = false;
|
|
15235
|
+
if (!center) {
|
|
15236
|
+
const split = await run(HOST, [
|
|
15237
|
+
"tmux",
|
|
15238
|
+
"split-window",
|
|
15239
|
+
"-h",
|
|
15240
|
+
"-t",
|
|
15241
|
+
options.sidebarPane,
|
|
15242
|
+
"-c",
|
|
15243
|
+
options.cwd,
|
|
15244
|
+
"-P",
|
|
15245
|
+
"-F",
|
|
15246
|
+
"#{pane_id}",
|
|
15247
|
+
"tail -f /dev/null"
|
|
15248
|
+
]);
|
|
15249
|
+
const pane = split.stdout.trim();
|
|
15250
|
+
if (!split.ok || !pane) return { ok: false, pane: "", created: false, recovered: false };
|
|
15251
|
+
center = { pane, dead: false, remain: false, command: "tail -f /dev/null" };
|
|
15252
|
+
created = true;
|
|
15253
|
+
}
|
|
15254
|
+
const kept = await run(HOST, buildKeepPaneAfterExit(center.pane));
|
|
15255
|
+
if (!kept.ok) return { ok: false, pane: center.pane, created, recovered: false };
|
|
15256
|
+
const recovered = created || center.dead;
|
|
15257
|
+
if (recovered) {
|
|
15258
|
+
const command = options.terminalCommand === void 0 ? makeTerminalCommand(options.bootPath) : options.terminalCommand;
|
|
15259
|
+
const respawn = ["tmux", "respawn-pane", "-k", "-t", center.pane, "-c", options.cwd];
|
|
15260
|
+
if (command) respawn.push(command);
|
|
15261
|
+
const restored = await run(HOST, respawn);
|
|
15262
|
+
if (!restored.ok) return { ok: false, pane: center.pane, created, recovered: false };
|
|
15263
|
+
const spaceName = options.cwd.split("/").filter(Boolean).pop() || "space";
|
|
15264
|
+
const metadata = [
|
|
15265
|
+
["tmux", "set-option", "-t", center.pane, "-w", "-q", "@synkro_agent", ""],
|
|
15266
|
+
["tmux", "set-option", "-t", center.pane, "-w", "-q", "@synkro_cwd", options.cwd],
|
|
15267
|
+
["tmux", "set-option", "-t", center.pane, "-w", "-q", "@synkro_kind", "terminal"],
|
|
15268
|
+
["tmux", "set-option", "-t", center.pane, "-w", "automatic-rename", "off"],
|
|
15269
|
+
["tmux", "rename-window", "-t", center.pane, tabTitle("terminal", spaceName)]
|
|
15270
|
+
];
|
|
15271
|
+
for (const argv of metadata) {
|
|
15272
|
+
if (!(await run(HOST, argv)).ok) return { ok: false, pane: center.pane, created, recovered: false };
|
|
15273
|
+
}
|
|
15274
|
+
}
|
|
15275
|
+
return { ok: true, pane: center.pane, created, recovered };
|
|
15276
|
+
}
|
|
15277
|
+
async function reconcileUiShell(bootPath, repoCwd, options = {}) {
|
|
15278
|
+
const listed = await run(HOST, [
|
|
15279
|
+
"tmux",
|
|
15280
|
+
"list-panes",
|
|
15281
|
+
"-s",
|
|
15282
|
+
"-t",
|
|
15283
|
+
UI_SESSION,
|
|
15284
|
+
"-F",
|
|
15285
|
+
"#{window_id}|#{pane_id}|#{pane_width}|#{pane_dead}|#{remain-on-exit}|#{@synkro_cwd}|#{pane_start_command}"
|
|
15286
|
+
]);
|
|
15287
|
+
if (!listed.ok) return { ok: false, changed: false };
|
|
15288
|
+
const panes = parseUiPanes(listed.stdout);
|
|
15289
|
+
const sidebars = panes.filter((row2) => row2.command.includes("--sidebar"));
|
|
15290
|
+
if (sidebars.length === 0) return { ok: false, changed: false };
|
|
15291
|
+
let changed = false;
|
|
15292
|
+
for (const sidebar of sidebars) {
|
|
15293
|
+
const previous = panes.find((row2) => row2.window === sidebar.window && row2.pane !== sidebar.pane);
|
|
15294
|
+
const cwd = sidebar.cwd ? sidebar.cwd.startsWith("/") ? sidebar.cwd : join34(repoCwd, sidebar.cwd) : repoCwd;
|
|
15295
|
+
const center = await ensureUiCenterPane({
|
|
15296
|
+
sidebarPane: sidebar.pane,
|
|
15297
|
+
bootPath,
|
|
15298
|
+
cwd,
|
|
15299
|
+
terminalCommand: options.terminalCommand
|
|
15300
|
+
});
|
|
15301
|
+
if (!center.ok) return { ok: false, changed };
|
|
15302
|
+
if (sidebar.cwd !== cwd) {
|
|
15303
|
+
const migrated2 = await run(HOST, ["tmux", "set-option", "-t", sidebar.window, "-w", "-q", "@synkro_cwd", cwd]);
|
|
15304
|
+
if (!migrated2.ok) return { ok: false, changed };
|
|
15305
|
+
changed = true;
|
|
15306
|
+
}
|
|
15307
|
+
const staleBinding = !sidebar.command.includes("SYNKRO_UI_CENTER=" + center.pane + " ");
|
|
15308
|
+
if (options.refreshSidebars || center.recovered || staleBinding) {
|
|
15309
|
+
const rebound = await run(HOST, [
|
|
15310
|
+
"tmux",
|
|
15311
|
+
"respawn-pane",
|
|
15312
|
+
"-k",
|
|
15313
|
+
"-t",
|
|
15314
|
+
sidebar.pane,
|
|
15315
|
+
sidebarCommand(bootPath, center.pane, repoCwd)
|
|
15316
|
+
]);
|
|
15317
|
+
if (!rebound.ok) return { ok: false, changed };
|
|
15318
|
+
changed = true;
|
|
15319
|
+
}
|
|
15320
|
+
if (!previous?.remain || center.created || center.recovered) changed = true;
|
|
15321
|
+
}
|
|
15322
|
+
return { ok: true, changed };
|
|
15323
|
+
}
|
|
15324
|
+
async function refreshUiShell(bootPath, repoCwd, options = {}) {
|
|
15325
|
+
const expected = buildStamp(bootPath);
|
|
15326
|
+
const current = await run(HOST, ["tmux", "show-options", "-t", UI_SESSION, "-v", "@synkro_build"]);
|
|
15327
|
+
const buildChanged = !current.ok || current.stdout.trim() !== expected;
|
|
15328
|
+
if (buildChanged) {
|
|
15329
|
+
const width = await currentWindowColumns(UI_SESSION);
|
|
15330
|
+
if (!await styleOuterSession(bootPath, repoCwd, Number(sidebarColumns(width)))) return false;
|
|
15331
|
+
}
|
|
15332
|
+
const reconciled = await reconcileUiShell(bootPath, repoCwd, {
|
|
15333
|
+
terminalCommand: options.terminalCommand,
|
|
15334
|
+
refreshSidebars: buildChanged
|
|
15335
|
+
});
|
|
15336
|
+
if (!reconciled.ok) return false;
|
|
15337
|
+
if (reconciled.changed && options.persistLayout !== false) await snapshotTabs();
|
|
15338
|
+
if (buildChanged) {
|
|
15339
|
+
const stamped = await run(HOST, ["tmux", "set-option", "-t", UI_SESSION, "-q", "@synkro_build", expected]);
|
|
15340
|
+
if (!stamped.ok) return false;
|
|
15341
|
+
}
|
|
15342
|
+
return true;
|
|
15192
15343
|
}
|
|
15193
15344
|
async function buildTab(bootPath, repoCwd, spec) {
|
|
15194
15345
|
await run(HOST, ["tmux", "set-option", "-g", "history-limit", "50000"]);
|
|
@@ -15196,8 +15347,20 @@ async function buildTab(bootPath, repoCwd, spec) {
|
|
|
15196
15347
|
if (!await uiSessionExists()) {
|
|
15197
15348
|
const cols = String(Number(process.stdout.columns || 0) || 220);
|
|
15198
15349
|
const rows = String(Number(process.stdout.rows || 0) || 55);
|
|
15199
|
-
const create = [
|
|
15200
|
-
|
|
15350
|
+
const create = [
|
|
15351
|
+
"tmux",
|
|
15352
|
+
"new-session",
|
|
15353
|
+
"-d",
|
|
15354
|
+
"-s",
|
|
15355
|
+
UI_SESSION,
|
|
15356
|
+
"-x",
|
|
15357
|
+
cols,
|
|
15358
|
+
"-y",
|
|
15359
|
+
rows,
|
|
15360
|
+
"-c",
|
|
15361
|
+
spec.cwd,
|
|
15362
|
+
"tail -f /dev/null"
|
|
15363
|
+
];
|
|
15201
15364
|
const made = await run(HOST, create);
|
|
15202
15365
|
if (!made.ok) {
|
|
15203
15366
|
throw new Error("tmux new-session failed: " + made.stderr.trim());
|
|
@@ -15207,14 +15370,26 @@ async function buildTab(bootPath, repoCwd, spec) {
|
|
|
15207
15370
|
const first = await run(HOST, ["tmux", "list-windows", "-t", UI_SESSION, "-F", "#{window_id}"]);
|
|
15208
15371
|
windowTarget = first.stdout.split("\n")[0]?.trim() || UI_SESSION + ":1";
|
|
15209
15372
|
} else {
|
|
15210
|
-
const create = [
|
|
15211
|
-
|
|
15373
|
+
const create = [
|
|
15374
|
+
"tmux",
|
|
15375
|
+
"new-window",
|
|
15376
|
+
"-t",
|
|
15377
|
+
UI_SESSION,
|
|
15378
|
+
"-P",
|
|
15379
|
+
"-F",
|
|
15380
|
+
"#{window_id}",
|
|
15381
|
+
"-c",
|
|
15382
|
+
spec.cwd,
|
|
15383
|
+
"tail -f /dev/null"
|
|
15384
|
+
];
|
|
15212
15385
|
const created = await run(HOST, create);
|
|
15213
15386
|
windowTarget = created.stdout.trim();
|
|
15214
15387
|
if (!created.ok || !windowTarget) return;
|
|
15215
15388
|
}
|
|
15216
15389
|
const sidebarCols = sidebarColumns(await currentWindowColumns(windowTarget));
|
|
15217
|
-
await styleOuterSession(bootPath, repoCwd, Number(sidebarCols))
|
|
15390
|
+
if (!await styleOuterSession(bootPath, repoCwd, Number(sidebarCols))) {
|
|
15391
|
+
throw new Error("tmux outer session styling failed");
|
|
15392
|
+
}
|
|
15218
15393
|
const split = await run(HOST, [
|
|
15219
15394
|
"tmux",
|
|
15220
15395
|
"split-window",
|
|
@@ -15229,18 +15404,35 @@ async function buildTab(bootPath, repoCwd, spec) {
|
|
|
15229
15404
|
"tail -f /dev/null"
|
|
15230
15405
|
]);
|
|
15231
15406
|
const sidebarPane = split.stdout.trim();
|
|
15407
|
+
if (!split.ok || !sidebarPane) throw new Error("tmux sidebar split failed: " + split.stderr.trim());
|
|
15232
15408
|
const panes = await run(HOST, ["tmux", "list-panes", "-t", windowTarget, "-F", "#{pane_id}"]);
|
|
15233
15409
|
const centerPane = panes.stdout.split("\n").map((line) => line.trim()).filter(Boolean).find((id) => id !== sidebarPane) || "";
|
|
15234
|
-
|
|
15410
|
+
if (!centerPane) throw new Error("tmux center pane was not created");
|
|
15411
|
+
const kept = await run(HOST, buildKeepPaneAfterExit(centerPane));
|
|
15412
|
+
if (!kept.ok) throw new Error("tmux center durability failed: " + kept.stderr.trim());
|
|
15235
15413
|
if (spec.agentSession) {
|
|
15236
15414
|
await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_agent", spec.agentSession]);
|
|
15237
15415
|
}
|
|
15238
15416
|
await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_cwd", spec.cwd]);
|
|
15239
15417
|
await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_kind", spec.kind || "terminal"]);
|
|
15240
|
-
await run(HOST, [
|
|
15418
|
+
const sidebarStarted = await run(HOST, [
|
|
15419
|
+
"tmux",
|
|
15420
|
+
"respawn-pane",
|
|
15421
|
+
"-k",
|
|
15422
|
+
"-t",
|
|
15423
|
+
sidebarPane,
|
|
15424
|
+
sidebarCommand(bootPath, centerPane, repoCwd)
|
|
15425
|
+
]);
|
|
15426
|
+
if (!sidebarStarted.ok) throw new Error("tmux sidebar start failed: " + sidebarStarted.stderr.trim());
|
|
15427
|
+
const centerStart = ["tmux", "respawn-pane", "-k", "-t", centerPane, "-c", spec.cwd];
|
|
15428
|
+
if (spec.center) centerStart.push(spec.center);
|
|
15429
|
+
const centerStarted = await run(HOST, centerStart);
|
|
15430
|
+
if (!centerStarted.ok) throw new Error("tmux center start failed: " + centerStarted.stderr.trim());
|
|
15241
15431
|
const title = spec.title || tabTitle("terminal", spec.cwd.split("/").filter(Boolean).pop() || "space");
|
|
15242
15432
|
await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "automatic-rename", "off"]);
|
|
15243
15433
|
await run(HOST, ["tmux", "rename-window", "-t", windowTarget, title]);
|
|
15434
|
+
const stamped = await run(HOST, ["tmux", "set-option", "-t", UI_SESSION, "-q", "@synkro_build", buildStamp(bootPath)]);
|
|
15435
|
+
if (!stamped.ok) throw new Error("tmux build stamp failed: " + stamped.stderr.trim());
|
|
15244
15436
|
await snapshotTabs();
|
|
15245
15437
|
await run(HOST, ["tmux", "select-pane", "-t", spec.focus === "sidebar" ? sidebarPane : centerPane]);
|
|
15246
15438
|
}
|
|
@@ -15282,6 +15474,10 @@ async function launchUi(bootPath, repoCwd) {
|
|
|
15282
15474
|
rememberSpace(repoCwd);
|
|
15283
15475
|
if (!await uiSessionExists()) {
|
|
15284
15476
|
await buildTab(bootPath, repoCwd, { cwd: repoCwd, center: makeTerminalCommand(bootPath), focus: "sidebar" });
|
|
15477
|
+
} else {
|
|
15478
|
+
if (!await refreshUiShell(bootPath, repoCwd)) {
|
|
15479
|
+
throw new Error("Synkro UI could not repair its tmux layout");
|
|
15480
|
+
}
|
|
15285
15481
|
}
|
|
15286
15482
|
await pruneCollapsedClients();
|
|
15287
15483
|
return runInherit(process.env.TMUX ? ["tmux", "switch-client", "-t", UI_SESSION] : ["tmux", "attach-session", "-t", UI_SESSION]);
|
|
@@ -15338,10 +15534,10 @@ function row(selected, width, content) {
|
|
|
15338
15534
|
return STYLE.select + body.split(STYLE.reset).join(STYLE.reset + STYLE.select) + STYLE.reset;
|
|
15339
15535
|
}
|
|
15340
15536
|
function stripForPad(text, width) {
|
|
15341
|
-
let
|
|
15537
|
+
let visible2 = 0;
|
|
15342
15538
|
let out = "";
|
|
15343
15539
|
let index = 0;
|
|
15344
|
-
while (index < text.length &&
|
|
15540
|
+
while (index < text.length && visible2 < width) {
|
|
15345
15541
|
if (text.startsWith(ESC, index)) {
|
|
15346
15542
|
const end = text.indexOf("m", index);
|
|
15347
15543
|
if (end === -1) break;
|
|
@@ -15350,13 +15546,13 @@ function stripForPad(text, width) {
|
|
|
15350
15546
|
} else {
|
|
15351
15547
|
out += text[index];
|
|
15352
15548
|
index += 1;
|
|
15353
|
-
|
|
15549
|
+
visible2 += 1;
|
|
15354
15550
|
}
|
|
15355
15551
|
}
|
|
15356
|
-
return out + " ".repeat(Math.max(0, width -
|
|
15552
|
+
return out + " ".repeat(Math.max(0, width - visible2));
|
|
15357
15553
|
}
|
|
15358
15554
|
function visibleLength(text) {
|
|
15359
|
-
let
|
|
15555
|
+
let visible2 = 0;
|
|
15360
15556
|
let index = 0;
|
|
15361
15557
|
while (index < text.length) {
|
|
15362
15558
|
if (text.startsWith(ESC, index)) {
|
|
@@ -15364,11 +15560,11 @@ function visibleLength(text) {
|
|
|
15364
15560
|
if (end === -1) break;
|
|
15365
15561
|
index = end + 1;
|
|
15366
15562
|
} else {
|
|
15367
|
-
|
|
15563
|
+
visible2 += 1;
|
|
15368
15564
|
index += 1;
|
|
15369
15565
|
}
|
|
15370
15566
|
}
|
|
15371
|
-
return
|
|
15567
|
+
return visible2;
|
|
15372
15568
|
}
|
|
15373
15569
|
function splitRow(width, left, right) {
|
|
15374
15570
|
const gap = Math.max(1, width - 2 - visibleLength(left) - visibleLength(right));
|
|
@@ -15380,10 +15576,10 @@ function windowAround(count, selected, capacity) {
|
|
|
15380
15576
|
return { start, end: start + capacity };
|
|
15381
15577
|
}
|
|
15382
15578
|
function renderCollapsed(state, width, height) {
|
|
15383
|
-
const
|
|
15579
|
+
const lines2 = [];
|
|
15384
15580
|
const targets = [];
|
|
15385
15581
|
const push2 = (text, target = null) => {
|
|
15386
|
-
|
|
15582
|
+
lines2.push(stripForPad(" " + text, width));
|
|
15387
15583
|
targets.push(target);
|
|
15388
15584
|
};
|
|
15389
15585
|
push2("");
|
|
@@ -15400,16 +15596,16 @@ function renderCollapsed(state, width, height) {
|
|
|
15400
15596
|
});
|
|
15401
15597
|
push2("");
|
|
15402
15598
|
push2(STYLE.dim + "+" + STYLE.reset, { kind: "new" });
|
|
15403
|
-
while (
|
|
15599
|
+
while (lines2.length < height - 1) push2("");
|
|
15404
15600
|
push2(STYLE.dim + "\u203A\u203A" + STYLE.reset, { kind: "collapse" });
|
|
15405
|
-
return { lines:
|
|
15601
|
+
return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
|
|
15406
15602
|
}
|
|
15407
15603
|
function renderLayout(state, width = 30, height = 40) {
|
|
15408
15604
|
if (state.collapsed) return renderCollapsed(state, width, height);
|
|
15409
|
-
const
|
|
15605
|
+
const lines2 = [];
|
|
15410
15606
|
const targets = [];
|
|
15411
15607
|
const push2 = (line, target = null) => {
|
|
15412
|
-
|
|
15608
|
+
lines2.push(line);
|
|
15413
15609
|
targets.push(target);
|
|
15414
15610
|
};
|
|
15415
15611
|
const chrome = 8;
|
|
@@ -15431,7 +15627,7 @@ function renderLayout(state, width = 30, height = 40) {
|
|
|
15431
15627
|
push2(row(selected, width, " " + STYLE.branch + clip(space.branch, width - 6 - (space.track ? space.track.length + 1 : 0)) + STYLE.reset + drift), target);
|
|
15432
15628
|
});
|
|
15433
15629
|
if (state.spaces.length === 0) push2(row(false, width, STYLE.dim + "no spaces found" + STYLE.reset));
|
|
15434
|
-
while (
|
|
15630
|
+
while (lines2.length < 3 + spacesCapacity * 2) push2(pad("", width));
|
|
15435
15631
|
push2("");
|
|
15436
15632
|
push2(splitRow(width, "new", "menu"), { kind: "new" });
|
|
15437
15633
|
push2(STYLE.dim + "\u2500".repeat(Math.max(0, width)) + STYLE.reset);
|
|
@@ -15476,13 +15672,13 @@ function renderLayout(state, width = 30, height = 40) {
|
|
|
15476
15672
|
push2("");
|
|
15477
15673
|
push2(row(false, width, STYLE.blocked + "\u26D4 needs consent" + STYLE.reset));
|
|
15478
15674
|
for (const action of actionsForAsk(selectedAgent.ask)) {
|
|
15479
|
-
const
|
|
15480
|
-
push2(row(false, width, STYLE.dim + " " +
|
|
15675
|
+
const key2 = action === "track" ? "g" : action === "skip" ? "s" : "y";
|
|
15676
|
+
push2(row(false, width, STYLE.dim + " " + key2 + " \u2014 " + action + STYLE.reset));
|
|
15481
15677
|
}
|
|
15482
15678
|
}
|
|
15483
|
-
while (
|
|
15679
|
+
while (lines2.length < height - 1) push2(pad("", width));
|
|
15484
15680
|
push2(stripForPad(pad("", width - 3) + STYLE.dim + "\u2039\u2039 " + STYLE.reset, width), { kind: "collapse" });
|
|
15485
|
-
return { lines:
|
|
15681
|
+
return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
|
|
15486
15682
|
}
|
|
15487
15683
|
function nextSelection(state, spaces, agents, delta) {
|
|
15488
15684
|
const flat = state.section === "spaces" ? state.spaceIndex : spaces + state.agentIndex;
|
|
@@ -15631,8 +15827,8 @@ async function spawnAgent(info, request) {
|
|
|
15631
15827
|
if (request.backend === "container") {
|
|
15632
15828
|
cwd = request.cwd.startsWith(CONTAINER_WORK) ? request.cwd : await provisionContainerWorkspace(runner, slug);
|
|
15633
15829
|
}
|
|
15634
|
-
const command = harnessCommand(request);
|
|
15635
|
-
for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend })) {
|
|
15830
|
+
const command = request.command || harnessCommand(request);
|
|
15831
|
+
for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend, mode: request.mode })) {
|
|
15636
15832
|
const result = await run(runner, argv);
|
|
15637
15833
|
if (!result.ok && argv[1] === "new-session") {
|
|
15638
15834
|
return { ok: false, session, error: result.stderr.trim() || "tmux new-session failed" };
|
|
@@ -15646,7 +15842,8 @@ async function spawnAgent(info, request) {
|
|
|
15646
15842
|
harness: request.harness,
|
|
15647
15843
|
space: cwd,
|
|
15648
15844
|
spaceName: request.spaceName,
|
|
15649
|
-
backend: request.backend
|
|
15845
|
+
backend: request.backend,
|
|
15846
|
+
mode: request.mode || "native"
|
|
15650
15847
|
});
|
|
15651
15848
|
return { ok: true, session };
|
|
15652
15849
|
}
|
|
@@ -15753,8 +15950,11 @@ var init_awake = __esm({
|
|
|
15753
15950
|
function runnerFor(backend) {
|
|
15754
15951
|
return backend === "container" ? { kind: "container", container: CONTAINER_NAME2 } : { kind: "host" };
|
|
15755
15952
|
}
|
|
15953
|
+
function hasLiveAgentCenter(paneOutput, session) {
|
|
15954
|
+
return String(paneOutput || "").split("\n").map((line) => line.split("|")).some(([dead, ...command]) => dead === "0" && !command.join("|").includes("--sidebar") && command.join("|").includes(session));
|
|
15955
|
+
}
|
|
15756
15956
|
async function runSidebar() {
|
|
15757
|
-
|
|
15957
|
+
let centerPane = process.env.SYNKRO_UI_CENTER || "";
|
|
15758
15958
|
const outerSession = process.env.SYNKRO_UI_OUTER || "synkro-ui";
|
|
15759
15959
|
const bootPath = process.env.SYNKRO_UI_BOOT || process.argv[1];
|
|
15760
15960
|
const repoCwd = process.env.SYNKRO_UI_REPO || process.cwd();
|
|
@@ -15922,53 +16122,114 @@ async function runSidebar() {
|
|
|
15922
16122
|
const agent = state.agents[state.agentIndex];
|
|
15923
16123
|
if (agent) await showAgent(agent);
|
|
15924
16124
|
}
|
|
16125
|
+
async function resolveCenterPane(cwd) {
|
|
16126
|
+
if (!ownPane || !cwd) return "";
|
|
16127
|
+
const resolved = await ensureUiCenterPane({ sidebarPane: ownPane, bootPath, cwd });
|
|
16128
|
+
if (!resolved.ok) {
|
|
16129
|
+
state.message = "could not restore this tab";
|
|
16130
|
+
return "";
|
|
16131
|
+
}
|
|
16132
|
+
centerPane = resolved.pane;
|
|
16133
|
+
if (resolved.recovered) {
|
|
16134
|
+
state.viewing = "";
|
|
16135
|
+
await snapshotTabs();
|
|
16136
|
+
stripWidth = -1;
|
|
16137
|
+
await syncChipStrip();
|
|
16138
|
+
}
|
|
16139
|
+
return centerPane;
|
|
16140
|
+
}
|
|
15925
16141
|
async function showWorkspaceTerminal(cwd) {
|
|
15926
|
-
|
|
16142
|
+
const pane = await resolveCenterPane(cwd);
|
|
16143
|
+
if (!pane) return false;
|
|
15927
16144
|
const command = makeTerminalCommand(bootPath);
|
|
15928
|
-
const argv = ["tmux", "respawn-pane", "-k", "-t",
|
|
16145
|
+
const argv = ["tmux", "respawn-pane", "-k", "-t", pane, "-c", cwd];
|
|
15929
16146
|
if (command) argv.push(command);
|
|
15930
16147
|
const respawned = await run(host, argv);
|
|
15931
16148
|
if (!respawned.ok) {
|
|
15932
16149
|
state.message = "could not restore workspace terminal";
|
|
15933
|
-
return;
|
|
16150
|
+
return false;
|
|
15934
16151
|
}
|
|
15935
16152
|
const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
16153
|
+
const metadata = [
|
|
16154
|
+
["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_agent", ""],
|
|
16155
|
+
["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_cwd", cwd],
|
|
16156
|
+
["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_kind", "terminal"],
|
|
16157
|
+
["tmux", "rename-window", "-t", pane, tabTitle("terminal", spaceName)]
|
|
16158
|
+
];
|
|
16159
|
+
for (const commandArgv of metadata) {
|
|
16160
|
+
if (!(await run(host, commandArgv)).ok) {
|
|
16161
|
+
state.message = "could not update this tab";
|
|
16162
|
+
return false;
|
|
16163
|
+
}
|
|
16164
|
+
}
|
|
15940
16165
|
state.viewing = "";
|
|
15941
16166
|
await snapshotTabs();
|
|
16167
|
+
return true;
|
|
15942
16168
|
}
|
|
15943
|
-
async function
|
|
15944
|
-
if (!centerPane) return;
|
|
15945
|
-
if (agent.status === "offline") {
|
|
15946
|
-
await restoreSelected();
|
|
15947
|
-
return;
|
|
15948
|
-
}
|
|
16169
|
+
async function markAgentViewed(agent) {
|
|
15949
16170
|
const current = memory.get(agent.session);
|
|
15950
16171
|
memory.set(agent.session, { hash: current?.hash || "", wasWorking: false, seen: true });
|
|
15951
16172
|
if (agent.space) rememberLastAgent(await repoOf(agent.space), agent.session);
|
|
15952
16173
|
state.viewing = agent.session;
|
|
16174
|
+
}
|
|
16175
|
+
async function showAgent(agent) {
|
|
16176
|
+
if (agent.status === "offline") {
|
|
16177
|
+
await restoreSelected();
|
|
16178
|
+
return false;
|
|
16179
|
+
}
|
|
15953
16180
|
const windows = await run(host, ["tmux", "list-windows", "-t", outerSession, "-F", "#{window_id}|#{@synkro_agent}"]);
|
|
15954
16181
|
const holder = windows.stdout.split("\n").map((line) => line.split("|")).find((cols) => cols[1] === agent.session);
|
|
15955
16182
|
if (holder) {
|
|
15956
|
-
await run(host, [
|
|
15957
|
-
|
|
15958
|
-
|
|
16183
|
+
const holderPanes = await run(host, [
|
|
16184
|
+
"tmux",
|
|
16185
|
+
"list-panes",
|
|
16186
|
+
"-t",
|
|
16187
|
+
holder[0],
|
|
16188
|
+
"-F",
|
|
16189
|
+
"#{pane_dead}|#{pane_start_command}"
|
|
16190
|
+
]);
|
|
16191
|
+
const liveCenter = holderPanes.ok && hasLiveAgentCenter(holderPanes.stdout, agent.session);
|
|
16192
|
+
if (liveCenter) {
|
|
16193
|
+
const selected = await run(host, ["tmux", "select-window", "-t", holder[0]]);
|
|
16194
|
+
if (selected.ok) {
|
|
16195
|
+
await markAgentViewed(agent);
|
|
16196
|
+
state.message = agent.name;
|
|
16197
|
+
return true;
|
|
16198
|
+
}
|
|
16199
|
+
}
|
|
16200
|
+
await run(host, ["tmux", "set-option", "-t", holder[0], "-w", "-q", "@synkro_agent", ""]);
|
|
15959
16201
|
}
|
|
16202
|
+
const pane = await resolveCenterPane(agent.space || selectedSpace()?.path || repoCwd);
|
|
16203
|
+
if (!pane) return false;
|
|
15960
16204
|
await run(runnerFor(agent.backend), buildEnableMouse(agent.session));
|
|
15961
16205
|
for (const argv of buildClipboardBindings(agent.session)) {
|
|
15962
16206
|
await run(runnerFor(agent.backend), argv);
|
|
15963
16207
|
}
|
|
15964
16208
|
const command = buildCenterAttachCommand(runnerFor(agent.backend), agent.session);
|
|
15965
|
-
await run(host, ["tmux", "respawn-pane", "-k", "-t",
|
|
16209
|
+
const attached = await run(host, ["tmux", "respawn-pane", "-k", "-t", pane, command]);
|
|
16210
|
+
if (!attached.ok) {
|
|
16211
|
+
state.message = "could not open " + agent.name;
|
|
16212
|
+
await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
|
|
16213
|
+
return false;
|
|
16214
|
+
}
|
|
15966
16215
|
const where = agent.space ? agent.space.split("/").filter(Boolean).pop() || "" : "";
|
|
15967
|
-
|
|
15968
|
-
|
|
15969
|
-
|
|
16216
|
+
const metadata = [
|
|
16217
|
+
["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_agent", agent.session],
|
|
16218
|
+
["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_cwd", agent.space || repoCwd],
|
|
16219
|
+
["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_kind", agent.harness],
|
|
16220
|
+
["tmux", "rename-window", "-t", pane, tabTitle(agent.harness, where)]
|
|
16221
|
+
];
|
|
16222
|
+
for (const commandArgv of metadata) {
|
|
16223
|
+
if (!(await run(host, commandArgv)).ok) {
|
|
16224
|
+
state.message = "could not update " + agent.name + " tab";
|
|
16225
|
+
await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
|
|
16226
|
+
return false;
|
|
16227
|
+
}
|
|
16228
|
+
}
|
|
16229
|
+
await markAgentViewed(agent);
|
|
15970
16230
|
await snapshotTabs();
|
|
15971
16231
|
state.message = "attached " + agent.name;
|
|
16232
|
+
return true;
|
|
15972
16233
|
}
|
|
15973
16234
|
async function confirmKillAgent() {
|
|
15974
16235
|
const agent = state.agents[state.agentIndex];
|
|
@@ -15979,7 +16240,8 @@ async function runSidebar() {
|
|
|
15979
16240
|
if (allAgents.some((candidate) => candidate.session === agent.session && candidate.status !== "offline")) return;
|
|
15980
16241
|
state.message = "closed " + agent.name;
|
|
15981
16242
|
if (!wasViewing) return;
|
|
15982
|
-
await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
|
|
16243
|
+
const restored = await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
|
|
16244
|
+
if (!restored) return;
|
|
15983
16245
|
const successor = nextLiveAgent(allAgents, agent.repo || "");
|
|
15984
16246
|
if (successor) await showAgent(successor);
|
|
15985
16247
|
}
|
|
@@ -16046,45 +16308,45 @@ async function runSidebar() {
|
|
|
16046
16308
|
}
|
|
16047
16309
|
await activate(target);
|
|
16048
16310
|
}
|
|
16049
|
-
async function handleKey(
|
|
16050
|
-
if (
|
|
16051
|
-
else if (
|
|
16052
|
-
else if (
|
|
16053
|
-
else if (
|
|
16311
|
+
async function handleKey(key2) {
|
|
16312
|
+
if (key2 === " ") state.section = state.section === "spaces" ? "agents" : "spaces";
|
|
16313
|
+
else if (key2 === "j" || key2 === CSI + "B") moveSelection(1);
|
|
16314
|
+
else if (key2 === "k" || key2 === CSI + "A") moveSelection(-1);
|
|
16315
|
+
else if (key2 === "\r") {
|
|
16054
16316
|
if (state.section === "agents") await attachSelected();
|
|
16055
16317
|
else await openDialog("new-tab");
|
|
16056
|
-
} else if (
|
|
16318
|
+
} else if (key2 === "n" || key2 === "T") {
|
|
16057
16319
|
worldChanged = true;
|
|
16058
16320
|
await openDialog("new-tab");
|
|
16059
|
-
} else if (
|
|
16060
|
-
else if (
|
|
16061
|
-
else if (
|
|
16321
|
+
} else if (key2 === "m") await mainMenu();
|
|
16322
|
+
else if (key2 === "O") void openDialog("new-workspace");
|
|
16323
|
+
else if (key2 === "C") {
|
|
16062
16324
|
worldChanged = true;
|
|
16063
16325
|
closeSelectedWorkspace();
|
|
16064
|
-
} else if (
|
|
16326
|
+
} else if (key2 === "r") {
|
|
16065
16327
|
worldChanged = true;
|
|
16066
16328
|
await restoreSelected();
|
|
16067
|
-
} else if (
|
|
16329
|
+
} else if (key2 === "d") {
|
|
16068
16330
|
const client = await attachedClient();
|
|
16069
16331
|
await run(host, client ? ["tmux", "detach-client", "-t", client] : ["tmux", "detach-client"]);
|
|
16070
|
-
} else if (
|
|
16071
|
-
else if (
|
|
16332
|
+
} else if (key2 === "K") await keybindsMenu();
|
|
16333
|
+
else if (key2 === "G") {
|
|
16072
16334
|
state.grouped = !state.grouped;
|
|
16073
16335
|
applyFilter();
|
|
16074
|
-
} else if (
|
|
16336
|
+
} else if (key2 === "a") {
|
|
16075
16337
|
state.filter = state.filter === "space" ? "all" : "space";
|
|
16076
16338
|
applyFilter();
|
|
16077
|
-
} else if (
|
|
16078
|
-
else if (
|
|
16339
|
+
} else if (key2 === "<" || key2 === ">" || key2 === "," || key2 === ".") await toggleCollapsed();
|
|
16340
|
+
else if (key2 === "x") {
|
|
16079
16341
|
worldChanged = true;
|
|
16080
16342
|
await confirmKillAgent();
|
|
16081
|
-
} else if (
|
|
16343
|
+
} else if (key2 === "i") {
|
|
16082
16344
|
const agent = state.agents[state.agentIndex];
|
|
16083
16345
|
if (agent) await run(runnerFor(agent.backend), buildInterrupt(agent.session));
|
|
16084
|
-
} else if (
|
|
16085
|
-
else if (
|
|
16086
|
-
else if (
|
|
16087
|
-
else if (
|
|
16346
|
+
} else if (key2 === "g") await consent("track");
|
|
16347
|
+
else if (key2 === "s") await consent("skip");
|
|
16348
|
+
else if (key2 === "y") await consent("stay");
|
|
16349
|
+
else if (key2 === "q" || key2 === KEY_CTRL_C) {
|
|
16088
16350
|
await snapshotTabs();
|
|
16089
16351
|
releaseAwake();
|
|
16090
16352
|
await run(host, ["tmux", "kill-session", "-t", outerSession]);
|
|
@@ -16206,8 +16468,8 @@ var init_repos = __esm({
|
|
|
16206
16468
|
|
|
16207
16469
|
// cli/ui/tabs.ts
|
|
16208
16470
|
import { execSync as execSync7 } from "child_process";
|
|
16209
|
-
function
|
|
16210
|
-
return
|
|
16471
|
+
function embeddedSessionCommand(bootPath, harness, cwd) {
|
|
16472
|
+
return ["node", bootPath, "ui", "--run", harness, cwd].map(shellQuote3).join(" ");
|
|
16211
16473
|
}
|
|
16212
16474
|
function repoRoot() {
|
|
16213
16475
|
try {
|
|
@@ -16229,6 +16491,29 @@ async function createTab(bootPath, kind, spacePath) {
|
|
|
16229
16491
|
});
|
|
16230
16492
|
return;
|
|
16231
16493
|
}
|
|
16494
|
+
if (kind === "cursor-synkro" || kind === "codex-synkro") {
|
|
16495
|
+
const harness2 = kind === "codex-synkro" ? "codex" : "cursor";
|
|
16496
|
+
const command = embeddedSessionCommand(bootPath, harness2, spacePath);
|
|
16497
|
+
const spawned2 = await spawnAgent({ runner: HOST2, backend: "host", note: "runtime: host" }, {
|
|
16498
|
+
name: space + "-" + harness2 + "-synkro-" + String(process.pid % 1e4),
|
|
16499
|
+
harness: harness2,
|
|
16500
|
+
spaceName: space,
|
|
16501
|
+
cwd: spacePath,
|
|
16502
|
+
backend: "host",
|
|
16503
|
+
command,
|
|
16504
|
+
mode: "embedded"
|
|
16505
|
+
});
|
|
16506
|
+
if (!spawned2.ok) return;
|
|
16507
|
+
await buildTab(bootPath, repo, {
|
|
16508
|
+
cwd: spacePath,
|
|
16509
|
+
center: buildCenterAttachCommand(HOST2, spawned2.session),
|
|
16510
|
+
title: tabTitle(harness2, space),
|
|
16511
|
+
kind,
|
|
16512
|
+
agentSession: spawned2.session,
|
|
16513
|
+
focus: "center"
|
|
16514
|
+
});
|
|
16515
|
+
return;
|
|
16516
|
+
}
|
|
16232
16517
|
if (kind === "settings") {
|
|
16233
16518
|
await buildTab(bootPath, repo, {
|
|
16234
16519
|
cwd: spacePath,
|
|
@@ -16272,43 +16557,26 @@ async function openAgentTab(bootPath, session) {
|
|
|
16272
16557
|
"-p",
|
|
16273
16558
|
"-t",
|
|
16274
16559
|
session,
|
|
16275
|
-
["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}"].join("|")
|
|
16560
|
+
["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}", "#{@synkro_mode}"].join("|")
|
|
16276
16561
|
]);
|
|
16277
|
-
const [harness, space, backend] = (meta.stdout.trim() || "
|
|
16562
|
+
const [harness, space, backend, mode] = (meta.stdout.trim() || "|||").split("|");
|
|
16278
16563
|
const containerHosted = backend === "container";
|
|
16279
|
-
|
|
16280
|
-
let activeSession = session;
|
|
16564
|
+
const runner = mode === "embedded" ? HOST2 : containerHosted ? info.runner : HOST2;
|
|
16281
16565
|
const spaceName = (space || "").split("/").filter(Boolean).pop() || "space";
|
|
16282
|
-
|
|
16283
|
-
const command = await run(HOST2, ["tmux", "display-message", "-p", "-t", session, "#{pane_start_command}"]);
|
|
16284
|
-
if (command.ok && isLegacyCodexRenderer(command.stdout)) {
|
|
16285
|
-
await run(HOST2, buildKillSession(session));
|
|
16286
|
-
const restored = await spawnAgent(info, {
|
|
16287
|
-
name: session.replace(/^synkro-agent-/, ""),
|
|
16288
|
-
harness: "codex",
|
|
16289
|
-
spaceName,
|
|
16290
|
-
cwd: space || repoRoot(),
|
|
16291
|
-
backend: "host",
|
|
16292
|
-
resume: true
|
|
16293
|
-
});
|
|
16294
|
-
if (!restored.ok) return;
|
|
16295
|
-
activeSession = restored.session;
|
|
16296
|
-
runner = HOST2;
|
|
16297
|
-
}
|
|
16298
|
-
}
|
|
16299
|
-
const alive = (await run(runner, ["tmux", "has-session", "-t", activeSession])).ok;
|
|
16566
|
+
const alive = (await run(runner, ["tmux", "has-session", "-t", session])).ok;
|
|
16300
16567
|
if (!alive) return;
|
|
16301
16568
|
await buildTab(bootPath, repoRoot(), {
|
|
16302
16569
|
cwd: space || repoRoot(),
|
|
16303
|
-
center: buildCenterAttachCommand(runner,
|
|
16570
|
+
center: buildCenterAttachCommand(runner, session),
|
|
16304
16571
|
title: tabTitle(harness || "claude", spaceName),
|
|
16305
|
-
kind: harness || "claude",
|
|
16306
|
-
agentSession:
|
|
16572
|
+
kind: mode === "embedded" ? (harness || "cursor") + "-synkro" : harness || "claude",
|
|
16573
|
+
agentSession: session,
|
|
16307
16574
|
focus: "center"
|
|
16308
16575
|
});
|
|
16309
16576
|
}
|
|
16310
16577
|
async function restoreTabs(bootPath, repoCwd) {
|
|
16311
16578
|
const tabs = loadTabs();
|
|
16579
|
+
const records = loadRecords();
|
|
16312
16580
|
if (tabs.length === 0) return false;
|
|
16313
16581
|
const info = await detectContainerBackend();
|
|
16314
16582
|
const hostSessions = (await run(HOST2, ["tmux", "list-sessions", "-F", "#{session_name}"])).stdout;
|
|
@@ -16318,16 +16586,11 @@ async function restoreTabs(bootPath, repoCwd) {
|
|
|
16318
16586
|
for (const tab of tabs) {
|
|
16319
16587
|
const space = tab.cwd.split("/").filter(Boolean).pop() || "space";
|
|
16320
16588
|
const containerHosted = info.backend === "container" && (await run(info.runner, ["test", "-d", tab.cwd])).ok;
|
|
16321
|
-
const
|
|
16322
|
-
const
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
if (command.ok && isLegacyCodexRenderer(command.stdout)) {
|
|
16327
|
-
await run(HOST2, buildKillSession(tab.agentSession));
|
|
16328
|
-
sessionAlive = false;
|
|
16329
|
-
}
|
|
16330
|
-
}
|
|
16589
|
+
const embedded = tab.kind === "cursor-synkro" || tab.kind === "codex-synkro";
|
|
16590
|
+
const harness = embedded ? tab.kind.replace("-synkro", "") : ["claude", "codex", "cursor"].includes(tab.kind) ? tab.kind : "";
|
|
16591
|
+
const sessionRunner = embedded || harness === "codex" ? HOST2 : containerHosted ? info.runner : HOST2;
|
|
16592
|
+
const sessionAlive = Boolean(tab.agentSession && alive.has(tab.agentSession));
|
|
16593
|
+
const record2 = records.find((row2) => row2.session === tab.agentSession);
|
|
16331
16594
|
if (tab.agentSession && sessionAlive) {
|
|
16332
16595
|
await run(sessionRunner, buildEnableMouse(tab.agentSession));
|
|
16333
16596
|
for (const argv of buildClipboardBindings(tab.agentSession)) {
|
|
@@ -16342,20 +16605,23 @@ async function restoreTabs(bootPath, repoCwd) {
|
|
|
16342
16605
|
focus: "center"
|
|
16343
16606
|
});
|
|
16344
16607
|
} else if (harness) {
|
|
16608
|
+
const command = embedded ? embeddedSessionCommand(bootPath, harness, tab.cwd) : void 0;
|
|
16345
16609
|
const spawned = await spawnAgent(info, {
|
|
16346
|
-
name: space + "-" + harness + "-" + String(process.pid % 1e4),
|
|
16610
|
+
name: record2?.name || space + "-" + harness + "-" + String(process.pid % 1e4),
|
|
16347
16611
|
harness,
|
|
16348
16612
|
spaceName: space,
|
|
16349
16613
|
cwd: tab.cwd,
|
|
16350
|
-
backend: harness === "codex" ? "host" : containerHosted ? "container" : "host",
|
|
16351
|
-
resume:
|
|
16614
|
+
backend: embedded || harness === "codex" ? "host" : containerHosted ? "container" : "host",
|
|
16615
|
+
resume: !embedded,
|
|
16616
|
+
command,
|
|
16617
|
+
mode: embedded ? "embedded" : "native"
|
|
16352
16618
|
});
|
|
16353
|
-
const restoredInContainer = harness !== "codex" && containerHosted;
|
|
16619
|
+
const restoredInContainer = !embedded && harness !== "codex" && containerHosted;
|
|
16354
16620
|
await buildTab(bootPath, repoCwd, spawned.ok ? {
|
|
16355
16621
|
cwd: tab.cwd,
|
|
16356
16622
|
center: buildCenterAttachCommand(restoredInContainer ? info.runner : HOST2, spawned.session),
|
|
16357
16623
|
title: tabTitle(harness, space),
|
|
16358
|
-
kind: harness,
|
|
16624
|
+
kind: embedded ? tab.kind : harness,
|
|
16359
16625
|
agentSession: spawned.session,
|
|
16360
16626
|
focus: "center"
|
|
16361
16627
|
} : { cwd: tab.cwd, center: makeTerminalCommand(bootPath), title: tabTitle("terminal", space), kind: "terminal", focus: "center" });
|
|
@@ -16387,11 +16653,20 @@ var init_tabs = __esm({
|
|
|
16387
16653
|
// cli/ui/dialog.ts
|
|
16388
16654
|
import { existsSync as existsSync37 } from "fs";
|
|
16389
16655
|
import { homedir as homedir37 } from "os";
|
|
16656
|
+
function providerChoices(harnesses) {
|
|
16657
|
+
return [
|
|
16658
|
+
{ value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
|
|
16659
|
+
...harnesses.map((harness) => ({
|
|
16660
|
+
value: harness === "cursor" || harness === "codex" ? harness + "-synkro" : harness,
|
|
16661
|
+
label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
|
|
16662
|
+
}))
|
|
16663
|
+
];
|
|
16664
|
+
}
|
|
16390
16665
|
function write(text) {
|
|
16391
16666
|
process.stdout.write(text);
|
|
16392
16667
|
}
|
|
16393
16668
|
function visibleLength2(text) {
|
|
16394
|
-
let
|
|
16669
|
+
let visible2 = 0;
|
|
16395
16670
|
let index = 0;
|
|
16396
16671
|
while (index < text.length) {
|
|
16397
16672
|
if (text.startsWith(CSI2, index)) {
|
|
@@ -16399,11 +16674,11 @@ function visibleLength2(text) {
|
|
|
16399
16674
|
if (end === -1) break;
|
|
16400
16675
|
index = end + 1;
|
|
16401
16676
|
} else {
|
|
16402
|
-
|
|
16677
|
+
visible2 += 1;
|
|
16403
16678
|
index += 1;
|
|
16404
16679
|
}
|
|
16405
16680
|
}
|
|
16406
|
-
return
|
|
16681
|
+
return visible2;
|
|
16407
16682
|
}
|
|
16408
16683
|
function clipPath(text, max) {
|
|
16409
16684
|
const value = String(text || "");
|
|
@@ -16446,34 +16721,34 @@ async function pick(opts) {
|
|
|
16446
16721
|
const perItem = opts.choices.some((choice) => choice.detail) ? 2 : 1;
|
|
16447
16722
|
const visibleItems = Math.max(1, Math.floor(Math.max(2, height - 5) / perItem));
|
|
16448
16723
|
const firstRow = 4;
|
|
16449
|
-
return new Promise((
|
|
16724
|
+
return new Promise((resolve9) => {
|
|
16450
16725
|
const view = () => opts.choices.filter((choice) => matches(choice, filter));
|
|
16451
16726
|
const draw = () => {
|
|
16452
16727
|
const shown = view();
|
|
16453
16728
|
if (selected >= shown.length) selected = Math.max(0, shown.length - 1);
|
|
16454
16729
|
if (selected < top) top = selected;
|
|
16455
16730
|
if (selected >= top + visibleItems) top = selected - visibleItems + 1;
|
|
16456
|
-
const
|
|
16457
|
-
|
|
16731
|
+
const lines2 = [];
|
|
16732
|
+
lines2.push(" " + S.title + opts.title + S.reset);
|
|
16458
16733
|
const left = filter && !opts.menu ? " " + S.accent + "/ " + S.reset + filter + "\u258C" : " " + S.dim + opts.hint + S.reset;
|
|
16459
16734
|
const right = opts.menu ? "" : S.dim + String(shown.length) + (shown.length === 1 ? " match" : " matches") + S.reset;
|
|
16460
|
-
|
|
16461
|
-
|
|
16462
|
-
if (shown.length === 0)
|
|
16735
|
+
lines2.push(padRow(left, Math.max(0, width - visibleLength2(right) - 1)) + right);
|
|
16736
|
+
lines2.push("");
|
|
16737
|
+
if (shown.length === 0) lines2.push(" " + S.dim + (opts.emptyNote || "nothing matches") + S.reset);
|
|
16463
16738
|
shown.slice(top, top + visibleItems).forEach((choice, offset) => {
|
|
16464
16739
|
const index = top + offset;
|
|
16465
16740
|
const isSelected = index === selected;
|
|
16466
16741
|
const marker = isSelected ? S.accent + "\u203A" + S.reset + " " : " ";
|
|
16467
16742
|
const note = choice.note ? S.dim + choice.note + S.reset : "";
|
|
16468
16743
|
const head = " " + marker + S.bold + clip2(choice.label, width - 6 - visibleLength2(note)) + S.reset;
|
|
16469
|
-
|
|
16744
|
+
lines2.push(selectable(padRow(head, Math.max(0, width - visibleLength2(note) - 1)) + note, width, isSelected));
|
|
16470
16745
|
if (perItem === 2) {
|
|
16471
|
-
|
|
16746
|
+
lines2.push(selectable(" " + S.muted + clipPath(choice.detail || "", width - 6) + S.reset, width, isSelected));
|
|
16472
16747
|
}
|
|
16473
16748
|
});
|
|
16474
|
-
while (
|
|
16475
|
-
|
|
16476
|
-
write(CSI2 + "H" +
|
|
16749
|
+
while (lines2.length < height - 1) lines2.push("");
|
|
16750
|
+
lines2.push(" " + S.dim + opts.footer + S.reset);
|
|
16751
|
+
write(CSI2 + "H" + lines2.slice(0, height).map((line) => padRow(line, width)).join("\n"));
|
|
16477
16752
|
};
|
|
16478
16753
|
const rowOfItem = (index) => firstRow + (index - top) * perItem;
|
|
16479
16754
|
const MOUSE = /\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
@@ -16494,7 +16769,7 @@ async function pick(opts) {
|
|
|
16494
16769
|
const hit = shown.findIndex((_, index) => y >= rowOfItem(index) && y < rowOfItem(index) + perItem);
|
|
16495
16770
|
if (hit >= 0) {
|
|
16496
16771
|
process.stdin.off("data", onData);
|
|
16497
|
-
|
|
16772
|
+
resolve9(shown[hit].value);
|
|
16498
16773
|
return;
|
|
16499
16774
|
}
|
|
16500
16775
|
}
|
|
@@ -16502,13 +16777,13 @@ async function pick(opts) {
|
|
|
16502
16777
|
if (!sawMouse) {
|
|
16503
16778
|
if (input === KEY_ESC && !input.includes("[<") || input === KEY_CTRL_C2) {
|
|
16504
16779
|
process.stdin.off("data", onData);
|
|
16505
|
-
|
|
16780
|
+
resolve9(null);
|
|
16506
16781
|
return;
|
|
16507
16782
|
}
|
|
16508
16783
|
if (input === "\r") {
|
|
16509
16784
|
if (shown.length === 0) return;
|
|
16510
16785
|
process.stdin.off("data", onData);
|
|
16511
|
-
|
|
16786
|
+
resolve9(shown[selected].value);
|
|
16512
16787
|
return;
|
|
16513
16788
|
}
|
|
16514
16789
|
const down = input === CSI2 + "B" || opts.menu && (input === "j" || input === CSI2 + "C");
|
|
@@ -16532,9 +16807,9 @@ async function readLine(opts) {
|
|
|
16532
16807
|
let value = "";
|
|
16533
16808
|
let error = "";
|
|
16534
16809
|
const width = Math.max(20, Number(process.stdout.columns || 80));
|
|
16535
|
-
return new Promise((
|
|
16810
|
+
return new Promise((resolve9) => {
|
|
16536
16811
|
const draw = () => {
|
|
16537
|
-
const
|
|
16812
|
+
const lines2 = [
|
|
16538
16813
|
" " + S.title + opts.title + S.reset,
|
|
16539
16814
|
"",
|
|
16540
16815
|
" " + S.dim + opts.label + S.reset,
|
|
@@ -16545,13 +16820,13 @@ async function readLine(opts) {
|
|
|
16545
16820
|
"",
|
|
16546
16821
|
" " + S.dim + opts.footer + S.reset
|
|
16547
16822
|
];
|
|
16548
|
-
write(CSI2 + "2J" + CSI2 + "H" +
|
|
16823
|
+
write(CSI2 + "2J" + CSI2 + "H" + lines2.map((line) => padRow(line, width)).join("\n"));
|
|
16549
16824
|
};
|
|
16550
16825
|
const onData = (chunk) => {
|
|
16551
16826
|
const input = chunk.toString("utf8");
|
|
16552
16827
|
if (input === KEY_ESC || input === KEY_CTRL_C2) {
|
|
16553
16828
|
process.stdin.off("data", onData);
|
|
16554
|
-
|
|
16829
|
+
resolve9(null);
|
|
16555
16830
|
return;
|
|
16556
16831
|
}
|
|
16557
16832
|
if (input === "\r") {
|
|
@@ -16563,7 +16838,7 @@ async function readLine(opts) {
|
|
|
16563
16838
|
return;
|
|
16564
16839
|
}
|
|
16565
16840
|
process.stdin.off("data", onData);
|
|
16566
|
-
|
|
16841
|
+
resolve9(value.trim());
|
|
16567
16842
|
})();
|
|
16568
16843
|
return;
|
|
16569
16844
|
}
|
|
@@ -16823,13 +17098,7 @@ async function runDialog(kind, repoCwd, argA = "", argB = "") {
|
|
|
16823
17098
|
process.exit(0);
|
|
16824
17099
|
}
|
|
16825
17100
|
const harnesses = await detectHarnesses();
|
|
16826
|
-
const sessions =
|
|
16827
|
-
{ value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
|
|
16828
|
-
...harnesses.map((harness) => ({
|
|
16829
|
-
value: harness,
|
|
16830
|
-
label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
|
|
16831
|
-
}))
|
|
16832
|
-
];
|
|
17101
|
+
const sessions = providerChoices(harnesses);
|
|
16833
17102
|
for (; ; ) {
|
|
16834
17103
|
const session = await pick({
|
|
16835
17104
|
menu: true,
|
|
@@ -16888,6 +17157,42 @@ var init_dialog = __esm({
|
|
|
16888
17157
|
}
|
|
16889
17158
|
});
|
|
16890
17159
|
|
|
17160
|
+
// cli/harness/events.ts
|
|
17161
|
+
function fixPollIds(raw) {
|
|
17162
|
+
const text = String(raw || "");
|
|
17163
|
+
const ids = [
|
|
17164
|
+
...Array.from(text.matchAll(FIX_POLL_MARKERS), (match) => match[1]),
|
|
17165
|
+
...Array.from(text.matchAll(LEGACY_FIX_POLL_MARKERS), (match) => match[1])
|
|
17166
|
+
];
|
|
17167
|
+
return Array.from(new Set(ids.filter(Boolean)));
|
|
17168
|
+
}
|
|
17169
|
+
function fixPollId(raw) {
|
|
17170
|
+
return fixPollIds(raw)[0] || "";
|
|
17171
|
+
}
|
|
17172
|
+
function cleanGuardText(raw) {
|
|
17173
|
+
return String(raw || "").replace(FIX_POLL_MARKERS, "").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();
|
|
17174
|
+
}
|
|
17175
|
+
function blockReason(raw) {
|
|
17176
|
+
const text = cleanGuardText(raw);
|
|
17177
|
+
if (!text) return "blocked by policy";
|
|
17178
|
+
const guardAt = text.lastIndexOf("Guard:");
|
|
17179
|
+
if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
|
|
17180
|
+
const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
|
|
17181
|
+
if (afterTag) return afterTag[1].trim();
|
|
17182
|
+
const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
|
|
17183
|
+
if (afterHook) return afterHook[1].trim();
|
|
17184
|
+
return text;
|
|
17185
|
+
}
|
|
17186
|
+
var BLOCK_MARKER, FIX_POLL_MARKERS, LEGACY_FIX_POLL_MARKERS;
|
|
17187
|
+
var init_events = __esm({
|
|
17188
|
+
"cli/harness/events.ts"() {
|
|
17189
|
+
"use strict";
|
|
17190
|
+
BLOCK_MARKER = /blocked by a hook|\[synkro:/i;
|
|
17191
|
+
FIX_POLL_MARKERS = /\[synkro:fix-poll\s+item_id=\\*["']?([A-Za-z0-9_-]+)\\*["']?\s*\]/gi;
|
|
17192
|
+
LEGACY_FIX_POLL_MARKERS = /SYNKRO FIX POLL\s*\(item_id=([A-Za-z0-9_-]+)\)/gi;
|
|
17193
|
+
}
|
|
17194
|
+
});
|
|
17195
|
+
|
|
16891
17196
|
// cli/harness/render.ts
|
|
16892
17197
|
function spinnerFrame(tick) {
|
|
16893
17198
|
return SPINNER[Math.abs(tick) % SPINNER.length];
|
|
@@ -16896,15 +17201,27 @@ function terminalText(text) {
|
|
|
16896
17201
|
return String(text).replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
|
16897
17202
|
}
|
|
16898
17203
|
function clip3(text, max) {
|
|
16899
|
-
const value =
|
|
17204
|
+
const value = cleanOutput(text).replace(/\s+/g, " ").trim();
|
|
16900
17205
|
if (max <= 1) return value;
|
|
16901
17206
|
return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
|
|
16902
17207
|
}
|
|
17208
|
+
function cleanOutput(text) {
|
|
17209
|
+
return String(text || "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
17210
|
+
}
|
|
16903
17211
|
function seconds(ms) {
|
|
16904
17212
|
const total = Math.max(0, Math.round(ms / 1e3));
|
|
16905
17213
|
if (total < 60) return total + "s";
|
|
16906
17214
|
return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
|
|
16907
17215
|
}
|
|
17216
|
+
function blockNextStep(reason) {
|
|
17217
|
+
if (/tracking (?:decision|is required)|task tracking|skip tracking|\bconductor\b/i.test(reason)) {
|
|
17218
|
+
return "Next: reply \u201Ctrack it\u201D to create a task, or \u201Cskip tracking\u201D to continue untracked.";
|
|
17219
|
+
}
|
|
17220
|
+
if (/\bCWE-\d+\b|\bCVE-\d{4}-\d+\b/i.test(reason)) {
|
|
17221
|
+
return "Next: revise the flagged code, then retry. Do not bypass the rule.";
|
|
17222
|
+
}
|
|
17223
|
+
return "Next: resolve the rule above, then retry.";
|
|
17224
|
+
}
|
|
16908
17225
|
function statusLine(opts) {
|
|
16909
17226
|
const hint = opts.hint ? " \xB7 " + opts.hint : "";
|
|
16910
17227
|
const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
|
|
@@ -16914,86 +17231,105 @@ function statusLine(opts) {
|
|
|
16914
17231
|
function renderEvent(event, width = 100) {
|
|
16915
17232
|
const body = Math.max(30, width - 4);
|
|
16916
17233
|
switch (event.type) {
|
|
16917
|
-
// The workspace and both accounts are already in the session header, so
|
|
16918
|
-
// this line carries only what the header could not know before the harness
|
|
16919
|
-
// started: which model answered, and whether a subscription or a key paid.
|
|
16920
17234
|
case "session-start":
|
|
16921
|
-
return [
|
|
16922
|
-
"",
|
|
16923
|
-
S2.dim + " " + clip3(event.model, body - 20) + (event.authSource === "login" ? " \xB7 subscription" : " \xB7 api key") + S2.reset,
|
|
16924
|
-
""
|
|
16925
|
-
];
|
|
17235
|
+
return [];
|
|
16926
17236
|
case "user-message":
|
|
16927
|
-
return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset
|
|
17237
|
+
return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset];
|
|
16928
17238
|
// Live state, not transcript. The runner promotes these to the status line.
|
|
16929
17239
|
case "thinking":
|
|
16930
17240
|
return [];
|
|
16931
17241
|
case "assistant-message": {
|
|
16932
|
-
const
|
|
17242
|
+
const lines2 = wrap(event.text, body - 2);
|
|
16933
17243
|
return [
|
|
16934
17244
|
"",
|
|
16935
|
-
...
|
|
16936
|
-
""
|
|
17245
|
+
...lines2.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset)
|
|
16937
17246
|
];
|
|
16938
17247
|
}
|
|
16939
17248
|
case "tool-start": {
|
|
16940
17249
|
const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
|
|
16941
17250
|
return [
|
|
16942
|
-
|
|
17251
|
+
"",
|
|
17252
|
+
S2.panel + S2.tool + " " + BULLET + " " + label + S2.reset + S2.panel + S2.secondary + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
|
|
16943
17253
|
];
|
|
16944
17254
|
}
|
|
16945
17255
|
case "tool-end": {
|
|
16946
17256
|
if (event.blocked) {
|
|
17257
|
+
const reason = cleanGuardText(event.reason);
|
|
16947
17258
|
return [
|
|
16948
|
-
S2.
|
|
16949
|
-
...wrap(terminalText(
|
|
17259
|
+
S2.panel + S2.blocked + " \u29C9 Blocked" + S2.reset + S2.panel + S2.secondary + " " + clip3(terminalText(event.target), body - 21) + S2.reset,
|
|
17260
|
+
...wrap(terminalText(reason), body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset),
|
|
17261
|
+
...wrap(blockNextStep(reason), body - 6).map((line) => S2.secondary + " " + line + S2.reset)
|
|
16950
17262
|
];
|
|
16951
17263
|
}
|
|
17264
|
+
const timing = event.durationMs == null ? "" : " \xB7 " + seconds(event.durationMs);
|
|
16952
17265
|
const detail = event.ok ? event.output.trim() ? clip3(terminalText(event.output), body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
|
|
16953
17266
|
const tint = event.ok ? S2.ok : S2.blocked;
|
|
16954
|
-
|
|
17267
|
+
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));
|
|
17268
|
+
return [
|
|
17269
|
+
S2.panel + tint + " " + ELBOW + " " + S2.reset + S2.panel + S2.secondary + detail + timing + S2.reset,
|
|
17270
|
+
...changes
|
|
17271
|
+
];
|
|
16955
17272
|
}
|
|
16956
17273
|
// A clean finish needs no announcement: the prompt returning IS the signal.
|
|
16957
17274
|
case "turn-end":
|
|
16958
17275
|
return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
|
|
16959
17276
|
case "notice":
|
|
16960
|
-
return [S2.
|
|
17277
|
+
return [S2.secondary + " " + clip3(event.text, body) + S2.reset];
|
|
17278
|
+
case "plan":
|
|
17279
|
+
return [
|
|
17280
|
+
S2.panel + S2.tool + " Plan" + S2.reset,
|
|
17281
|
+
...event.steps.map((row2) => S2.panel + (row2.status === "completed" ? S2.ok + " \u2713 " : S2.secondary + " \xB7 ") + clip3(row2.step, body - 4) + S2.reset)
|
|
17282
|
+
];
|
|
17283
|
+
case "assistant-delta":
|
|
17284
|
+
case "usage":
|
|
17285
|
+
return [];
|
|
16961
17286
|
default:
|
|
16962
17287
|
return [];
|
|
16963
17288
|
}
|
|
16964
17289
|
}
|
|
16965
17290
|
function wrap(text, width) {
|
|
16966
|
-
const words =
|
|
17291
|
+
const words = cleanOutput(text).replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
|
|
16967
17292
|
if (words.length === 0) return [];
|
|
16968
|
-
const
|
|
17293
|
+
const lines2 = [];
|
|
16969
17294
|
let line = "";
|
|
16970
17295
|
for (const word of words) {
|
|
16971
17296
|
if (!line) line = word;
|
|
16972
17297
|
else if ((line + " " + word).length <= width) line += " " + word;
|
|
16973
17298
|
else {
|
|
16974
|
-
|
|
17299
|
+
lines2.push(line);
|
|
16975
17300
|
line = word;
|
|
16976
17301
|
}
|
|
16977
17302
|
}
|
|
16978
|
-
if (line)
|
|
16979
|
-
return
|
|
17303
|
+
if (line) lines2.push(line);
|
|
17304
|
+
return lines2;
|
|
16980
17305
|
}
|
|
16981
|
-
var ESC2, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
|
|
17306
|
+
var ESC2, color, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
|
|
16982
17307
|
var init_render2 = __esm({
|
|
16983
17308
|
"cli/harness/render.ts"() {
|
|
16984
17309
|
"use strict";
|
|
17310
|
+
init_events();
|
|
16985
17311
|
ESC2 = "\x1B[";
|
|
17312
|
+
color = (hex, plane = 38) => {
|
|
17313
|
+
const [r, g, b] = hex.match(/../g).map((part) => Number.parseInt(part, 16));
|
|
17314
|
+
return ESC2 + plane + ";2;" + r + ";" + g + ";" + b + "m";
|
|
17315
|
+
};
|
|
16986
17316
|
S2 = {
|
|
16987
17317
|
reset: ESC2 + "0m",
|
|
16988
17318
|
dim: ESC2 + "2m",
|
|
16989
17319
|
bold: ESC2 + "1m",
|
|
16990
|
-
|
|
16991
|
-
|
|
16992
|
-
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
16996
|
-
|
|
17320
|
+
canvas: color("101416", 48),
|
|
17321
|
+
panel: color("171D1F", 48),
|
|
17322
|
+
composer: color("13191B", 48),
|
|
17323
|
+
text: color("C4C8C4"),
|
|
17324
|
+
secondary: color("7F8A86"),
|
|
17325
|
+
border: color("2A3334"),
|
|
17326
|
+
user: color("87A68E"),
|
|
17327
|
+
agent: color("C4C8C4"),
|
|
17328
|
+
think: color("B29A6A"),
|
|
17329
|
+
tool: color("7F9CA5"),
|
|
17330
|
+
ok: color("789B82"),
|
|
17331
|
+
blocked: color("B87474"),
|
|
17332
|
+
rule: color("B87474")
|
|
16997
17333
|
};
|
|
16998
17334
|
CLEAR_LINE = "\r" + ESC2 + "2K";
|
|
16999
17335
|
BULLET = "\u23FA";
|
|
@@ -17015,297 +17351,207 @@ var init_render2 = __esm({
|
|
|
17015
17351
|
|
|
17016
17352
|
// cli/harness/composer.ts
|
|
17017
17353
|
import { execFileSync as execFileSync5, spawnSync as spawnSync12 } from "child_process";
|
|
17018
|
-
import { existsSync as existsSync38, mkdtempSync as mkdtempSync2, rmSync as rmSync7, statSync as
|
|
17354
|
+
import { existsSync as existsSync38, mkdtempSync as mkdtempSync2, rmSync as rmSync7, statSync as statSync6, writeFileSync as writeFileSync27 } from "fs";
|
|
17019
17355
|
import { homedir as homedir38, tmpdir } from "os";
|
|
17020
17356
|
import { basename as basename3, dirname as dirname12, extname, isAbsolute as isAbsolute2, join as join37, resolve as resolve5, sep as sep3 } from "path";
|
|
17021
17357
|
import { createInterface as createInterface5 } from "readline";
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
return 0;
|
|
17027
|
-
}
|
|
17028
|
-
function normalizePathToken(raw) {
|
|
17029
|
-
let value = raw.trim();
|
|
17030
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
17031
|
-
value = value.slice(1, -1);
|
|
17358
|
+
var init_composer = __esm({
|
|
17359
|
+
"cli/harness/composer.ts"() {
|
|
17360
|
+
"use strict";
|
|
17361
|
+
init_render2();
|
|
17032
17362
|
}
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17363
|
+
});
|
|
17364
|
+
|
|
17365
|
+
// cli/harness/changes.ts
|
|
17366
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync33, readlinkSync } from "fs";
|
|
17367
|
+
import { resolve as resolve6, relative } from "path";
|
|
17368
|
+
function lines(text) {
|
|
17369
|
+
if (!text) return [];
|
|
17370
|
+
const rows = text.replace(/\r\n/g, "\n").split("\n");
|
|
17371
|
+
if (rows[rows.length - 1] === "") rows.pop();
|
|
17372
|
+
return rows;
|
|
17037
17373
|
}
|
|
17038
|
-
function
|
|
17039
|
-
const
|
|
17040
|
-
const
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17374
|
+
function lineChangeCounts(before, after) {
|
|
17375
|
+
const a = lines(before);
|
|
17376
|
+
const b = lines(after);
|
|
17377
|
+
if (a.length === 0) return { additions: b.length, deletions: 0 };
|
|
17378
|
+
if (b.length === 0) return { additions: 0, deletions: a.length };
|
|
17379
|
+
const max = a.length + b.length;
|
|
17380
|
+
let frontier = /* @__PURE__ */ new Map([[1, 0]]);
|
|
17381
|
+
for (let distance = 0; distance <= Math.min(max, MAX_DIFF_DISTANCE); distance++) {
|
|
17382
|
+
const next = /* @__PURE__ */ new Map();
|
|
17383
|
+
for (let diagonal = -distance; diagonal <= distance; diagonal += 2) {
|
|
17384
|
+
const down = frontier.get(diagonal + 1) ?? -1;
|
|
17385
|
+
const right = (frontier.get(diagonal - 1) ?? -1) + 1;
|
|
17386
|
+
let x = diagonal === -distance || diagonal !== distance && right < down ? down : right;
|
|
17387
|
+
if (x < 0) x = 0;
|
|
17388
|
+
let y = x - diagonal;
|
|
17389
|
+
while (x < a.length && y < b.length && a[x] === b[y]) {
|
|
17390
|
+
x++;
|
|
17391
|
+
y++;
|
|
17392
|
+
}
|
|
17393
|
+
next.set(diagonal, x);
|
|
17394
|
+
if (x >= a.length && y >= b.length) {
|
|
17395
|
+
return {
|
|
17396
|
+
additions: (distance + b.length - a.length) / 2,
|
|
17397
|
+
deletions: (distance + a.length - b.length) / 2
|
|
17398
|
+
};
|
|
17399
|
+
}
|
|
17048
17400
|
}
|
|
17401
|
+
frontier = next;
|
|
17049
17402
|
}
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
|
|
17057
|
-
}
|
|
17058
|
-
function clipboardText() {
|
|
17059
|
-
try {
|
|
17060
|
-
if (process.platform === "darwin") return execFileSync5("pbpaste", [], { encoding: "utf8", timeout: 1500 });
|
|
17061
|
-
const wayland = spawnSync12("wl-paste", ["--no-newline"], { encoding: "utf8", timeout: 1500 });
|
|
17062
|
-
if (wayland.status === 0) return String(wayland.stdout || "");
|
|
17063
|
-
const x11 = spawnSync12("xclip", ["-selection", "clipboard", "-o"], { encoding: "utf8", timeout: 1500 });
|
|
17064
|
-
return x11.status === 0 ? String(x11.stdout || "") : "";
|
|
17065
|
-
} catch {
|
|
17066
|
-
return "";
|
|
17403
|
+
let start = 0;
|
|
17404
|
+
while (start < a.length && start < b.length && a[start] === b[start]) start++;
|
|
17405
|
+
let aEnd = a.length;
|
|
17406
|
+
let bEnd = b.length;
|
|
17407
|
+
while (aEnd > start && bEnd > start && a[aEnd - 1] === b[bEnd - 1]) {
|
|
17408
|
+
aEnd--;
|
|
17409
|
+
bEnd--;
|
|
17067
17410
|
}
|
|
17411
|
+
return { additions: bEnd - start, deletions: aEnd - start };
|
|
17068
17412
|
}
|
|
17069
|
-
function
|
|
17070
|
-
|
|
17071
|
-
|
|
17072
|
-
|
|
17073
|
-
"
|
|
17074
|
-
"
|
|
17075
|
-
|
|
17076
|
-
"write imageData to outputFile",
|
|
17077
|
-
"close access outputFile",
|
|
17078
|
-
"end run"
|
|
17079
|
-
].join("\n");
|
|
17080
|
-
const result = spawnSync12("osascript", ["-e", script, target], { encoding: "utf8", timeout: 3e3 });
|
|
17081
|
-
return result.status === 0 && existsSync38(target);
|
|
17082
|
-
}
|
|
17083
|
-
function linuxClipboardPng(target) {
|
|
17084
|
-
const wayland = spawnSync12("wl-paste", ["--type", "image/png"], { encoding: null, timeout: 3e3 });
|
|
17085
|
-
if (wayland.status === 0 && Buffer.isBuffer(wayland.stdout) && wayland.stdout.length > 0) {
|
|
17086
|
-
writeFileSync27(target, wayland.stdout);
|
|
17087
|
-
return true;
|
|
17088
|
-
}
|
|
17089
|
-
const x11 = spawnSync12("xclip", ["-selection", "clipboard", "-t", "image/png", "-o"], { encoding: null, timeout: 3e3 });
|
|
17090
|
-
if (x11.status === 0 && Buffer.isBuffer(x11.stdout) && x11.stdout.length > 0) {
|
|
17091
|
-
writeFileSync27(target, x11.stdout);
|
|
17092
|
-
return true;
|
|
17413
|
+
function unifiedDiffCounts(diff) {
|
|
17414
|
+
let additions = 0;
|
|
17415
|
+
let deletions = 0;
|
|
17416
|
+
for (const line of String(diff || "").split("\n")) {
|
|
17417
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
17418
|
+
if (line.startsWith("+")) additions++;
|
|
17419
|
+
else if (line.startsWith("-")) deletions++;
|
|
17093
17420
|
}
|
|
17094
|
-
return
|
|
17421
|
+
return { additions, deletions };
|
|
17422
|
+
}
|
|
17423
|
+
function safePath(cwd, file) {
|
|
17424
|
+
const absolute = resolve6(cwd, file);
|
|
17425
|
+
const rel = relative(cwd, absolute);
|
|
17426
|
+
return rel && rel !== ".." && !rel.startsWith("../") ? rel : null;
|
|
17095
17427
|
}
|
|
17096
|
-
function
|
|
17097
|
-
const
|
|
17098
|
-
|
|
17428
|
+
function worktreeContent(cwd, file) {
|
|
17429
|
+
const rel = safePath(cwd, file);
|
|
17430
|
+
if (!rel) return null;
|
|
17099
17431
|
try {
|
|
17100
|
-
const
|
|
17101
|
-
|
|
17432
|
+
const absolute = resolve6(cwd, rel);
|
|
17433
|
+
const stat = lstatSync2(absolute);
|
|
17434
|
+
if (stat.isSymbolicLink()) return "symlink:" + readlinkSync(absolute);
|
|
17435
|
+
if (!stat.isFile() || stat.size > MAX_TEXT_BYTES) return null;
|
|
17436
|
+
const content = readFileSync33(absolute);
|
|
17437
|
+
return content.includes(0) ? null : content.toString("utf8");
|
|
17102
17438
|
} catch {
|
|
17439
|
+
return null;
|
|
17103
17440
|
}
|
|
17104
|
-
rmSync7(directory, { recursive: true, force: true });
|
|
17105
|
-
return "";
|
|
17106
17441
|
}
|
|
17107
|
-
function
|
|
17108
|
-
|
|
17109
|
-
const directory = dirname12(resolve5(path));
|
|
17110
|
-
const temporaryRoot = resolve5(tmpdir()) + sep3;
|
|
17111
|
-
if (!directory.startsWith(temporaryRoot) || !basename3(directory).startsWith("synkro-paste-")) continue;
|
|
17112
|
-
try {
|
|
17113
|
-
rmSync7(directory, { recursive: true, force: true });
|
|
17114
|
-
} catch {
|
|
17115
|
-
}
|
|
17116
|
-
}
|
|
17442
|
+
function fileOperation(event) {
|
|
17443
|
+
return (event.type === "tool-start" || event.type === "tool-end") && /^(edit|write|delete)$/.test(event.kind);
|
|
17117
17444
|
}
|
|
17118
|
-
function
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
const finish = (value) => {
|
|
17123
|
-
if (settled) return;
|
|
17124
|
-
settled = true;
|
|
17125
|
-
rl.close();
|
|
17126
|
-
resolveDraft(value);
|
|
17127
|
-
};
|
|
17128
|
-
rl.once("close", () => finish(null));
|
|
17129
|
-
rl.once("SIGINT", () => finish(null));
|
|
17130
|
-
rl.question(S2.user + " \u276F " + S2.reset, (text) => {
|
|
17131
|
-
finish({ text, images: imagePathsInText(text, cwd), temporaryImages: [] });
|
|
17132
|
-
});
|
|
17133
|
-
});
|
|
17445
|
+
function eventPaths(cwd, event) {
|
|
17446
|
+
const provided = event.type === "tool-end" ? (event.fileChanges || []).map((change) => change.path) : [];
|
|
17447
|
+
const targets = String(event.target || "").split(/,\s*/);
|
|
17448
|
+
return [...new Set([...provided, ...targets].map((file) => safePath(cwd, file)).filter((file) => Boolean(file)))];
|
|
17134
17449
|
}
|
|
17135
|
-
function
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
const prompt = S2.user + " \u276F " + S2.reset;
|
|
17149
|
-
const redraw = () => {
|
|
17150
|
-
const safe = text.replace(/[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g, "").replace(/[\r\n]+/g, " / ");
|
|
17151
|
-
const attachmentLabel = images.length ? " [" + images.length + " image" + (images.length === 1 ? "" : "s") + "]" : "";
|
|
17152
|
-
const attachments = attachmentLabel ? S2.dim + attachmentLabel + S2.reset : "";
|
|
17153
|
-
output.write("\r\x1B[2K" + prompt + safe + attachments);
|
|
17154
|
-
const after = text.slice(cursor).replace(/[\r\n]+/g, " / ").length + attachmentLabel.length;
|
|
17155
|
-
if (after > 0) output.write("\x1B[" + after + "D");
|
|
17156
|
-
};
|
|
17157
|
-
const finish = (draft) => {
|
|
17158
|
-
if (settled) return;
|
|
17159
|
-
settled = true;
|
|
17160
|
-
input.off("data", onData);
|
|
17161
|
-
input.setRawMode?.(wasRaw);
|
|
17162
|
-
output.write("\x1B[?2004l\n");
|
|
17163
|
-
if (!draft) cleanupPromptDraft({ temporaryImages });
|
|
17164
|
-
resolveDraft(draft);
|
|
17165
|
-
};
|
|
17166
|
-
const insert = (value) => {
|
|
17167
|
-
text = text.slice(0, cursor) + value + text.slice(cursor);
|
|
17168
|
-
cursor += value.length;
|
|
17169
|
-
};
|
|
17170
|
-
const attachClipboard = () => {
|
|
17171
|
-
const image = clipboardImage();
|
|
17172
|
-
if (image) {
|
|
17173
|
-
images.push(image);
|
|
17174
|
-
temporaryImages.push(image);
|
|
17175
|
-
return;
|
|
17450
|
+
function snapshotOperation(cwd, event) {
|
|
17451
|
+
return new Map(eventPaths(cwd, event).map((file) => [file, worktreeContent(cwd, file)]));
|
|
17452
|
+
}
|
|
17453
|
+
var MAX_TEXT_BYTES, MAX_DIFF_DISTANCE, OperationChangeTracker;
|
|
17454
|
+
var init_changes = __esm({
|
|
17455
|
+
"cli/harness/changes.ts"() {
|
|
17456
|
+
"use strict";
|
|
17457
|
+
MAX_TEXT_BYTES = 2 * 1024 * 1024;
|
|
17458
|
+
MAX_DIFF_DISTANCE = 4e3;
|
|
17459
|
+
OperationChangeTracker = class {
|
|
17460
|
+
constructor(cwd, capture) {
|
|
17461
|
+
this.cwd = cwd;
|
|
17462
|
+
this.capture = capture;
|
|
17176
17463
|
}
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
break;
|
|
17188
|
-
}
|
|
17189
|
-
insert(stream.slice(0, end));
|
|
17190
|
-
stream = stream.slice(end + PASTE_END.length);
|
|
17191
|
-
pasting = false;
|
|
17192
|
-
continue;
|
|
17193
|
-
}
|
|
17194
|
-
if (stream.startsWith(PASTE_START)) {
|
|
17195
|
-
stream = stream.slice(PASTE_START.length);
|
|
17196
|
-
pasting = true;
|
|
17197
|
-
continue;
|
|
17198
|
-
}
|
|
17199
|
-
if (PASTE_START.startsWith(stream)) break;
|
|
17200
|
-
if (stream.startsWith("\x1B[D")) {
|
|
17201
|
-
cursor = Math.max(0, cursor - 1);
|
|
17202
|
-
stream = stream.slice(3);
|
|
17203
|
-
continue;
|
|
17204
|
-
}
|
|
17205
|
-
if (stream.startsWith("\x1B[C")) {
|
|
17206
|
-
cursor = Math.min(text.length, cursor + 1);
|
|
17207
|
-
stream = stream.slice(3);
|
|
17208
|
-
continue;
|
|
17209
|
-
}
|
|
17210
|
-
if (stream.startsWith("\x1B[H")) {
|
|
17211
|
-
cursor = 0;
|
|
17212
|
-
stream = stream.slice(3);
|
|
17213
|
-
continue;
|
|
17214
|
-
}
|
|
17215
|
-
if (stream.startsWith("\x1B[F")) {
|
|
17216
|
-
cursor = text.length;
|
|
17217
|
-
stream = stream.slice(3);
|
|
17218
|
-
continue;
|
|
17219
|
-
}
|
|
17220
|
-
if (stream.startsWith("\x1B[A") || stream.startsWith("\x1B[B")) {
|
|
17221
|
-
stream = stream.slice(3);
|
|
17222
|
-
continue;
|
|
17464
|
+
cwd;
|
|
17465
|
+
capture;
|
|
17466
|
+
starts = /* @__PURE__ */ new Map();
|
|
17467
|
+
snapshot(event) {
|
|
17468
|
+
return this.capture ? this.capture() : snapshotOperation(this.cwd, event);
|
|
17469
|
+
}
|
|
17470
|
+
observe(event) {
|
|
17471
|
+
if (event.type === "tool-start") {
|
|
17472
|
+
if (fileOperation(event)) this.starts.set(event.id, this.snapshot(event));
|
|
17473
|
+
return event;
|
|
17223
17474
|
}
|
|
17224
|
-
if (
|
|
17225
|
-
const
|
|
17226
|
-
|
|
17227
|
-
if (
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
if (
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
}
|
|
17238
|
-
} else if (char === "") {
|
|
17239
|
-
text = text.slice(cursor);
|
|
17240
|
-
cursor = 0;
|
|
17241
|
-
} else if (char === "") {
|
|
17242
|
-
const before = text.slice(0, cursor).replace(/\s*\S+\s*$/, "");
|
|
17243
|
-
text = before + text.slice(cursor);
|
|
17244
|
-
cursor = before.length;
|
|
17245
|
-
} else if (char === "") {
|
|
17246
|
-
attachClipboard();
|
|
17247
|
-
} else if (char >= " " || char === " ") {
|
|
17248
|
-
insert(char);
|
|
17475
|
+
if (event.type !== "tool-end") return event;
|
|
17476
|
+
const before = this.starts.get(event.id);
|
|
17477
|
+
this.starts.delete(event.id);
|
|
17478
|
+
if (!before) return event;
|
|
17479
|
+
const after = this.snapshot(event);
|
|
17480
|
+
const files = [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].sort();
|
|
17481
|
+
const fileChanges2 = [];
|
|
17482
|
+
for (const file of files) {
|
|
17483
|
+
const oldText = before.get(file);
|
|
17484
|
+
const newText = after.has(file) ? after.get(file) : worktreeContent(this.cwd, file);
|
|
17485
|
+
if (oldText === newText || oldText === null && newText === null) continue;
|
|
17486
|
+
const counts = lineChangeCounts(oldText || "", newText || "");
|
|
17487
|
+
fileChanges2.push({ path: file, ...counts });
|
|
17249
17488
|
}
|
|
17489
|
+
const provided = event.fileChanges || [];
|
|
17490
|
+
const measured = new Map(fileChanges2.map((change) => [change.path, change]));
|
|
17491
|
+
const merged = provided.map((change) => {
|
|
17492
|
+
const actual = measured.get(change.path) || measured.get(safePath(this.cwd, change.path) || "");
|
|
17493
|
+
if (actual && change.additions === 0 && change.deletions === 0) return actual;
|
|
17494
|
+
return change;
|
|
17495
|
+
});
|
|
17496
|
+
const providedPaths = new Set(provided.flatMap((change) => [change.path, safePath(this.cwd, change.path) || ""]));
|
|
17497
|
+
for (const change of fileChanges2) if (!providedPaths.has(change.path)) merged.push(change);
|
|
17498
|
+
return { ...event, fileChanges: merged.length ? merged : fileChanges2 };
|
|
17250
17499
|
}
|
|
17251
|
-
if (!settled) redraw();
|
|
17252
|
-
};
|
|
17253
|
-
let queue = Promise.resolve();
|
|
17254
|
-
const onData = (chunk) => {
|
|
17255
|
-
queue = queue.then(() => {
|
|
17256
|
-
stream += chunk.toString("utf8");
|
|
17257
|
-
processStream();
|
|
17258
|
-
});
|
|
17259
17500
|
};
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17264
|
-
|
|
17501
|
+
}
|
|
17502
|
+
});
|
|
17503
|
+
|
|
17504
|
+
// cli/codexUsage.ts
|
|
17505
|
+
function record(value) {
|
|
17506
|
+
return value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17265
17507
|
}
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17508
|
+
function finiteNumber(value) {
|
|
17509
|
+
const n = Number(value);
|
|
17510
|
+
return Number.isFinite(n) ? n : null;
|
|
17511
|
+
}
|
|
17512
|
+
function rateWindow(value) {
|
|
17513
|
+
const row2 = record(value);
|
|
17514
|
+
if (!row2) return null;
|
|
17515
|
+
const usedPercent = finiteNumber(row2.usedPercent);
|
|
17516
|
+
if (usedPercent == null) return null;
|
|
17517
|
+
return {
|
|
17518
|
+
durationMins: finiteNumber(row2.windowDurationMins),
|
|
17519
|
+
usedPercent,
|
|
17520
|
+
resetsAt: finiteNumber(row2.resetsAt)
|
|
17521
|
+
};
|
|
17522
|
+
}
|
|
17523
|
+
function codexUsageFromRateLimitsResponse(value) {
|
|
17524
|
+
const response = record(value);
|
|
17525
|
+
if (!response) return null;
|
|
17526
|
+
const byLimitId = record(response.rateLimitsByLimitId);
|
|
17527
|
+
const snapshot = record(byLimitId?.codex) || record(response.rateLimits);
|
|
17528
|
+
if (!snapshot) return null;
|
|
17529
|
+
const primary = rateWindow(snapshot.primary);
|
|
17530
|
+
const secondary = rateWindow(snapshot.secondary);
|
|
17531
|
+
const windows = [primary, secondary].filter((w) => w != null);
|
|
17532
|
+
if (windows.length === 0) return null;
|
|
17533
|
+
const fiveHour = windows.find((w) => w.durationMins === 300) || windows.find((w) => w.durationMins != null && w.durationMins <= 360) || (primary?.durationMins == null ? primary : null);
|
|
17534
|
+
const sevenDay = windows.find((w) => w.durationMins === 10080) || windows.find((w) => w.durationMins != null && w.durationMins >= 1440) || (secondary !== fiveHour && secondary?.durationMins == null ? secondary : null);
|
|
17535
|
+
const status = typeof snapshot.rateLimitReachedType === "string" ? snapshot.rateLimitReachedType : snapshot.spendControlReached === true ? "spend_control_reached" : null;
|
|
17536
|
+
return {
|
|
17537
|
+
util5h: fiveHour ? fiveHour.usedPercent / 100 : null,
|
|
17538
|
+
util7d: sevenDay ? sevenDay.usedPercent / 100 : null,
|
|
17539
|
+
reset5h: fiveHour?.resetsAt ?? null,
|
|
17540
|
+
reset7d: sevenDay?.resetsAt ?? null,
|
|
17541
|
+
status,
|
|
17542
|
+
planType: typeof snapshot.planType === "string" ? snapshot.planType : null,
|
|
17543
|
+
limitId: typeof snapshot.limitId === "string" ? snapshot.limitId : null
|
|
17544
|
+
};
|
|
17545
|
+
}
|
|
17546
|
+
var init_codexUsage = __esm({
|
|
17547
|
+
"cli/codexUsage.ts"() {
|
|
17269
17548
|
"use strict";
|
|
17270
|
-
init_render2();
|
|
17271
|
-
PASTE_START = "\x1B[200~";
|
|
17272
|
-
PASTE_END = "\x1B[201~";
|
|
17273
|
-
IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
17274
17549
|
}
|
|
17275
17550
|
});
|
|
17276
17551
|
|
|
17277
|
-
// cli/harness/
|
|
17278
|
-
function
|
|
17279
|
-
|
|
17280
|
-
return FIX_POLL_MARKER.exec(text)?.[1] || LEGACY_FIX_POLL_MARKER.exec(text)?.[1] || "";
|
|
17281
|
-
}
|
|
17282
|
-
function cleanGuardText(raw) {
|
|
17283
|
-
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();
|
|
17284
|
-
}
|
|
17285
|
-
function blockReason(raw) {
|
|
17286
|
-
const text = cleanGuardText(raw);
|
|
17287
|
-
if (!text) return "blocked by policy";
|
|
17288
|
-
const guardAt = text.lastIndexOf("Guard:");
|
|
17289
|
-
if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
|
|
17290
|
-
const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
|
|
17291
|
-
if (afterTag) return afterTag[1].trim();
|
|
17292
|
-
const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
|
|
17293
|
-
if (afterHook) return afterHook[1].trim();
|
|
17294
|
-
return text;
|
|
17295
|
-
}
|
|
17296
|
-
var BLOCK_MARKER, FIX_POLL_MARKER, LEGACY_FIX_POLL_MARKER;
|
|
17297
|
-
var init_events = __esm({
|
|
17298
|
-
"cli/harness/events.ts"() {
|
|
17299
|
-
"use strict";
|
|
17300
|
-
BLOCK_MARKER = /blocked by a hook|\[synkro:/i;
|
|
17301
|
-
FIX_POLL_MARKER = /\[synkro:fix-poll\s+item_id=\\?["']?([A-Za-z0-9_-]+)\\?["']?\]/i;
|
|
17302
|
-
LEGACY_FIX_POLL_MARKER = /SYNKRO FIX POLL\s*\(item_id=([A-Za-z0-9_-]+)\)/i;
|
|
17303
|
-
}
|
|
17304
|
-
});
|
|
17305
|
-
|
|
17306
|
-
// cli/harness/cursor.ts
|
|
17307
|
-
function unwrapReplay(text) {
|
|
17308
|
-
return text.replace(/<\/?user_query>/gi, "").trim();
|
|
17552
|
+
// cli/harness/cursor.ts
|
|
17553
|
+
function unwrapReplay(text) {
|
|
17554
|
+
return text.replace(/<\/?user_query>/gi, "").trim();
|
|
17309
17555
|
}
|
|
17310
17556
|
function textOf(message) {
|
|
17311
17557
|
const content = message?.content;
|
|
@@ -17315,10 +17561,10 @@ function textOf(message) {
|
|
|
17315
17561
|
}
|
|
17316
17562
|
function toolPayload(toolCall) {
|
|
17317
17563
|
if (!toolCall) return { kind: "other", body: {} };
|
|
17318
|
-
for (const [
|
|
17319
|
-
if (toolCall[
|
|
17564
|
+
for (const [key2, kind] of Object.entries(TOOL_KINDS)) {
|
|
17565
|
+
if (toolCall[key2]) return { kind, body: toolCall[key2] };
|
|
17320
17566
|
}
|
|
17321
|
-
const fallback = Object.keys(toolCall).find((
|
|
17567
|
+
const fallback = Object.keys(toolCall).find((key2) => key2.endsWith("ToolCall"));
|
|
17322
17568
|
return fallback ? { kind: "other", body: toolCall[fallback] } : { kind: "other", body: {} };
|
|
17323
17569
|
}
|
|
17324
17570
|
function targetOf(kind, body) {
|
|
@@ -17327,7 +17573,25 @@ function targetOf(kind, body) {
|
|
|
17327
17573
|
const text = typeof candidate === "string" ? candidate : JSON.stringify(candidate ?? "");
|
|
17328
17574
|
return text || body?.description || kind;
|
|
17329
17575
|
}
|
|
17330
|
-
function
|
|
17576
|
+
function completedFileChanges(body) {
|
|
17577
|
+
const success = body?.result?.success || {};
|
|
17578
|
+
const rows = Array.isArray(success.changes) ? success.changes : Array.isArray(success.files) ? success.files : [];
|
|
17579
|
+
return rows.flatMap((change) => {
|
|
17580
|
+
const path = String(change?.path || change?.filePath || change?.file_path || "");
|
|
17581
|
+
if (!path) return [];
|
|
17582
|
+
const diff = String(change?.diff || change?.patch || "");
|
|
17583
|
+
const hasAdditions = change?.additions != null || change?.linesAdded != null;
|
|
17584
|
+
const hasDeletions = change?.deletions != null || change?.linesRemoved != null;
|
|
17585
|
+
if (!diff && !hasAdditions && !hasDeletions) return [];
|
|
17586
|
+
const counted = unifiedDiffCounts(diff);
|
|
17587
|
+
return [{
|
|
17588
|
+
path,
|
|
17589
|
+
additions: hasAdditions ? Number(change?.additions ?? change?.linesAdded) : counted.additions,
|
|
17590
|
+
deletions: hasDeletions ? Number(change?.deletions ?? change?.linesRemoved) : counted.deletions
|
|
17591
|
+
}];
|
|
17592
|
+
});
|
|
17593
|
+
}
|
|
17594
|
+
function parseCursorLine(line, streamPartial = false) {
|
|
17331
17595
|
const trimmed = String(line || "").trim();
|
|
17332
17596
|
if (!trimmed) return [];
|
|
17333
17597
|
let frame;
|
|
@@ -17353,7 +17617,7 @@ function parseCursorLine(line) {
|
|
|
17353
17617
|
}
|
|
17354
17618
|
if (type === "assistant") {
|
|
17355
17619
|
const text = textOf(frame.message);
|
|
17356
|
-
return text ? [{ type: "assistant-message", text }] : [];
|
|
17620
|
+
return text ? [streamPartial ? { type: "assistant-delta", id: String(frame.model_call_id || ""), text } : { type: "assistant-message", text }] : [];
|
|
17357
17621
|
}
|
|
17358
17622
|
if (type === "thinking" && subtype === "delta" && frame.text) {
|
|
17359
17623
|
return [{ type: "thinking", text: String(frame.text) }];
|
|
@@ -17381,8 +17645,10 @@ function parseCursorLine(line) {
|
|
|
17381
17645
|
blocked,
|
|
17382
17646
|
reason: rejected ? blocked ? blockReason(rawReason) : rawReason || "rejected" : "",
|
|
17383
17647
|
pollId: blocked ? fixPollId(rawReason) : "",
|
|
17648
|
+
pollIds: blocked ? fixPollIds(rawReason) : [],
|
|
17384
17649
|
exitCode: success ? Number(success.exitCode ?? 0) : null,
|
|
17385
|
-
output: String(success?.stdout || success?.stderr || "")
|
|
17650
|
+
output: String(success?.stdout || success?.stderr || ""),
|
|
17651
|
+
fileChanges: completedFileChanges(body)
|
|
17386
17652
|
}];
|
|
17387
17653
|
}
|
|
17388
17654
|
return [];
|
|
@@ -17397,11 +17663,15 @@ function parseCursorLine(line) {
|
|
|
17397
17663
|
}];
|
|
17398
17664
|
}
|
|
17399
17665
|
if (type === "result") {
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17666
|
+
const result = String(frame.result || "");
|
|
17667
|
+
return [
|
|
17668
|
+
...streamPartial && result ? [{ type: "assistant-message", text: result }] : [],
|
|
17669
|
+
{
|
|
17670
|
+
type: "turn-end",
|
|
17671
|
+
ok: !frame.is_error,
|
|
17672
|
+
text: result
|
|
17673
|
+
}
|
|
17674
|
+
];
|
|
17405
17675
|
}
|
|
17406
17676
|
return [];
|
|
17407
17677
|
}
|
|
@@ -17420,12 +17690,14 @@ function feed(buffer, chunk) {
|
|
|
17420
17690
|
const rest = parts.pop() ?? "";
|
|
17421
17691
|
return { lines: parts, rest };
|
|
17422
17692
|
}
|
|
17423
|
-
function cursorArgs(prompt) {
|
|
17693
|
+
function cursorArgs(prompt, resumeId = "") {
|
|
17424
17694
|
return [
|
|
17425
17695
|
"-p",
|
|
17426
17696
|
prompt,
|
|
17427
17697
|
"--output-format",
|
|
17428
17698
|
"stream-json",
|
|
17699
|
+
"--stream-partial-output",
|
|
17700
|
+
...resumeId ? ["--resume", resumeId] : [],
|
|
17429
17701
|
// --force auto-runs tools but does NOT bypass hooks (verified live), so
|
|
17430
17702
|
// Synkro's guards still gate every call; --trust loads workspace hooks.
|
|
17431
17703
|
"--force",
|
|
@@ -17437,6 +17709,7 @@ var init_cursor = __esm({
|
|
|
17437
17709
|
"cli/harness/cursor.ts"() {
|
|
17438
17710
|
"use strict";
|
|
17439
17711
|
init_events();
|
|
17712
|
+
init_changes();
|
|
17440
17713
|
TOOL_KINDS = {
|
|
17441
17714
|
shellToolCall: "shell",
|
|
17442
17715
|
readToolCall: "read",
|
|
@@ -17473,6 +17746,7 @@ function createTurnSink(opts) {
|
|
|
17473
17746
|
const now = opts.now || (() => Date.now());
|
|
17474
17747
|
const showPrompt = opts.showPrompt !== false;
|
|
17475
17748
|
let blocked = 0;
|
|
17749
|
+
let sessionId = "";
|
|
17476
17750
|
let replaying = false;
|
|
17477
17751
|
const seen = /* @__PURE__ */ new Set();
|
|
17478
17752
|
let sawTurnEnd = false;
|
|
@@ -17553,6 +17827,7 @@ function createTurnSink(opts) {
|
|
|
17553
17827
|
};
|
|
17554
17828
|
const emit2 = (event) => {
|
|
17555
17829
|
events.push(event);
|
|
17830
|
+
if (event.type === "session-start" && event.sessionId) sessionId = event.sessionId;
|
|
17556
17831
|
if (event.type === "tool-end" && event.blocked) blocked += 1;
|
|
17557
17832
|
opts.onEvent?.(event);
|
|
17558
17833
|
if (event.type === "notice" && event.kind === "retry") {
|
|
@@ -17603,25 +17878,25 @@ function createTurnSink(opts) {
|
|
|
17603
17878
|
}
|
|
17604
17879
|
replaying = true;
|
|
17605
17880
|
}
|
|
17606
|
-
const
|
|
17607
|
-
if (
|
|
17608
|
-
if (replaying && seen.has(
|
|
17881
|
+
const key2 = replayKey(event);
|
|
17882
|
+
if (key2) {
|
|
17883
|
+
if (replaying && seen.has(key2)) {
|
|
17609
17884
|
lastWasAnswer = event.type === "assistant-message";
|
|
17610
17885
|
settle();
|
|
17611
17886
|
return;
|
|
17612
17887
|
}
|
|
17613
|
-
seen.add(
|
|
17888
|
+
seen.add(key2);
|
|
17614
17889
|
}
|
|
17615
|
-
if (
|
|
17890
|
+
if (key2 || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
|
|
17616
17891
|
if (event.type === "turn-end") sawTurnEnd = true;
|
|
17617
17892
|
if (event.type === "assistant-message") sawAnswer = true;
|
|
17618
17893
|
emit2(event);
|
|
17619
17894
|
};
|
|
17620
17895
|
return {
|
|
17621
17896
|
chunk(text) {
|
|
17622
|
-
const { lines, rest } = feed(buffer, text);
|
|
17897
|
+
const { lines: lines2, rest } = feed(buffer, text);
|
|
17623
17898
|
buffer = rest;
|
|
17624
|
-
for (const line of
|
|
17899
|
+
for (const line of lines2) for (const event of parseCursorLine(line, opts.streamPartial)) take(event);
|
|
17625
17900
|
},
|
|
17626
17901
|
notice(text) {
|
|
17627
17902
|
emit2({ type: "notice", text });
|
|
@@ -17644,7 +17919,7 @@ function createTurnSink(opts) {
|
|
|
17644
17919
|
}
|
|
17645
17920
|
}
|
|
17646
17921
|
stopStatus();
|
|
17647
|
-
return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit };
|
|
17922
|
+
return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit, sessionId };
|
|
17648
17923
|
}
|
|
17649
17924
|
};
|
|
17650
17925
|
}
|
|
@@ -17660,10 +17935,11 @@ async function runCursorTurn(opts) {
|
|
|
17660
17935
|
animate: Boolean(process.stdout.isTTY),
|
|
17661
17936
|
showPrompt: opts.showPrompt,
|
|
17662
17937
|
showHeader: opts.showHeader,
|
|
17663
|
-
onGiveUp: () => stopHarness()
|
|
17938
|
+
onGiveUp: () => stopHarness(),
|
|
17939
|
+
streamPartial: true
|
|
17664
17940
|
});
|
|
17665
|
-
return new Promise((
|
|
17666
|
-
const child = spawn9("cursor-agent", cursorArgs(opts.prompt), {
|
|
17941
|
+
return new Promise((resolve9) => {
|
|
17942
|
+
const child = spawn9("cursor-agent", cursorArgs(opts.prompt, opts.resumeId), {
|
|
17667
17943
|
cwd: opts.cwd,
|
|
17668
17944
|
stdio: ["ignore", "pipe", "pipe"]
|
|
17669
17945
|
});
|
|
@@ -17691,11 +17967,11 @@ async function runCursorTurn(opts) {
|
|
|
17691
17967
|
});
|
|
17692
17968
|
child.on("close", (code) => {
|
|
17693
17969
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
17694
|
-
|
|
17970
|
+
resolve9(sink.finish(code ?? 0, interrupted));
|
|
17695
17971
|
});
|
|
17696
17972
|
child.on("error", (error) => {
|
|
17697
17973
|
sink.notice("failed to start cursor-agent: " + String(error));
|
|
17698
|
-
|
|
17974
|
+
resolve9(sink.finish(1, interrupted));
|
|
17699
17975
|
});
|
|
17700
17976
|
});
|
|
17701
17977
|
}
|
|
@@ -17711,360 +17987,562 @@ var init_run = __esm({
|
|
|
17711
17987
|
|
|
17712
17988
|
// cli/harness/codex.ts
|
|
17713
17989
|
import { spawn as spawn10 } from "child_process";
|
|
17714
|
-
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
const paths = Array.isArray(item.changes) ? item.changes.map((change) => String(change?.path || "")).filter(Boolean) : [];
|
|
17720
|
-
return { kind: "edit", target: paths.join(", ") || "files", description: "" };
|
|
17721
|
-
}
|
|
17722
|
-
if (item?.type === "mcpToolCall") {
|
|
17723
|
-
return { kind: "other", target: String(item.server || "") + "::" + String(item.tool || ""), description: "MCP tool" };
|
|
17724
|
-
}
|
|
17725
|
-
if (item?.type === "dynamicToolCall") {
|
|
17726
|
-
return { kind: "other", target: [item.namespace, item.tool].filter(Boolean).join("::"), description: "tool" };
|
|
17727
|
-
}
|
|
17728
|
-
return { kind: "other", target: String(item?.type || "tool"), description: "" };
|
|
17990
|
+
import { createInterface as createInterface6 } from "readline";
|
|
17991
|
+
function codexStderrNotice(raw, initialized) {
|
|
17992
|
+
const text = String(raw || "").trim();
|
|
17993
|
+
if (!text || initialized) return "";
|
|
17994
|
+
return /fatal|panic|authentication failed|not logged in/i.test(text) ? text.split("\n")[0].slice(0, 300) : "";
|
|
17729
17995
|
}
|
|
17730
|
-
function
|
|
17731
|
-
if (
|
|
17732
|
-
|
|
17733
|
-
|
|
17734
|
-
|
|
17996
|
+
function stringify(value) {
|
|
17997
|
+
if (typeof value === "string") return value;
|
|
17998
|
+
try {
|
|
17999
|
+
return JSON.stringify(value);
|
|
18000
|
+
} catch {
|
|
18001
|
+
return String(value ?? "");
|
|
18002
|
+
}
|
|
18003
|
+
}
|
|
18004
|
+
function toolKind(item) {
|
|
18005
|
+
if (item.type === "commandExecution") return "shell";
|
|
18006
|
+
if (item.type === "fileChange") return "edit";
|
|
18007
|
+
if (item.type === "webSearch") return "search";
|
|
18008
|
+
return "other";
|
|
18009
|
+
}
|
|
18010
|
+
function toolTarget(item) {
|
|
18011
|
+
if (item.type === "commandExecution") return item.command || "command";
|
|
18012
|
+
if (item.type === "fileChange") return (item.changes || []).map((change) => change.path || change.filePath || "").filter(Boolean).join(", ") || "workspace files";
|
|
18013
|
+
if (item.type === "mcpToolCall") return [item.server, item.tool].filter(Boolean).join(".") || "MCP tool";
|
|
18014
|
+
if (item.type === "dynamicToolCall") return [item.namespace, item.tool].filter(Boolean).join(".") || "tool";
|
|
18015
|
+
return item.query || item.type || "tool";
|
|
18016
|
+
}
|
|
18017
|
+
function fileChanges(item) {
|
|
18018
|
+
if (item.type !== "fileChange" || !Array.isArray(item.changes)) return [];
|
|
18019
|
+
return item.changes.flatMap((change) => {
|
|
18020
|
+
const path = String(change.path || change.filePath || "");
|
|
18021
|
+
if (!path) return [];
|
|
18022
|
+
const diff = String(change.diff || change.patch || "");
|
|
18023
|
+
const hasAdditions = change.additions != null || change.linesAdded != null;
|
|
18024
|
+
const hasDeletions = change.deletions != null || change.linesRemoved != null;
|
|
18025
|
+
if (!diff && !hasAdditions && !hasDeletions) return [];
|
|
18026
|
+
const counted = unifiedDiffCounts(diff);
|
|
18027
|
+
const additions = hasAdditions ? Number(change.additions ?? change.linesAdded) : counted.additions;
|
|
18028
|
+
const deletions = hasDeletions ? Number(change.deletions ?? change.linesRemoved) : counted.deletions;
|
|
18029
|
+
return [{ path, additions, deletions }];
|
|
18030
|
+
});
|
|
17735
18031
|
}
|
|
17736
|
-
function
|
|
17737
|
-
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17741
|
-
|
|
17742
|
-
|
|
17743
|
-
|
|
17744
|
-
|
|
17745
|
-
|
|
17746
|
-
|
|
17747
|
-
|
|
17748
|
-
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
-
|
|
17752
|
-
|
|
17753
|
-
|
|
17754
|
-
|
|
17755
|
-
|
|
17756
|
-
|
|
17757
|
-
|
|
17758
|
-
|
|
17759
|
-
|
|
17760
|
-
|
|
17761
|
-
|
|
17762
|
-
}
|
|
17763
|
-
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
|
|
17767
|
-
|
|
17768
|
-
|
|
17769
|
-
|
|
17770
|
-
if (method === "item/completed") {
|
|
17771
|
-
const item = params?.item;
|
|
17772
|
-
if (!item?.id) return [];
|
|
17773
|
-
if (item.type === "hookPrompt") {
|
|
17774
|
-
const text = Array.isArray(item.fragments) ? item.fragments.map((fragment) => String(fragment?.text || "")).filter(Boolean).join("\n") : "";
|
|
17775
|
-
if (text) {
|
|
17776
|
-
const start2 = startedAt.get(String(item.id));
|
|
17777
|
-
const completed2 = Number(params.completedAtMs || Date.now());
|
|
17778
|
-
approvalReasons.set("__latest_hook__", text);
|
|
17779
|
-
approvalReasons.set("__latest_hook_id__", String(item.id));
|
|
17780
|
-
approvalReasons.set("__latest_hook_duration__", String(start2 === void 0 ? 0 : Math.max(0, completed2 - start2)));
|
|
17781
|
-
}
|
|
17782
|
-
startedAt.delete(String(item.id));
|
|
17783
|
-
return [];
|
|
17784
|
-
}
|
|
17785
|
-
if (item.type === "agentMessage") {
|
|
17786
|
-
return item.text ? [{ type: "assistant-message", text: String(item.text) }] : [];
|
|
17787
|
-
}
|
|
17788
|
-
if (item.type === "reasoning") {
|
|
17789
|
-
const text = [...item.summary || [], ...item.content || []].join(" ").trim();
|
|
17790
|
-
return text ? [{ type: "thinking", text }] : [];
|
|
17791
|
-
}
|
|
17792
|
-
if (!["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall"].includes(item.type)) return [];
|
|
17793
|
-
const output = itemOutput(item);
|
|
17794
|
-
const approvalReason = approvalReasons.get(String(item.id)) || "";
|
|
17795
|
-
const hookReason = approvalReasons.get("__latest_hook__") || "";
|
|
17796
|
-
const rawReason = [approvalReason, hookReason, output].filter(Boolean).join("\n");
|
|
18032
|
+
function codexAccountUsageEvent(value) {
|
|
18033
|
+
const usage2 = codexUsageFromRateLimitsResponse(value);
|
|
18034
|
+
if (usage2?.util7d == null) return null;
|
|
18035
|
+
return {
|
|
18036
|
+
type: "account-usage",
|
|
18037
|
+
period: "weekly",
|
|
18038
|
+
remainingPercent: Math.max(0, Math.min(100, Math.round((1 - usage2.util7d) * 100))),
|
|
18039
|
+
resetsAt: usage2.reset7d
|
|
18040
|
+
};
|
|
18041
|
+
}
|
|
18042
|
+
function codexMessageTurnId(message) {
|
|
18043
|
+
return String(message.params?.turnId || message.params?.turn?.id || "");
|
|
18044
|
+
}
|
|
18045
|
+
function codexMessageIsTurnScoped(message) {
|
|
18046
|
+
const method = String(message.method || "");
|
|
18047
|
+
return method.startsWith("item/") || method === "hook/completed" || method === "turn/plan/updated";
|
|
18048
|
+
}
|
|
18049
|
+
function codexEvents(message) {
|
|
18050
|
+
const method = String(message.method || "");
|
|
18051
|
+
const params = message.params || {};
|
|
18052
|
+
if (method === "account/rateLimits/updated") {
|
|
18053
|
+
const event = codexAccountUsageEvent(params);
|
|
18054
|
+
return event ? [event] : [];
|
|
18055
|
+
}
|
|
18056
|
+
const item = params.item || {};
|
|
18057
|
+
if (method === "item/agentMessage/delta") return [{ type: "assistant-delta", id: params.itemId || "", text: params.delta || "" }];
|
|
18058
|
+
if (method === "item/reasoning/summaryTextDelta") return [{ type: "thinking", text: params.delta || "Thinking" }];
|
|
18059
|
+
const tools = ["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall", "webSearch"];
|
|
18060
|
+
if (method === "item/started" && tools.includes(item.type)) {
|
|
18061
|
+
return [{ type: "tool-start", id: item.id || "", kind: toolKind(item), target: toolTarget(item), description: "" }];
|
|
18062
|
+
}
|
|
18063
|
+
if (method === "item/completed" && item.type === "agentMessage") return [{ type: "assistant-message", text: item.text || "" }];
|
|
18064
|
+
if (method === "item/completed" && tools.includes(item.type)) {
|
|
18065
|
+
const raw = stringify(item.aggregatedOutput || item.error?.message || item.result?.content || item.contentItems || "");
|
|
17797
18066
|
const status = String(item.status || "");
|
|
17798
|
-
const blocked = BLOCK_MARKER.test(
|
|
17799
|
-
const
|
|
17800
|
-
const hookStart = Number(approvalReasons.get("__pretool_started__") || 0);
|
|
17801
|
-
const start = itemStart === void 0 ? hookStart || void 0 : hookStart ? Math.min(itemStart, hookStart) : itemStart;
|
|
17802
|
-
const completed = Number(params.completedAtMs || Date.now());
|
|
17803
|
-
const durationMs = start === void 0 ? item.durationMs ?? null : Math.max(0, completed - start);
|
|
17804
|
-
startedAt.delete(String(item.id));
|
|
17805
|
-
approvalReasons.delete(String(item.id));
|
|
17806
|
-
approvalReasons.delete("__latest_hook__");
|
|
17807
|
-
approvalReasons.delete("__latest_hook_id__");
|
|
17808
|
-
approvalReasons.delete("__latest_hook_duration__");
|
|
17809
|
-
approvalReasons.delete("__pretool_started__");
|
|
17810
|
-
approvalReasons.delete("__pretool_completed__");
|
|
18067
|
+
const blocked = BLOCK_MARKER.test(raw) || /declined|blocked|denied/.test(status);
|
|
18068
|
+
const ok = !blocked && !/failed|error/.test(status) && (item.exitCode === null || item.exitCode === void 0 || item.exitCode === 0);
|
|
17811
18069
|
return [{
|
|
17812
18070
|
type: "tool-end",
|
|
17813
|
-
id:
|
|
17814
|
-
|
|
17815
|
-
|
|
18071
|
+
id: item.id || "",
|
|
18072
|
+
kind: toolKind(item),
|
|
18073
|
+
target: toolTarget(item),
|
|
18074
|
+
ok,
|
|
17816
18075
|
blocked,
|
|
17817
|
-
reason: blocked ? blockReason(
|
|
17818
|
-
pollId: blocked ? fixPollId(
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
18076
|
+
reason: blocked ? blockReason(raw || status) : "",
|
|
18077
|
+
pollId: blocked ? fixPollId(raw) : "",
|
|
18078
|
+
pollIds: blocked ? fixPollIds(raw) : [],
|
|
18079
|
+
exitCode: typeof item.exitCode === "number" ? item.exitCode : null,
|
|
18080
|
+
output: raw,
|
|
18081
|
+
durationMs: typeof item.durationMs === "number" ? item.durationMs : null,
|
|
18082
|
+
fileChanges: fileChanges(item)
|
|
17822
18083
|
}];
|
|
17823
18084
|
}
|
|
17824
|
-
if (method === "
|
|
17825
|
-
const
|
|
17826
|
-
|
|
17827
|
-
const
|
|
17828
|
-
|
|
17829
|
-
|
|
17830
|
-
|
|
17831
|
-
|
|
17832
|
-
|
|
17833
|
-
|
|
17834
|
-
|
|
17835
|
-
|
|
17836
|
-
|
|
17837
|
-
|
|
17838
|
-
|
|
17839
|
-
|
|
17840
|
-
|
|
17841
|
-
|
|
17842
|
-
approvalReasons.delete("__latest_hook__");
|
|
17843
|
-
approvalReasons.delete("__latest_hook_id__");
|
|
17844
|
-
approvalReasons.delete("__latest_hook_duration__");
|
|
17845
|
-
}
|
|
17846
|
-
approvalReasons.delete("__pretool_started__");
|
|
17847
|
-
approvalReasons.delete("__pretool_completed__");
|
|
17848
|
-
events.push({ type: "turn-end", ok: status === "completed", text: String(params?.turn?.error?.message || "") });
|
|
17849
|
-
return events;
|
|
18085
|
+
if (method === "hook/completed") {
|
|
18086
|
+
const run2 = params.run || {};
|
|
18087
|
+
if (run2.status !== "blocked") return [];
|
|
18088
|
+
const reason = (run2.entries || []).map((entry) => entry.text || "").filter(Boolean).join("\n") || run2.statusMessage || "blocked by policy";
|
|
18089
|
+
return [{
|
|
18090
|
+
type: "tool-end",
|
|
18091
|
+
id: "hook:" + (run2.id || ""),
|
|
18092
|
+
kind: "other",
|
|
18093
|
+
target: run2.eventName || "Guard",
|
|
18094
|
+
ok: false,
|
|
18095
|
+
blocked: true,
|
|
18096
|
+
reason: blockReason(reason),
|
|
18097
|
+
exitCode: null,
|
|
18098
|
+
output: reason,
|
|
18099
|
+
pollId: fixPollId(reason),
|
|
18100
|
+
pollIds: fixPollIds(reason),
|
|
18101
|
+
durationMs: typeof run2.durationMs === "number" ? run2.durationMs : null
|
|
18102
|
+
}];
|
|
17850
18103
|
}
|
|
17851
|
-
|
|
17852
|
-
}
|
|
17853
|
-
|
|
17854
|
-
|
|
17855
|
-
|
|
17856
|
-
|
|
17857
|
-
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
const output = process.stdout;
|
|
17861
|
-
if (!input.isTTY || !output.isTTY) return false;
|
|
17862
|
-
output.write("\n" + S2.rule + " Synkro approval required" + S2.reset + "\n " + text.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ").slice(0, 600) + "\n");
|
|
17863
|
-
output.write(S2.dim + " Press y to allow once; any other key denies." + S2.reset + "\n");
|
|
17864
|
-
return new Promise((resolve8) => {
|
|
17865
|
-
const wasRaw = Boolean(input.isRaw);
|
|
17866
|
-
const onData = (chunk) => {
|
|
17867
|
-
input.off("data", onData);
|
|
17868
|
-
if (input.setRawMode) input.setRawMode(wasRaw);
|
|
17869
|
-
resolve8(chunk.toString("utf8").toLowerCase() === "y");
|
|
17870
|
-
};
|
|
17871
|
-
if (input.setRawMode) input.setRawMode(true);
|
|
17872
|
-
input.resume();
|
|
17873
|
-
input.on("data", onData);
|
|
17874
|
-
});
|
|
17875
|
-
}
|
|
17876
|
-
async function runCodexTurn(opts) {
|
|
17877
|
-
const session = new CodexAppSession();
|
|
17878
|
-
try {
|
|
17879
|
-
return await session.turn(opts);
|
|
17880
|
-
} finally {
|
|
17881
|
-
session.close();
|
|
18104
|
+
if (method === "turn/plan/updated") {
|
|
18105
|
+
return [{ type: "plan", steps: (params.plan || []).map((row2) => ({ step: row2.step || "", status: row2.status || "pending" })) }];
|
|
18106
|
+
}
|
|
18107
|
+
if (method === "thread/tokenUsage/updated") {
|
|
18108
|
+
const usage2 = params.tokenUsage || {};
|
|
18109
|
+
const total = usage2.total || usage2.last || {};
|
|
18110
|
+
const used = Number(total.totalTokens ?? total.total_tokens ?? usage2.totalTokens ?? 0);
|
|
18111
|
+
const context = Number(usage2.modelContextWindow ?? usage2.model_context_window ?? 0);
|
|
18112
|
+
return [{ type: "usage", used, contextWindow: context || null }];
|
|
17882
18113
|
}
|
|
18114
|
+
if (method === "warning" || method === "error") return [{ type: "notice", text: params.message || params.error?.message || "Codex transport warning" }];
|
|
18115
|
+
return [];
|
|
17883
18116
|
}
|
|
17884
|
-
var
|
|
18117
|
+
var CodexSession;
|
|
17885
18118
|
var init_codex = __esm({
|
|
17886
18119
|
"cli/harness/codex.ts"() {
|
|
17887
18120
|
"use strict";
|
|
17888
18121
|
init_composer();
|
|
17889
18122
|
init_events();
|
|
18123
|
+
init_changes();
|
|
18124
|
+
init_codexUsage();
|
|
17890
18125
|
init_run();
|
|
17891
18126
|
init_render2();
|
|
17892
|
-
|
|
18127
|
+
CodexSession = class {
|
|
18128
|
+
constructor(cwd, resumeId = "") {
|
|
18129
|
+
this.cwd = cwd;
|
|
18130
|
+
this.resumeId = resumeId;
|
|
18131
|
+
}
|
|
18132
|
+
cwd;
|
|
18133
|
+
resumeId;
|
|
17893
18134
|
child = null;
|
|
17894
|
-
|
|
18135
|
+
initialized = false;
|
|
17895
18136
|
nextId = 1;
|
|
17896
18137
|
pending = /* @__PURE__ */ new Map();
|
|
17897
|
-
|
|
18138
|
+
onEvent = null;
|
|
18139
|
+
activeTurn = "";
|
|
18140
|
+
turnDone = null;
|
|
18141
|
+
completedTurns = /* @__PURE__ */ new Map();
|
|
18142
|
+
accountUsage = null;
|
|
17898
18143
|
threadId = "";
|
|
17899
|
-
model = "
|
|
17900
|
-
|
|
17901
|
-
|
|
17902
|
-
send(message) {
|
|
17903
|
-
this.child?.stdin.write(JSON.stringify({ jsonrpc: "2.0", ...message }) + "\n");
|
|
17904
|
-
}
|
|
17905
|
-
request(method, params) {
|
|
18144
|
+
model = "";
|
|
18145
|
+
request(method, params, timeoutMs = 0) {
|
|
18146
|
+
if (!this.child) return Promise.reject(new Error("Codex app-server is not running"));
|
|
17906
18147
|
const id = this.nextId++;
|
|
17907
|
-
|
|
17908
|
-
|
|
17909
|
-
|
|
18148
|
+
this.child.stdin.write(JSON.stringify({ method, id, params }) + "\n");
|
|
18149
|
+
return new Promise((resolve9, reject) => {
|
|
18150
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
18151
|
+
this.pending.delete(id);
|
|
18152
|
+
reject(new Error(method + " timed out"));
|
|
18153
|
+
}, timeoutMs) : null;
|
|
18154
|
+
this.pending.set(id, {
|
|
18155
|
+
resolve: (value) => {
|
|
18156
|
+
if (timer) clearTimeout(timer);
|
|
18157
|
+
resolve9(value);
|
|
18158
|
+
},
|
|
18159
|
+
reject: (error) => {
|
|
18160
|
+
if (timer) clearTimeout(timer);
|
|
18161
|
+
reject(error);
|
|
18162
|
+
}
|
|
18163
|
+
});
|
|
17910
18164
|
});
|
|
17911
18165
|
}
|
|
17912
|
-
|
|
17913
|
-
this.
|
|
18166
|
+
notify(method, params = {}) {
|
|
18167
|
+
this.child?.stdin.write(JSON.stringify({ method, params }) + "\n");
|
|
17914
18168
|
}
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
this.respond(message.id, { decision: accepted ? "accept" : "decline" });
|
|
17923
|
-
return;
|
|
17924
|
-
}
|
|
17925
|
-
if (method === "item/tool/requestUserInput") {
|
|
17926
|
-
const answers = {};
|
|
17927
|
-
for (const question of params.questions || []) answers[String(question.id)] = { answers: [] };
|
|
17928
|
-
this.respond(message.id, { answers });
|
|
17929
|
-
return;
|
|
17930
|
-
}
|
|
17931
|
-
if (method === "mcpServer/elicitation/request") {
|
|
17932
|
-
this.respond(message.id, { action: "decline" });
|
|
17933
|
-
return;
|
|
17934
|
-
}
|
|
17935
|
-
this.respond(message.id, {});
|
|
17936
|
-
}
|
|
17937
|
-
handleLine(line) {
|
|
17938
|
-
let message;
|
|
17939
|
-
try {
|
|
17940
|
-
message = JSON.parse(line);
|
|
17941
|
-
} catch {
|
|
18169
|
+
receive(message) {
|
|
18170
|
+
if (message.id !== void 0 && !message.method) {
|
|
18171
|
+
const pending = this.pending.get(message.id);
|
|
18172
|
+
if (!pending) return;
|
|
18173
|
+
this.pending.delete(message.id);
|
|
18174
|
+
if (message.error) pending.reject(new Error(message.error.message || stringify(message.error)));
|
|
18175
|
+
else pending.resolve(message.result);
|
|
17942
18176
|
return;
|
|
17943
18177
|
}
|
|
17944
|
-
if (message.
|
|
17945
|
-
|
|
18178
|
+
if (message.id !== void 0 && message.method) {
|
|
18179
|
+
this.child?.stdin.write(JSON.stringify({ id: message.id, result: { decision: "decline" } }) + "\n");
|
|
18180
|
+
this.onEvent?.({ type: "notice", text: "Codex requested an approval the embedded policy did not permit." });
|
|
17946
18181
|
return;
|
|
17947
18182
|
}
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
if (message.method === "turn/started") active.turnId = String(message.params?.turn?.id || "");
|
|
17954
|
-
if (message.method === "turn/completed") {
|
|
17955
|
-
const result = active.sink.finish(message.params?.turn?.status === "completed" ? 0 : 1, active.interrupted);
|
|
17956
|
-
this.active = null;
|
|
17957
|
-
active.resolve(result);
|
|
17958
|
-
}
|
|
17959
|
-
return;
|
|
18183
|
+
const messageTurnId = codexMessageTurnId(message);
|
|
18184
|
+
const staleTurnEvent = codexMessageIsTurnScoped(message) && (!this.activeTurn || messageTurnId && messageTurnId !== this.activeTurn);
|
|
18185
|
+
for (const event of codexEvents(message)) {
|
|
18186
|
+
if (event.type === "account-usage") this.accountUsage = event;
|
|
18187
|
+
if (!staleTurnEvent) this.onEvent?.(event);
|
|
17960
18188
|
}
|
|
17961
|
-
if (message.id
|
|
17962
|
-
const
|
|
17963
|
-
|
|
17964
|
-
this.
|
|
17965
|
-
|
|
17966
|
-
|
|
18189
|
+
if (message.method === "turn/completed" && message.params?.turn?.id === this.activeTurn) {
|
|
18190
|
+
const status = String(message.params.turn.status || "");
|
|
18191
|
+
this.completedTurns.set(message.params.turn.id, status);
|
|
18192
|
+
this.turnDone?.(status);
|
|
18193
|
+
} else if (message.method === "turn/completed" && message.params?.turn?.id) {
|
|
18194
|
+
this.completedTurns.set(message.params.turn.id, String(message.params.turn.status || ""));
|
|
17967
18195
|
}
|
|
17968
18196
|
}
|
|
17969
|
-
async start(
|
|
17970
|
-
|
|
17971
|
-
this.
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
this.
|
|
17975
|
-
|
|
17976
|
-
|
|
17977
|
-
|
|
17978
|
-
|
|
17979
|
-
|
|
17980
|
-
|
|
18197
|
+
async start() {
|
|
18198
|
+
this.child = spawn10("codex", ["app-server", "--stdio"], { cwd: this.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
18199
|
+
this.child.on("error", (cause) => {
|
|
18200
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
18201
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
18202
|
+
this.pending.clear();
|
|
18203
|
+
this.turnDone?.("failed");
|
|
18204
|
+
});
|
|
18205
|
+
createInterface6({ input: this.child.stdout }).on("line", (line) => {
|
|
18206
|
+
try {
|
|
18207
|
+
this.receive(JSON.parse(line));
|
|
18208
|
+
} catch {
|
|
17981
18209
|
}
|
|
17982
18210
|
});
|
|
17983
18211
|
this.child.stderr.on("data", (chunk) => {
|
|
17984
|
-
const
|
|
17985
|
-
if (
|
|
18212
|
+
const message = codexStderrNotice(String(chunk || ""), this.initialized);
|
|
18213
|
+
if (message) this.onEvent?.({ type: "notice", text: message });
|
|
17986
18214
|
});
|
|
17987
|
-
this.child.
|
|
17988
|
-
const
|
|
17989
|
-
|
|
17990
|
-
this.active = null;
|
|
17991
|
-
active.resolve(active.sink.finish(code ?? 1, active.interrupted));
|
|
17992
|
-
}
|
|
17993
|
-
for (const pending of this.pending.values()) pending.reject(new Error("codex app-server exited"));
|
|
18215
|
+
this.child.once("exit", (code) => {
|
|
18216
|
+
const error = new Error("Codex app-server exited" + (code === null ? "" : " (" + code + ")"));
|
|
18217
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
17994
18218
|
this.pending.clear();
|
|
17995
|
-
this.
|
|
18219
|
+
this.turnDone?.("failed");
|
|
17996
18220
|
});
|
|
17997
|
-
this.
|
|
17998
|
-
|
|
17999
|
-
const active = this.active;
|
|
18000
|
-
if (active) {
|
|
18001
|
-
active.sink.notice(failure.message);
|
|
18002
|
-
this.active = null;
|
|
18003
|
-
active.resolve(active.sink.finish(1, active.interrupted));
|
|
18004
|
-
}
|
|
18005
|
-
for (const pending of this.pending.values()) pending.reject(failure);
|
|
18006
|
-
this.pending.clear();
|
|
18007
|
-
});
|
|
18008
|
-
const initialized = await this.request("initialize", { clientInfo: { name: "synkro-governed", version: "1.0.0" } });
|
|
18221
|
+
await this.request("initialize", { clientInfo: { name: "synkro-terminal", title: "Synkro", version: "1" }, capabilities: { experimentalApi: true } });
|
|
18222
|
+
this.notify("initialized");
|
|
18009
18223
|
this.initialized = true;
|
|
18010
|
-
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18224
|
+
try {
|
|
18225
|
+
const event = codexAccountUsageEvent(await this.request("account/rateLimits/read", null, 4e3));
|
|
18226
|
+
if (event) this.accountUsage = event;
|
|
18227
|
+
} catch {
|
|
18228
|
+
}
|
|
18229
|
+
let result;
|
|
18230
|
+
try {
|
|
18231
|
+
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" });
|
|
18232
|
+
} catch (error) {
|
|
18233
|
+
if (!this.resumeId) throw error;
|
|
18234
|
+
result = await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write", serviceName: "Synkro" });
|
|
18235
|
+
}
|
|
18236
|
+
this.threadId = result.thread?.id || "";
|
|
18237
|
+
this.model = result.model || "";
|
|
18021
18238
|
}
|
|
18022
|
-
async
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
18028
|
-
|
|
18029
|
-
animate: false,
|
|
18030
|
-
showPrompt: opts.showPrompt,
|
|
18031
|
-
showHeader: opts.showHeader
|
|
18032
|
-
});
|
|
18033
|
-
sink.event({ type: "session-start", sessionId: this.threadId, model: this.model, cwd: this.cwd, authSource: "login" });
|
|
18034
|
-
sink.event({
|
|
18035
|
-
type: "user-message",
|
|
18036
|
-
text: opts.prompt || (opts.images?.length ? "[" + opts.images.length + " image attached]" : "")
|
|
18037
|
-
});
|
|
18038
|
-
const result = new Promise((resolve8) => {
|
|
18039
|
-
this.active = { sink, resolve: resolve8, startedAt: /* @__PURE__ */ new Map(), approvalReasons: /* @__PURE__ */ new Map(), turnId: "", interrupted: false };
|
|
18040
|
-
});
|
|
18041
|
-
const onAbort = () => {
|
|
18042
|
-
if (!this.active) return;
|
|
18043
|
-
this.active.interrupted = true;
|
|
18044
|
-
if (this.active.turnId) void this.request("turn/interrupt", { threadId: this.threadId, turnId: this.active.turnId }).catch(() => {
|
|
18045
|
-
});
|
|
18239
|
+
async runTurn(prompt, onEvent, signal) {
|
|
18240
|
+
let blocked = 0;
|
|
18241
|
+
let turnId = "";
|
|
18242
|
+
let interrupt = null;
|
|
18243
|
+
this.onEvent = (event) => {
|
|
18244
|
+
if (event.type === "tool-end" && event.blocked) blocked++;
|
|
18245
|
+
onEvent(event);
|
|
18046
18246
|
};
|
|
18047
|
-
if (opts.signal?.aborted) onAbort();
|
|
18048
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
18049
18247
|
try {
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18248
|
+
onEvent({ type: "session-start", sessionId: this.threadId, model: this.model || "Codex", cwd: this.cwd, authSource: "login" });
|
|
18249
|
+
if (this.accountUsage) onEvent(this.accountUsage);
|
|
18250
|
+
onEvent({ type: "user-message", text: prompt });
|
|
18251
|
+
const response = await this.request("turn/start", { threadId: this.threadId, input: [{ type: "text", text: prompt }] });
|
|
18252
|
+
turnId = response.turn?.id || "";
|
|
18253
|
+
this.activeTurn = turnId;
|
|
18254
|
+
let aborted = false;
|
|
18255
|
+
let interruptSent = false;
|
|
18256
|
+
interrupt = () => {
|
|
18257
|
+
aborted = true;
|
|
18258
|
+
if (interruptSent || !this.activeTurn) return;
|
|
18259
|
+
interruptSent = true;
|
|
18260
|
+
void this.request("turn/interrupt", { threadId: this.threadId, turnId: this.activeTurn }).catch(() => {
|
|
18261
|
+
});
|
|
18262
|
+
};
|
|
18263
|
+
if (signal?.aborted) interrupt();
|
|
18264
|
+
else signal?.addEventListener("abort", interrupt, { once: true });
|
|
18265
|
+
const alreadyDone = this.completedTurns.get(turnId);
|
|
18266
|
+
const status = alreadyDone ?? await new Promise((resolve9) => {
|
|
18267
|
+
this.turnDone = resolve9;
|
|
18054
18268
|
});
|
|
18055
|
-
|
|
18269
|
+
this.completedTurns.delete(turnId);
|
|
18270
|
+
signal?.removeEventListener("abort", interrupt);
|
|
18271
|
+
this.activeTurn = "";
|
|
18272
|
+
this.turnDone = null;
|
|
18273
|
+
this.onEvent = null;
|
|
18274
|
+
onEvent({ type: "turn-end", ok: aborted || /completed/.test(status), text: aborted ? "interrupted" : status });
|
|
18275
|
+
return { exitCode: aborted ? 130 : /completed/.test(status) ? 0 : 1, blocked, sessionId: this.threadId };
|
|
18056
18276
|
} finally {
|
|
18057
|
-
|
|
18277
|
+
if (interrupt) signal?.removeEventListener("abort", interrupt);
|
|
18278
|
+
if (turnId) this.completedTurns.delete(turnId);
|
|
18279
|
+
this.activeTurn = "";
|
|
18280
|
+
this.turnDone = null;
|
|
18281
|
+
this.onEvent = null;
|
|
18058
18282
|
}
|
|
18059
18283
|
}
|
|
18060
18284
|
close() {
|
|
18061
|
-
this.child?.kill();
|
|
18285
|
+
this.child?.kill("SIGTERM");
|
|
18062
18286
|
this.child = null;
|
|
18063
18287
|
}
|
|
18064
18288
|
};
|
|
18065
18289
|
}
|
|
18066
18290
|
});
|
|
18067
18291
|
|
|
18292
|
+
// cli/harness/screen.ts
|
|
18293
|
+
var ANSI, REVERSE, STREAM_DRAW_MS, STATUS_TICK_MS, resetDateFormat, visible, pathLabel, SynkroScreen;
|
|
18294
|
+
var init_screen = __esm({
|
|
18295
|
+
"cli/harness/screen.ts"() {
|
|
18296
|
+
"use strict";
|
|
18297
|
+
init_render2();
|
|
18298
|
+
init_changes();
|
|
18299
|
+
ANSI = /\x1b\[[0-?]*[ -\/]*[@-~]/g;
|
|
18300
|
+
REVERSE = "\x1B[7m";
|
|
18301
|
+
STREAM_DRAW_MS = 80;
|
|
18302
|
+
STATUS_TICK_MS = 240;
|
|
18303
|
+
resetDateFormat = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" });
|
|
18304
|
+
visible = (value) => value.replace(ANSI, "").length;
|
|
18305
|
+
pathLabel = (cwd) => {
|
|
18306
|
+
const parts = cwd.split("/").filter(Boolean);
|
|
18307
|
+
return parts.length > 2 ? "\u2026/" + parts.slice(-2).join("/") : cwd;
|
|
18308
|
+
};
|
|
18309
|
+
SynkroScreen = class {
|
|
18310
|
+
constructor(harness, cwd, write2 = (text) => process.stdout.write(text)) {
|
|
18311
|
+
this.harness = harness;
|
|
18312
|
+
this.cwd = cwd;
|
|
18313
|
+
this.write = write2;
|
|
18314
|
+
this.changes = new OperationChangeTracker(cwd);
|
|
18315
|
+
}
|
|
18316
|
+
harness;
|
|
18317
|
+
cwd;
|
|
18318
|
+
write;
|
|
18319
|
+
transcript = [];
|
|
18320
|
+
draft = "";
|
|
18321
|
+
input = "";
|
|
18322
|
+
selectionAnchor = null;
|
|
18323
|
+
selectionFocus = null;
|
|
18324
|
+
status = "Ready";
|
|
18325
|
+
statusSince = Date.now();
|
|
18326
|
+
tick = 0;
|
|
18327
|
+
used = 0;
|
|
18328
|
+
context = null;
|
|
18329
|
+
weeklyRemaining = null;
|
|
18330
|
+
weeklyResetsAt = null;
|
|
18331
|
+
model = "";
|
|
18332
|
+
timer = null;
|
|
18333
|
+
drawTimer = null;
|
|
18334
|
+
lastDrawAt = 0;
|
|
18335
|
+
scrollOffset = 0;
|
|
18336
|
+
changes;
|
|
18337
|
+
resize = () => this.draw();
|
|
18338
|
+
enter() {
|
|
18339
|
+
this.write("\x1B[?1049h\x1B[?25l\x1B[?1000h\x1B[?1002h\x1B[?1006h");
|
|
18340
|
+
process.stdout.on("resize", this.resize);
|
|
18341
|
+
this.timer = setInterval(() => {
|
|
18342
|
+
if (this.status === "Ready") return;
|
|
18343
|
+
this.tick++;
|
|
18344
|
+
this.queueDraw();
|
|
18345
|
+
}, STATUS_TICK_MS);
|
|
18346
|
+
this.timer.unref();
|
|
18347
|
+
this.draw();
|
|
18348
|
+
}
|
|
18349
|
+
leave() {
|
|
18350
|
+
if (this.timer) clearInterval(this.timer);
|
|
18351
|
+
if (this.drawTimer) clearTimeout(this.drawTimer);
|
|
18352
|
+
process.stdout.removeListener("resize", this.resize);
|
|
18353
|
+
this.write(S2.reset + "\x1B[?1006l\x1B[?1002l\x1B[?1000l\x1B[?25h\x1B[?1049l");
|
|
18354
|
+
}
|
|
18355
|
+
setInput(value) {
|
|
18356
|
+
this.input = cleanOutput(value);
|
|
18357
|
+
this.selectionAnchor = null;
|
|
18358
|
+
this.selectionFocus = null;
|
|
18359
|
+
this.draw();
|
|
18360
|
+
}
|
|
18361
|
+
selectionRange() {
|
|
18362
|
+
if (this.selectionAnchor === null || this.selectionFocus === null || this.selectionAnchor === this.selectionFocus) return null;
|
|
18363
|
+
return [Math.min(this.selectionAnchor, this.selectionFocus), Math.max(this.selectionAnchor, this.selectionFocus)];
|
|
18364
|
+
}
|
|
18365
|
+
clearSelection() {
|
|
18366
|
+
this.selectionAnchor = null;
|
|
18367
|
+
this.selectionFocus = null;
|
|
18368
|
+
this.draw();
|
|
18369
|
+
}
|
|
18370
|
+
scrollBy(lines2) {
|
|
18371
|
+
this.scrollOffset = Math.max(0, this.scrollOffset + lines2);
|
|
18372
|
+
this.draw();
|
|
18373
|
+
}
|
|
18374
|
+
scrollToBottom() {
|
|
18375
|
+
this.scrollOffset = 0;
|
|
18376
|
+
this.draw();
|
|
18377
|
+
}
|
|
18378
|
+
/** Select text using one-based SGR mouse coordinates from the terminal. */
|
|
18379
|
+
selectInputAt(x, y, extend, width = this.width(), height = this.height()) {
|
|
18380
|
+
const rows = this.inputRows(width);
|
|
18381
|
+
const composerLength = rows.length + 2;
|
|
18382
|
+
const viewport = Math.max(3, height - composerLength - 2);
|
|
18383
|
+
const lineIndex = y - (viewport + 2);
|
|
18384
|
+
const row2 = rows[lineIndex];
|
|
18385
|
+
if (!row2) return;
|
|
18386
|
+
const index = Math.min(row2.end, row2.start + Math.max(0, x - 5));
|
|
18387
|
+
if (!extend || this.selectionAnchor === null) this.selectionAnchor = index;
|
|
18388
|
+
this.selectionFocus = index;
|
|
18389
|
+
this.draw();
|
|
18390
|
+
}
|
|
18391
|
+
updateStatus(value) {
|
|
18392
|
+
if (this.status === value) return false;
|
|
18393
|
+
this.status = value;
|
|
18394
|
+
this.statusSince = Date.now();
|
|
18395
|
+
return true;
|
|
18396
|
+
}
|
|
18397
|
+
setStatus(value) {
|
|
18398
|
+
if (this.updateStatus(value)) this.draw();
|
|
18399
|
+
}
|
|
18400
|
+
add(event) {
|
|
18401
|
+
event = this.changes.observe(event);
|
|
18402
|
+
if (event.type === "session-start") {
|
|
18403
|
+
this.model = event.model;
|
|
18404
|
+
this.draw();
|
|
18405
|
+
return;
|
|
18406
|
+
}
|
|
18407
|
+
if (event.type === "assistant-delta") {
|
|
18408
|
+
this.draft += event.text;
|
|
18409
|
+
this.updateStatus("Writing\u2026");
|
|
18410
|
+
this.queueDraw();
|
|
18411
|
+
return;
|
|
18412
|
+
}
|
|
18413
|
+
if (event.type === "assistant-message") this.draft = "";
|
|
18414
|
+
if (event.type === "usage") {
|
|
18415
|
+
this.used = event.used;
|
|
18416
|
+
this.context = event.contextWindow;
|
|
18417
|
+
if (event.model) this.model = event.model;
|
|
18418
|
+
this.draw();
|
|
18419
|
+
return;
|
|
18420
|
+
}
|
|
18421
|
+
if (event.type === "account-usage") {
|
|
18422
|
+
this.weeklyRemaining = event.remainingPercent;
|
|
18423
|
+
this.weeklyResetsAt = event.resetsAt;
|
|
18424
|
+
this.draw();
|
|
18425
|
+
return;
|
|
18426
|
+
}
|
|
18427
|
+
if (event.type === "thinking") {
|
|
18428
|
+
if (this.updateStatus(event.text || "Thinking\u2026")) this.draw();
|
|
18429
|
+
return;
|
|
18430
|
+
}
|
|
18431
|
+
if (event.type === "tool-start") this.updateStatus("Working\u2026");
|
|
18432
|
+
if (event.type === "tool-end") this.updateStatus(event.blocked ? "Guard enforced" : "Working\u2026");
|
|
18433
|
+
if (event.type === "turn-end") this.updateStatus(event.ok ? "Ready" : "Turn ended");
|
|
18434
|
+
const rendered = renderEvent(event, this.width());
|
|
18435
|
+
if (this.scrollOffset > 0) this.scrollOffset += rendered.length;
|
|
18436
|
+
this.transcript.push(...rendered);
|
|
18437
|
+
this.draw();
|
|
18438
|
+
}
|
|
18439
|
+
width() {
|
|
18440
|
+
return Math.max(50, Number(process.stdout.columns || 100));
|
|
18441
|
+
}
|
|
18442
|
+
height() {
|
|
18443
|
+
return Math.max(16, Number(process.stdout.rows || 30));
|
|
18444
|
+
}
|
|
18445
|
+
inputRows(width) {
|
|
18446
|
+
const lineWidth = Math.max(1, Math.max(30, width - 4) - 4);
|
|
18447
|
+
if (!this.input) return [{ text: "", start: 0, end: 0 }];
|
|
18448
|
+
const rows = [];
|
|
18449
|
+
for (let start = 0; start < this.input.length; start += lineWidth) {
|
|
18450
|
+
const text = this.input.slice(start, start + lineWidth);
|
|
18451
|
+
rows.push({ text, start, end: start + text.length });
|
|
18452
|
+
}
|
|
18453
|
+
return rows.slice(-3);
|
|
18454
|
+
}
|
|
18455
|
+
selectedText(row2) {
|
|
18456
|
+
const range = this.selectionRange();
|
|
18457
|
+
if (!range || range[1] <= row2.start || range[0] >= row2.end) return S2.text + row2.text;
|
|
18458
|
+
const from = Math.max(range[0], row2.start) - row2.start;
|
|
18459
|
+
const to = Math.min(range[1], row2.end) - row2.start;
|
|
18460
|
+
return S2.text + row2.text.slice(0, from) + REVERSE + row2.text.slice(from, to) + S2.reset + S2.composer + S2.text + row2.text.slice(to);
|
|
18461
|
+
}
|
|
18462
|
+
frame(width = this.width(), height = this.height()) {
|
|
18463
|
+
const inner = Math.max(30, width - 4);
|
|
18464
|
+
const provider = this.harness === "codex" ? "Codex" : "Cursor";
|
|
18465
|
+
const draft = this.draft ? ["", ...wrap(this.draft, inner - 2).map((line, i) => S2.agent + " " + (i ? " " : "\u23FA ") + line + S2.reset)] : [];
|
|
18466
|
+
const status = this.status === "Ready" ? S2.secondary + " \xB7 Ready" + S2.reset : statusLine({ tick: this.tick, text: this.status, elapsedMs: Date.now() - this.statusSince, width });
|
|
18467
|
+
const rule = S2.border + " " + "\u2500".repeat(inner) + S2.reset;
|
|
18468
|
+
const inputRows = this.inputRows(width);
|
|
18469
|
+
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];
|
|
18470
|
+
const usedPercent = this.context ? Math.min(100, Math.round(this.used / this.context * 100)) : null;
|
|
18471
|
+
const contextUsage = usedPercent === null ? "\u2014 context" : usedPercent + "% context";
|
|
18472
|
+
const resetDate = this.weeklyResetsAt == null ? "" : " \xB7 " + resetDateFormat.format(new Date(this.weeklyResetsAt * 1e3));
|
|
18473
|
+
const accountUsage = this.weeklyRemaining === null ? "Weekly \u2014" : "Weekly " + this.weeklyRemaining + "% left" + resetDate;
|
|
18474
|
+
const usage2 = contextUsage + " \xB7 " + accountUsage;
|
|
18475
|
+
const left = S2.secondary + " " + pathLabel(this.cwd) + S2.reset + S2.canvas;
|
|
18476
|
+
const badge = S2.panel + S2.secondary + " " + usage2 + " " + S2.reset + S2.canvas;
|
|
18477
|
+
const right = badge + S2.secondary + " \xB7 " + provider + (this.model ? " " + cleanOutput(this.model) : "") + " \xB7 \u29C9 powered by synkro " + S2.reset + S2.canvas;
|
|
18478
|
+
const footer = left + " ".repeat(Math.max(1, width - visible(left) - visible(right))) + right;
|
|
18479
|
+
const viewport = Math.max(3, height - composer.length - 2);
|
|
18480
|
+
const fullContent = [...this.transcript, ...draft, status];
|
|
18481
|
+
const maxScroll = Math.max(0, fullContent.length - viewport);
|
|
18482
|
+
const effectiveOffset = Math.min(this.scrollOffset, maxScroll);
|
|
18483
|
+
const end = fullContent.length - effectiveOffset;
|
|
18484
|
+
const content = fullContent.slice(Math.max(0, end - viewport), end);
|
|
18485
|
+
while (content.length < viewport) content.unshift("");
|
|
18486
|
+
const rows = [...content, ...composer, footer].slice(0, height);
|
|
18487
|
+
while (rows.length < height) rows.push("");
|
|
18488
|
+
return rows.map((line) => {
|
|
18489
|
+
const fill = line.startsWith(S2.panel) ? S2.panel : line.includes(S2.composer) ? S2.composer : S2.canvas;
|
|
18490
|
+
return S2.canvas + line + fill + " ".repeat(Math.max(0, width - visible(line))) + S2.reset;
|
|
18491
|
+
}).join("\n");
|
|
18492
|
+
}
|
|
18493
|
+
queueDraw() {
|
|
18494
|
+
if (this.drawTimer) return;
|
|
18495
|
+
const delay = Math.max(0, STREAM_DRAW_MS - (Date.now() - this.lastDrawAt));
|
|
18496
|
+
this.drawTimer = setTimeout(() => {
|
|
18497
|
+
this.drawTimer = null;
|
|
18498
|
+
this.draw();
|
|
18499
|
+
}, delay);
|
|
18500
|
+
this.drawTimer.unref?.();
|
|
18501
|
+
}
|
|
18502
|
+
draw() {
|
|
18503
|
+
if (this.drawTimer) {
|
|
18504
|
+
clearTimeout(this.drawTimer);
|
|
18505
|
+
this.drawTimer = null;
|
|
18506
|
+
}
|
|
18507
|
+
this.lastDrawAt = Date.now();
|
|
18508
|
+
this.write("\x1B[H" + this.frame());
|
|
18509
|
+
}
|
|
18510
|
+
};
|
|
18511
|
+
}
|
|
18512
|
+
});
|
|
18513
|
+
|
|
18514
|
+
// cli/harness/state.ts
|
|
18515
|
+
import { mkdirSync as mkdirSync24, readFileSync as readFileSync34, writeFileSync as writeFileSync28 } from "fs";
|
|
18516
|
+
import { homedir as homedir39 } from "os";
|
|
18517
|
+
import { dirname as dirname13, join as join38 } from "path";
|
|
18518
|
+
function load(file = STATE_FILE) {
|
|
18519
|
+
try {
|
|
18520
|
+
const value = JSON.parse(readFileSync34(file, "utf8"));
|
|
18521
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
18522
|
+
} catch {
|
|
18523
|
+
return {};
|
|
18524
|
+
}
|
|
18525
|
+
}
|
|
18526
|
+
function loadSessionId(harness, cwd, file = STATE_FILE) {
|
|
18527
|
+
return load(file)[key(harness, cwd)] || "";
|
|
18528
|
+
}
|
|
18529
|
+
function saveSessionId(harness, cwd, sessionId, file = STATE_FILE) {
|
|
18530
|
+
if (!sessionId) return;
|
|
18531
|
+
try {
|
|
18532
|
+
mkdirSync24(dirname13(file), { recursive: true });
|
|
18533
|
+
writeFileSync28(file, JSON.stringify({ ...load(file), [key(harness, cwd)]: sessionId }, null, 2));
|
|
18534
|
+
} catch {
|
|
18535
|
+
}
|
|
18536
|
+
}
|
|
18537
|
+
var STATE_FILE, key;
|
|
18538
|
+
var init_state = __esm({
|
|
18539
|
+
"cli/harness/state.ts"() {
|
|
18540
|
+
"use strict";
|
|
18541
|
+
STATE_FILE = join38(homedir39(), ".synkro", "terminal-sessions.json");
|
|
18542
|
+
key = (harness, cwd) => harness + ":" + cwd.replace(/\/+$/, "");
|
|
18543
|
+
}
|
|
18544
|
+
});
|
|
18545
|
+
|
|
18068
18546
|
// cli/harness/fixPoll.ts
|
|
18069
18547
|
function pollBaseUrl() {
|
|
18070
18548
|
const port = String(process.env.SYNKRO_MCP_PORT || "18931");
|
|
@@ -18079,11 +18557,13 @@ async function loadFixPoll(itemId, fetchImpl = fetch) {
|
|
|
18079
18557
|
const body = await response.json();
|
|
18080
18558
|
const candidates = Array.isArray(body.candidates) ? body.candidates.filter((candidate) => typeof candidate === "string").slice(0, 8) : [];
|
|
18081
18559
|
if (!candidates.length || body.status !== "pending") return null;
|
|
18560
|
+
const recommendedIdx = Number.isInteger(body.recommended_idx) && body.recommended_idx >= 0 && body.recommended_idx < candidates.length ? body.recommended_idx : -1;
|
|
18082
18561
|
return {
|
|
18083
18562
|
itemId,
|
|
18084
18563
|
filePath: String(body.file_path || ""),
|
|
18085
18564
|
ruleId: String(body.rule_id || ""),
|
|
18086
|
-
candidates
|
|
18565
|
+
candidates,
|
|
18566
|
+
recommendedIdx
|
|
18087
18567
|
};
|
|
18088
18568
|
}
|
|
18089
18569
|
async function recordFixPoll(itemId, chosenIdx, fetchImpl = fetch) {
|
|
@@ -18098,7 +18578,7 @@ function fixPollLines(poll, selected, width = 100) {
|
|
|
18098
18578
|
const max = Math.max(32, width - 10);
|
|
18099
18579
|
const choices = [...poll.candidates, "None of the above"];
|
|
18100
18580
|
const scope = [poll.ruleId, poll.filePath].filter(Boolean).join(" \xB7 ");
|
|
18101
|
-
const
|
|
18581
|
+
const lines2 = [
|
|
18102
18582
|
"",
|
|
18103
18583
|
S2.bold + S2.rule + " Synkro needs your decision" + S2.reset,
|
|
18104
18584
|
...scope ? [S2.dim + " " + neutralizeTerminalControls(scope) + S2.reset] : [],
|
|
@@ -18106,42 +18586,43 @@ function fixPollLines(poll, selected, width = 100) {
|
|
|
18106
18586
|
];
|
|
18107
18587
|
choices.forEach((choice, index) => {
|
|
18108
18588
|
const prefix = index === selected ? S2.user + " \u276F " : S2.dim + " ";
|
|
18109
|
-
const
|
|
18110
|
-
|
|
18111
|
-
|
|
18589
|
+
const recommendation = index === poll.recommendedIdx ? "Recommended: " : "";
|
|
18590
|
+
const text = wrap(index + 1 + ". " + recommendation + neutralizeTerminalControls(choice), max);
|
|
18591
|
+
lines2.push(prefix + (text[0] || "") + S2.reset);
|
|
18592
|
+
for (const continuation of text.slice(1)) lines2.push(" " + continuation);
|
|
18112
18593
|
});
|
|
18113
|
-
|
|
18114
|
-
return
|
|
18594
|
+
lines2.push("", S2.dim + " \u2191/\u2193 select \xB7 Enter confirm \xB7 1-" + choices.length + " choose" + S2.reset);
|
|
18595
|
+
return lines2;
|
|
18115
18596
|
}
|
|
18116
18597
|
async function pickFixPoll(poll, io = {}) {
|
|
18117
18598
|
const input = io.input || process.stdin;
|
|
18118
18599
|
const output = io.output || process.stdout;
|
|
18119
18600
|
if (!input.isTTY || !output.isTTY) return -1;
|
|
18120
|
-
let selected = 0;
|
|
18601
|
+
let selected = poll.recommendedIdx >= 0 && poll.recommendedIdx < poll.candidates.length ? poll.recommendedIdx : 0;
|
|
18121
18602
|
let rendered = 0;
|
|
18122
18603
|
const draw = () => {
|
|
18123
18604
|
if (rendered) output.write("\x1B[" + rendered + "A\x1B[J");
|
|
18124
|
-
const
|
|
18125
|
-
output.write(
|
|
18126
|
-
rendered =
|
|
18605
|
+
const lines2 = fixPollLines(poll, selected, Number(output.columns || 100));
|
|
18606
|
+
output.write(lines2.join("\n") + "\n");
|
|
18607
|
+
rendered = lines2.length;
|
|
18127
18608
|
};
|
|
18128
|
-
const choice = await new Promise((
|
|
18609
|
+
const choice = await new Promise((resolve9) => {
|
|
18129
18610
|
const choices = poll.candidates.length + 1;
|
|
18130
18611
|
const wasRaw = Boolean(input.isRaw);
|
|
18131
18612
|
const done = (index) => {
|
|
18132
18613
|
input.off("data", onData);
|
|
18133
18614
|
if (input.setRawMode) input.setRawMode(wasRaw);
|
|
18134
|
-
|
|
18615
|
+
resolve9(index === poll.candidates.length ? -1 : index);
|
|
18135
18616
|
};
|
|
18136
18617
|
const onData = (chunk) => {
|
|
18137
|
-
const
|
|
18138
|
-
if (
|
|
18139
|
-
if (
|
|
18140
|
-
if (
|
|
18141
|
-
if (
|
|
18142
|
-
else if (
|
|
18143
|
-
else if (/^[1-9]$/.test(
|
|
18144
|
-
const index = Number(
|
|
18618
|
+
const key2 = chunk.toString("utf8");
|
|
18619
|
+
if (key2 === "") return done(poll.candidates.length);
|
|
18620
|
+
if (key2 === "\x1B" || key2.toLowerCase() === "n") return done(poll.candidates.length);
|
|
18621
|
+
if (key2 === "\r" || key2 === "\n") return done(selected);
|
|
18622
|
+
if (key2 === "\x1B[A" || key2 === "k") selected = (selected - 1 + choices) % choices;
|
|
18623
|
+
else if (key2 === "\x1B[B" || key2 === "j") selected = (selected + 1) % choices;
|
|
18624
|
+
else if (/^[1-9]$/.test(key2)) {
|
|
18625
|
+
const index = Number(key2) - 1;
|
|
18145
18626
|
if (index < choices) return done(index);
|
|
18146
18627
|
} else return;
|
|
18147
18628
|
draw();
|
|
@@ -18154,7 +18635,7 @@ async function pickFixPoll(poll, io = {}) {
|
|
|
18154
18635
|
return choice;
|
|
18155
18636
|
}
|
|
18156
18637
|
async function resolveFixPolls(events, io = {}) {
|
|
18157
|
-
const ids = Array.from(new Set(events.filter((event) => event.type === "tool-end").
|
|
18638
|
+
const ids = Array.from(new Set(events.filter((event) => event.type === "tool-end").flatMap((event) => event.pollIds?.length ? event.pollIds : [event.pollId || ""]).filter(Boolean)));
|
|
18158
18639
|
const recorded = [];
|
|
18159
18640
|
for (const itemId of ids) {
|
|
18160
18641
|
const poll = await loadFixPoll(itemId, io.fetchImpl).catch(() => null);
|
|
@@ -18173,134 +18654,227 @@ var init_fixPoll = __esm({
|
|
|
18173
18654
|
}
|
|
18174
18655
|
});
|
|
18175
18656
|
|
|
18176
|
-
// cli/harness/
|
|
18177
|
-
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
const value = String(path || "");
|
|
18181
|
-
if (home && value === home) return "~";
|
|
18182
|
-
if (home && value.startsWith(home + "/")) return "~" + value.slice(home.length);
|
|
18183
|
-
return value;
|
|
18184
|
-
}
|
|
18185
|
-
function parseCursorAccount(output) {
|
|
18186
|
-
const match = String(output || "").match(/logged in as\s+(\S+)/i);
|
|
18187
|
-
return match ? match[1].trim() : "";
|
|
18657
|
+
// cli/harness/session.ts
|
|
18658
|
+
function deleteSelectedText(value, range) {
|
|
18659
|
+
if (!range) return value.slice(0, -1);
|
|
18660
|
+
return value.slice(0, range[0]) + value.slice(range[1]);
|
|
18188
18661
|
}
|
|
18189
|
-
function
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
|
|
18198
|
-
|
|
18662
|
+
function handleScrollInput(screen, data) {
|
|
18663
|
+
let handled = false;
|
|
18664
|
+
const mousePattern = /\x1b\[<(\d+);\d+;\d+[Mm]/g;
|
|
18665
|
+
let mouse;
|
|
18666
|
+
while ((mouse = mousePattern.exec(data)) !== null) {
|
|
18667
|
+
const button = Number(mouse[1]);
|
|
18668
|
+
if (button === 64 || button === 65) {
|
|
18669
|
+
screen.scrollBy(button === 64 ? 3 : -3);
|
|
18670
|
+
handled = true;
|
|
18671
|
+
}
|
|
18199
18672
|
}
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
|
|
18203
|
-
try {
|
|
18204
|
-
email = String(getUserInfo().email || "");
|
|
18205
|
-
} catch {
|
|
18206
|
-
return { email: "", needsLogin: false };
|
|
18673
|
+
if (data.includes("\x1B[5~")) {
|
|
18674
|
+
screen.scrollBy(10);
|
|
18675
|
+
handled = true;
|
|
18207
18676
|
}
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
} catch {
|
|
18677
|
+
if (data.includes("\x1B[6~")) {
|
|
18678
|
+
screen.scrollBy(-10);
|
|
18679
|
+
handled = true;
|
|
18212
18680
|
}
|
|
18213
|
-
return
|
|
18214
|
-
}
|
|
18215
|
-
function readIdentity(harness) {
|
|
18216
|
-
const synkro = synkroAccount();
|
|
18217
|
-
return {
|
|
18218
|
-
harness: harness === "cursor" ? cursorAccount() : "",
|
|
18219
|
-
synkro: synkro.email,
|
|
18220
|
-
needsLogin: synkro.needsLogin
|
|
18221
|
-
};
|
|
18681
|
+
return handled;
|
|
18222
18682
|
}
|
|
18223
|
-
function
|
|
18224
|
-
const
|
|
18225
|
-
|
|
18226
|
-
if (opts.identity.harness) lines.push(label(opts.harness, opts.identity.harness));
|
|
18227
|
-
if (opts.identity.synkro) {
|
|
18228
|
-
lines.push(label("synkro", opts.identity.synkro, opts.identity.needsLogin ? S2.blocked + " \xB7 session ended, run synkro login" + S2.reset : ""));
|
|
18229
|
-
}
|
|
18230
|
-
lines.push("");
|
|
18231
|
-
return lines;
|
|
18683
|
+
function printEvent(event) {
|
|
18684
|
+
const lines2 = renderEvent(event, Number(process.stdout.columns || 100));
|
|
18685
|
+
if (lines2.length) process.stdout.write(lines2.join("\n") + "\n");
|
|
18232
18686
|
}
|
|
18233
|
-
var init_identity2 = __esm({
|
|
18234
|
-
"cli/harness/identity.ts"() {
|
|
18235
|
-
"use strict";
|
|
18236
|
-
init_auth();
|
|
18237
|
-
init_render2();
|
|
18238
|
-
}
|
|
18239
|
-
});
|
|
18240
|
-
|
|
18241
|
-
// cli/harness/session.ts
|
|
18242
18687
|
async function runOnce(harness, cwd, prompt, echoPrompt = true, showHeader = true, signal) {
|
|
18243
|
-
if (harness
|
|
18688
|
+
if (!supported(harness)) {
|
|
18244
18689
|
process.stdout.write(S2.dim + " " + harness + " sessions are not embedded yet\n" + S2.reset);
|
|
18245
18690
|
return 1;
|
|
18246
18691
|
}
|
|
18247
|
-
const
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18692
|
+
const resumeId = loadSessionId(harness, cwd);
|
|
18693
|
+
if (harness === "cursor") {
|
|
18694
|
+
const result = await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal, resumeId });
|
|
18695
|
+
saveSessionId(harness, cwd, result.sessionId || "");
|
|
18696
|
+
await resolveFixPolls(result.events);
|
|
18697
|
+
return result.exitCode;
|
|
18698
|
+
}
|
|
18699
|
+
const session = new CodexSession(cwd, resumeId);
|
|
18700
|
+
const events = [];
|
|
18701
|
+
try {
|
|
18702
|
+
await session.start();
|
|
18703
|
+
const result = await session.runTurn(prompt, (event) => {
|
|
18704
|
+
events.push(event);
|
|
18705
|
+
printEvent(event);
|
|
18706
|
+
}, signal);
|
|
18707
|
+
saveSessionId(harness, cwd, result.sessionId);
|
|
18708
|
+
await resolveFixPolls(events);
|
|
18709
|
+
return result.exitCode;
|
|
18710
|
+
} finally {
|
|
18711
|
+
session.close();
|
|
18253
18712
|
}
|
|
18254
|
-
return result.exitCode;
|
|
18255
18713
|
}
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18714
|
+
function readPrompt(screen) {
|
|
18715
|
+
return new Promise((resolve9) => {
|
|
18716
|
+
let value = "";
|
|
18717
|
+
const finish = (answer) => {
|
|
18718
|
+
process.stdin.removeListener("data", onData);
|
|
18719
|
+
screen.setInput("");
|
|
18720
|
+
resolve9(answer);
|
|
18721
|
+
};
|
|
18722
|
+
const onData = (chunk) => {
|
|
18723
|
+
const data = chunk.toString("utf8");
|
|
18724
|
+
const scrolled = handleScrollInput(screen, data);
|
|
18725
|
+
const mousePattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
18726
|
+
let mouse;
|
|
18727
|
+
let sawMouse = false;
|
|
18728
|
+
while ((mouse = mousePattern.exec(data)) !== null) {
|
|
18729
|
+
sawMouse = true;
|
|
18730
|
+
const button = Number(mouse[1]);
|
|
18731
|
+
if (button === 0 || button === 32) {
|
|
18732
|
+
screen.selectInputAt(Number(mouse[2]), Number(mouse[3]), button === 32 || mouse[4] === "m");
|
|
18733
|
+
}
|
|
18734
|
+
}
|
|
18735
|
+
if (sawMouse || scrolled) return;
|
|
18736
|
+
const selection = screen.selectionRange();
|
|
18737
|
+
if (data === "\x1B[3~") {
|
|
18738
|
+
value = deleteSelectedText(value, selection);
|
|
18739
|
+
screen.setInput(value);
|
|
18740
|
+
return;
|
|
18741
|
+
}
|
|
18742
|
+
if (data === "\x1B") {
|
|
18743
|
+
if (selection) {
|
|
18744
|
+
value = deleteSelectedText(value, selection);
|
|
18745
|
+
screen.setInput(value);
|
|
18746
|
+
} else finish(null);
|
|
18747
|
+
return;
|
|
18748
|
+
}
|
|
18749
|
+
if (data.startsWith("\x1B")) return;
|
|
18750
|
+
for (const char of data) {
|
|
18751
|
+
if (char === "" || char === "") {
|
|
18752
|
+
finish(null);
|
|
18753
|
+
return;
|
|
18754
|
+
}
|
|
18755
|
+
if (char === "\r" || char === "\n") {
|
|
18756
|
+
finish(value);
|
|
18757
|
+
return;
|
|
18758
|
+
}
|
|
18759
|
+
if (char === "\x7F" || char === "\b") value = deleteSelectedText(value, screen.selectionRange());
|
|
18760
|
+
else if (char === "") value = "";
|
|
18761
|
+
else if (char === "") value = value.replace(/\s*\S+\s*$/, "");
|
|
18762
|
+
else if (char >= " ") {
|
|
18763
|
+
const range = screen.selectionRange();
|
|
18764
|
+
value = range ? value.slice(0, range[0]) + char + value.slice(range[1]) : value + char;
|
|
18765
|
+
}
|
|
18766
|
+
screen.setInput(value);
|
|
18767
|
+
}
|
|
18768
|
+
};
|
|
18769
|
+
process.stdin.on("data", onData);
|
|
18770
|
+
screen.setInput(value);
|
|
18771
|
+
});
|
|
18772
|
+
}
|
|
18773
|
+
async function cancellable(screen, run2) {
|
|
18774
|
+
const controller = new AbortController();
|
|
18775
|
+
const onData = (chunk) => {
|
|
18776
|
+
const text = chunk.toString("utf8");
|
|
18777
|
+
if (handleScrollInput(screen, text)) return;
|
|
18778
|
+
if (text.includes("") || text === "\x1B") {
|
|
18779
|
+
screen.setStatus("Interrupting\u2026");
|
|
18780
|
+
controller.abort();
|
|
18781
|
+
}
|
|
18263
18782
|
};
|
|
18264
|
-
process.on("
|
|
18265
|
-
|
|
18783
|
+
process.stdin.on("data", onData);
|
|
18784
|
+
try {
|
|
18785
|
+
return await run2(controller.signal);
|
|
18786
|
+
} finally {
|
|
18787
|
+
process.stdin.removeListener("data", onData);
|
|
18788
|
+
}
|
|
18789
|
+
}
|
|
18790
|
+
async function resolveScreenFixPolls(screen, events) {
|
|
18791
|
+
if (!events.some((event) => event.type === "tool-end" && Boolean(event.pollId))) return;
|
|
18792
|
+
screen.leave();
|
|
18793
|
+
try {
|
|
18794
|
+
await resolveFixPolls(events);
|
|
18795
|
+
} finally {
|
|
18796
|
+
screen.enter();
|
|
18797
|
+
}
|
|
18798
|
+
}
|
|
18799
|
+
async function runSynkroSession(harness, cwd, prompt) {
|
|
18800
|
+
if (prompt) return runOnce(harness, cwd, prompt);
|
|
18801
|
+
if (!supported(harness)) return runOnce(harness, cwd, "");
|
|
18802
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
18803
|
+
process.stderr.write("synkro terminal requires a TTY\n");
|
|
18804
|
+
return 1;
|
|
18805
|
+
}
|
|
18806
|
+
const screen = new SynkroScreen(harness, cwd);
|
|
18807
|
+
const resumeId = loadSessionId(harness, cwd);
|
|
18808
|
+
let codex = null;
|
|
18809
|
+
process.stdin.setRawMode(true);
|
|
18810
|
+
process.stdin.resume();
|
|
18811
|
+
screen.enter();
|
|
18266
18812
|
try {
|
|
18813
|
+
if (harness === "codex") {
|
|
18814
|
+
screen.setStatus("Connecting to Codex\u2026");
|
|
18815
|
+
codex = new CodexSession(cwd, resumeId);
|
|
18816
|
+
await codex.start();
|
|
18817
|
+
saveSessionId(harness, cwd, codex.threadId);
|
|
18818
|
+
screen.add({ type: "session-start", sessionId: codex.threadId, model: codex.model || "Codex", cwd, authSource: "login" });
|
|
18819
|
+
screen.setStatus("Ready");
|
|
18820
|
+
}
|
|
18267
18821
|
for (; ; ) {
|
|
18268
|
-
const
|
|
18269
|
-
if (
|
|
18270
|
-
const line =
|
|
18271
|
-
if (!line
|
|
18272
|
-
if (
|
|
18822
|
+
const answer = await readPrompt(screen);
|
|
18823
|
+
if (answer === null) break;
|
|
18824
|
+
const line = answer.trim();
|
|
18825
|
+
if (!line) continue;
|
|
18826
|
+
if (line === "/exit" || line === "/quit" || line === "exit" || line === "quit") break;
|
|
18827
|
+
screen.setStatus("Working\u2026");
|
|
18828
|
+
const events = [];
|
|
18273
18829
|
try {
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18280
|
-
|
|
18281
|
-
|
|
18830
|
+
if (harness === "cursor") {
|
|
18831
|
+
const result = await cancellable(screen, (signal) => runCursorTurn({
|
|
18832
|
+
prompt: line,
|
|
18833
|
+
cwd,
|
|
18834
|
+
signal,
|
|
18835
|
+
resumeId: loadSessionId(harness, cwd),
|
|
18836
|
+
write: () => {
|
|
18837
|
+
},
|
|
18838
|
+
onEvent: (event) => {
|
|
18839
|
+
events.push(event);
|
|
18840
|
+
screen.add(event);
|
|
18841
|
+
},
|
|
18842
|
+
showHeader: false
|
|
18843
|
+
}));
|
|
18844
|
+
saveSessionId(harness, cwd, result.sessionId || "");
|
|
18845
|
+
} else {
|
|
18846
|
+
const result = await cancellable(screen, (signal) => codex.runTurn(line, (event) => {
|
|
18847
|
+
events.push(event);
|
|
18848
|
+
screen.add(event);
|
|
18849
|
+
}, signal));
|
|
18850
|
+
saveSessionId(harness, cwd, result.sessionId);
|
|
18282
18851
|
}
|
|
18283
|
-
|
|
18284
|
-
}
|
|
18285
|
-
|
|
18286
|
-
turn
|
|
18852
|
+
await resolveScreenFixPolls(screen, events);
|
|
18853
|
+
} catch (error) {
|
|
18854
|
+
screen.add({ type: "notice", text: error instanceof Error ? error.message : String(error) });
|
|
18855
|
+
screen.add({ type: "turn-end", ok: false, text: "" });
|
|
18287
18856
|
}
|
|
18857
|
+
screen.setStatus("Ready");
|
|
18288
18858
|
}
|
|
18859
|
+
return 0;
|
|
18289
18860
|
} finally {
|
|
18290
|
-
process.off("SIGINT", interruptTurn);
|
|
18291
18861
|
codex?.close();
|
|
18862
|
+
screen.leave();
|
|
18863
|
+
process.stdin.setRawMode(false);
|
|
18864
|
+
process.stdin.pause();
|
|
18292
18865
|
}
|
|
18293
|
-
return 0;
|
|
18294
18866
|
}
|
|
18867
|
+
var supported;
|
|
18295
18868
|
var init_session = __esm({
|
|
18296
18869
|
"cli/harness/session.ts"() {
|
|
18297
18870
|
"use strict";
|
|
18298
|
-
init_composer();
|
|
18299
|
-
init_run();
|
|
18300
18871
|
init_codex();
|
|
18301
|
-
|
|
18302
|
-
init_identity2();
|
|
18872
|
+
init_run();
|
|
18303
18873
|
init_render2();
|
|
18874
|
+
init_screen();
|
|
18875
|
+
init_state();
|
|
18876
|
+
init_fixPoll();
|
|
18877
|
+
supported = (harness) => harness === "cursor" || harness === "codex";
|
|
18304
18878
|
}
|
|
18305
18879
|
});
|
|
18306
18880
|
|
|
@@ -18341,17 +18915,19 @@ async function takeover(kind, cwd) {
|
|
|
18341
18915
|
const runner = backend === "container" ? info.runner : HOST3;
|
|
18342
18916
|
return runInherit(["env", "TMUX=", ...runnerInteractiveArgs(runner, ["tmux", "attach-session", "-t", spawned.session])]);
|
|
18343
18917
|
}
|
|
18344
|
-
async function restoreSession(session) {
|
|
18345
|
-
const
|
|
18346
|
-
if (!
|
|
18918
|
+
async function restoreSession(session, bootPath) {
|
|
18919
|
+
const record2 = loadRecords().find((row2) => row2.session === session);
|
|
18920
|
+
if (!record2) return;
|
|
18347
18921
|
const info = await detectContainerBackend();
|
|
18348
18922
|
await spawnAgent(info, {
|
|
18349
|
-
name:
|
|
18350
|
-
harness:
|
|
18351
|
-
spaceName:
|
|
18352
|
-
cwd:
|
|
18353
|
-
backend:
|
|
18354
|
-
resume:
|
|
18923
|
+
name: record2.name,
|
|
18924
|
+
harness: record2.harness,
|
|
18925
|
+
spaceName: record2.spaceName,
|
|
18926
|
+
cwd: record2.space,
|
|
18927
|
+
backend: record2.mode === "embedded" ? "host" : record2.backend,
|
|
18928
|
+
resume: record2.mode !== "embedded",
|
|
18929
|
+
mode: record2.mode,
|
|
18930
|
+
command: record2.mode === "embedded" ? embeddedSessionCommand(bootPath, record2.harness, record2.space) : void 0
|
|
18355
18931
|
});
|
|
18356
18932
|
}
|
|
18357
18933
|
async function printStatus() {
|
|
@@ -18404,7 +18980,7 @@ async function uiCommand(args2) {
|
|
|
18404
18980
|
const harness = args2[runAt + 1] || "cursor";
|
|
18405
18981
|
const cwd = args2[runAt + 2] || repoRoot();
|
|
18406
18982
|
const prompt = args2.slice(runAt + 3).join(" ").trim();
|
|
18407
|
-
process.exitCode = await
|
|
18983
|
+
process.exitCode = await runSynkroSession(harness, cwd, prompt);
|
|
18408
18984
|
return;
|
|
18409
18985
|
}
|
|
18410
18986
|
const takeoverAt = args2.indexOf("--takeover");
|
|
@@ -18414,7 +18990,7 @@ async function uiCommand(args2) {
|
|
|
18414
18990
|
}
|
|
18415
18991
|
const restoreAt = args2.indexOf("--restore");
|
|
18416
18992
|
if (restoreAt !== -1) {
|
|
18417
|
-
await restoreSession(args2[restoreAt + 1] || "");
|
|
18993
|
+
await restoreSession(args2[restoreAt + 1] || "", bootPath);
|
|
18418
18994
|
return;
|
|
18419
18995
|
}
|
|
18420
18996
|
if (args2.includes("--status")) {
|
|
@@ -18492,12 +19068,12 @@ __export(linear_exports, {
|
|
|
18492
19068
|
formatLinks: () => formatLinks,
|
|
18493
19069
|
linearCommand: () => linearCommand
|
|
18494
19070
|
});
|
|
18495
|
-
import { readFileSync as
|
|
19071
|
+
import { readFileSync as readFileSync35 } from "fs";
|
|
18496
19072
|
import { homedir as homedir40 } from "os";
|
|
18497
|
-
import { join as
|
|
19073
|
+
import { join as join39 } from "path";
|
|
18498
19074
|
function mcpJwt() {
|
|
18499
19075
|
try {
|
|
18500
|
-
return
|
|
19076
|
+
return readFileSync35(join39(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
|
|
18501
19077
|
} catch {
|
|
18502
19078
|
return "";
|
|
18503
19079
|
}
|
|
@@ -18536,7 +19112,7 @@ var SYNKRO_DIR14, PORT2, BASE;
|
|
|
18536
19112
|
var init_linear = __esm({
|
|
18537
19113
|
"cli/commands/linear.ts"() {
|
|
18538
19114
|
"use strict";
|
|
18539
|
-
SYNKRO_DIR14 =
|
|
19115
|
+
SYNKRO_DIR14 = join39(homedir40(), ".synkro");
|
|
18540
19116
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
18541
19117
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
18542
19118
|
}
|
|
@@ -18544,13 +19120,13 @@ var init_linear = __esm({
|
|
|
18544
19120
|
|
|
18545
19121
|
// cli/scanning/cveReachability.ts
|
|
18546
19122
|
import { parse } from "@babel/parser";
|
|
18547
|
-
import { readFileSync as
|
|
19123
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
18548
19124
|
function walk(node, visit) {
|
|
18549
19125
|
if (!node || typeof node.type !== "string") return;
|
|
18550
19126
|
visit(node);
|
|
18551
|
-
for (const
|
|
18552
|
-
if (
|
|
18553
|
-
const child = node[
|
|
19127
|
+
for (const key2 of Object.keys(node)) {
|
|
19128
|
+
if (key2 === "loc" || key2 === "start" || key2 === "end" || key2 === "range" || key2 === "leadingComments" || key2 === "trailingComments") continue;
|
|
19129
|
+
const child = node[key2];
|
|
18554
19130
|
if (Array.isArray(child)) {
|
|
18555
19131
|
for (const c of child) if (c && typeof c.type === "string") walk(c, visit);
|
|
18556
19132
|
} else if (child && typeof child.type === "string") walk(child, visit);
|
|
@@ -18685,9 +19261,9 @@ var init_cveReachability = __esm({
|
|
|
18685
19261
|
});
|
|
18686
19262
|
|
|
18687
19263
|
// cli/reachability/reachabilityScan.ts
|
|
18688
|
-
import { spawnSync as spawnSync13, execFileSync as
|
|
18689
|
-
import { readFileSync as
|
|
18690
|
-
import { join as
|
|
19264
|
+
import { spawnSync as spawnSync13, execFileSync as execFileSync6 } from "child_process";
|
|
19265
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync29, existsSync as existsSync39, readdirSync as readdirSync10 } from "fs";
|
|
19266
|
+
import { join as join40 } from "path";
|
|
18691
19267
|
import { homedir as homedir41 } from "os";
|
|
18692
19268
|
import { createRequire } from "module";
|
|
18693
19269
|
function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
@@ -18705,7 +19281,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
18705
19281
|
}
|
|
18706
19282
|
for (const e of ents) {
|
|
18707
19283
|
if (files.length >= maxFiles) break;
|
|
18708
|
-
const full =
|
|
19284
|
+
const full = join40(dir, e.name);
|
|
18709
19285
|
if (e.isDirectory()) {
|
|
18710
19286
|
if (!SKIP2.has(e.name) && !e.name.startsWith(".")) stack.push(full);
|
|
18711
19287
|
continue;
|
|
@@ -18713,7 +19289,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
18713
19289
|
if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
|
|
18714
19290
|
const rel = full.startsWith(repoRoot3 + "/") ? full.slice(repoRoot3.length + 1) : full;
|
|
18715
19291
|
try {
|
|
18716
|
-
const content =
|
|
19292
|
+
const content = readFileSync37(full, "utf8");
|
|
18717
19293
|
if (content.length <= maxBytes) files.push({ path: rel, content });
|
|
18718
19294
|
} catch {
|
|
18719
19295
|
}
|
|
@@ -18732,12 +19308,12 @@ function cleanVersion(spec) {
|
|
|
18732
19308
|
function gatherManifestVersions(repoRoot3) {
|
|
18733
19309
|
const out = {};
|
|
18734
19310
|
const dirs = [repoRoot3];
|
|
18735
|
-
const pkgsDir =
|
|
19311
|
+
const pkgsDir = join40(repoRoot3, "packages");
|
|
18736
19312
|
if (existsSync39(pkgsDir)) {
|
|
18737
19313
|
try {
|
|
18738
19314
|
for (const d of readdirSync10(pkgsDir)) {
|
|
18739
|
-
const pd =
|
|
18740
|
-
if (existsSync39(
|
|
19315
|
+
const pd = join40(pkgsDir, d);
|
|
19316
|
+
if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
|
|
18741
19317
|
}
|
|
18742
19318
|
} catch {
|
|
18743
19319
|
}
|
|
@@ -18746,7 +19322,7 @@ function gatherManifestVersions(repoRoot3) {
|
|
|
18746
19322
|
for (const dir of dirs) {
|
|
18747
19323
|
let pkg;
|
|
18748
19324
|
try {
|
|
18749
|
-
pkg = JSON.parse(
|
|
19325
|
+
pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
|
|
18750
19326
|
} catch {
|
|
18751
19327
|
continue;
|
|
18752
19328
|
}
|
|
@@ -18766,28 +19342,28 @@ function findJelly(repoRoot3) {
|
|
|
18766
19342
|
try {
|
|
18767
19343
|
const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
|
|
18768
19344
|
const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
|
|
18769
|
-
const pkg = JSON.parse(
|
|
19345
|
+
const pkg = JSON.parse(readFileSync37(pkgJson, "utf8"));
|
|
18770
19346
|
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
|
|
18771
19347
|
if (bin) {
|
|
18772
|
-
const p =
|
|
19348
|
+
const p = join40(dir, bin);
|
|
18773
19349
|
if (existsSync39(p)) return p;
|
|
18774
19350
|
}
|
|
18775
19351
|
} catch {
|
|
18776
19352
|
}
|
|
18777
19353
|
for (const base of [repoRoot3, process.cwd()]) {
|
|
18778
|
-
const b =
|
|
19354
|
+
const b = join40(base, "node_modules", ".bin", "jelly");
|
|
18779
19355
|
if (existsSync39(b)) return b;
|
|
18780
19356
|
}
|
|
18781
19357
|
return null;
|
|
18782
19358
|
}
|
|
18783
19359
|
function findEntries(repoRoot3) {
|
|
18784
19360
|
const dirs = [repoRoot3];
|
|
18785
|
-
const pkgsDir =
|
|
19361
|
+
const pkgsDir = join40(repoRoot3, "packages");
|
|
18786
19362
|
if (existsSync39(pkgsDir)) {
|
|
18787
19363
|
try {
|
|
18788
19364
|
for (const d of readdirSync10(pkgsDir)) {
|
|
18789
|
-
const pd =
|
|
18790
|
-
if (existsSync39(
|
|
19365
|
+
const pd = join40(pkgsDir, d);
|
|
19366
|
+
if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
|
|
18791
19367
|
}
|
|
18792
19368
|
} catch {
|
|
18793
19369
|
}
|
|
@@ -18795,11 +19371,11 @@ function findEntries(repoRoot3) {
|
|
|
18795
19371
|
const entries = [];
|
|
18796
19372
|
for (const dir of dirs) {
|
|
18797
19373
|
try {
|
|
18798
|
-
const pkg = JSON.parse(
|
|
19374
|
+
const pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
|
|
18799
19375
|
const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
|
|
18800
19376
|
for (const c of cands) {
|
|
18801
19377
|
if (typeof c !== "string") continue;
|
|
18802
|
-
const f =
|
|
19378
|
+
const f = join40(dir, c);
|
|
18803
19379
|
if (existsSync39(f)) {
|
|
18804
19380
|
entries.push(f);
|
|
18805
19381
|
break;
|
|
@@ -18812,7 +19388,7 @@ function findEntries(repoRoot3) {
|
|
|
18812
19388
|
}
|
|
18813
19389
|
function currentCommit(repoRoot3) {
|
|
18814
19390
|
try {
|
|
18815
|
-
return
|
|
19391
|
+
return execFileSync6("git", ["rev-parse", "HEAD"], { cwd: repoRoot3, encoding: "utf8" }).trim();
|
|
18816
19392
|
} catch {
|
|
18817
19393
|
return "";
|
|
18818
19394
|
}
|
|
@@ -18835,7 +19411,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
|
|
|
18835
19411
|
const commit = currentCommit(repoRoot3);
|
|
18836
19412
|
if (!opts.force && commit && existsSync39(REACHABILITY_PATH)) {
|
|
18837
19413
|
try {
|
|
18838
|
-
const prev = JSON.parse(
|
|
19414
|
+
const prev = JSON.parse(readFileSync37(REACHABILITY_PATH, "utf8"));
|
|
18839
19415
|
if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
|
|
18840
19416
|
} catch {
|
|
18841
19417
|
}
|
|
@@ -18924,7 +19500,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
|
|
|
18924
19500
|
if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
|
|
18925
19501
|
const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot3) };
|
|
18926
19502
|
try {
|
|
18927
|
-
|
|
19503
|
+
writeFileSync29(REACHABILITY_PATH, JSON.stringify(file, null, 2));
|
|
18928
19504
|
} catch (e) {
|
|
18929
19505
|
return { ok: false, reason: "write failed: " + String(e.message || e) };
|
|
18930
19506
|
}
|
|
@@ -18936,7 +19512,7 @@ var init_reachabilityScan = __esm({
|
|
|
18936
19512
|
"use strict";
|
|
18937
19513
|
init_cveReachability();
|
|
18938
19514
|
require2 = createRequire(import.meta.url);
|
|
18939
|
-
REACHABILITY_PATH =
|
|
19515
|
+
REACHABILITY_PATH = join40(homedir41(), ".synkro", "reachability.json");
|
|
18940
19516
|
}
|
|
18941
19517
|
});
|
|
18942
19518
|
|
|
@@ -18945,15 +19521,15 @@ var reachabilityScan_exports = {};
|
|
|
18945
19521
|
__export(reachabilityScan_exports, {
|
|
18946
19522
|
reachabilityScanCommand: () => reachabilityScanCommand
|
|
18947
19523
|
});
|
|
18948
|
-
import { readFileSync as
|
|
18949
|
-
import { join as
|
|
19524
|
+
import { readFileSync as readFileSync38, existsSync as existsSync40 } from "fs";
|
|
19525
|
+
import { join as join41 } from "path";
|
|
18950
19526
|
import { homedir as homedir42 } from "os";
|
|
18951
|
-
import { execFileSync as
|
|
19527
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
18952
19528
|
function readConfigEnv4() {
|
|
18953
|
-
const p =
|
|
19529
|
+
const p = join41(SYNKRO_DIR15, "config.env");
|
|
18954
19530
|
if (!existsSync40(p)) return {};
|
|
18955
19531
|
const out = {};
|
|
18956
|
-
for (const line of
|
|
19532
|
+
for (const line of readFileSync38(p, "utf-8").split("\n")) {
|
|
18957
19533
|
const t = line.trim();
|
|
18958
19534
|
if (!t || t.startsWith("#")) continue;
|
|
18959
19535
|
const eq = t.indexOf("=");
|
|
@@ -18963,7 +19539,7 @@ function readConfigEnv4() {
|
|
|
18963
19539
|
}
|
|
18964
19540
|
function repoRoot2() {
|
|
18965
19541
|
try {
|
|
18966
|
-
return
|
|
19542
|
+
return execFileSync7("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
|
|
18967
19543
|
} catch {
|
|
18968
19544
|
return process.cwd();
|
|
18969
19545
|
}
|
|
@@ -18971,7 +19547,7 @@ function repoRoot2() {
|
|
|
18971
19547
|
function repoSlug(root) {
|
|
18972
19548
|
const run2 = (a) => {
|
|
18973
19549
|
try {
|
|
18974
|
-
return
|
|
19550
|
+
return execFileSync7("git", a, { encoding: "utf-8" }).trim();
|
|
18975
19551
|
} catch {
|
|
18976
19552
|
return "";
|
|
18977
19553
|
}
|
|
@@ -18985,11 +19561,11 @@ async function pushToCloud(cfg, repo) {
|
|
|
18985
19561
|
while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
|
|
18986
19562
|
let jwt2 = "";
|
|
18987
19563
|
try {
|
|
18988
|
-
jwt2 =
|
|
19564
|
+
jwt2 = readFileSync38(join41(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
|
|
18989
19565
|
} catch {
|
|
18990
19566
|
}
|
|
18991
19567
|
if (!jwt2 || !existsSync40(REACHABILITY_PATH)) return;
|
|
18992
|
-
const body =
|
|
19568
|
+
const body = readFileSync38(REACHABILITY_PATH, "utf-8");
|
|
18993
19569
|
try {
|
|
18994
19570
|
const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
|
|
18995
19571
|
method: "POST",
|
|
@@ -19021,7 +19597,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
19021
19597
|
"cli/commands/reachabilityScan.ts"() {
|
|
19022
19598
|
"use strict";
|
|
19023
19599
|
init_reachabilityScan();
|
|
19024
|
-
SYNKRO_DIR15 =
|
|
19600
|
+
SYNKRO_DIR15 = join41(homedir42(), ".synkro");
|
|
19025
19601
|
}
|
|
19026
19602
|
});
|
|
19027
19603
|
|
|
@@ -19151,13 +19727,13 @@ var config_exports = {};
|
|
|
19151
19727
|
__export(config_exports, {
|
|
19152
19728
|
configCommand: () => configCommand
|
|
19153
19729
|
});
|
|
19154
|
-
import { readFileSync as
|
|
19155
|
-
import { join as
|
|
19730
|
+
import { readFileSync as readFileSync39, writeFileSync as writeFileSync30, existsSync as existsSync41 } from "fs";
|
|
19731
|
+
import { join as join42 } from "path";
|
|
19156
19732
|
import { homedir as homedir43 } from "os";
|
|
19157
19733
|
function readConfigEnv5() {
|
|
19158
19734
|
if (!existsSync41(CONFIG_PATH9)) return {};
|
|
19159
19735
|
const out = {};
|
|
19160
|
-
for (const line of
|
|
19736
|
+
for (const line of readFileSync39(CONFIG_PATH9, "utf-8").split("\n")) {
|
|
19161
19737
|
const t = line.trim();
|
|
19162
19738
|
if (!t || t.startsWith("#")) continue;
|
|
19163
19739
|
const eq = t.indexOf("=");
|
|
@@ -19165,23 +19741,23 @@ function readConfigEnv5() {
|
|
|
19165
19741
|
}
|
|
19166
19742
|
return out;
|
|
19167
19743
|
}
|
|
19168
|
-
function updateConfigValue(
|
|
19744
|
+
function updateConfigValue(key2, value) {
|
|
19169
19745
|
if (!existsSync41(CONFIG_PATH9)) {
|
|
19170
19746
|
console.error("No config found. Run `synkro install` first.");
|
|
19171
19747
|
process.exit(1);
|
|
19172
19748
|
}
|
|
19173
|
-
const
|
|
19174
|
-
const pattern = new RegExp(`^${
|
|
19749
|
+
const lines2 = readFileSync39(CONFIG_PATH9, "utf-8").split("\n");
|
|
19750
|
+
const pattern = new RegExp(`^${key2}=`);
|
|
19175
19751
|
let found = false;
|
|
19176
|
-
const updated =
|
|
19752
|
+
const updated = lines2.map((line) => {
|
|
19177
19753
|
if (pattern.test(line.trim())) {
|
|
19178
19754
|
found = true;
|
|
19179
|
-
return `${
|
|
19755
|
+
return `${key2}='${value}'`;
|
|
19180
19756
|
}
|
|
19181
19757
|
return line;
|
|
19182
19758
|
});
|
|
19183
|
-
if (!found) updated.splice(updated.length - 1, 0, `${
|
|
19184
|
-
|
|
19759
|
+
if (!found) updated.splice(updated.length - 1, 0, `${key2}='${value}'`);
|
|
19760
|
+
writeFileSync30(CONFIG_PATH9, updated.join("\n"), "utf-8");
|
|
19185
19761
|
}
|
|
19186
19762
|
function resolveInferenceMode(cfg) {
|
|
19187
19763
|
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
@@ -19339,8 +19915,8 @@ var init_config = __esm({
|
|
|
19339
19915
|
"use strict";
|
|
19340
19916
|
init_stub();
|
|
19341
19917
|
init_optout();
|
|
19342
|
-
SYNKRO_DIR16 =
|
|
19343
|
-
CONFIG_PATH9 =
|
|
19918
|
+
SYNKRO_DIR16 = join42(homedir43(), ".synkro");
|
|
19919
|
+
CONFIG_PATH9 = join42(SYNKRO_DIR16, "config.env");
|
|
19344
19920
|
}
|
|
19345
19921
|
});
|
|
19346
19922
|
|
|
@@ -19349,7 +19925,7 @@ var telemetry_exports2 = {};
|
|
|
19349
19925
|
__export(telemetry_exports2, {
|
|
19350
19926
|
telemetryCommand: () => telemetryCommand
|
|
19351
19927
|
});
|
|
19352
|
-
import { createInterface as
|
|
19928
|
+
import { createInterface as createInterface7 } from "readline";
|
|
19353
19929
|
function parseFlag(args2, name) {
|
|
19354
19930
|
const prefix = `--${name}=`;
|
|
19355
19931
|
for (const a of args2) if (a.startsWith(prefix)) return a.slice(prefix.length);
|
|
@@ -19430,12 +20006,12 @@ async function runExport(args2) {
|
|
|
19430
20006
|
}
|
|
19431
20007
|
function confirmYesNo(question) {
|
|
19432
20008
|
if (!process.stdin.isTTY) return Promise.resolve(false);
|
|
19433
|
-
return new Promise((
|
|
19434
|
-
const rl =
|
|
20009
|
+
return new Promise((resolve9) => {
|
|
20010
|
+
const rl = createInterface7({ input: process.stdin, output: process.stdout });
|
|
19435
20011
|
rl.question(`${question} (y/N): `, (answer) => {
|
|
19436
20012
|
rl.close();
|
|
19437
20013
|
const t = answer.trim().toLowerCase();
|
|
19438
|
-
|
|
20014
|
+
resolve9(t === "y" || t === "yes");
|
|
19439
20015
|
});
|
|
19440
20016
|
});
|
|
19441
20017
|
}
|
|
@@ -19530,11 +20106,11 @@ Usage:
|
|
|
19530
20106
|
|
|
19531
20107
|
// cli/inventory/identity.ts
|
|
19532
20108
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
19533
|
-
import { existsSync as existsSync42, mkdirSync as
|
|
20109
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync25, readFileSync as readFileSync40, renameSync as renameSync9, writeFileSync as writeFileSync31 } from "fs";
|
|
19534
20110
|
import { homedir as homedir44 } from "os";
|
|
19535
|
-
import { dirname as
|
|
20111
|
+
import { dirname as dirname14, join as join43 } from "path";
|
|
19536
20112
|
function operationalIdentityPath() {
|
|
19537
|
-
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH ||
|
|
20113
|
+
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join43(homedir44(), ".synkro", "installation.json");
|
|
19538
20114
|
}
|
|
19539
20115
|
function validIdentity(value) {
|
|
19540
20116
|
if (!value || typeof value !== "object") return false;
|
|
@@ -19542,9 +20118,9 @@ function validIdentity(value) {
|
|
|
19542
20118
|
return typeof row2.installation_id === "string" && UUID_RE.test(row2.installation_id) && typeof row2.created_at === "string" && Number.isFinite(Date.parse(row2.created_at));
|
|
19543
20119
|
}
|
|
19544
20120
|
function writeIdentity(path, identity) {
|
|
19545
|
-
|
|
20121
|
+
mkdirSync25(dirname14(path), { recursive: true, mode: 448 });
|
|
19546
20122
|
const temp = `${path}.${process.pid}.${randomUUID5()}.tmp`;
|
|
19547
|
-
|
|
20123
|
+
writeFileSync31(temp, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
19548
20124
|
renameSync9(temp, path);
|
|
19549
20125
|
}
|
|
19550
20126
|
function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
@@ -19552,7 +20128,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
|
19552
20128
|
if (prior) return prior;
|
|
19553
20129
|
if (existsSync42(path)) {
|
|
19554
20130
|
try {
|
|
19555
|
-
const parsed = JSON.parse(
|
|
20131
|
+
const parsed = JSON.parse(readFileSync40(path, "utf8"));
|
|
19556
20132
|
if (validIdentity(parsed)) {
|
|
19557
20133
|
cached4.set(path, parsed);
|
|
19558
20134
|
return parsed;
|
|
@@ -19566,7 +20142,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
|
19566
20142
|
return identity;
|
|
19567
20143
|
}
|
|
19568
20144
|
var UUID_RE, cached4;
|
|
19569
|
-
var
|
|
20145
|
+
var init_identity2 = __esm({
|
|
19570
20146
|
"cli/inventory/identity.ts"() {
|
|
19571
20147
|
"use strict";
|
|
19572
20148
|
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;
|
|
@@ -19578,12 +20154,12 @@ var init_identity3 = __esm({
|
|
|
19578
20154
|
import { createHash as createHash5 } from "crypto";
|
|
19579
20155
|
import {
|
|
19580
20156
|
existsSync as existsSync43,
|
|
19581
|
-
readFileSync as
|
|
20157
|
+
readFileSync as readFileSync41,
|
|
19582
20158
|
readdirSync as readdirSync11,
|
|
19583
|
-
statSync as
|
|
20159
|
+
statSync as statSync7
|
|
19584
20160
|
} from "fs";
|
|
19585
20161
|
import { arch, homedir as homedir45, hostname as hostname2, platform as platform6, release as release2 } from "os";
|
|
19586
|
-
import { basename as basename4, join as
|
|
20162
|
+
import { basename as basename4, join as join44, relative as relative2, resolve as resolve7 } from "path";
|
|
19587
20163
|
import { fileURLToPath } from "url";
|
|
19588
20164
|
function sha256(value) {
|
|
19589
20165
|
return createHash5("sha256").update(value).digest("hex");
|
|
@@ -19593,7 +20169,7 @@ function pseudonymousHostnameHash(installationId, host) {
|
|
|
19593
20169
|
}
|
|
19594
20170
|
function cliVersion() {
|
|
19595
20171
|
try {
|
|
19596
|
-
return "1.10.
|
|
20172
|
+
return "1.10.10";
|
|
19597
20173
|
} catch {
|
|
19598
20174
|
return "0.0.0";
|
|
19599
20175
|
}
|
|
@@ -19601,7 +20177,7 @@ function cliVersion() {
|
|
|
19601
20177
|
function readJson(path) {
|
|
19602
20178
|
try {
|
|
19603
20179
|
if (!existsSync43(path)) return null;
|
|
19604
|
-
const parsed = JSON.parse(
|
|
20180
|
+
const parsed = JSON.parse(readFileSync41(path, "utf8"));
|
|
19605
20181
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
19606
20182
|
} catch {
|
|
19607
20183
|
return null;
|
|
@@ -19610,7 +20186,7 @@ function readJson(path) {
|
|
|
19610
20186
|
function readText(path) {
|
|
19611
20187
|
try {
|
|
19612
20188
|
if (!existsSync43(path)) return "";
|
|
19613
|
-
return
|
|
20189
|
+
return readFileSync41(path, "utf8");
|
|
19614
20190
|
} catch {
|
|
19615
20191
|
return "";
|
|
19616
20192
|
}
|
|
@@ -19696,16 +20272,16 @@ function mcpArtifactsFromJson(harness, config, configScope = "user") {
|
|
|
19696
20272
|
}
|
|
19697
20273
|
function claudeDesktopConfigCandidates(home, targetPlatform) {
|
|
19698
20274
|
if (targetPlatform === "darwin") {
|
|
19699
|
-
return [
|
|
20275
|
+
return [join44(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
|
|
19700
20276
|
}
|
|
19701
20277
|
if (targetPlatform === "linux") {
|
|
19702
20278
|
return [
|
|
19703
|
-
|
|
19704
|
-
|
|
20279
|
+
join44(home, ".config", "Claude", "claude_desktop_config.json"),
|
|
20280
|
+
join44(home, ".config", "claude", "claude_desktop_config.json")
|
|
19705
20281
|
];
|
|
19706
20282
|
}
|
|
19707
20283
|
if (targetPlatform === "win32" && process.env.APPDATA) {
|
|
19708
|
-
return [
|
|
20284
|
+
return [join44(process.env.APPDATA, "Claude", "claude_desktop_config.json")];
|
|
19709
20285
|
}
|
|
19710
20286
|
return [];
|
|
19711
20287
|
}
|
|
@@ -19713,7 +20289,7 @@ function claudeManagedMcpConfigCandidates(targetPlatform) {
|
|
|
19713
20289
|
if (targetPlatform === "darwin") return ["/Library/Application Support/ClaudeCode/managed-mcp.json"];
|
|
19714
20290
|
if (targetPlatform === "linux") return ["/etc/claude-code/managed-mcp.json"];
|
|
19715
20291
|
if (targetPlatform === "win32" && process.env.ProgramFiles) {
|
|
19716
|
-
return [
|
|
20292
|
+
return [join44(process.env.ProgramFiles, "ClaudeCode", "managed-mcp.json")];
|
|
19717
20293
|
}
|
|
19718
20294
|
return [];
|
|
19719
20295
|
}
|
|
@@ -19721,7 +20297,7 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
|
|
|
19721
20297
|
const roots = /* @__PURE__ */ new Set();
|
|
19722
20298
|
const add = (value) => {
|
|
19723
20299
|
if (typeof value !== "string" || !value.trim()) return;
|
|
19724
|
-
const path =
|
|
20300
|
+
const path = resolve7(value);
|
|
19725
20301
|
if (existsSync43(path)) roots.add(path);
|
|
19726
20302
|
};
|
|
19727
20303
|
add(currentDirectory);
|
|
@@ -19733,10 +20309,10 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
|
|
|
19733
20309
|
return [...roots];
|
|
19734
20310
|
}
|
|
19735
20311
|
function cursorWorkspaceStorageCandidates(home, targetPlatform) {
|
|
19736
|
-
if (targetPlatform === "darwin") return [
|
|
19737
|
-
if (targetPlatform === "linux") return [
|
|
20312
|
+
if (targetPlatform === "darwin") return [join44(home, "Library", "Application Support", "Cursor", "User", "workspaceStorage")];
|
|
20313
|
+
if (targetPlatform === "linux") return [join44(home, ".config", "Cursor", "User", "workspaceStorage")];
|
|
19738
20314
|
if (targetPlatform === "win32" && process.env.APPDATA) {
|
|
19739
|
-
return [
|
|
20315
|
+
return [join44(process.env.APPDATA, "Cursor", "User", "workspaceStorage")];
|
|
19740
20316
|
}
|
|
19741
20317
|
return [];
|
|
19742
20318
|
}
|
|
@@ -19752,12 +20328,12 @@ function cursorWorkspaceRoots(home, targetPlatform) {
|
|
|
19752
20328
|
}
|
|
19753
20329
|
for (const entry of entries) {
|
|
19754
20330
|
if (!entry.isDirectory() || entry.isSymbolicLink?.()) continue;
|
|
19755
|
-
const state = readJson(
|
|
20331
|
+
const state = readJson(join44(storage, entry.name, "workspace.json"));
|
|
19756
20332
|
const raw = state?.folder;
|
|
19757
20333
|
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
19758
20334
|
try {
|
|
19759
20335
|
const path = raw.startsWith("file:") ? fileURLToPath(raw) : raw;
|
|
19760
|
-
if (existsSync43(path)) roots.add(
|
|
20336
|
+
if (existsSync43(path)) roots.add(resolve7(path));
|
|
19761
20337
|
} catch {
|
|
19762
20338
|
}
|
|
19763
20339
|
}
|
|
@@ -19769,8 +20345,8 @@ function codexMcpArtifacts(content) {
|
|
|
19769
20345
|
const sections = [...content.matchAll(/^\s*\[\s*([^\]]+)\s*\]\s*$/gm)];
|
|
19770
20346
|
for (let index = 0; index < sections.length && artifacts.length < 1e3; index++) {
|
|
19771
20347
|
const section = sections[index];
|
|
19772
|
-
const
|
|
19773
|
-
const root =
|
|
20348
|
+
const key2 = section[1].trim();
|
|
20349
|
+
const root = key2.match(/^mcp_servers\s*\.\s*(?:"((?:[^"\\]|\\.)+)"|'([^']+)'|([A-Za-z0-9_-]+))$/);
|
|
19774
20350
|
if (!root) continue;
|
|
19775
20351
|
let name = root[1] || root[2] || root[3];
|
|
19776
20352
|
if (root[1]) {
|
|
@@ -19783,8 +20359,8 @@ function codexMcpArtifacts(content) {
|
|
|
19783
20359
|
const start = (section.index ?? 0) + section[0].length;
|
|
19784
20360
|
const end = sections[index + 1]?.index ?? content.length;
|
|
19785
20361
|
const block = content.slice(start, end);
|
|
19786
|
-
const stringValue = (
|
|
19787
|
-
const found = block.match(new RegExp(`^\\s*${
|
|
20362
|
+
const stringValue = (key3) => {
|
|
20363
|
+
const found = block.match(new RegExp(`^\\s*${key3}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")`, "m"));
|
|
19788
20364
|
if (!found) return void 0;
|
|
19789
20365
|
try {
|
|
19790
20366
|
return JSON.parse(found[1]);
|
|
@@ -19816,9 +20392,9 @@ function flattenHookEntries(value) {
|
|
|
19816
20392
|
const out = [];
|
|
19817
20393
|
for (const entry of value) {
|
|
19818
20394
|
if (!entry || typeof entry !== "object") continue;
|
|
19819
|
-
const
|
|
19820
|
-
if (typeof
|
|
19821
|
-
if (Array.isArray(
|
|
20395
|
+
const record2 = entry;
|
|
20396
|
+
if (typeof record2.command === "string") out.push(record2);
|
|
20397
|
+
if (Array.isArray(record2.hooks)) out.push(...flattenHookEntries(record2.hooks));
|
|
19822
20398
|
}
|
|
19823
20399
|
return out;
|
|
19824
20400
|
}
|
|
@@ -19847,7 +20423,7 @@ function hookArtifacts(harness, config) {
|
|
|
19847
20423
|
function parseFrontmatter(content) {
|
|
19848
20424
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
19849
20425
|
if (!match) return {};
|
|
19850
|
-
const value = (
|
|
20426
|
+
const value = (key2) => match[1].match(new RegExp(`^${key2}:\\s*["']?([^"'\\n]+)`, "m"))?.[1]?.trim();
|
|
19851
20427
|
return { name: value("name"), version: value("version") };
|
|
19852
20428
|
}
|
|
19853
20429
|
function skillArtifacts(harness, root) {
|
|
@@ -19862,7 +20438,7 @@ function skillArtifacts(harness, root) {
|
|
|
19862
20438
|
}
|
|
19863
20439
|
for (const entry of entries) {
|
|
19864
20440
|
if (entry.isSymbolicLink?.()) continue;
|
|
19865
|
-
const path =
|
|
20441
|
+
const path = join44(dir, entry.name);
|
|
19866
20442
|
if (entry.isFile() && entry.name === "SKILL.md") manifests.push(path);
|
|
19867
20443
|
else if (entry.isDirectory()) visit(path);
|
|
19868
20444
|
}
|
|
@@ -19871,8 +20447,8 @@ function skillArtifacts(harness, root) {
|
|
|
19871
20447
|
return manifests.map((path) => {
|
|
19872
20448
|
const content = readText(path);
|
|
19873
20449
|
const frontmatter = parseFrontmatter(content);
|
|
19874
|
-
const rel =
|
|
19875
|
-
const name = frontmatter.name || basename4(
|
|
20450
|
+
const rel = relative2(root, path).replaceAll("\\", "/");
|
|
20451
|
+
const name = frontmatter.name || basename4(join44(path, "..")) || "skill";
|
|
19876
20452
|
return {
|
|
19877
20453
|
harness,
|
|
19878
20454
|
type: "skill",
|
|
@@ -19896,7 +20472,7 @@ function cursorExtensionArtifacts(root) {
|
|
|
19896
20472
|
}
|
|
19897
20473
|
const artifacts = [];
|
|
19898
20474
|
for (const dir of dirs) {
|
|
19899
|
-
const pkg = readJson(
|
|
20475
|
+
const pkg = readJson(join44(root, dir.name, "package.json"));
|
|
19900
20476
|
if (!pkg) continue;
|
|
19901
20477
|
const publisher = typeof pkg.publisher === "string" ? pkg.publisher : void 0;
|
|
19902
20478
|
const name = typeof pkg.name === "string" ? pkg.name : dir.name;
|
|
@@ -19916,21 +20492,21 @@ function cursorExtensionArtifacts(root) {
|
|
|
19916
20492
|
return artifacts;
|
|
19917
20493
|
}
|
|
19918
20494
|
function deploymentMode2(home) {
|
|
19919
|
-
const raw = readText(
|
|
19920
|
-
const value = (
|
|
20495
|
+
const raw = readText(join44(home, ".synkro", "config.env"));
|
|
20496
|
+
const value = (key2) => raw.match(new RegExp(`^${key2}=['"]?([^'"\\n]*)`, "m"))?.[1]?.toLowerCase();
|
|
19921
20497
|
if (value("SYNKRO_GRADING_MODE") === "byok") return "byok";
|
|
19922
20498
|
if (value("SYNKRO_STORAGE_MODE") === "cloud") return "cloud";
|
|
19923
20499
|
return "local";
|
|
19924
20500
|
}
|
|
19925
20501
|
function telemetryHealth(home) {
|
|
19926
|
-
const meta = readJson(
|
|
20502
|
+
const meta = readJson(join44(home, ".synkro", "telemetry-meta.json"));
|
|
19927
20503
|
const health = {};
|
|
19928
20504
|
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;
|
|
19929
20505
|
if (meta?.last_flush_error) health.telemetry_last_error = "flush_failed";
|
|
19930
|
-
const queue =
|
|
20506
|
+
const queue = join44(home, ".synkro", "telemetry-pending.jsonl");
|
|
19931
20507
|
try {
|
|
19932
|
-
const size =
|
|
19933
|
-
health.telemetry_backlog = size <= 5 * 1024 * 1024 ?
|
|
20508
|
+
const size = statSync7(queue).size;
|
|
20509
|
+
health.telemetry_backlog = size <= 5 * 1024 * 1024 ? readFileSync41(queue, "utf8").split("\n").filter(Boolean).length : Math.ceil(size / 1024);
|
|
19934
20510
|
} catch {
|
|
19935
20511
|
}
|
|
19936
20512
|
return health;
|
|
@@ -19971,7 +20547,7 @@ function harnessSnapshot(agent) {
|
|
|
19971
20547
|
}
|
|
19972
20548
|
const config = readJson(agent.settingsPath);
|
|
19973
20549
|
const coverage = inspectCodexHooks(agent.settingsPath);
|
|
19974
|
-
const toml = readText(
|
|
20550
|
+
const toml = readText(join44(agent.configDir, "config.toml"));
|
|
19975
20551
|
const permission = toml.match(/^\s*approval_policy\s*=\s*["']([^"']+)/m)?.[1];
|
|
19976
20552
|
return {
|
|
19977
20553
|
row: {
|
|
@@ -19992,7 +20568,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
19992
20568
|
const detected = options.detectedAgents ?? detectAgents();
|
|
19993
20569
|
const identity = getOperationalInstallationIdentity(options.identityPath);
|
|
19994
20570
|
const targetPlatform = options.platformName ?? platform6();
|
|
19995
|
-
const codexHome = options.homeDir ?
|
|
20571
|
+
const codexHome = options.homeDir ? join44(home, ".codex") : process.env.CODEX_HOME || join44(home, ".codex");
|
|
19996
20572
|
const harnesses = [];
|
|
19997
20573
|
const artifacts = [];
|
|
19998
20574
|
for (const agent of detected) {
|
|
@@ -20000,7 +20576,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
20000
20576
|
harnesses.push(row2);
|
|
20001
20577
|
artifacts.push(...hookArtifacts(row2.harness, config));
|
|
20002
20578
|
}
|
|
20003
|
-
const claudeJson = readJson(
|
|
20579
|
+
const claudeJson = readJson(join44(home, ".claude.json"));
|
|
20004
20580
|
artifacts.push(...mcpArtifactsFromJson("claude_code", claudeJson));
|
|
20005
20581
|
if (claudeJson?.projects && typeof claudeJson.projects === "object") {
|
|
20006
20582
|
for (const [projectPath, project] of Object.entries(claudeJson.projects)) {
|
|
@@ -20008,8 +20584,8 @@ function collectOperationalInventory(options = {}) {
|
|
|
20008
20584
|
artifacts.push(...mcpArtifactsFromJson("claude_code", project, `local:${sha256(projectPath).slice(0, 16)}`));
|
|
20009
20585
|
}
|
|
20010
20586
|
}
|
|
20011
|
-
artifacts.push(...mcpArtifactsFromJson("cursor", readJson(
|
|
20012
|
-
artifacts.push(...codexMcpArtifacts(readText(
|
|
20587
|
+
artifacts.push(...mcpArtifactsFromJson("cursor", readJson(join44(home, ".cursor", "mcp.json"))));
|
|
20588
|
+
artifacts.push(...codexMcpArtifacts(readText(join44(codexHome, "config.toml"))));
|
|
20013
20589
|
const projectRoots = discoveredProjectRoots(
|
|
20014
20590
|
claudeJson,
|
|
20015
20591
|
options.currentDirectory ?? process.cwd(),
|
|
@@ -20020,11 +20596,11 @@ function collectOperationalInventory(options = {}) {
|
|
|
20020
20596
|
const scopeHash = sha256(projectRoot).slice(0, 16);
|
|
20021
20597
|
artifacts.push(...mcpArtifactsFromJson(
|
|
20022
20598
|
"claude_code",
|
|
20023
|
-
readJson(
|
|
20599
|
+
readJson(join44(projectRoot, ".mcp.json")),
|
|
20024
20600
|
`project:${scopeHash}`
|
|
20025
20601
|
));
|
|
20026
|
-
const cursorProjectConfig =
|
|
20027
|
-
if (
|
|
20602
|
+
const cursorProjectConfig = join44(projectRoot, ".cursor", "mcp.json");
|
|
20603
|
+
if (resolve7(cursorProjectConfig) !== resolve7(join44(home, ".cursor", "mcp.json"))) {
|
|
20028
20604
|
artifacts.push(...mcpArtifactsFromJson(
|
|
20029
20605
|
"cursor",
|
|
20030
20606
|
readJson(cursorProjectConfig),
|
|
@@ -20046,7 +20622,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
20046
20622
|
});
|
|
20047
20623
|
artifacts.push(...mcpArtifactsFromJson("claude_desktop", desktopConfig));
|
|
20048
20624
|
}
|
|
20049
|
-
const claudeSettings = readJson(
|
|
20625
|
+
const claudeSettings = readJson(join44(home, ".claude", "settings.json"));
|
|
20050
20626
|
if (claudeSettings?.enabledPlugins && typeof claudeSettings.enabledPlugins === "object") {
|
|
20051
20627
|
for (const [name, enabled] of Object.entries(claudeSettings.enabledPlugins)) {
|
|
20052
20628
|
artifacts.push({
|
|
@@ -20060,14 +20636,14 @@ function collectOperationalInventory(options = {}) {
|
|
|
20060
20636
|
});
|
|
20061
20637
|
}
|
|
20062
20638
|
}
|
|
20063
|
-
artifacts.push(...skillArtifacts("claude_code",
|
|
20064
|
-
artifacts.push(...skillArtifacts("cursor",
|
|
20065
|
-
artifacts.push(...skillArtifacts("codex",
|
|
20066
|
-
artifacts.push(...cursorExtensionArtifacts(
|
|
20639
|
+
artifacts.push(...skillArtifacts("claude_code", join44(home, ".claude", "skills")));
|
|
20640
|
+
artifacts.push(...skillArtifacts("cursor", join44(home, ".cursor", "skills")));
|
|
20641
|
+
artifacts.push(...skillArtifacts("codex", join44(codexHome, "skills")));
|
|
20642
|
+
artifacts.push(...cursorExtensionArtifacts(join44(home, ".cursor", "extensions")));
|
|
20067
20643
|
const uniqueArtifacts = /* @__PURE__ */ new Map();
|
|
20068
20644
|
for (const artifact of artifacts) {
|
|
20069
|
-
const
|
|
20070
|
-
uniqueArtifacts.set(
|
|
20645
|
+
const key2 = `${artifact.harness || "global"}:${artifact.type}:${artifact.canonical_id}`;
|
|
20646
|
+
uniqueArtifacts.set(key2, artifact);
|
|
20071
20647
|
}
|
|
20072
20648
|
const codingHarnesses = harnesses.filter((row2) => row2.harness === "claude_code" || row2.harness === "cursor" || row2.harness === "codex");
|
|
20073
20649
|
const health = telemetryHealth(home) ?? {};
|
|
@@ -20100,7 +20676,7 @@ var init_collector = __esm({
|
|
|
20100
20676
|
init_ccHookConfig();
|
|
20101
20677
|
init_cursorHookConfig();
|
|
20102
20678
|
init_codexHookConfig();
|
|
20103
|
-
|
|
20679
|
+
init_identity2();
|
|
20104
20680
|
}
|
|
20105
20681
|
});
|
|
20106
20682
|
|
|
@@ -20119,19 +20695,19 @@ import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
|
|
|
20119
20695
|
import { spawn as spawn11 } from "child_process";
|
|
20120
20696
|
import {
|
|
20121
20697
|
existsSync as existsSync44,
|
|
20122
|
-
mkdirSync as
|
|
20123
|
-
readFileSync as
|
|
20698
|
+
mkdirSync as mkdirSync26,
|
|
20699
|
+
readFileSync as readFileSync42,
|
|
20124
20700
|
renameSync as renameSync10,
|
|
20125
|
-
writeFileSync as
|
|
20701
|
+
writeFileSync as writeFileSync32
|
|
20126
20702
|
} from "fs";
|
|
20127
20703
|
import { homedir as homedir46 } from "os";
|
|
20128
|
-
import { dirname as
|
|
20704
|
+
import { dirname as dirname15, join as join45 } from "path";
|
|
20129
20705
|
function syncStatePath() {
|
|
20130
|
-
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH ||
|
|
20706
|
+
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join45(homedir46(), ".synkro", "inventory-sync.json");
|
|
20131
20707
|
}
|
|
20132
20708
|
function readState(path = syncStatePath()) {
|
|
20133
20709
|
try {
|
|
20134
|
-
const parsed = JSON.parse(
|
|
20710
|
+
const parsed = JSON.parse(readFileSync42(path, "utf8"));
|
|
20135
20711
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
20136
20712
|
} catch {
|
|
20137
20713
|
return {};
|
|
@@ -20139,9 +20715,9 @@ function readState(path = syncStatePath()) {
|
|
|
20139
20715
|
}
|
|
20140
20716
|
function writeState(state, path = syncStatePath()) {
|
|
20141
20717
|
try {
|
|
20142
|
-
|
|
20718
|
+
mkdirSync26(dirname15(path), { recursive: true, mode: 448 });
|
|
20143
20719
|
const temp = `${path}.${process.pid}.tmp`;
|
|
20144
|
-
|
|
20720
|
+
writeFileSync32(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
20145
20721
|
renameSync10(temp, path);
|
|
20146
20722
|
} catch {
|
|
20147
20723
|
}
|
|
@@ -20154,18 +20730,18 @@ function shouldSyncInventory(state, now = Date.now(), target) {
|
|
|
20154
20730
|
return !Number.isFinite(lastAttempt) || lastAttempt <= 0 || now - lastAttempt >= FAILURE_RETRY_MS;
|
|
20155
20731
|
}
|
|
20156
20732
|
function readConfig() {
|
|
20157
|
-
const path =
|
|
20733
|
+
const path = join45(homedir46(), ".synkro", "config.env");
|
|
20158
20734
|
const out = {};
|
|
20159
20735
|
try {
|
|
20160
|
-
for (const rawLine of
|
|
20736
|
+
for (const rawLine of readFileSync42(path, "utf8").split("\n")) {
|
|
20161
20737
|
const line = rawLine.trim();
|
|
20162
20738
|
if (!line || line.startsWith("#")) continue;
|
|
20163
20739
|
const index = line.indexOf("=");
|
|
20164
20740
|
if (index <= 0) continue;
|
|
20165
|
-
const
|
|
20741
|
+
const key2 = line.slice(0, index).trim();
|
|
20166
20742
|
let value = line.slice(index + 1).trim();
|
|
20167
20743
|
if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
|
|
20168
|
-
out[
|
|
20744
|
+
out[key2] = value;
|
|
20169
20745
|
}
|
|
20170
20746
|
} catch {
|
|
20171
20747
|
}
|
|
@@ -20196,7 +20772,7 @@ function resolveInventoryGateway(raw) {
|
|
|
20196
20772
|
}
|
|
20197
20773
|
async function loadToken() {
|
|
20198
20774
|
try {
|
|
20199
|
-
const durable =
|
|
20775
|
+
const durable = readFileSync42(join45(homedir46(), ".synkro", ".mcp-jwt"), "utf8").trim();
|
|
20200
20776
|
if (durable) return durable;
|
|
20201
20777
|
} catch {
|
|
20202
20778
|
}
|
|
@@ -20212,7 +20788,7 @@ async function loadToken() {
|
|
|
20212
20788
|
function stable(value) {
|
|
20213
20789
|
if (Array.isArray(value)) return value.map(stable);
|
|
20214
20790
|
if (!value || typeof value !== "object") return value;
|
|
20215
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([
|
|
20791
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key2, child]) => [key2, stable(child)]));
|
|
20216
20792
|
}
|
|
20217
20793
|
function inventorySnapshotChunks(snapshot, maxBytes = INVENTORY_CHUNK_BYTES) {
|
|
20218
20794
|
const { collected_at: _heartbeat, ...material } = snapshot;
|
|
@@ -20338,23 +20914,23 @@ var init_sync2 = __esm({
|
|
|
20338
20914
|
});
|
|
20339
20915
|
|
|
20340
20916
|
// cli/bootstrap.js
|
|
20341
|
-
import { readFileSync as
|
|
20342
|
-
import { resolve as
|
|
20917
|
+
import { readFileSync as readFileSync43, existsSync as existsSync45 } from "fs";
|
|
20918
|
+
import { resolve as resolve8 } from "path";
|
|
20343
20919
|
process.title = "synkro";
|
|
20344
20920
|
var envCandidates = [
|
|
20345
|
-
|
|
20921
|
+
resolve8(process.env.HOME ?? "", ".synkro", "config.env")
|
|
20346
20922
|
];
|
|
20347
20923
|
for (const envPath of envCandidates) {
|
|
20348
20924
|
if (!existsSync45(envPath)) continue;
|
|
20349
|
-
const envContent =
|
|
20925
|
+
const envContent = readFileSync43(envPath, "utf-8");
|
|
20350
20926
|
for (const line of envContent.split("\n")) {
|
|
20351
20927
|
const trimmed = line.trim();
|
|
20352
20928
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
20353
20929
|
const eqIndex = trimmed.indexOf("=");
|
|
20354
20930
|
if (eqIndex <= 0) continue;
|
|
20355
|
-
const
|
|
20931
|
+
const key2 = trimmed.slice(0, eqIndex).trim();
|
|
20356
20932
|
const value = trimmed.slice(eqIndex + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
20357
|
-
if (!process.env[
|
|
20933
|
+
if (!process.env[key2] && !value.startsWith("op://")) process.env[key2] = value;
|
|
20358
20934
|
}
|
|
20359
20935
|
}
|
|
20360
20936
|
var args = process.argv.slice(2);
|
|
@@ -20363,7 +20939,7 @@ var subArgs = args.slice(1);
|
|
|
20363
20939
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
20364
20940
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
20365
20941
|
function printVersion() {
|
|
20366
|
-
console.log("1.10.
|
|
20942
|
+
console.log("1.10.10");
|
|
20367
20943
|
}
|
|
20368
20944
|
function printHelp2() {
|
|
20369
20945
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|