@synkro-sh/cli 1.10.5 → 1.10.8
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 +1612 -388
- package/dist/bootstrap.js.map +1 -1
- package/package.json +3 -1
package/dist/bootstrap.js
CHANGED
|
@@ -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.8";
|
|
151
151
|
} catch {
|
|
152
152
|
}
|
|
153
153
|
const creds = loadCredentialsIdentity();
|
|
@@ -2351,7 +2351,7 @@ function buildCodexHookTrustEdits(hooks) {
|
|
|
2351
2351
|
return [...edits.values()];
|
|
2352
2352
|
}
|
|
2353
2353
|
function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTrust = false) {
|
|
2354
|
-
return new Promise((
|
|
2354
|
+
return new Promise((resolve8) => {
|
|
2355
2355
|
let settled = false;
|
|
2356
2356
|
let stdout = "";
|
|
2357
2357
|
let pending = "";
|
|
@@ -2369,7 +2369,7 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
|
|
|
2369
2369
|
child.kill();
|
|
2370
2370
|
} catch {
|
|
2371
2371
|
}
|
|
2372
|
-
|
|
2372
|
+
resolve8(summary);
|
|
2373
2373
|
};
|
|
2374
2374
|
const timer = setTimeout(() => finish(null), 1e4);
|
|
2375
2375
|
try {
|
|
@@ -3255,10 +3255,15 @@ function pruneToolCallMarks(now: number): void {
|
|
|
3255
3255
|
|
|
3256
3256
|
// The user's answer to "move into the task worktree, or stay here?". Written by the
|
|
3257
3257
|
// "synkro workspace stay" command (the CLI owns ~/.synkro, so nothing hand-writes it)
|
|
3258
|
-
// and read here.
|
|
3259
|
-
//
|
|
3258
|
+
// and read here. The filename includes a one-way session key so consent in one
|
|
3259
|
+
// Claude/Codex chat can never silently authorize another chat working the task.
|
|
3260
3260
|
const WORKSPACE_CHOICE_DIR = join(HOME, '.synkro', 'workspace-choice');
|
|
3261
3261
|
|
|
3262
|
+
function taskWorkspaceChoicePath(taskId: string, sessionId: string): string {
|
|
3263
|
+
const sessionKey = createHash('sha256').update(String(sessionId || '')).digest('hex').slice(0, 16);
|
|
3264
|
+
return join(WORKSPACE_CHOICE_DIR, taskId + '.' + sessionKey + '.stay');
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3262
3267
|
// The user answers the workspace question in plain language on their next
|
|
3263
3268
|
// prompt. Writing the marker is exactly what the CLI's "workspace stay"
|
|
3264
3269
|
// command does — but the consent matcher only accepts one literal spelling,
|
|
@@ -3279,14 +3284,23 @@ function isWorkspaceStayIntent(prompt: string): boolean {
|
|
|
3279
3284
|
].some((pattern) => pattern.test(normalized));
|
|
3280
3285
|
}
|
|
3281
3286
|
|
|
3282
|
-
function taskWorkspaceStayRecorded(taskId: string): boolean {
|
|
3283
|
-
if (!/^task_[a-z0-9]{8}$/i.test(String(taskId || ''))) return false;
|
|
3284
|
-
try { return existsSync(
|
|
3287
|
+
function taskWorkspaceStayRecorded(taskId: string, sessionId: string): boolean {
|
|
3288
|
+
if (!/^task_[a-z0-9]{8}$/i.test(String(taskId || '')) || !sessionId) return false;
|
|
3289
|
+
try { return existsSync(taskWorkspaceChoicePath(taskId, sessionId)); } catch { return false; }
|
|
3285
3290
|
}
|
|
3286
3291
|
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3292
|
+
function recordTaskWorkspaceStay(taskId: string, sessionId: string): boolean {
|
|
3293
|
+
if (!/^task_[a-z0-9]{8}$/i.test(String(taskId || '')) || !sessionId) return false;
|
|
3294
|
+
try {
|
|
3295
|
+
mkdirSync(WORKSPACE_CHOICE_DIR, { recursive: true });
|
|
3296
|
+
writeFileSync(taskWorkspaceChoicePath(taskId, sessionId), new Date().toISOString() + '\n', { mode: 0o600 });
|
|
3297
|
+
return true;
|
|
3298
|
+
} catch { return false; }
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// Our own context string, so the shape is stable: '... task=<id> branch=<name> ...'.
|
|
3302
|
+
function fieldFromScmContext(context: string, field: string): string {
|
|
3303
|
+
const marker = ' ' + field + '=';
|
|
3290
3304
|
const at = String(context || '').indexOf(marker);
|
|
3291
3305
|
if (at === -1) return '';
|
|
3292
3306
|
const rest = context.slice(at + marker.length);
|
|
@@ -3294,14 +3308,73 @@ function taskIdFromScmContext(context: string): string {
|
|
|
3294
3308
|
return (end === -1 ? rest : rest.slice(0, end)).trim();
|
|
3295
3309
|
}
|
|
3296
3310
|
|
|
3311
|
+
function taskIdFromScmContext(context: string): string {
|
|
3312
|
+
return fieldFromScmContext(context, 'task');
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
// An exact-command allowance must not be defeated by the output plumbing agents
|
|
3316
|
+
// reflexively append ('2>&1 | tail -2') — requiring the bare spelling made the
|
|
3317
|
+
// gate's own escape hatch block itself (observed live). Only harmless trailing
|
|
3318
|
+
// redirection plus a single display filter pass; anything that could chain a
|
|
3319
|
+
// second command (&&, ;, extra pipes, substitution) still rejects.
|
|
3320
|
+
function isBareCommandWithPlumbing(command: string, head: string): boolean {
|
|
3321
|
+
if (!command.startsWith(head)) return false;
|
|
3322
|
+
const rest = command.slice(head.length);
|
|
3323
|
+
return /^(?:\s+2>&1)?(?:\s*\|\s*(?:tail|head|cat)(?:\s+[-\w.+]+)*)?\s*$/.test(rest);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
function bashCommandOf(payload: any): string {
|
|
3327
|
+
if (String(payload?.tool_name || '') !== 'Bash') return '';
|
|
3328
|
+
const input = payload?.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
|
|
3329
|
+
return String(input.command || input.cmd || '').trim();
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3297
3332
|
// Consent must never deadlock behind the block it resolves: the command that records
|
|
3298
3333
|
// the answer is allowed through for the task currently being asked about, and nothing
|
|
3299
3334
|
// else is.
|
|
3300
|
-
function
|
|
3301
|
-
|
|
3335
|
+
function workspaceReadOnlyShellSegment(segment: string): boolean {
|
|
3336
|
+
const value = String(segment || '').trim();
|
|
3337
|
+
if (!value || /[;&><$]/.test(value) || value.includes(String.fromCharCode(96))) return false;
|
|
3338
|
+
const tokens = value.split(/\s+/);
|
|
3339
|
+
const verb = String(tokens[0] || '').toLowerCase();
|
|
3340
|
+
const writeOptions = new Set(['-i', '--in-place', '-o', '--output', '--output-document']);
|
|
3341
|
+
if (tokens.some((token) => writeOptions.has(token) || token.startsWith('--output='))) return false;
|
|
3342
|
+
const reads = new Set(['cat', 'head', 'tail', 'grep', 'egrep', 'fgrep', 'rg', 'ag', 'find', 'fd', 'ls', 'wc', 'cmp', 'diff', 'file', 'stat', 'pwd', 'sort', 'uniq', 'cut', 'tr', 'jq', 'yq']);
|
|
3343
|
+
if (reads.has(verb)) {
|
|
3344
|
+
if ((verb === 'find' || verb === 'fd') && tokens.some((token) => new Set(['-exec', '-execdir', '-ok', '-okdir', '-delete', '-fprint', '-fprint0', '-fls', '--exec', '--exec-batch']).has(token))) return false;
|
|
3345
|
+
return true;
|
|
3346
|
+
}
|
|
3347
|
+
if (verb !== 'git') return false;
|
|
3348
|
+
return new Set(['log', 'show', 'diff', 'blame', 'status', 'rev-parse', 'ls-files', 'ls-tree', 'cat-file', 'shortlog', 'describe']).has(String(tokens[1] || '').toLowerCase());
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
function isWorkspaceReadOnlyDiagnostic(payload: any): boolean {
|
|
3352
|
+
const toolName = String(payload?.tool_name || '');
|
|
3353
|
+
if (/^(?:Read|ReadFile|read_file|Grep|grep_search|codebase_search|file_search|Glob|list_dir)$/i.test(toolName)) return true;
|
|
3354
|
+
if (!/^(?:Bash|Shell|terminal|run_terminal_cmd|execute_command)$/i.test(toolName)) return false;
|
|
3302
3355
|
const input = payload?.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
|
|
3303
3356
|
const command = String(input.command || input.cmd || '').trim();
|
|
3304
|
-
|
|
3357
|
+
if (!command || /\|\||(^|[^&])&([^&]|$)|;/.test(command)) return false;
|
|
3358
|
+
return command.split('|').every(workspaceReadOnlyShellSegment);
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
function isWorkspaceConsentCommand(payload: any, taskId: string): boolean {
|
|
3362
|
+
if (!taskId) return false;
|
|
3363
|
+
const command = bashCommandOf(payload);
|
|
3364
|
+
return Boolean(command) && isBareCommandWithPlumbing(command, 'synkro workspace stay ' + taskId);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
// A session that is already inside the task worktree but on the wrong branch needs
|
|
3368
|
+
// exactly one repair: checking out the canonical branch. The gate demands that
|
|
3369
|
+
// branch, so blocking the checkout that reaches it would deadlock (observed live
|
|
3370
|
+
// after an agent created a side branch in the task worktree). Elsewhere the command
|
|
3371
|
+
// is harmless: git itself refuses to check out a branch held by another worktree.
|
|
3372
|
+
function isWorkspaceRepairCommand(payload: any, branchName: string): boolean {
|
|
3373
|
+
if (!branchName) return false;
|
|
3374
|
+
const command = bashCommandOf(payload);
|
|
3375
|
+
if (!command) return false;
|
|
3376
|
+
return isBareCommandWithPlumbing(command, 'git checkout ' + branchName)
|
|
3377
|
+
|| isBareCommandWithPlumbing(command, 'git switch ' + branchName);
|
|
3305
3378
|
}
|
|
3306
3379
|
|
|
3307
3380
|
function firstHookForToolCall(sessionId: string, payload: any): boolean {
|
|
@@ -3666,7 +3739,7 @@ function shellTaskWorkspaceArg(value: string): string {
|
|
|
3666
3739
|
return "'" + value.replace(/'/g, "'\"'\"'") + "'";
|
|
3667
3740
|
}
|
|
3668
3741
|
|
|
3669
|
-
function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspace: any): string {
|
|
3742
|
+
function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspace: any, root = ''): string {
|
|
3670
3743
|
const context = taskScmWorkspaceContext(workspace, harness);
|
|
3671
3744
|
const worktreePath = String(workspace?.worktreePath || '');
|
|
3672
3745
|
const branchName = String(workspace?.branchName || '');
|
|
@@ -3676,6 +3749,15 @@ function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspa
|
|
|
3676
3749
|
+ 'the task worktree and canonical branch.';
|
|
3677
3750
|
if (harness === 'cc') {
|
|
3678
3751
|
if (!worktreePath) return context + '\nTask worktree creation failed closed; retry workspace preparation.';
|
|
3752
|
+
// Already inside the task worktree, just not on its branch: the move/stay
|
|
3753
|
+
// question was answered the moment the session got here, and re-asking it
|
|
3754
|
+
// offers two exits that both fail. Name the one command that repairs it —
|
|
3755
|
+
// the gate lets exactly that command through.
|
|
3756
|
+
if (root && samePath(root, worktreePath)) {
|
|
3757
|
+
return context + '\nThis session is already in the task worktree but not on its canonical branch. '
|
|
3758
|
+
+ 'Run "git checkout ' + branchName + '" to continue; that command is allowed through this gate, '
|
|
3759
|
+
+ 'and substantive tools stay blocked until the branch matches.';
|
|
3760
|
+
}
|
|
3679
3761
|
// Ask, do not command. The user may legitimately want to keep working where they
|
|
3680
3762
|
// are, and Synkro should not move them without their say-so.
|
|
3681
3763
|
return context + '\nAsk the user whether to move this task into its isolated worktree '
|
|
@@ -3691,14 +3773,18 @@ function taskScmWorkspaceInstruction(harness: string, sessionId: string, workspa
|
|
|
3691
3773
|
+ '. Confirm the resumed workspace is on branch ' + JSON.stringify(branchName) + '. ' + requirement;
|
|
3692
3774
|
}
|
|
3693
3775
|
if (!worktreePath) {
|
|
3694
|
-
return context + '\
|
|
3695
|
-
+
|
|
3696
|
-
+ '
|
|
3697
|
-
+ '
|
|
3776
|
+
return context + '\nAsk the user whether to move this task into a generated isolated Codex worktree on branch '
|
|
3777
|
+
+ JSON.stringify(branchName) + ', or keep working in the current workspace. Do not decide for them. '
|
|
3778
|
+
+ 'To move: use Codex\'s Environment control or native Handoff for this task; never hand off a different task as a workaround. '
|
|
3779
|
+
+ 'To stay: run "synkro workspace stay ' + String(workspace?.taskId || '') + '". '
|
|
3780
|
+
+ 'Never switch the shared Local checkout.';
|
|
3698
3781
|
}
|
|
3699
|
-
return context + '\
|
|
3700
|
-
+
|
|
3701
|
-
+ '
|
|
3782
|
+
return context + '\nAsk the user whether to move this task to its prepared worktree '
|
|
3783
|
+
+ JSON.stringify(worktreePath) + ' on branch ' + JSON.stringify(branchName)
|
|
3784
|
+
+ ', or keep working in the current isolated Codex worktree. Do not decide for them. '
|
|
3785
|
+
+ 'To move: use Codex\'s Environment control or native Handoff for this task; never hand off a different task as a workaround. '
|
|
3786
|
+
+ 'To stay: run "synkro workspace stay ' + String(workspace?.taskId || '') + '". '
|
|
3787
|
+
+ 'Never switch the shared Local checkout.';
|
|
3702
3788
|
}
|
|
3703
3789
|
|
|
3704
3790
|
// CC validates hookSpecificOutput.hookEventName against the event that actually fired.
|
|
@@ -3780,7 +3866,7 @@ async function reconcileTaskScm(
|
|
|
3780
3866
|
: result?.workspace;
|
|
3781
3867
|
return {
|
|
3782
3868
|
reason: result?.waiting
|
|
3783
|
-
? (taskScmWorkspaceInstruction(harness, sessionId, workspace)
|
|
3869
|
+
? (taskScmWorkspaceInstruction(harness, sessionId, workspace, root)
|
|
3784
3870
|
|| String(result.reason || 'task source-control preparation is pending'))
|
|
3785
3871
|
: '',
|
|
3786
3872
|
context: taskScmWorkspaceContext(workspace, harness),
|
|
@@ -4479,19 +4565,21 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
4479
4565
|
// prompt records the same durable marker the CLI command writes. Reconcile
|
|
4480
4566
|
// just told us WHICH task is being asked, so no extra state is needed and
|
|
4481
4567
|
// ordinary prompts outside a pending ask are never scanned.
|
|
4482
|
-
if (surface === 'prompt-submit' && scm.reason && scmTaskId && !taskWorkspaceStayRecorded(scmTaskId)) {
|
|
4568
|
+
if (surface === 'prompt-submit' && scm.reason && scmTaskId && !taskWorkspaceStayRecorded(scmTaskId, sessionId)) {
|
|
4483
4569
|
const promptText = String(payload.prompt || payload.user_message || '');
|
|
4484
4570
|
if (isWorkspaceStayIntent(promptText)) {
|
|
4485
|
-
|
|
4486
|
-
mkdirSync(WORKSPACE_CHOICE_DIR, { recursive: true });
|
|
4487
|
-
writeFileSync(join(WORKSPACE_CHOICE_DIR, scmTaskId + '.stay'), new Date().toISOString() + '\n');
|
|
4488
|
-
} catch { /* fail-open: the CLI command and the exact-string path remain */ }
|
|
4571
|
+
recordTaskWorkspaceStay(scmTaskId, sessionId);
|
|
4489
4572
|
}
|
|
4490
4573
|
}
|
|
4491
|
-
|
|
4574
|
+
const workspaceConsentCommand = Boolean(scmTaskId) && isWorkspaceConsentCommand(payload, scmTaskId);
|
|
4575
|
+
if (workspaceConsentCommand) recordTaskWorkspaceStay(scmTaskId, sessionId);
|
|
4576
|
+
// The user was asked and chose to keep working here, is answering right now,
|
|
4577
|
+
// or the tool call is the one git command that repairs the workspace itself.
|
|
4492
4578
|
const workspaceConsentSettled = Boolean(scmTaskId)
|
|
4493
|
-
&& (taskWorkspaceStayRecorded(scmTaskId
|
|
4494
|
-
|
|
4579
|
+
&& (taskWorkspaceStayRecorded(scmTaskId, sessionId)
|
|
4580
|
+
|| workspaceConsentCommand
|
|
4581
|
+
|| isWorkspaceRepairCommand(payload, fieldFromScmContext(scm.context, 'branch')));
|
|
4582
|
+
if (scm.reason && substantiveTool && !workspaceConsentSettled && !isWorkspaceReadOnlyDiagnostic(payload)) {
|
|
4495
4583
|
out(taskScmBlockResponse(harness, scm.reason, firstHookForToolCall(sessionId, payload)));
|
|
4496
4584
|
return;
|
|
4497
4585
|
}
|
|
@@ -4680,7 +4768,7 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
4680
4768
|
let scmContext = scm.context && surface === 'prompt-submit' && firstHookForToolCall(sessionId, payload) ? scm.context : '';
|
|
4681
4769
|
// Say plainly that work is continuing outside the task worktree by the user's choice,
|
|
4682
4770
|
// so the state is never mistaken for a binding that silently failed.
|
|
4683
|
-
if (scmContext && scmTaskId && taskWorkspaceStayRecorded(scmTaskId)) {
|
|
4771
|
+
if (scmContext && scmTaskId && taskWorkspaceStayRecorded(scmTaskId, sessionId)) {
|
|
4684
4772
|
scmContext += ' workspace=staying-here-by-user-choice';
|
|
4685
4773
|
}
|
|
4686
4774
|
const responseText = withActualHookEvent(withTaskScmContext(contractResponseText, harness, scmContext), payload);
|
|
@@ -5023,6 +5111,7 @@ if (process.env.SYNKRO_HOOK_FORMAT === 'codex' && hookEventName === 'PermissionR
|
|
|
5023
5111
|
// cli/auth/stub.ts
|
|
5024
5112
|
var stub_exports = {};
|
|
5025
5113
|
__export(stub_exports, {
|
|
5114
|
+
accessTokenExpired: () => accessTokenExpired,
|
|
5026
5115
|
authedFetch: () => authedFetch,
|
|
5027
5116
|
authenticate: () => authenticate,
|
|
5028
5117
|
clearCredentials: () => clearCredentials,
|
|
@@ -5092,7 +5181,7 @@ function createCallbackServer() {
|
|
|
5092
5181
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
5093
5182
|
"Vary": "Origin"
|
|
5094
5183
|
};
|
|
5095
|
-
return new Promise((
|
|
5184
|
+
return new Promise((resolve8, reject) => {
|
|
5096
5185
|
const server = createServer((req, res) => {
|
|
5097
5186
|
if (req.method === "OPTIONS") {
|
|
5098
5187
|
const origin = req.headers.origin;
|
|
@@ -5181,7 +5270,7 @@ function createCallbackServer() {
|
|
|
5181
5270
|
res.end(JSON.stringify({ ok: true }));
|
|
5182
5271
|
setTimeout(() => {
|
|
5183
5272
|
server.close();
|
|
5184
|
-
|
|
5273
|
+
resolve8(authData);
|
|
5185
5274
|
}, 200);
|
|
5186
5275
|
});
|
|
5187
5276
|
req.on("error", (e) => {
|
|
@@ -5280,19 +5369,23 @@ function getAccessToken() {
|
|
|
5280
5369
|
const creds = loadCredentials();
|
|
5281
5370
|
return creds?.access_token || null;
|
|
5282
5371
|
}
|
|
5283
|
-
function
|
|
5284
|
-
const creds = loadCredentials();
|
|
5285
|
-
if (!creds) return true;
|
|
5372
|
+
function accessTokenExpired(accessToken, now = Date.now()) {
|
|
5286
5373
|
try {
|
|
5287
|
-
const decoded = jwt.decode(
|
|
5374
|
+
const decoded = jwt.decode(accessToken);
|
|
5288
5375
|
if (!decoded?.exp) return true;
|
|
5289
5376
|
const expiresAt = decoded.exp * 1e3;
|
|
5290
|
-
const
|
|
5291
|
-
|
|
5377
|
+
const lifetimeMs = decoded.iat ? (decoded.exp - decoded.iat) * 1e3 : 0;
|
|
5378
|
+
const buffer = lifetimeMs > 0 ? Math.min(MAX_EXPIRY_BUFFER_MS, Math.floor(lifetimeMs / 5)) : MAX_EXPIRY_BUFFER_MS;
|
|
5379
|
+
return now > expiresAt - buffer;
|
|
5292
5380
|
} catch {
|
|
5293
5381
|
return true;
|
|
5294
5382
|
}
|
|
5295
5383
|
}
|
|
5384
|
+
function isTokenExpired() {
|
|
5385
|
+
const creds = loadCredentials();
|
|
5386
|
+
if (!creds?.access_token) return true;
|
|
5387
|
+
return accessTokenExpired(creds.access_token);
|
|
5388
|
+
}
|
|
5296
5389
|
async function refreshToken() {
|
|
5297
5390
|
const creds = loadCredentials();
|
|
5298
5391
|
if (!creds?.refresh_token) return false;
|
|
@@ -5360,7 +5453,7 @@ async function getSecrets(userId, integrationId) {
|
|
|
5360
5453
|
LANGSMITH_API_KEY: process.env.USER_LANGSMITH_KEY || ""
|
|
5361
5454
|
};
|
|
5362
5455
|
}
|
|
5363
|
-
var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, RAW_API_URL, SYNKRO_API_URL, ERROR_HTML, refreshPromise;
|
|
5456
|
+
var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, RAW_API_URL, SYNKRO_API_URL, ERROR_HTML, MAX_EXPIRY_BUFFER_MS, refreshPromise;
|
|
5364
5457
|
var init_stub = __esm({
|
|
5365
5458
|
"cli/auth/stub.ts"() {
|
|
5366
5459
|
"use strict";
|
|
@@ -5410,6 +5503,7 @@ var init_stub = __esm({
|
|
|
5410
5503
|
</body>
|
|
5411
5504
|
</html>
|
|
5412
5505
|
`;
|
|
5506
|
+
MAX_EXPIRY_BUFFER_MS = 60 * 1e3;
|
|
5413
5507
|
refreshPromise = null;
|
|
5414
5508
|
}
|
|
5415
5509
|
});
|
|
@@ -5512,7 +5606,7 @@ function detectSubdirRepos() {
|
|
|
5512
5606
|
}
|
|
5513
5607
|
}
|
|
5514
5608
|
function ask(rl, question) {
|
|
5515
|
-
return new Promise((
|
|
5609
|
+
return new Promise((resolve8) => rl.question(question, resolve8));
|
|
5516
5610
|
}
|
|
5517
5611
|
async function linkRepo(repo, linkedNames) {
|
|
5518
5612
|
try {
|
|
@@ -5751,7 +5845,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
5751
5845
|
writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
|
|
5752
5846
|
const runnerPath = join13(sessionDir, "run.sh");
|
|
5753
5847
|
writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
|
|
5754
|
-
await new Promise((
|
|
5848
|
+
await new Promise((resolve8) => {
|
|
5755
5849
|
const child = spawn3("bash", [runnerPath], {
|
|
5756
5850
|
stdio: "inherit",
|
|
5757
5851
|
env: { ...process.env, SYNKRO_CAPTURE_URL: CAPTURE_URL, SYNKRO_SCAN_URL: SCAN_URL, SYNKRO_SCAN_TURN_URL: SCAN_TURN_URL, SYNKRO_DLP_POLICY_URL: DLP_POLICY_URL, SYNKRO_TURN_VERDICTS_URL: TURN_VERDICTS_URL, SYNKRO_TURN_VERDICT_URL: TURN_VERDICT_URL, SYNKRO_MCP_EVENT_URL: MCP_EVENT_URL, SYNKRO_TAP_TOKEN: token, SYNKRO_TAP_TOKEN_FILE: JWT_PATH, SYNKRO_CD_BACKFILL: opts.backfill ? "1" : "" }
|
|
@@ -5767,7 +5861,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
5767
5861
|
child.on("exit", () => {
|
|
5768
5862
|
process.off("SIGINT", forward);
|
|
5769
5863
|
process.off("SIGTERM", forward);
|
|
5770
|
-
|
|
5864
|
+
resolve8();
|
|
5771
5865
|
});
|
|
5772
5866
|
});
|
|
5773
5867
|
}
|
|
@@ -7840,7 +7934,7 @@ var init_dockerInstall = __esm({
|
|
|
7840
7934
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
7841
7935
|
CONTAINER_NAME = resolveContainerName();
|
|
7842
7936
|
defaultImageVersion = () => {
|
|
7843
|
-
if (true) return "1.10.
|
|
7937
|
+
if (true) return "1.10.8";
|
|
7844
7938
|
try {
|
|
7845
7939
|
const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
|
|
7846
7940
|
if (pkg.version) return pkg.version;
|
|
@@ -7872,7 +7966,7 @@ function captureClaudeSetupToken() {
|
|
|
7872
7966
|
const bin = "script";
|
|
7873
7967
|
const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
|
|
7874
7968
|
const OAUTH_HINT = 'The browser approval did not return a token. This usually means claude.ai rejected the OAuth request (its "Authorization failed \u2014 Unsupported media type" page), most often because a browser extension (Grammarly, ad/script blockers, AI-assistant toolbars) stripped the request headers, or you approved on the wrong Claude account. Fix: retry in a clean incognito window with extensions disabled, and approve on the Claude account you want the cloud workers to use.';
|
|
7875
|
-
return new Promise((
|
|
7969
|
+
return new Promise((resolve8, reject) => {
|
|
7876
7970
|
const proc = nodeSpawn(bin, args2, {
|
|
7877
7971
|
stdio: "inherit",
|
|
7878
7972
|
env: { ...process.env, FORCE_COLOR: "3", COLORTERM: "truecolor", TERM: "xterm-256color" }
|
|
@@ -7939,7 +8033,7 @@ function captureClaudeSetupToken() {
|
|
|
7939
8033
|
reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));
|
|
7940
8034
|
return;
|
|
7941
8035
|
}
|
|
7942
|
-
|
|
8036
|
+
resolve8(token);
|
|
7943
8037
|
});
|
|
7944
8038
|
});
|
|
7945
8039
|
}
|
|
@@ -7965,13 +8059,13 @@ function findCodexBinary() {
|
|
|
7965
8059
|
function runCodexLogin(codexBin, codexHome) {
|
|
7966
8060
|
mkdirSync13(codexHome, { recursive: true, mode: 448 });
|
|
7967
8061
|
writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
|
|
7968
|
-
return new Promise((
|
|
8062
|
+
return new Promise((resolve8, reject) => {
|
|
7969
8063
|
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
7970
8064
|
stdio: "inherit",
|
|
7971
8065
|
env: { ...process.env, CODEX_HOME: codexHome }
|
|
7972
8066
|
});
|
|
7973
8067
|
proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
|
|
7974
|
-
proc.on("close", (code) => code === 0 ?
|
|
8068
|
+
proc.on("close", (code) => code === 0 ? resolve8() : reject(new Error(`codex login exited with code ${code}`)));
|
|
7975
8069
|
});
|
|
7976
8070
|
}
|
|
7977
8071
|
async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
@@ -8673,20 +8767,20 @@ async function promptAgentSelection(detected) {
|
|
|
8673
8767
|
detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
|
|
8674
8768
|
console.log(` ${detected.length + 1}. Both / all (default)`);
|
|
8675
8769
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8676
|
-
const ask3 = () => new Promise((
|
|
8770
|
+
const ask3 = () => new Promise((resolve8) => {
|
|
8677
8771
|
rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
|
|
8678
8772
|
const t = answer.trim().toLowerCase();
|
|
8679
8773
|
if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
|
|
8680
8774
|
rl.close();
|
|
8681
|
-
return
|
|
8775
|
+
return resolve8(detected);
|
|
8682
8776
|
}
|
|
8683
8777
|
const n = parseInt(t, 10);
|
|
8684
8778
|
if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
|
|
8685
8779
|
rl.close();
|
|
8686
|
-
return
|
|
8780
|
+
return resolve8([detected[n - 1]]);
|
|
8687
8781
|
}
|
|
8688
8782
|
console.log("Invalid choice. Try again.");
|
|
8689
|
-
|
|
8783
|
+
resolve8(ask3());
|
|
8690
8784
|
});
|
|
8691
8785
|
});
|
|
8692
8786
|
return ask3();
|
|
@@ -8709,12 +8803,12 @@ async function promptCursorApiKey(opts) {
|
|
|
8709
8803
|
return;
|
|
8710
8804
|
}
|
|
8711
8805
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8712
|
-
const key = await new Promise((
|
|
8806
|
+
const key = await new Promise((resolve8) => {
|
|
8713
8807
|
rl.question(
|
|
8714
8808
|
"Cursor grading needs a Cursor API key (cursor.com \u2192 Settings \u2192 API Keys).\nPaste it now, or press Enter to skip (Cursor workers stay idle until set): ",
|
|
8715
8809
|
(answer) => {
|
|
8716
8810
|
rl.close();
|
|
8717
|
-
|
|
8811
|
+
resolve8(answer.trim());
|
|
8718
8812
|
}
|
|
8719
8813
|
);
|
|
8720
8814
|
});
|
|
@@ -8729,7 +8823,7 @@ async function promptDeployLocation(current = "local") {
|
|
|
8729
8823
|
if (!process.stdin.isTTY) return current;
|
|
8730
8824
|
const other = current === "cloud" ? "local" : "cloud";
|
|
8731
8825
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8732
|
-
return new Promise((
|
|
8826
|
+
return new Promise((resolve8) => {
|
|
8733
8827
|
rl.question(
|
|
8734
8828
|
`Where should Synkro run?
|
|
8735
8829
|
local \u2014 a grading container on this machine (Docker)
|
|
@@ -8738,7 +8832,7 @@ Each worker uses the account credentials you authorize. Choose [${current}] / ${
|
|
|
8738
8832
|
(answer) => {
|
|
8739
8833
|
rl.close();
|
|
8740
8834
|
const a = answer.trim().toLowerCase();
|
|
8741
|
-
|
|
8835
|
+
resolve8(a === "cloud" ? "cloud" : a === "local" ? "local" : current);
|
|
8742
8836
|
}
|
|
8743
8837
|
);
|
|
8744
8838
|
});
|
|
@@ -8900,7 +8994,7 @@ function writeConfigEnv(opts) {
|
|
|
8900
8994
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
8901
8995
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
8902
8996
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
8903
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.
|
|
8997
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.10.8")}`
|
|
8904
8998
|
];
|
|
8905
8999
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
8906
9000
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
@@ -9647,7 +9741,7 @@ async function installCommand(opts = {}) {
|
|
|
9647
9741
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
9648
9742
|
emit("install", {
|
|
9649
9743
|
phase: "started",
|
|
9650
|
-
cli_version_to: "1.10.
|
|
9744
|
+
cli_version_to: "1.10.8",
|
|
9651
9745
|
agents_detected: agents.map((a) => a.kind),
|
|
9652
9746
|
with_github: false,
|
|
9653
9747
|
with_local_cc: false,
|
|
@@ -10715,8 +10809,8 @@ function ensureReachabilityGitHook() {
|
|
|
10715
10809
|
}
|
|
10716
10810
|
return "updated";
|
|
10717
10811
|
}
|
|
10718
|
-
const
|
|
10719
|
-
writeFileSync17(hookPath, cur +
|
|
10812
|
+
const sep4 = cur.endsWith("\n") ? "" : "\n";
|
|
10813
|
+
writeFileSync17(hookPath, cur + sep4 + "\n" + block + "\n");
|
|
10720
10814
|
try {
|
|
10721
10815
|
chmodSync5(hookPath, 493);
|
|
10722
10816
|
} catch {
|
|
@@ -11936,10 +12030,10 @@ function confirmPurge() {
|
|
|
11936
12030
|
return Promise.resolve(false);
|
|
11937
12031
|
}
|
|
11938
12032
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
11939
|
-
return new Promise((
|
|
12033
|
+
return new Promise((resolve8) => {
|
|
11940
12034
|
rl.question(" Type 'yes' to wipe everything (anything else cancels): ", (answer) => {
|
|
11941
12035
|
rl.close();
|
|
11942
|
-
|
|
12036
|
+
resolve8(answer.trim().toLowerCase() === "yes");
|
|
11943
12037
|
});
|
|
11944
12038
|
});
|
|
11945
12039
|
}
|
|
@@ -12192,7 +12286,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12192
12286
|
const port = opts.port ?? CHANNEL_PORT;
|
|
12193
12287
|
const startedAt = Date.now();
|
|
12194
12288
|
try {
|
|
12195
|
-
const result = await new Promise((
|
|
12289
|
+
const result = await new Promise((resolve8, reject) => {
|
|
12196
12290
|
const req = httpRequest({
|
|
12197
12291
|
host: CHANNEL_HOST,
|
|
12198
12292
|
port,
|
|
@@ -12218,7 +12312,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12218
12312
|
reject(new LocalCCError(parsed.error));
|
|
12219
12313
|
return;
|
|
12220
12314
|
}
|
|
12221
|
-
|
|
12315
|
+
resolve8(String(parsed.result ?? ""));
|
|
12222
12316
|
} catch (err) {
|
|
12223
12317
|
reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
|
|
12224
12318
|
}
|
|
@@ -12244,14 +12338,14 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
12244
12338
|
}
|
|
12245
12339
|
}
|
|
12246
12340
|
function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
|
|
12247
|
-
return new Promise((
|
|
12341
|
+
return new Promise((resolve8) => {
|
|
12248
12342
|
const sock = connect(port, CHANNEL_HOST);
|
|
12249
12343
|
const done = (ok) => {
|
|
12250
12344
|
try {
|
|
12251
12345
|
sock.destroy();
|
|
12252
12346
|
} catch {
|
|
12253
12347
|
}
|
|
12254
|
-
|
|
12348
|
+
resolve8(ok);
|
|
12255
12349
|
};
|
|
12256
12350
|
sock.once("connect", () => done(true));
|
|
12257
12351
|
sock.once("error", () => done(false));
|
|
@@ -12283,10 +12377,10 @@ __export(grade_exports, {
|
|
|
12283
12377
|
gradeCommand: () => gradeCommand
|
|
12284
12378
|
});
|
|
12285
12379
|
async function readStdin() {
|
|
12286
|
-
return new Promise((
|
|
12380
|
+
return new Promise((resolve8, reject) => {
|
|
12287
12381
|
const chunks = [];
|
|
12288
12382
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
12289
|
-
process.stdin.on("end", () =>
|
|
12383
|
+
process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
|
|
12290
12384
|
process.stdin.on("error", reject);
|
|
12291
12385
|
});
|
|
12292
12386
|
}
|
|
@@ -12540,7 +12634,7 @@ function spawnClaudeJudge(file, claudeToken, promptHeader) {
|
|
|
12540
12634
|
Diff:
|
|
12541
12635
|
${hunks}`;
|
|
12542
12636
|
const fullPrompt = promptHeader + userMessage;
|
|
12543
|
-
return new Promise((
|
|
12637
|
+
return new Promise((resolve8) => {
|
|
12544
12638
|
const t0 = Date.now();
|
|
12545
12639
|
const proc = spawn6(
|
|
12546
12640
|
"claude",
|
|
@@ -12568,7 +12662,7 @@ ${hunks}`;
|
|
|
12568
12662
|
const latencyMs = Date.now() - t0;
|
|
12569
12663
|
if (code !== 0) {
|
|
12570
12664
|
console.warn(` claude exited ${code}: ${(stderr || stdout).slice(0, 500)}`);
|
|
12571
|
-
|
|
12665
|
+
resolve8({ findings: [], latencyMs });
|
|
12572
12666
|
return;
|
|
12573
12667
|
}
|
|
12574
12668
|
try {
|
|
@@ -12587,10 +12681,10 @@ ${hunks}`;
|
|
|
12587
12681
|
description: f.description,
|
|
12588
12682
|
fix: f.fix
|
|
12589
12683
|
}));
|
|
12590
|
-
|
|
12684
|
+
resolve8({ findings, latencyMs });
|
|
12591
12685
|
} catch (parseErr) {
|
|
12592
12686
|
console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
|
|
12593
|
-
|
|
12687
|
+
resolve8({ findings: [], latencyMs });
|
|
12594
12688
|
}
|
|
12595
12689
|
});
|
|
12596
12690
|
});
|
|
@@ -12639,7 +12733,7 @@ ${JSON.stringify(findings, null, 2)}
|
|
|
12639
12733
|
`;
|
|
12640
12734
|
}
|
|
12641
12735
|
function spawnOpusConsolidator(findings, claudeToken) {
|
|
12642
|
-
return new Promise((
|
|
12736
|
+
return new Promise((resolve8) => {
|
|
12643
12737
|
const prompt = buildConsolidationPrompt(findings);
|
|
12644
12738
|
const proc = spawn6(
|
|
12645
12739
|
"claude",
|
|
@@ -12666,7 +12760,7 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12666
12760
|
proc.on("close", (code) => {
|
|
12667
12761
|
if (code !== 0) {
|
|
12668
12762
|
console.warn(` opus consolidation exited ${code}: ${(stderr || stdout).slice(0, 300)}`);
|
|
12669
|
-
|
|
12763
|
+
resolve8(fallbackReview(findings));
|
|
12670
12764
|
return;
|
|
12671
12765
|
}
|
|
12672
12766
|
try {
|
|
@@ -12687,10 +12781,10 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
12687
12781
|
const order = ["low", "medium", "high", "critical"];
|
|
12688
12782
|
return order.indexOf(f.severity) > order.indexOf(max) ? f.severity : max;
|
|
12689
12783
|
}, "low");
|
|
12690
|
-
|
|
12784
|
+
resolve8({ summary: review.summary || "", comments, severity: maxSeverity });
|
|
12691
12785
|
} catch {
|
|
12692
12786
|
console.warn(` failed to parse opus response, using fallback`);
|
|
12693
|
-
|
|
12787
|
+
resolve8(fallbackReview(findings));
|
|
12694
12788
|
}
|
|
12695
12789
|
});
|
|
12696
12790
|
});
|
|
@@ -13173,14 +13267,14 @@ function ensureRunning(opts = {}) {
|
|
|
13173
13267
|
return startTask(opts);
|
|
13174
13268
|
}
|
|
13175
13269
|
function probePort(host, port, timeoutMs = 500) {
|
|
13176
|
-
return new Promise((
|
|
13270
|
+
return new Promise((resolve8) => {
|
|
13177
13271
|
const sock = connect2(port, host);
|
|
13178
13272
|
const done = (ok) => {
|
|
13179
13273
|
try {
|
|
13180
13274
|
sock.destroy();
|
|
13181
13275
|
} catch {
|
|
13182
13276
|
}
|
|
13183
|
-
|
|
13277
|
+
resolve8(ok);
|
|
13184
13278
|
};
|
|
13185
13279
|
sock.once("connect", () => done(true));
|
|
13186
13280
|
sock.once("error", () => done(false));
|
|
@@ -13730,7 +13824,7 @@ function cmdLogs(rest) {
|
|
|
13730
13824
|
if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
|
|
13731
13825
|
return;
|
|
13732
13826
|
}
|
|
13733
|
-
return new Promise((
|
|
13827
|
+
return new Promise((resolve8) => {
|
|
13734
13828
|
console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
|
|
13735
13829
|
const stop = followTurns((t) => {
|
|
13736
13830
|
console.log(" " + formatTurn(t, raw));
|
|
@@ -13738,7 +13832,7 @@ function cmdLogs(rest) {
|
|
|
13738
13832
|
const onSigint = () => {
|
|
13739
13833
|
stop();
|
|
13740
13834
|
process.removeListener("SIGINT", onSigint);
|
|
13741
|
-
|
|
13835
|
+
resolve8();
|
|
13742
13836
|
};
|
|
13743
13837
|
process.on("SIGINT", onSigint);
|
|
13744
13838
|
});
|
|
@@ -14027,9 +14121,9 @@ function parseSession(file, seenStableIds) {
|
|
|
14027
14121
|
}
|
|
14028
14122
|
function ask2(q) {
|
|
14029
14123
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
14030
|
-
return new Promise((
|
|
14124
|
+
return new Promise((resolve8) => rl.question(q, (a) => {
|
|
14031
14125
|
rl.close();
|
|
14032
|
-
|
|
14126
|
+
resolve8(/^y(es)?$/i.test(a.trim()));
|
|
14033
14127
|
}));
|
|
14034
14128
|
}
|
|
14035
14129
|
async function importCommand() {
|
|
@@ -14429,6 +14523,16 @@ import { homedir as homedir31 } from "os";
|
|
|
14429
14523
|
function markerPath(taskId) {
|
|
14430
14524
|
return join31(WORKSPACE_CHOICE_DIR, `${taskId}.stay`);
|
|
14431
14525
|
}
|
|
14526
|
+
function markerNamesForTask(taskId) {
|
|
14527
|
+
try {
|
|
14528
|
+
return existsSync33(WORKSPACE_CHOICE_DIR) ? readdirSync8(WORKSPACE_CHOICE_DIR).filter((name) => name === `${taskId}.stay` || name.startsWith(`${taskId}.`) && name.endsWith(".stay")) : [];
|
|
14529
|
+
} catch {
|
|
14530
|
+
return [];
|
|
14531
|
+
}
|
|
14532
|
+
}
|
|
14533
|
+
function taskIdFromMarker(name) {
|
|
14534
|
+
return (name.match(/^(task_[a-z0-9]{8})(?:\.[a-f0-9]{16})?\.stay$/i) || [])[1] || "";
|
|
14535
|
+
}
|
|
14432
14536
|
function usage() {
|
|
14433
14537
|
console.log(`synkro workspace \u2014 record where a task's work happens
|
|
14434
14538
|
|
|
@@ -14437,8 +14541,8 @@ Usage:
|
|
|
14437
14541
|
synkro workspace clear <taskId> forget the choice (the gate asks again)
|
|
14438
14542
|
synkro workspace status [taskId] show recorded choices
|
|
14439
14543
|
|
|
14440
|
-
The gate asks once per task. "stay" is remembered
|
|
14441
|
-
the active task changes.`);
|
|
14544
|
+
The gate asks once per task session. "stay" is remembered in that chat until the
|
|
14545
|
+
choice is cleared or the active task changes.`);
|
|
14442
14546
|
}
|
|
14443
14547
|
async function workspaceCommand(args2) {
|
|
14444
14548
|
const sub = String(args2[0] || "").trim();
|
|
@@ -14455,7 +14559,7 @@ async function workspaceCommand(args2) {
|
|
|
14455
14559
|
recorded = [];
|
|
14456
14560
|
}
|
|
14457
14561
|
if (taskId) {
|
|
14458
|
-
const on =
|
|
14562
|
+
const on = markerNamesForTask(taskId).length > 0;
|
|
14459
14563
|
console.log(`${taskId}: ${on ? "stay recorded" : "no choice recorded"}`);
|
|
14460
14564
|
return;
|
|
14461
14565
|
}
|
|
@@ -14464,7 +14568,7 @@ async function workspaceCommand(args2) {
|
|
|
14464
14568
|
return;
|
|
14465
14569
|
}
|
|
14466
14570
|
console.log("Staying in the current checkout for:");
|
|
14467
|
-
for (const
|
|
14571
|
+
for (const id of [...new Set(recorded.map(taskIdFromMarker).filter(Boolean))].sort()) console.log(` ${id}`);
|
|
14468
14572
|
return;
|
|
14469
14573
|
}
|
|
14470
14574
|
if (sub !== "stay" && sub !== "clear") {
|
|
@@ -14479,9 +14583,11 @@ async function workspaceCommand(args2) {
|
|
|
14479
14583
|
return;
|
|
14480
14584
|
}
|
|
14481
14585
|
if (sub === "clear") {
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14586
|
+
for (const name of markerNamesForTask(taskId)) {
|
|
14587
|
+
try {
|
|
14588
|
+
rmSync5(join31(WORKSPACE_CHOICE_DIR, name), { force: true });
|
|
14589
|
+
} catch {
|
|
14590
|
+
}
|
|
14485
14591
|
}
|
|
14486
14592
|
console.log(`Cleared the workspace choice for ${taskId}.`);
|
|
14487
14593
|
return;
|
|
@@ -14544,6 +14650,7 @@ function buildSpawnAgent(opts) {
|
|
|
14544
14650
|
// and its own history scrolls — without it only the last screen is
|
|
14545
14651
|
// reachable once the session is nested inside the layout.
|
|
14546
14652
|
["tmux", "set-option", "-t", session, "mouse", "on"],
|
|
14653
|
+
...buildClipboardBindings(session),
|
|
14547
14654
|
["tmux", "set-option", "-t", session, "-q", "@synkro_harness", opts.harness],
|
|
14548
14655
|
["tmux", "set-option", "-t", session, "-q", "@synkro_space", opts.space],
|
|
14549
14656
|
["tmux", "set-option", "-t", session, "-q", "@synkro_backend", opts.backend],
|
|
@@ -14552,6 +14659,13 @@ function buildSpawnAgent(opts) {
|
|
|
14552
14659
|
["tmux", "set-option", "-t", session, "remain-on-exit", "on"]
|
|
14553
14660
|
];
|
|
14554
14661
|
}
|
|
14662
|
+
function buildClipboardBindings(session) {
|
|
14663
|
+
return [
|
|
14664
|
+
["tmux", "set-option", "-t", session, "set-clipboard", "on"],
|
|
14665
|
+
["tmux", "bind-key", "-T", "copy-mode", "MouseDragEnd1Pane", "send-keys", "-X", "copy-pipe-and-cancel", SYSTEM_CLIPBOARD],
|
|
14666
|
+
["tmux", "bind-key", "-T", "copy-mode-vi", "MouseDragEnd1Pane", "send-keys", "-X", "copy-pipe-and-cancel", SYSTEM_CLIPBOARD]
|
|
14667
|
+
];
|
|
14668
|
+
}
|
|
14555
14669
|
function buildListAgents() {
|
|
14556
14670
|
return ["tmux", "list-sessions", "-F", ["#{session_name}", "#{?pane_dead,dead,alive}", "#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}", "#{@synkro_pueue}", "#{pane_current_path}"].join(FIELD_SEP)];
|
|
14557
14671
|
}
|
|
@@ -14595,6 +14709,9 @@ function buildSendText(session, text) {
|
|
|
14595
14709
|
function buildEnableMouse(session) {
|
|
14596
14710
|
return ["tmux", "set-option", "-t", session, "mouse", "on"];
|
|
14597
14711
|
}
|
|
14712
|
+
function buildKeepPaneAfterExit(pane) {
|
|
14713
|
+
return ["tmux", "set-option", "-p", "-t", pane, "remain-on-exit", "on"];
|
|
14714
|
+
}
|
|
14598
14715
|
function buildInterrupt(session) {
|
|
14599
14716
|
return ["tmux", "send-keys", "-t", session, "Escape"];
|
|
14600
14717
|
}
|
|
@@ -14661,7 +14778,7 @@ function parseWorktrees(porcelain) {
|
|
|
14661
14778
|
}
|
|
14662
14779
|
return rows;
|
|
14663
14780
|
}
|
|
14664
|
-
var execFileAsync, AGENT_PREFIX, UI_SESSION, CONTAINER_USER, FIELD_SEP;
|
|
14781
|
+
var execFileAsync, AGENT_PREFIX, UI_SESSION, CONTAINER_USER, SYSTEM_CLIPBOARD, FIELD_SEP;
|
|
14665
14782
|
var init_tmux = __esm({
|
|
14666
14783
|
"cli/ui/tmux.ts"() {
|
|
14667
14784
|
"use strict";
|
|
@@ -14669,6 +14786,7 @@ var init_tmux = __esm({
|
|
|
14669
14786
|
AGENT_PREFIX = "synkro-agent-";
|
|
14670
14787
|
UI_SESSION = "synkro-ui";
|
|
14671
14788
|
CONTAINER_USER = "synkro";
|
|
14789
|
+
SYSTEM_CLIPBOARD = "if command -v pbcopy >/dev/null 2>&1; then pbcopy; elif command -v wl-copy >/dev/null 2>&1; then wl-copy; elif command -v xclip >/dev/null 2>&1; then xclip -selection clipboard; else cat >/dev/null; fi";
|
|
14672
14790
|
FIELD_SEP = "|";
|
|
14673
14791
|
}
|
|
14674
14792
|
});
|
|
@@ -14801,6 +14919,10 @@ function visibleAgents(all, space, filter, grouped) {
|
|
|
14801
14919
|
if (grouped || filter === "all" || !space) return all;
|
|
14802
14920
|
return all.filter((agent) => agent.repo === space.path);
|
|
14803
14921
|
}
|
|
14922
|
+
function nextLiveAgent(all, preferredRepo = "") {
|
|
14923
|
+
const live = all.filter((agent) => agent.status !== "offline" && agent.status !== "done");
|
|
14924
|
+
return live.find((agent) => Boolean(preferredRepo) && agent.repo === preferredRepo) || live[0];
|
|
14925
|
+
}
|
|
14804
14926
|
async function discoverAgents(runner, backend, memory) {
|
|
14805
14927
|
const snapshot = await run(runner, buildAgentSnapshot());
|
|
14806
14928
|
const { list, captures } = parseAgentSnapshot(snapshot.ok ? snapshot.stdout : "");
|
|
@@ -15039,6 +15161,10 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
|
|
|
15039
15161
|
// focus it, click rows/chips. Without this the fixed split reads as
|
|
15040
15162
|
// "blocked in".
|
|
15041
15163
|
["set-option", "-t", UI_SESSION, "mouse", "on"],
|
|
15164
|
+
// Scrollback deep enough to hold a real working session. tmux defaults to
|
|
15165
|
+
// 2000 lines, which a long harness session blows through — the start of the
|
|
15166
|
+
// conversation would silently fall off the top with no way back to it.
|
|
15167
|
+
["set-option", "-t", UI_SESSION, "history-limit", "50000"],
|
|
15042
15168
|
["set-option", "-t", UI_SESSION, "status-position", "top"],
|
|
15043
15169
|
["set-option", "-t", UI_SESSION, "status-style", "bg=colour233,fg=colour245"],
|
|
15044
15170
|
["set-option", "-t", UI_SESSION, "status-format[0]", chipStrip(sidebarWidth)],
|
|
@@ -15062,8 +15188,10 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
|
|
|
15062
15188
|
["bind-key", "-n", "M-s", "select-pane", "-L"]
|
|
15063
15189
|
];
|
|
15064
15190
|
for (const argv of style) await run(HOST, ["tmux", ...argv]);
|
|
15191
|
+
for (const argv of buildClipboardBindings(UI_SESSION)) await run(HOST, argv);
|
|
15065
15192
|
}
|
|
15066
15193
|
async function buildTab(bootPath, repoCwd, spec) {
|
|
15194
|
+
await run(HOST, ["tmux", "set-option", "-g", "history-limit", "50000"]);
|
|
15067
15195
|
let windowTarget;
|
|
15068
15196
|
if (!await uiSessionExists()) {
|
|
15069
15197
|
const cols = String(Number(process.stdout.columns || 0) || 220);
|
|
@@ -15103,6 +15231,7 @@ async function buildTab(bootPath, repoCwd, spec) {
|
|
|
15103
15231
|
const sidebarPane = split.stdout.trim();
|
|
15104
15232
|
const panes = await run(HOST, ["tmux", "list-panes", "-t", windowTarget, "-F", "#{pane_id}"]);
|
|
15105
15233
|
const centerPane = panes.stdout.split("\n").map((line) => line.trim()).filter(Boolean).find((id) => id !== sidebarPane) || "";
|
|
15234
|
+
await run(HOST, buildKeepPaneAfterExit(centerPane));
|
|
15106
15235
|
if (spec.agentSession) {
|
|
15107
15236
|
await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_agent", spec.agentSession]);
|
|
15108
15237
|
}
|
|
@@ -15132,11 +15261,29 @@ async function uiSessionExists() {
|
|
|
15132
15261
|
const result = await run(HOST, ["tmux", "has-session", "-t", UI_SESSION]);
|
|
15133
15262
|
return result.ok;
|
|
15134
15263
|
}
|
|
15264
|
+
function parseCollapsedClients(listOutput) {
|
|
15265
|
+
return String(listOutput || "").split("\n").map((line) => line.trim().split("|")).filter(([name, width, height]) => Boolean(name) && Number.isFinite(Number(width)) && Number.isFinite(Number(height)) && (Number(width) < 20 || Number(height) < 5)).map(([name]) => name);
|
|
15266
|
+
}
|
|
15267
|
+
async function pruneCollapsedClients() {
|
|
15268
|
+
const listed = await run(HOST, [
|
|
15269
|
+
"tmux",
|
|
15270
|
+
"list-clients",
|
|
15271
|
+
"-t",
|
|
15272
|
+
UI_SESSION,
|
|
15273
|
+
"-F",
|
|
15274
|
+
"#{client_name}|#{client_width}|#{client_height}"
|
|
15275
|
+
]);
|
|
15276
|
+
if (!listed.ok) return 0;
|
|
15277
|
+
const collapsed = parseCollapsedClients(listed.stdout);
|
|
15278
|
+
for (const name of collapsed) await run(HOST, ["tmux", "detach-client", "-t", name]);
|
|
15279
|
+
return collapsed.length;
|
|
15280
|
+
}
|
|
15135
15281
|
async function launchUi(bootPath, repoCwd) {
|
|
15136
15282
|
rememberSpace(repoCwd);
|
|
15137
15283
|
if (!await uiSessionExists()) {
|
|
15138
15284
|
await buildTab(bootPath, repoCwd, { cwd: repoCwd, center: makeTerminalCommand(bootPath), focus: "sidebar" });
|
|
15139
15285
|
}
|
|
15286
|
+
await pruneCollapsedClients();
|
|
15140
15287
|
return runInherit(process.env.TMUX ? ["tmux", "switch-client", "-t", UI_SESSION] : ["tmux", "attach-session", "-t", UI_SESSION]);
|
|
15141
15288
|
}
|
|
15142
15289
|
var HOST, TAB_GLYPHS;
|
|
@@ -15464,6 +15611,9 @@ async function detectContainerBackend() {
|
|
|
15464
15611
|
}
|
|
15465
15612
|
return { runner: { kind: "host" }, backend: "host", note: "runtime: host (container unavailable)" };
|
|
15466
15613
|
}
|
|
15614
|
+
function harnessCommand(request) {
|
|
15615
|
+
return request.resume ? resumeCommand(request.harness) : HARNESS_COMMANDS[request.harness] || "claude";
|
|
15616
|
+
}
|
|
15467
15617
|
async function provisionContainerWorkspace(runner, slug) {
|
|
15468
15618
|
const dir = CONTAINER_WORK + "/ui-" + slug;
|
|
15469
15619
|
await run(runner, [
|
|
@@ -15481,7 +15631,7 @@ async function spawnAgent(info, request) {
|
|
|
15481
15631
|
if (request.backend === "container") {
|
|
15482
15632
|
cwd = request.cwd.startsWith(CONTAINER_WORK) ? request.cwd : await provisionContainerWorkspace(runner, slug);
|
|
15483
15633
|
}
|
|
15484
|
-
const command =
|
|
15634
|
+
const command = harnessCommand(request);
|
|
15485
15635
|
for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend })) {
|
|
15486
15636
|
const result = await run(runner, argv);
|
|
15487
15637
|
if (!result.ok && argv[1] === "new-session") {
|
|
@@ -15772,6 +15922,24 @@ async function runSidebar() {
|
|
|
15772
15922
|
const agent = state.agents[state.agentIndex];
|
|
15773
15923
|
if (agent) await showAgent(agent);
|
|
15774
15924
|
}
|
|
15925
|
+
async function showWorkspaceTerminal(cwd) {
|
|
15926
|
+
if (!centerPane || !cwd) return;
|
|
15927
|
+
const command = makeTerminalCommand(bootPath);
|
|
15928
|
+
const argv = ["tmux", "respawn-pane", "-k", "-t", centerPane, "-c", cwd];
|
|
15929
|
+
if (command) argv.push(command);
|
|
15930
|
+
const respawned = await run(host, argv);
|
|
15931
|
+
if (!respawned.ok) {
|
|
15932
|
+
state.message = "could not restore workspace terminal";
|
|
15933
|
+
return;
|
|
15934
|
+
}
|
|
15935
|
+
const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
|
|
15936
|
+
await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-u", "@synkro_agent"]);
|
|
15937
|
+
await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_cwd", cwd]);
|
|
15938
|
+
await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_kind", "terminal"]);
|
|
15939
|
+
await run(host, ["tmux", "rename-window", "-t", centerPane, tabTitle("terminal", spaceName)]);
|
|
15940
|
+
state.viewing = "";
|
|
15941
|
+
await snapshotTabs();
|
|
15942
|
+
}
|
|
15775
15943
|
async function showAgent(agent) {
|
|
15776
15944
|
if (!centerPane) return;
|
|
15777
15945
|
if (agent.status === "offline") {
|
|
@@ -15790,6 +15958,9 @@ async function runSidebar() {
|
|
|
15790
15958
|
return;
|
|
15791
15959
|
}
|
|
15792
15960
|
await run(runnerFor(agent.backend), buildEnableMouse(agent.session));
|
|
15961
|
+
for (const argv of buildClipboardBindings(agent.session)) {
|
|
15962
|
+
await run(runnerFor(agent.backend), argv);
|
|
15963
|
+
}
|
|
15793
15964
|
const command = buildCenterAttachCommand(runnerFor(agent.backend), agent.session);
|
|
15794
15965
|
await run(host, ["tmux", "respawn-pane", "-k", "-t", centerPane, command]);
|
|
15795
15966
|
const where = agent.space ? agent.space.split("/").filter(Boolean).pop() || "" : "";
|
|
@@ -15802,7 +15973,15 @@ async function runSidebar() {
|
|
|
15802
15973
|
async function confirmKillAgent() {
|
|
15803
15974
|
const agent = state.agents[state.agentIndex];
|
|
15804
15975
|
if (!agent) return;
|
|
15976
|
+
const wasViewing = state.viewing === agent.session;
|
|
15805
15977
|
await openDialog("kill", [agent.backend, agent.session]);
|
|
15978
|
+
await refresh();
|
|
15979
|
+
if (allAgents.some((candidate) => candidate.session === agent.session && candidate.status !== "offline")) return;
|
|
15980
|
+
state.message = "closed " + agent.name;
|
|
15981
|
+
if (!wasViewing) return;
|
|
15982
|
+
await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
|
|
15983
|
+
const successor = nextLiveAgent(allAgents, agent.repo || "");
|
|
15984
|
+
if (successor) await showAgent(successor);
|
|
15806
15985
|
}
|
|
15807
15986
|
async function consent(action) {
|
|
15808
15987
|
const agent = state.agents[state.agentIndex];
|
|
@@ -15825,6 +16004,7 @@ async function runSidebar() {
|
|
|
15825
16004
|
const remembered = space ? lastAgentFor(space.path) : "";
|
|
15826
16005
|
const agent = allAgents.find((row2) => row2.session === remembered && row2.status !== "offline");
|
|
15827
16006
|
if (agent) await showAgent(agent);
|
|
16007
|
+
else if (space) await showWorkspaceTerminal(space.path);
|
|
15828
16008
|
} else if (target.kind === "agent") {
|
|
15829
16009
|
state.section = "agents";
|
|
15830
16010
|
state.agentIndex = target.index;
|
|
@@ -16026,6 +16206,9 @@ var init_repos = __esm({
|
|
|
16026
16206
|
|
|
16027
16207
|
// cli/ui/tabs.ts
|
|
16028
16208
|
import { execSync as execSync7 } from "child_process";
|
|
16209
|
+
function isLegacyCodexRenderer(command) {
|
|
16210
|
+
return /(?:^|\s)ui\s+--run\s+codex(?:\s|$)/.test(String(command || "").trim());
|
|
16211
|
+
}
|
|
16029
16212
|
function repoRoot() {
|
|
16030
16213
|
try {
|
|
16031
16214
|
return execSync7("git rev-parse --show-toplevel", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim() || process.cwd();
|
|
@@ -16046,16 +16229,6 @@ async function createTab(bootPath, kind, spacePath) {
|
|
|
16046
16229
|
});
|
|
16047
16230
|
return;
|
|
16048
16231
|
}
|
|
16049
|
-
if (kind === "cursor-synkro") {
|
|
16050
|
-
await buildTab(bootPath, repo, {
|
|
16051
|
-
cwd: spacePath,
|
|
16052
|
-
center: ["node", bootPath, "ui", "--run", "cursor", spacePath].map(shellQuote3).join(" "),
|
|
16053
|
-
title: tabTitle("cursor", space),
|
|
16054
|
-
kind: "cursor-synkro",
|
|
16055
|
-
focus: "center"
|
|
16056
|
-
});
|
|
16057
|
-
return;
|
|
16058
|
-
}
|
|
16059
16232
|
if (kind === "settings") {
|
|
16060
16233
|
await buildTab(bootPath, repo, {
|
|
16061
16234
|
cwd: spacePath,
|
|
@@ -16066,12 +16239,13 @@ async function createTab(bootPath, kind, spacePath) {
|
|
|
16066
16239
|
});
|
|
16067
16240
|
return;
|
|
16068
16241
|
}
|
|
16242
|
+
if (!["claude", "codex", "cursor"].includes(kind)) return;
|
|
16069
16243
|
const info = await detectContainerBackend();
|
|
16070
16244
|
const spaceName = space;
|
|
16071
16245
|
const stamp = String(process.pid % 1e4);
|
|
16072
|
-
const harness =
|
|
16246
|
+
const harness = kind;
|
|
16073
16247
|
const reachable = info.backend === "container" && (await run(info.runner, ["test", "-d", spacePath])).ok;
|
|
16074
|
-
const backend = reachable ? "container" : "host";
|
|
16248
|
+
const backend = harness === "codex" ? "host" : reachable ? "container" : "host";
|
|
16075
16249
|
const spawned = await spawnAgent(info, {
|
|
16076
16250
|
name: spaceName + "-" + harness + "-" + stamp,
|
|
16077
16251
|
harness,
|
|
@@ -16102,16 +16276,34 @@ async function openAgentTab(bootPath, session) {
|
|
|
16102
16276
|
]);
|
|
16103
16277
|
const [harness, space, backend] = (meta.stdout.trim() || "||").split("|");
|
|
16104
16278
|
const containerHosted = backend === "container";
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
if (!alive) return;
|
|
16279
|
+
let runner = containerHosted ? info.runner : HOST2;
|
|
16280
|
+
let activeSession = session;
|
|
16108
16281
|
const spaceName = (space || "").split("/").filter(Boolean).pop() || "space";
|
|
16282
|
+
if (harness === "codex") {
|
|
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;
|
|
16300
|
+
if (!alive) return;
|
|
16109
16301
|
await buildTab(bootPath, repoRoot(), {
|
|
16110
16302
|
cwd: space || repoRoot(),
|
|
16111
|
-
center: buildCenterAttachCommand(runner,
|
|
16303
|
+
center: buildCenterAttachCommand(runner, activeSession),
|
|
16112
16304
|
title: tabTitle(harness || "claude", spaceName),
|
|
16113
16305
|
kind: harness || "claude",
|
|
16114
|
-
agentSession:
|
|
16306
|
+
agentSession: activeSession,
|
|
16115
16307
|
focus: "center"
|
|
16116
16308
|
});
|
|
16117
16309
|
}
|
|
@@ -16127,11 +16319,23 @@ async function restoreTabs(bootPath, repoCwd) {
|
|
|
16127
16319
|
const space = tab.cwd.split("/").filter(Boolean).pop() || "space";
|
|
16128
16320
|
const containerHosted = info.backend === "container" && (await run(info.runner, ["test", "-d", tab.cwd])).ok;
|
|
16129
16321
|
const harness = ["claude", "codex", "cursor"].includes(tab.kind) ? tab.kind : "";
|
|
16130
|
-
|
|
16131
|
-
|
|
16322
|
+
const sessionRunner = harness === "codex" ? HOST2 : containerHosted ? info.runner : HOST2;
|
|
16323
|
+
let sessionAlive = Boolean(tab.agentSession && alive.has(tab.agentSession));
|
|
16324
|
+
if (sessionAlive && harness === "codex") {
|
|
16325
|
+
const command = await run(HOST2, ["tmux", "display-message", "-p", "-t", tab.agentSession, "#{pane_start_command}"]);
|
|
16326
|
+
if (command.ok && isLegacyCodexRenderer(command.stdout)) {
|
|
16327
|
+
await run(HOST2, buildKillSession(tab.agentSession));
|
|
16328
|
+
sessionAlive = false;
|
|
16329
|
+
}
|
|
16330
|
+
}
|
|
16331
|
+
if (tab.agentSession && sessionAlive) {
|
|
16332
|
+
await run(sessionRunner, buildEnableMouse(tab.agentSession));
|
|
16333
|
+
for (const argv of buildClipboardBindings(tab.agentSession)) {
|
|
16334
|
+
await run(sessionRunner, argv);
|
|
16335
|
+
}
|
|
16132
16336
|
await buildTab(bootPath, repoCwd, {
|
|
16133
16337
|
cwd: tab.cwd,
|
|
16134
|
-
center: buildCenterAttachCommand(
|
|
16338
|
+
center: buildCenterAttachCommand(sessionRunner, tab.agentSession),
|
|
16135
16339
|
title: tab.title,
|
|
16136
16340
|
kind: tab.kind,
|
|
16137
16341
|
agentSession: tab.agentSession,
|
|
@@ -16143,12 +16347,13 @@ async function restoreTabs(bootPath, repoCwd) {
|
|
|
16143
16347
|
harness,
|
|
16144
16348
|
spaceName: space,
|
|
16145
16349
|
cwd: tab.cwd,
|
|
16146
|
-
backend: containerHosted ? "container" : "host",
|
|
16350
|
+
backend: harness === "codex" ? "host" : containerHosted ? "container" : "host",
|
|
16147
16351
|
resume: true
|
|
16148
16352
|
});
|
|
16353
|
+
const restoredInContainer = harness !== "codex" && containerHosted;
|
|
16149
16354
|
await buildTab(bootPath, repoCwd, spawned.ok ? {
|
|
16150
16355
|
cwd: tab.cwd,
|
|
16151
|
-
center: buildCenterAttachCommand(
|
|
16356
|
+
center: buildCenterAttachCommand(restoredInContainer ? info.runner : HOST2, spawned.session),
|
|
16152
16357
|
title: tabTitle(harness, space),
|
|
16153
16358
|
kind: harness,
|
|
16154
16359
|
agentSession: spawned.session,
|
|
@@ -16241,7 +16446,7 @@ async function pick(opts) {
|
|
|
16241
16446
|
const perItem = opts.choices.some((choice) => choice.detail) ? 2 : 1;
|
|
16242
16447
|
const visibleItems = Math.max(1, Math.floor(Math.max(2, height - 5) / perItem));
|
|
16243
16448
|
const firstRow = 4;
|
|
16244
|
-
return new Promise((
|
|
16449
|
+
return new Promise((resolve8) => {
|
|
16245
16450
|
const view = () => opts.choices.filter((choice) => matches(choice, filter));
|
|
16246
16451
|
const draw = () => {
|
|
16247
16452
|
const shown = view();
|
|
@@ -16289,7 +16494,7 @@ async function pick(opts) {
|
|
|
16289
16494
|
const hit = shown.findIndex((_, index) => y >= rowOfItem(index) && y < rowOfItem(index) + perItem);
|
|
16290
16495
|
if (hit >= 0) {
|
|
16291
16496
|
process.stdin.off("data", onData);
|
|
16292
|
-
|
|
16497
|
+
resolve8(shown[hit].value);
|
|
16293
16498
|
return;
|
|
16294
16499
|
}
|
|
16295
16500
|
}
|
|
@@ -16297,13 +16502,13 @@ async function pick(opts) {
|
|
|
16297
16502
|
if (!sawMouse) {
|
|
16298
16503
|
if (input === KEY_ESC && !input.includes("[<") || input === KEY_CTRL_C2) {
|
|
16299
16504
|
process.stdin.off("data", onData);
|
|
16300
|
-
|
|
16505
|
+
resolve8(null);
|
|
16301
16506
|
return;
|
|
16302
16507
|
}
|
|
16303
16508
|
if (input === "\r") {
|
|
16304
16509
|
if (shown.length === 0) return;
|
|
16305
16510
|
process.stdin.off("data", onData);
|
|
16306
|
-
|
|
16511
|
+
resolve8(shown[selected].value);
|
|
16307
16512
|
return;
|
|
16308
16513
|
}
|
|
16309
16514
|
const down = input === CSI2 + "B" || opts.menu && (input === "j" || input === CSI2 + "C");
|
|
@@ -16327,7 +16532,7 @@ async function readLine(opts) {
|
|
|
16327
16532
|
let value = "";
|
|
16328
16533
|
let error = "";
|
|
16329
16534
|
const width = Math.max(20, Number(process.stdout.columns || 80));
|
|
16330
|
-
return new Promise((
|
|
16535
|
+
return new Promise((resolve8) => {
|
|
16331
16536
|
const draw = () => {
|
|
16332
16537
|
const lines = [
|
|
16333
16538
|
" " + S.title + opts.title + S.reset,
|
|
@@ -16346,7 +16551,7 @@ async function readLine(opts) {
|
|
|
16346
16551
|
const input = chunk.toString("utf8");
|
|
16347
16552
|
if (input === KEY_ESC || input === KEY_CTRL_C2) {
|
|
16348
16553
|
process.stdin.off("data", onData);
|
|
16349
|
-
|
|
16554
|
+
resolve8(null);
|
|
16350
16555
|
return;
|
|
16351
16556
|
}
|
|
16352
16557
|
if (input === "\r") {
|
|
@@ -16358,7 +16563,7 @@ async function readLine(opts) {
|
|
|
16358
16563
|
return;
|
|
16359
16564
|
}
|
|
16360
16565
|
process.stdin.off("data", onData);
|
|
16361
|
-
|
|
16566
|
+
resolve8(value.trim());
|
|
16362
16567
|
})();
|
|
16363
16568
|
return;
|
|
16364
16569
|
}
|
|
@@ -16625,9 +16830,6 @@ async function runDialog(kind, repoCwd, argA = "", argB = "") {
|
|
|
16625
16830
|
label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
|
|
16626
16831
|
}))
|
|
16627
16832
|
];
|
|
16628
|
-
if (harnesses.includes("cursor")) {
|
|
16629
|
-
sessions.push({ value: "cursor-synkro", label: TAB_GLYPHS.cursor + " Cursor in Synkro UX" });
|
|
16630
|
-
}
|
|
16631
16833
|
for (; ; ) {
|
|
16632
16834
|
const session = await pick({
|
|
16633
16835
|
menu: true,
|
|
@@ -16686,21 +16888,418 @@ var init_dialog = __esm({
|
|
|
16686
16888
|
}
|
|
16687
16889
|
});
|
|
16688
16890
|
|
|
16891
|
+
// cli/harness/render.ts
|
|
16892
|
+
function spinnerFrame(tick) {
|
|
16893
|
+
return SPINNER[Math.abs(tick) % SPINNER.length];
|
|
16894
|
+
}
|
|
16895
|
+
function terminalText(text) {
|
|
16896
|
+
return String(text).replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
|
|
16897
|
+
}
|
|
16898
|
+
function clip3(text, max) {
|
|
16899
|
+
const value = String(text || "").replace(/\s+/g, " ").trim();
|
|
16900
|
+
if (max <= 1) return value;
|
|
16901
|
+
return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
|
|
16902
|
+
}
|
|
16903
|
+
function seconds(ms) {
|
|
16904
|
+
const total = Math.max(0, Math.round(ms / 1e3));
|
|
16905
|
+
if (total < 60) return total + "s";
|
|
16906
|
+
return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
|
|
16907
|
+
}
|
|
16908
|
+
function statusLine(opts) {
|
|
16909
|
+
const hint = opts.hint ? " \xB7 " + opts.hint : "";
|
|
16910
|
+
const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
|
|
16911
|
+
const width = Math.max(20, (opts.width || 100) - 4);
|
|
16912
|
+
return S2.think + " " + spinnerFrame(opts.tick) + " " + clip3(body, width) + S2.reset;
|
|
16913
|
+
}
|
|
16914
|
+
function renderEvent(event, width = 100) {
|
|
16915
|
+
const body = Math.max(30, width - 4);
|
|
16916
|
+
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
|
+
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
|
+
];
|
|
16926
|
+
case "user-message":
|
|
16927
|
+
return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset, ""];
|
|
16928
|
+
// Live state, not transcript. The runner promotes these to the status line.
|
|
16929
|
+
case "thinking":
|
|
16930
|
+
return [];
|
|
16931
|
+
case "assistant-message": {
|
|
16932
|
+
const lines = wrap(event.text, body - 2);
|
|
16933
|
+
return [
|
|
16934
|
+
"",
|
|
16935
|
+
...lines.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset),
|
|
16936
|
+
""
|
|
16937
|
+
];
|
|
16938
|
+
}
|
|
16939
|
+
case "tool-start": {
|
|
16940
|
+
const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
|
|
16941
|
+
return [
|
|
16942
|
+
S2.tool + " " + BULLET + " " + label + S2.reset + S2.dim + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
|
|
16943
|
+
];
|
|
16944
|
+
}
|
|
16945
|
+
case "tool-end": {
|
|
16946
|
+
if (event.blocked) {
|
|
16947
|
+
return [
|
|
16948
|
+
S2.blocked + " " + BULLET + " Blocked" + S2.reset + S2.dim + " " + clip3(terminalText(event.target), body - 14) + S2.reset,
|
|
16949
|
+
...wrap(terminalText(event.reason), body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset)
|
|
16950
|
+
];
|
|
16951
|
+
}
|
|
16952
|
+
const detail = event.ok ? event.output.trim() ? clip3(terminalText(event.output), body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
|
|
16953
|
+
const tint = event.ok ? S2.ok : S2.blocked;
|
|
16954
|
+
return [tint + " " + ELBOW + " " + S2.reset + S2.dim + detail + S2.reset];
|
|
16955
|
+
}
|
|
16956
|
+
// A clean finish needs no announcement: the prompt returning IS the signal.
|
|
16957
|
+
case "turn-end":
|
|
16958
|
+
return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
|
|
16959
|
+
case "notice":
|
|
16960
|
+
return [S2.dim + " " + clip3(event.text, body) + S2.reset];
|
|
16961
|
+
default:
|
|
16962
|
+
return [];
|
|
16963
|
+
}
|
|
16964
|
+
}
|
|
16965
|
+
function wrap(text, width) {
|
|
16966
|
+
const words = String(text || "").replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
|
|
16967
|
+
if (words.length === 0) return [];
|
|
16968
|
+
const lines = [];
|
|
16969
|
+
let line = "";
|
|
16970
|
+
for (const word of words) {
|
|
16971
|
+
if (!line) line = word;
|
|
16972
|
+
else if ((line + " " + word).length <= width) line += " " + word;
|
|
16973
|
+
else {
|
|
16974
|
+
lines.push(line);
|
|
16975
|
+
line = word;
|
|
16976
|
+
}
|
|
16977
|
+
}
|
|
16978
|
+
if (line) lines.push(line);
|
|
16979
|
+
return lines;
|
|
16980
|
+
}
|
|
16981
|
+
var ESC2, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
|
|
16982
|
+
var init_render2 = __esm({
|
|
16983
|
+
"cli/harness/render.ts"() {
|
|
16984
|
+
"use strict";
|
|
16985
|
+
ESC2 = "\x1B[";
|
|
16986
|
+
S2 = {
|
|
16987
|
+
reset: ESC2 + "0m",
|
|
16988
|
+
dim: ESC2 + "2m",
|
|
16989
|
+
bold: ESC2 + "1m",
|
|
16990
|
+
user: ESC2 + "38;5;111m",
|
|
16991
|
+
agent: ESC2 + "38;5;252m",
|
|
16992
|
+
think: ESC2 + "38;5;244m",
|
|
16993
|
+
tool: ESC2 + "38;5;180m",
|
|
16994
|
+
ok: ESC2 + "38;5;114m",
|
|
16995
|
+
blocked: ESC2 + "38;5;203m",
|
|
16996
|
+
rule: ESC2 + "38;5;211m"
|
|
16997
|
+
};
|
|
16998
|
+
CLEAR_LINE = "\r" + ESC2 + "2K";
|
|
16999
|
+
BULLET = "\u23FA";
|
|
17000
|
+
ELBOW = "\u23BF";
|
|
17001
|
+
SPINNER = ["\xB7", "\u2722", "\u2733", "\u2217", "\u273B", "\u273D"];
|
|
17002
|
+
TOOL_LABEL = {
|
|
17003
|
+
shell: "Shell",
|
|
17004
|
+
read: "Read",
|
|
17005
|
+
edit: "Edit",
|
|
17006
|
+
write: "Write",
|
|
17007
|
+
delete: "Delete",
|
|
17008
|
+
search: "Search",
|
|
17009
|
+
list: "List",
|
|
17010
|
+
todo: "Todo",
|
|
17011
|
+
other: "Tool"
|
|
17012
|
+
};
|
|
17013
|
+
}
|
|
17014
|
+
});
|
|
17015
|
+
|
|
17016
|
+
// cli/harness/composer.ts
|
|
17017
|
+
import { execFileSync as execFileSync5, spawnSync as spawnSync12 } from "child_process";
|
|
17018
|
+
import { existsSync as existsSync38, mkdtempSync as mkdtempSync2, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync27 } from "fs";
|
|
17019
|
+
import { homedir as homedir38, tmpdir } from "os";
|
|
17020
|
+
import { basename as basename3, dirname as dirname12, extname, isAbsolute as isAbsolute2, join as join37, resolve as resolve5, sep as sep3 } from "path";
|
|
17021
|
+
import { createInterface as createInterface5 } from "readline";
|
|
17022
|
+
function markerCarryLength(input, marker) {
|
|
17023
|
+
for (let length = Math.min(input.length, marker.length - 1); length > 0; length -= 1) {
|
|
17024
|
+
if (marker.startsWith(input.slice(-length))) return length;
|
|
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);
|
|
17032
|
+
}
|
|
17033
|
+
value = value.replace(/\\([\\ "'()])/g, "$1");
|
|
17034
|
+
if (value === "~") return homedir38();
|
|
17035
|
+
if (value.startsWith("~/")) return join37(homedir38(), value.slice(2));
|
|
17036
|
+
return value;
|
|
17037
|
+
}
|
|
17038
|
+
function imagePathsInText(text, cwd) {
|
|
17039
|
+
const candidates = String(text || "").match(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|(?:\\.|[^\s])+/g) || [];
|
|
17040
|
+
const images = [];
|
|
17041
|
+
for (const candidate of candidates) {
|
|
17042
|
+
const token = normalizePathToken(candidate);
|
|
17043
|
+
if (!IMAGE_EXTENSIONS.has(extname(token).toLowerCase())) continue;
|
|
17044
|
+
const path = isAbsolute2(token) ? token : resolve5(cwd, token);
|
|
17045
|
+
try {
|
|
17046
|
+
if (statSync5(path).isFile() && !images.includes(path)) images.push(path);
|
|
17047
|
+
} catch {
|
|
17048
|
+
}
|
|
17049
|
+
}
|
|
17050
|
+
return images;
|
|
17051
|
+
}
|
|
17052
|
+
function codexUserInput(draft) {
|
|
17053
|
+
const input = [];
|
|
17054
|
+
if (draft.text.trim()) input.push({ type: "text", text: draft.text, text_elements: [] });
|
|
17055
|
+
for (const path of draft.images) input.push({ type: "localImage", path });
|
|
17056
|
+
return input;
|
|
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 "";
|
|
17067
|
+
}
|
|
17068
|
+
}
|
|
17069
|
+
function macClipboardPng(target) {
|
|
17070
|
+
const script = [
|
|
17071
|
+
"on run argv",
|
|
17072
|
+
"set outputPath to item 1 of argv",
|
|
17073
|
+
"set imageData to the clipboard as \xABclass PNGf\xBB",
|
|
17074
|
+
"set outputFile to open for access POSIX file outputPath with write permission",
|
|
17075
|
+
"set eof outputFile to 0",
|
|
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;
|
|
17093
|
+
}
|
|
17094
|
+
return false;
|
|
17095
|
+
}
|
|
17096
|
+
function clipboardImage() {
|
|
17097
|
+
const directory = mkdtempSync2(join37(tmpdir(), "synkro-paste-"));
|
|
17098
|
+
const target = join37(directory, "clipboard.png");
|
|
17099
|
+
try {
|
|
17100
|
+
const copied = process.platform === "darwin" ? macClipboardPng(target) : process.platform === "linux" && linuxClipboardPng(target);
|
|
17101
|
+
if (copied && statSync5(target).size > 0) return target;
|
|
17102
|
+
} catch {
|
|
17103
|
+
}
|
|
17104
|
+
rmSync7(directory, { recursive: true, force: true });
|
|
17105
|
+
return "";
|
|
17106
|
+
}
|
|
17107
|
+
function cleanupPromptDraft(draft) {
|
|
17108
|
+
for (const path of draft.temporaryImages) {
|
|
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
|
+
}
|
|
17117
|
+
}
|
|
17118
|
+
function fallbackPrompt(cwd) {
|
|
17119
|
+
return new Promise((resolveDraft) => {
|
|
17120
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
17121
|
+
let settled = false;
|
|
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
|
+
});
|
|
17134
|
+
}
|
|
17135
|
+
function readPrompt(cwd) {
|
|
17136
|
+
const input = process.stdin;
|
|
17137
|
+
const output = process.stdout;
|
|
17138
|
+
if (!input.isTTY || !output.isTTY || !input.setRawMode) return fallbackPrompt(cwd);
|
|
17139
|
+
return new Promise((resolveDraft) => {
|
|
17140
|
+
let text = "";
|
|
17141
|
+
let cursor = 0;
|
|
17142
|
+
let stream = "";
|
|
17143
|
+
let pasting = false;
|
|
17144
|
+
let settled = false;
|
|
17145
|
+
const images = [];
|
|
17146
|
+
const temporaryImages = [];
|
|
17147
|
+
const wasRaw = Boolean(input.isRaw);
|
|
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;
|
|
17176
|
+
}
|
|
17177
|
+
insert(clipboardText());
|
|
17178
|
+
};
|
|
17179
|
+
const processStream = () => {
|
|
17180
|
+
while (stream && !settled) {
|
|
17181
|
+
if (pasting) {
|
|
17182
|
+
const end = stream.indexOf(PASTE_END);
|
|
17183
|
+
if (end < 0) {
|
|
17184
|
+
const keep = markerCarryLength(stream, PASTE_END);
|
|
17185
|
+
insert(stream.slice(0, stream.length - keep));
|
|
17186
|
+
stream = stream.slice(stream.length - keep);
|
|
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;
|
|
17223
|
+
}
|
|
17224
|
+
if (stream.startsWith("\x1B") && stream.length < 3) break;
|
|
17225
|
+
const char = stream[0];
|
|
17226
|
+
stream = stream.slice(1);
|
|
17227
|
+
if (char === "\r" || char === "\n") {
|
|
17228
|
+
const found = imagePathsInText(text, cwd);
|
|
17229
|
+
for (const path of found) if (!images.includes(path)) images.push(path);
|
|
17230
|
+
finish({ text, images, temporaryImages });
|
|
17231
|
+
} else if (char === "" || char === "" && !text && images.length === 0) {
|
|
17232
|
+
finish(null);
|
|
17233
|
+
} else if (char === "\x7F" || char === "\b") {
|
|
17234
|
+
if (cursor > 0) {
|
|
17235
|
+
text = text.slice(0, cursor - 1) + text.slice(cursor);
|
|
17236
|
+
cursor -= 1;
|
|
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);
|
|
17249
|
+
}
|
|
17250
|
+
}
|
|
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
|
+
};
|
|
17260
|
+
input.setRawMode(true);
|
|
17261
|
+
input.resume();
|
|
17262
|
+
input.on("data", onData);
|
|
17263
|
+
output.write("\x1B[?2004h" + prompt);
|
|
17264
|
+
});
|
|
17265
|
+
}
|
|
17266
|
+
var PASTE_START, PASTE_END, IMAGE_EXTENSIONS;
|
|
17267
|
+
var init_composer = __esm({
|
|
17268
|
+
"cli/harness/composer.ts"() {
|
|
17269
|
+
"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
|
+
}
|
|
17275
|
+
});
|
|
17276
|
+
|
|
16689
17277
|
// cli/harness/events.ts
|
|
17278
|
+
function fixPollId(raw) {
|
|
17279
|
+
const text = String(raw || "");
|
|
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
|
+
}
|
|
16690
17285
|
function blockReason(raw) {
|
|
16691
|
-
const text =
|
|
17286
|
+
const text = cleanGuardText(raw);
|
|
16692
17287
|
if (!text) return "blocked by policy";
|
|
17288
|
+
const guardAt = text.lastIndexOf("Guard:");
|
|
17289
|
+
if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
|
|
16693
17290
|
const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
|
|
16694
17291
|
if (afterTag) return afterTag[1].trim();
|
|
16695
17292
|
const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
|
|
16696
17293
|
if (afterHook) return afterHook[1].trim();
|
|
16697
17294
|
return text;
|
|
16698
17295
|
}
|
|
16699
|
-
var BLOCK_MARKER;
|
|
17296
|
+
var BLOCK_MARKER, FIX_POLL_MARKER, LEGACY_FIX_POLL_MARKER;
|
|
16700
17297
|
var init_events = __esm({
|
|
16701
17298
|
"cli/harness/events.ts"() {
|
|
16702
17299
|
"use strict";
|
|
16703
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;
|
|
16704
17303
|
}
|
|
16705
17304
|
});
|
|
16706
17305
|
|
|
@@ -16781,6 +17380,7 @@ function parseCursorLine(line) {
|
|
|
16781
17380
|
ok: Boolean(success) && Number(success?.exitCode ?? 0) === 0,
|
|
16782
17381
|
blocked,
|
|
16783
17382
|
reason: rejected ? blocked ? blockReason(rawReason) : rawReason || "rejected" : "",
|
|
17383
|
+
pollId: blocked ? fixPollId(rawReason) : "",
|
|
16784
17384
|
exitCode: success ? Number(success.exitCode ?? 0) : null,
|
|
16785
17385
|
output: String(success?.stdout || success?.stderr || "")
|
|
16786
17386
|
}];
|
|
@@ -16805,6 +17405,15 @@ function parseCursorLine(line) {
|
|
|
16805
17405
|
}
|
|
16806
17406
|
return [];
|
|
16807
17407
|
}
|
|
17408
|
+
function stderrNotice(raw) {
|
|
17409
|
+
const text = String(raw || "").trim();
|
|
17410
|
+
if (!text) return "";
|
|
17411
|
+
if (/ActionRequiredError/i.test(text) && /usage limit/i.test(text)) {
|
|
17412
|
+
return "Cursor usage limit reached \u2014 replies still land, but turns will keep stalling until the plan resets or is topped up";
|
|
17413
|
+
}
|
|
17414
|
+
if (/RetriableError/i.test(text)) return "";
|
|
17415
|
+
return /error|fatal/i.test(text) ? text : "";
|
|
17416
|
+
}
|
|
16808
17417
|
function feed(buffer, chunk) {
|
|
16809
17418
|
const combined = buffer + chunk;
|
|
16810
17419
|
const parts = combined.split("\n");
|
|
@@ -16843,97 +17452,6 @@ var init_cursor = __esm({
|
|
|
16843
17452
|
}
|
|
16844
17453
|
});
|
|
16845
17454
|
|
|
16846
|
-
// cli/harness/render.ts
|
|
16847
|
-
function clip3(text, max) {
|
|
16848
|
-
const value = String(text || "").replace(/\s+/g, " ").trim();
|
|
16849
|
-
return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
|
|
16850
|
-
}
|
|
16851
|
-
function renderEvent(event, width = 100) {
|
|
16852
|
-
const body = Math.max(30, width - 4);
|
|
16853
|
-
switch (event.type) {
|
|
16854
|
-
case "session-start":
|
|
16855
|
-
return [
|
|
16856
|
-
"",
|
|
16857
|
-
S2.dim + " " + event.model + " \xB7 " + clip3(event.cwd, body - 30) + (event.authSource === "login" ? " \xB7 subscription" : "") + S2.reset,
|
|
16858
|
-
""
|
|
16859
|
-
];
|
|
16860
|
-
case "user-message":
|
|
16861
|
-
return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset, ""];
|
|
16862
|
-
case "thinking":
|
|
16863
|
-
return [S2.think + " \xB7 thinking\u2026" + S2.reset];
|
|
16864
|
-
case "assistant-message":
|
|
16865
|
-
return ["", ...wrap(event.text, body).map((line) => S2.agent + " " + line + S2.reset), ""];
|
|
16866
|
-
case "tool-start": {
|
|
16867
|
-
const glyph = GLYPH[event.kind] || GLYPH.other;
|
|
16868
|
-
return [S2.tool + " " + glyph + " " + S2.reset + S2.dim + clip3(event.target, body - 6) + S2.reset];
|
|
16869
|
-
}
|
|
16870
|
-
case "tool-end": {
|
|
16871
|
-
if (event.blocked) {
|
|
16872
|
-
return [
|
|
16873
|
-
S2.blocked + " \u26D4 blocked" + S2.reset + S2.dim + " " + clip3(event.target, body - 14) + S2.reset,
|
|
16874
|
-
...wrap(event.reason, body - 6).map((line) => " " + S2.rule + line + S2.reset)
|
|
16875
|
-
];
|
|
16876
|
-
}
|
|
16877
|
-
const mark = event.ok ? S2.ok + " \u2713" : S2.blocked + " \u2717";
|
|
16878
|
-
const code = event.exitCode === null || event.exitCode === 0 ? "" : " (exit " + event.exitCode + ")";
|
|
16879
|
-
const out = event.output.trim() ? S2.dim + " " + clip3(event.output, body - 20) + S2.reset : "";
|
|
16880
|
-
return [mark + S2.reset + code + out];
|
|
16881
|
-
}
|
|
16882
|
-
case "turn-end":
|
|
16883
|
-
return ["", S2.dim + " " + (event.ok ? "done" : "ended with an error") + S2.reset, ""];
|
|
16884
|
-
case "notice":
|
|
16885
|
-
return [S2.dim + " " + clip3(event.text, body) + S2.reset];
|
|
16886
|
-
default:
|
|
16887
|
-
return [];
|
|
16888
|
-
}
|
|
16889
|
-
}
|
|
16890
|
-
function wrap(text, width) {
|
|
16891
|
-
const words = String(text || "").replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
|
|
16892
|
-
if (words.length === 0) return [];
|
|
16893
|
-
const lines = [];
|
|
16894
|
-
let line = "";
|
|
16895
|
-
for (const word of words) {
|
|
16896
|
-
if (!line) line = word;
|
|
16897
|
-
else if ((line + " " + word).length <= width) line += " " + word;
|
|
16898
|
-
else {
|
|
16899
|
-
lines.push(line);
|
|
16900
|
-
line = word;
|
|
16901
|
-
}
|
|
16902
|
-
}
|
|
16903
|
-
if (line) lines.push(line);
|
|
16904
|
-
return lines;
|
|
16905
|
-
}
|
|
16906
|
-
var ESC2, S2, GLYPH;
|
|
16907
|
-
var init_render2 = __esm({
|
|
16908
|
-
"cli/harness/render.ts"() {
|
|
16909
|
-
"use strict";
|
|
16910
|
-
ESC2 = "\x1B[";
|
|
16911
|
-
S2 = {
|
|
16912
|
-
reset: ESC2 + "0m",
|
|
16913
|
-
dim: ESC2 + "2m",
|
|
16914
|
-
bold: ESC2 + "1m",
|
|
16915
|
-
user: ESC2 + "38;5;111m",
|
|
16916
|
-
agent: ESC2 + "38;5;252m",
|
|
16917
|
-
think: ESC2 + "38;5;244m",
|
|
16918
|
-
tool: ESC2 + "38;5;180m",
|
|
16919
|
-
ok: ESC2 + "38;5;114m",
|
|
16920
|
-
blocked: ESC2 + "38;5;203m",
|
|
16921
|
-
rule: ESC2 + "38;5;211m"
|
|
16922
|
-
};
|
|
16923
|
-
GLYPH = {
|
|
16924
|
-
shell: "\u276F",
|
|
16925
|
-
read: "\u25C4",
|
|
16926
|
-
edit: "\u270E",
|
|
16927
|
-
write: "\u270E",
|
|
16928
|
-
delete: "\u2716",
|
|
16929
|
-
search: "\u2315",
|
|
16930
|
-
list: "\u2630",
|
|
16931
|
-
todo: "\u2611",
|
|
16932
|
-
other: "\u2022"
|
|
16933
|
-
};
|
|
16934
|
-
}
|
|
16935
|
-
});
|
|
16936
|
-
|
|
16937
17455
|
// cli/harness/run.ts
|
|
16938
17456
|
import { spawn as spawn9 } from "child_process";
|
|
16939
17457
|
function replayKey(event) {
|
|
@@ -16952,33 +17470,149 @@ function replayKey(event) {
|
|
|
16952
17470
|
}
|
|
16953
17471
|
function createTurnSink(opts) {
|
|
16954
17472
|
const events = [];
|
|
17473
|
+
const now = opts.now || (() => Date.now());
|
|
17474
|
+
const showPrompt = opts.showPrompt !== false;
|
|
16955
17475
|
let blocked = 0;
|
|
16956
|
-
let lastWasThinking = false;
|
|
16957
17476
|
let replaying = false;
|
|
16958
17477
|
const seen = /* @__PURE__ */ new Set();
|
|
16959
17478
|
let sawTurnEnd = false;
|
|
16960
17479
|
let sawAnswer = false;
|
|
16961
17480
|
let buffer = "";
|
|
17481
|
+
let reconnecting = false;
|
|
17482
|
+
let lastWasAnswer = false;
|
|
17483
|
+
const toolStartedAt = /* @__PURE__ */ new Map();
|
|
17484
|
+
let statusText = "";
|
|
17485
|
+
let statusKind = "progress";
|
|
17486
|
+
let statusShown = false;
|
|
17487
|
+
let tick = 0;
|
|
17488
|
+
const startedAt = now();
|
|
17489
|
+
let timer = null;
|
|
17490
|
+
const drawStatus = () => {
|
|
17491
|
+
if (!opts.animate || !statusText) return;
|
|
17492
|
+
opts.write(statusLine({
|
|
17493
|
+
tick,
|
|
17494
|
+
text: statusText,
|
|
17495
|
+
elapsedMs: now() - startedAt,
|
|
17496
|
+
width: opts.width,
|
|
17497
|
+
hint: "ctrl-c to interrupt"
|
|
17498
|
+
}));
|
|
17499
|
+
statusShown = true;
|
|
17500
|
+
};
|
|
17501
|
+
const clearStatus = () => {
|
|
17502
|
+
if (!statusShown) return;
|
|
17503
|
+
opts.write(CLEAR_LINE);
|
|
17504
|
+
statusShown = false;
|
|
17505
|
+
};
|
|
17506
|
+
const setStatus = (text, kind = "progress") => {
|
|
17507
|
+
statusText = text;
|
|
17508
|
+
statusKind = kind;
|
|
17509
|
+
if (!opts.animate) return;
|
|
17510
|
+
if (!timer) {
|
|
17511
|
+
timer = setInterval(() => {
|
|
17512
|
+
tick += 1;
|
|
17513
|
+
clearStatus();
|
|
17514
|
+
drawStatus();
|
|
17515
|
+
}, TICK_MS);
|
|
17516
|
+
timer.unref?.();
|
|
17517
|
+
}
|
|
17518
|
+
clearStatus();
|
|
17519
|
+
drawStatus();
|
|
17520
|
+
};
|
|
17521
|
+
const stopStatus = () => {
|
|
17522
|
+
statusText = "";
|
|
17523
|
+
statusKind = "progress";
|
|
17524
|
+
if (timer) {
|
|
17525
|
+
clearInterval(timer);
|
|
17526
|
+
timer = null;
|
|
17527
|
+
}
|
|
17528
|
+
clearStatus();
|
|
17529
|
+
};
|
|
17530
|
+
let gaveUp = false;
|
|
17531
|
+
let stall = null;
|
|
17532
|
+
const graceMs = opts.postAnswerGraceMs ?? 15e3;
|
|
17533
|
+
const disarmStall = () => {
|
|
17534
|
+
if (stall) {
|
|
17535
|
+
clearTimeout(stall);
|
|
17536
|
+
stall = null;
|
|
17537
|
+
}
|
|
17538
|
+
};
|
|
17539
|
+
const giveUp = () => {
|
|
17540
|
+
if (gaveUp) return;
|
|
17541
|
+
gaveUp = true;
|
|
17542
|
+
disarmStall();
|
|
17543
|
+
opts.onGiveUp?.();
|
|
17544
|
+
};
|
|
17545
|
+
const armStall = () => {
|
|
17546
|
+
disarmStall();
|
|
17547
|
+
stall = setTimeout(giveUp, graceMs);
|
|
17548
|
+
stall.unref?.();
|
|
17549
|
+
};
|
|
17550
|
+
const settle = () => {
|
|
17551
|
+
reconnecting = false;
|
|
17552
|
+
if (statusKind === "retry") setStatus("Thinking");
|
|
17553
|
+
};
|
|
16962
17554
|
const emit2 = (event) => {
|
|
16963
17555
|
events.push(event);
|
|
16964
17556
|
if (event.type === "tool-end" && event.blocked) blocked += 1;
|
|
16965
17557
|
opts.onEvent?.(event);
|
|
17558
|
+
if (event.type === "notice" && event.kind === "retry") {
|
|
17559
|
+
reconnecting = true;
|
|
17560
|
+
if (sawAnswer && lastWasAnswer) {
|
|
17561
|
+
setStatus("finishing up", "retry");
|
|
17562
|
+
armStall();
|
|
17563
|
+
} else {
|
|
17564
|
+
setStatus(event.text, "retry");
|
|
17565
|
+
}
|
|
17566
|
+
return;
|
|
17567
|
+
}
|
|
16966
17568
|
if (event.type === "thinking") {
|
|
16967
|
-
if (
|
|
16968
|
-
|
|
16969
|
-
}
|
|
16970
|
-
|
|
17569
|
+
if (!reconnecting) setStatus("Thinking");
|
|
17570
|
+
return;
|
|
17571
|
+
}
|
|
17572
|
+
if (event.type === "user-message" && !showPrompt) return;
|
|
17573
|
+
if (event.type === "session-start" && opts.showHeader === false) return;
|
|
17574
|
+
if (event.type === "assistant-message" || event.type === "tool-end") settle();
|
|
17575
|
+
if (event.type === "tool-start") {
|
|
17576
|
+
reconnecting = false;
|
|
17577
|
+
setStatus("Running " + event.kind);
|
|
16971
17578
|
}
|
|
17579
|
+
if (event.type === "turn-end") stopStatus();
|
|
16972
17580
|
const rendered = renderEvent(event, opts.width);
|
|
16973
|
-
if (rendered.length)
|
|
17581
|
+
if (!rendered.length) return;
|
|
17582
|
+
clearStatus();
|
|
17583
|
+
opts.write(rendered.join("\n") + "\n");
|
|
17584
|
+
drawStatus();
|
|
16974
17585
|
};
|
|
16975
|
-
const take = (
|
|
16976
|
-
|
|
17586
|
+
const take = (incoming) => {
|
|
17587
|
+
let event = incoming;
|
|
17588
|
+
if (event.type === "tool-start" && event.id && !toolStartedAt.has(event.id)) {
|
|
17589
|
+
toolStartedAt.set(event.id, now());
|
|
17590
|
+
} else if (event.type === "tool-end") {
|
|
17591
|
+
const started = toolStartedAt.get(event.id);
|
|
17592
|
+
if ((event.durationMs === null || event.durationMs === void 0) && started !== void 0) {
|
|
17593
|
+
event = { ...event, durationMs: Math.max(0, now() - started) };
|
|
17594
|
+
}
|
|
17595
|
+
toolStartedAt.delete(event.id);
|
|
17596
|
+
}
|
|
17597
|
+
const isRetry = event.type === "notice" && event.kind === "retry";
|
|
17598
|
+
if (!isRetry) disarmStall();
|
|
17599
|
+
if (isRetry) {
|
|
17600
|
+
if (reconnecting && sawAnswer && lastWasAnswer) {
|
|
17601
|
+
giveUp();
|
|
17602
|
+
return;
|
|
17603
|
+
}
|
|
17604
|
+
replaying = true;
|
|
17605
|
+
}
|
|
16977
17606
|
const key = replayKey(event);
|
|
16978
17607
|
if (key) {
|
|
16979
|
-
if (replaying && seen.has(key))
|
|
17608
|
+
if (replaying && seen.has(key)) {
|
|
17609
|
+
lastWasAnswer = event.type === "assistant-message";
|
|
17610
|
+
settle();
|
|
17611
|
+
return;
|
|
17612
|
+
}
|
|
16980
17613
|
seen.add(key);
|
|
16981
17614
|
}
|
|
17615
|
+
if (key || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
|
|
16982
17616
|
if (event.type === "turn-end") sawTurnEnd = true;
|
|
16983
17617
|
if (event.type === "assistant-message") sawAnswer = true;
|
|
16984
17618
|
emit2(event);
|
|
@@ -16992,57 +17626,626 @@ function createTurnSink(opts) {
|
|
|
16992
17626
|
notice(text) {
|
|
16993
17627
|
emit2({ type: "notice", text });
|
|
16994
17628
|
},
|
|
16995
|
-
|
|
17629
|
+
event(event) {
|
|
17630
|
+
take(event);
|
|
17631
|
+
},
|
|
17632
|
+
finish(exit, interrupted = false) {
|
|
17633
|
+
disarmStall();
|
|
17634
|
+
stopStatus();
|
|
16996
17635
|
if (!sawTurnEnd) {
|
|
16997
|
-
if (
|
|
16998
|
-
emit2({
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17636
|
+
if (interrupted) {
|
|
17637
|
+
emit2({ type: "notice", text: "interrupted" });
|
|
17638
|
+
emit2({ type: "turn-end", ok: true, text: "" });
|
|
17639
|
+
} else {
|
|
17640
|
+
if (exit !== 0 && !sawAnswer) {
|
|
17641
|
+
emit2({ type: "notice", text: "cursor-agent exited " + exit + " without completing the turn" });
|
|
17642
|
+
}
|
|
17643
|
+
emit2({ type: "turn-end", ok: sawAnswer, text: "" });
|
|
17002
17644
|
}
|
|
17003
|
-
emit2({ type: "turn-end", ok: sawAnswer, text: "" });
|
|
17004
17645
|
}
|
|
17005
|
-
|
|
17646
|
+
stopStatus();
|
|
17647
|
+
return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit };
|
|
17006
17648
|
}
|
|
17007
17649
|
};
|
|
17008
17650
|
}
|
|
17009
17651
|
async function runCursorTurn(opts) {
|
|
17010
17652
|
const write2 = opts.write || ((text) => process.stdout.write(text));
|
|
17011
17653
|
const width = opts.width || Number(process.stdout.columns || 100);
|
|
17012
|
-
|
|
17013
|
-
|
|
17654
|
+
let stopHarness = () => {
|
|
17655
|
+
};
|
|
17656
|
+
const sink = createTurnSink({
|
|
17657
|
+
write: write2,
|
|
17658
|
+
onEvent: opts.onEvent,
|
|
17659
|
+
width,
|
|
17660
|
+
animate: Boolean(process.stdout.isTTY),
|
|
17661
|
+
showPrompt: opts.showPrompt,
|
|
17662
|
+
showHeader: opts.showHeader,
|
|
17663
|
+
onGiveUp: () => stopHarness()
|
|
17664
|
+
});
|
|
17665
|
+
return new Promise((resolve8) => {
|
|
17014
17666
|
const child = spawn9("cursor-agent", cursorArgs(opts.prompt), {
|
|
17015
17667
|
cwd: opts.cwd,
|
|
17016
17668
|
stdio: ["ignore", "pipe", "pipe"]
|
|
17017
17669
|
});
|
|
17670
|
+
stopHarness = () => {
|
|
17671
|
+
child.kill();
|
|
17672
|
+
const hard = setTimeout(() => child.kill("SIGKILL"), 3e3);
|
|
17673
|
+
hard.unref?.();
|
|
17674
|
+
child.once("close", () => clearTimeout(hard));
|
|
17675
|
+
};
|
|
17676
|
+
let interrupted = false;
|
|
17677
|
+
const onAbort = () => {
|
|
17678
|
+
interrupted = true;
|
|
17679
|
+
stopHarness();
|
|
17680
|
+
};
|
|
17681
|
+
if (opts.signal?.aborted) onAbort();
|
|
17682
|
+
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
17018
17683
|
child.stdout.on("data", (chunk) => sink.chunk(chunk.toString("utf8")));
|
|
17684
|
+
let lastNote = "";
|
|
17019
17685
|
child.stderr.on("data", (chunk) => {
|
|
17020
|
-
const
|
|
17021
|
-
if (
|
|
17686
|
+
const note = stderrNotice(chunk.toString("utf8"));
|
|
17687
|
+
if (note && note !== lastNote) {
|
|
17688
|
+
lastNote = note;
|
|
17689
|
+
sink.notice(note);
|
|
17690
|
+
}
|
|
17691
|
+
});
|
|
17692
|
+
child.on("close", (code) => {
|
|
17693
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
17694
|
+
resolve8(sink.finish(code ?? 0, interrupted));
|
|
17022
17695
|
});
|
|
17023
|
-
child.on("close", (code) => resolve7(sink.finish(code ?? 0)));
|
|
17024
17696
|
child.on("error", (error) => {
|
|
17025
17697
|
sink.notice("failed to start cursor-agent: " + String(error));
|
|
17026
|
-
|
|
17698
|
+
resolve8(sink.finish(1, interrupted));
|
|
17027
17699
|
});
|
|
17028
17700
|
});
|
|
17029
17701
|
}
|
|
17702
|
+
var TICK_MS;
|
|
17030
17703
|
var init_run = __esm({
|
|
17031
17704
|
"cli/harness/run.ts"() {
|
|
17032
17705
|
"use strict";
|
|
17033
17706
|
init_cursor();
|
|
17034
17707
|
init_render2();
|
|
17708
|
+
TICK_MS = 120;
|
|
17709
|
+
}
|
|
17710
|
+
});
|
|
17711
|
+
|
|
17712
|
+
// cli/harness/codex.ts
|
|
17713
|
+
import { spawn as spawn10 } from "child_process";
|
|
17714
|
+
function itemTarget(item) {
|
|
17715
|
+
if (item?.type === "commandExecution") {
|
|
17716
|
+
return { kind: "shell", target: String(item.command || ""), description: "" };
|
|
17717
|
+
}
|
|
17718
|
+
if (item?.type === "fileChange") {
|
|
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: "" };
|
|
17729
|
+
}
|
|
17730
|
+
function itemOutput(item) {
|
|
17731
|
+
if (item?.type === "commandExecution") return String(item.aggregatedOutput || "");
|
|
17732
|
+
if (item?.type === "mcpToolCall") return item.error ? String(item.error?.message || JSON.stringify(item.error)) : item.result ? JSON.stringify(item.result) : "";
|
|
17733
|
+
if (item?.type === "dynamicToolCall") return item.contentItems ? JSON.stringify(item.contentItems) : "";
|
|
17734
|
+
return "";
|
|
17735
|
+
}
|
|
17736
|
+
function parseCodexNotification(method, params, startedAt = /* @__PURE__ */ new Map(), approvalReasons = /* @__PURE__ */ new Map()) {
|
|
17737
|
+
if (method === "hook/started" || method === "hook/completed") {
|
|
17738
|
+
const run2 = params?.run || {};
|
|
17739
|
+
if (run2.eventName !== "preToolUse") return [];
|
|
17740
|
+
const asMs = (value) => {
|
|
17741
|
+
const number = Number(value || 0);
|
|
17742
|
+
return number > 0 && number < 1e11 ? number * 1e3 : number;
|
|
17743
|
+
};
|
|
17744
|
+
const started = asMs(run2.startedAt);
|
|
17745
|
+
const priorStart = Number(approvalReasons.get("__pretool_started__") || 0);
|
|
17746
|
+
if (started && (!priorStart || started < priorStart)) approvalReasons.set("__pretool_started__", String(started));
|
|
17747
|
+
if (method === "hook/completed") {
|
|
17748
|
+
const completed = asMs(run2.completedAt) || started + Number(run2.durationMs || 0);
|
|
17749
|
+
const priorCompleted = Number(approvalReasons.get("__pretool_completed__") || 0);
|
|
17750
|
+
if (completed > priorCompleted) approvalReasons.set("__pretool_completed__", String(completed));
|
|
17751
|
+
const entries = Array.isArray(run2.entries) ? run2.entries : [];
|
|
17752
|
+
const pollEntry = entries.find((entry) => fixPollId(String(entry?.text || "")));
|
|
17753
|
+
const blockingEntry = entries.find((entry) => entry?.kind === "stop" || entry?.kind === "error");
|
|
17754
|
+
const text = String(pollEntry?.text || blockingEntry?.text || run2.statusMessage || "");
|
|
17755
|
+
if (text && (run2.status === "blocked" || run2.status === "failed" || BLOCK_MARKER.test(text))) {
|
|
17756
|
+
approvalReasons.set("__latest_hook__", text);
|
|
17757
|
+
approvalReasons.set("__latest_hook_id__", String(run2.id || "synkro-policy-block"));
|
|
17758
|
+
approvalReasons.set("__latest_hook_duration__", String(Number(run2.durationMs || Math.max(0, completed - started))));
|
|
17759
|
+
}
|
|
17760
|
+
}
|
|
17761
|
+
return [];
|
|
17762
|
+
}
|
|
17763
|
+
if (method === "item/started") {
|
|
17764
|
+
const item = params?.item;
|
|
17765
|
+
if (!item?.id) return [];
|
|
17766
|
+
startedAt.set(String(item.id), Number(params.startedAtMs || Date.now()));
|
|
17767
|
+
if (!["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall"].includes(item.type)) return [];
|
|
17768
|
+
return [{ type: "tool-start", id: String(item.id), ...itemTarget(item) }];
|
|
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");
|
|
17797
|
+
const status = String(item.status || "");
|
|
17798
|
+
const blocked = BLOCK_MARKER.test(rawReason) || /blocked by PreToolUse hook/i.test(rawReason) || Boolean(hookReason) && (status === "failed" || status === "declined");
|
|
17799
|
+
const itemStart = startedAt.get(String(item.id));
|
|
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__");
|
|
17811
|
+
return [{
|
|
17812
|
+
type: "tool-end",
|
|
17813
|
+
id: String(item.id),
|
|
17814
|
+
...itemTarget(item),
|
|
17815
|
+
ok: status === "completed" && Number(item.exitCode ?? 0) === 0,
|
|
17816
|
+
blocked,
|
|
17817
|
+
reason: blocked ? blockReason(rawReason) : "",
|
|
17818
|
+
pollId: blocked ? fixPollId(rawReason) : "",
|
|
17819
|
+
exitCode: item.exitCode === null || item.exitCode === void 0 ? null : Number(item.exitCode),
|
|
17820
|
+
output: blocked ? "" : output,
|
|
17821
|
+
durationMs
|
|
17822
|
+
}];
|
|
17823
|
+
}
|
|
17824
|
+
if (method === "turn/completed") {
|
|
17825
|
+
const status = String(params?.turn?.status || "failed");
|
|
17826
|
+
const hookReason = approvalReasons.get("__latest_hook__") || "";
|
|
17827
|
+
const events = [];
|
|
17828
|
+
if (hookReason) {
|
|
17829
|
+
events.push({
|
|
17830
|
+
type: "tool-end",
|
|
17831
|
+
id: approvalReasons.get("__latest_hook_id__") || "synkro-policy-block",
|
|
17832
|
+
kind: "other",
|
|
17833
|
+
target: "policy check",
|
|
17834
|
+
ok: false,
|
|
17835
|
+
blocked: true,
|
|
17836
|
+
reason: blockReason(hookReason),
|
|
17837
|
+
pollId: fixPollId(hookReason),
|
|
17838
|
+
exitCode: null,
|
|
17839
|
+
output: "",
|
|
17840
|
+
durationMs: Number(approvalReasons.get("__latest_hook_duration__") || 0)
|
|
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;
|
|
17850
|
+
}
|
|
17851
|
+
return [];
|
|
17852
|
+
}
|
|
17853
|
+
function codexStderrNotice(raw, initialized) {
|
|
17854
|
+
const text = String(raw || "").trim();
|
|
17855
|
+
if (!text || initialized) return "";
|
|
17856
|
+
return /fatal|panic|authentication failed|not logged in/i.test(text) ? text.split("\n")[0].slice(0, 300) : "";
|
|
17857
|
+
}
|
|
17858
|
+
async function askApproval(text) {
|
|
17859
|
+
const input = process.stdin;
|
|
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();
|
|
17882
|
+
}
|
|
17883
|
+
}
|
|
17884
|
+
var CodexAppSession;
|
|
17885
|
+
var init_codex = __esm({
|
|
17886
|
+
"cli/harness/codex.ts"() {
|
|
17887
|
+
"use strict";
|
|
17888
|
+
init_composer();
|
|
17889
|
+
init_events();
|
|
17890
|
+
init_run();
|
|
17891
|
+
init_render2();
|
|
17892
|
+
CodexAppSession = class {
|
|
17893
|
+
child = null;
|
|
17894
|
+
buffer = "";
|
|
17895
|
+
nextId = 1;
|
|
17896
|
+
pending = /* @__PURE__ */ new Map();
|
|
17897
|
+
initialized = false;
|
|
17898
|
+
threadId = "";
|
|
17899
|
+
model = "Codex";
|
|
17900
|
+
cwd = "";
|
|
17901
|
+
active = null;
|
|
17902
|
+
send(message) {
|
|
17903
|
+
this.child?.stdin.write(JSON.stringify({ jsonrpc: "2.0", ...message }) + "\n");
|
|
17904
|
+
}
|
|
17905
|
+
request(method, params) {
|
|
17906
|
+
const id = this.nextId++;
|
|
17907
|
+
return new Promise((resolve8, reject) => {
|
|
17908
|
+
this.pending.set(id, { resolve: resolve8, reject });
|
|
17909
|
+
this.send({ id, method, params });
|
|
17910
|
+
});
|
|
17911
|
+
}
|
|
17912
|
+
respond(id, result) {
|
|
17913
|
+
this.send({ id, result });
|
|
17914
|
+
}
|
|
17915
|
+
async handleServerRequest(message) {
|
|
17916
|
+
const method = String(message.method || "");
|
|
17917
|
+
const params = message.params || {};
|
|
17918
|
+
if (params.itemId && params.reason) this.active?.approvalReasons.set(String(params.itemId), String(params.reason));
|
|
17919
|
+
if (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval") {
|
|
17920
|
+
const summary = String(params.reason || params.command || params.grantRoot || "Allow this action?");
|
|
17921
|
+
const accepted = await askApproval(summary);
|
|
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 {
|
|
17942
|
+
return;
|
|
17943
|
+
}
|
|
17944
|
+
if (message.method && message.id !== void 0) {
|
|
17945
|
+
void this.handleServerRequest(message);
|
|
17946
|
+
return;
|
|
17947
|
+
}
|
|
17948
|
+
if (message.method) {
|
|
17949
|
+
const active = this.active;
|
|
17950
|
+
if (!active) return;
|
|
17951
|
+
const events = parseCodexNotification(message.method, message.params, active.startedAt, active.approvalReasons);
|
|
17952
|
+
for (const event of events) active.sink.event(event);
|
|
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;
|
|
17960
|
+
}
|
|
17961
|
+
if (message.id !== void 0) {
|
|
17962
|
+
const pending = this.pending.get(Number(message.id));
|
|
17963
|
+
if (!pending) return;
|
|
17964
|
+
this.pending.delete(Number(message.id));
|
|
17965
|
+
if (message.error) pending.reject(new Error(String(message.error?.message || JSON.stringify(message.error))));
|
|
17966
|
+
else pending.resolve(message.result);
|
|
17967
|
+
}
|
|
17968
|
+
}
|
|
17969
|
+
async start(cwd) {
|
|
17970
|
+
if (this.child) return;
|
|
17971
|
+
this.cwd = cwd;
|
|
17972
|
+
this.child = spawn10("codex", ["app-server"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
17973
|
+
this.child.stdout.on("data", (chunk) => {
|
|
17974
|
+
this.buffer += chunk.toString("utf8");
|
|
17975
|
+
let newline = this.buffer.indexOf("\n");
|
|
17976
|
+
while (newline !== -1) {
|
|
17977
|
+
const line = this.buffer.slice(0, newline).trim();
|
|
17978
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
17979
|
+
if (line) this.handleLine(line);
|
|
17980
|
+
newline = this.buffer.indexOf("\n");
|
|
17981
|
+
}
|
|
17982
|
+
});
|
|
17983
|
+
this.child.stderr.on("data", (chunk) => {
|
|
17984
|
+
const note = codexStderrNotice(chunk.toString("utf8"), this.initialized);
|
|
17985
|
+
if (note) this.active?.sink.notice(note);
|
|
17986
|
+
});
|
|
17987
|
+
this.child.on("close", (code) => {
|
|
17988
|
+
const active = this.active;
|
|
17989
|
+
if (active) {
|
|
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"));
|
|
17994
|
+
this.pending.clear();
|
|
17995
|
+
this.child = null;
|
|
17996
|
+
});
|
|
17997
|
+
this.child.on("error", (error) => {
|
|
17998
|
+
const failure = new Error("failed to start codex app-server: " + String(error));
|
|
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" } });
|
|
18009
|
+
this.initialized = true;
|
|
18010
|
+
this.send({ method: "initialized", params: {} });
|
|
18011
|
+
const started = await this.request("thread/start", {
|
|
18012
|
+
cwd,
|
|
18013
|
+
approvalPolicy: "on-request",
|
|
18014
|
+
approvalsReviewer: "user",
|
|
18015
|
+
sandbox: "workspace-write",
|
|
18016
|
+
ephemeral: false
|
|
18017
|
+
});
|
|
18018
|
+
this.threadId = String(started?.thread?.id || "");
|
|
18019
|
+
this.model = String(started?.model || initialized?.userAgent || "Codex");
|
|
18020
|
+
if (!this.threadId) throw new Error("codex app-server did not return a thread id");
|
|
18021
|
+
}
|
|
18022
|
+
async turn(opts) {
|
|
18023
|
+
await this.start(opts.cwd);
|
|
18024
|
+
if (this.active) throw new Error("a Codex turn is already running");
|
|
18025
|
+
const sink = createTurnSink({
|
|
18026
|
+
write: opts.write || ((text) => process.stdout.write(text)),
|
|
18027
|
+
onEvent: opts.onEvent,
|
|
18028
|
+
width: opts.width || Number(process.stdout.columns || 100),
|
|
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
|
+
});
|
|
18046
|
+
};
|
|
18047
|
+
if (opts.signal?.aborted) onAbort();
|
|
18048
|
+
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
18049
|
+
try {
|
|
18050
|
+
await this.request("turn/start", {
|
|
18051
|
+
threadId: this.threadId,
|
|
18052
|
+
cwd: opts.cwd,
|
|
18053
|
+
input: codexUserInput({ text: opts.prompt, images: opts.images || [] })
|
|
18054
|
+
});
|
|
18055
|
+
return await result;
|
|
18056
|
+
} finally {
|
|
18057
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
18058
|
+
}
|
|
18059
|
+
}
|
|
18060
|
+
close() {
|
|
18061
|
+
this.child?.kill();
|
|
18062
|
+
this.child = null;
|
|
18063
|
+
}
|
|
18064
|
+
};
|
|
18065
|
+
}
|
|
18066
|
+
});
|
|
18067
|
+
|
|
18068
|
+
// cli/harness/fixPoll.ts
|
|
18069
|
+
function pollBaseUrl() {
|
|
18070
|
+
const port = String(process.env.SYNKRO_MCP_PORT || "18931");
|
|
18071
|
+
return "http://127.0.0.1:" + port + "/api/local/fix-poll";
|
|
18072
|
+
}
|
|
18073
|
+
function neutralizeTerminalControls(value) {
|
|
18074
|
+
return value.replace(/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u001B]/g, "");
|
|
18075
|
+
}
|
|
18076
|
+
async function loadFixPoll(itemId, fetchImpl = fetch) {
|
|
18077
|
+
const response = await fetchImpl(pollBaseUrl() + "?item_id=" + encodeURIComponent(itemId));
|
|
18078
|
+
if (!response.ok) return null;
|
|
18079
|
+
const body = await response.json();
|
|
18080
|
+
const candidates = Array.isArray(body.candidates) ? body.candidates.filter((candidate) => typeof candidate === "string").slice(0, 8) : [];
|
|
18081
|
+
if (!candidates.length || body.status !== "pending") return null;
|
|
18082
|
+
return {
|
|
18083
|
+
itemId,
|
|
18084
|
+
filePath: String(body.file_path || ""),
|
|
18085
|
+
ruleId: String(body.rule_id || ""),
|
|
18086
|
+
candidates
|
|
18087
|
+
};
|
|
18088
|
+
}
|
|
18089
|
+
async function recordFixPoll(itemId, chosenIdx, fetchImpl = fetch) {
|
|
18090
|
+
const response = await fetchImpl(pollBaseUrl() + "/record", {
|
|
18091
|
+
method: "POST",
|
|
18092
|
+
headers: { "content-type": "application/json" },
|
|
18093
|
+
body: JSON.stringify({ item_id: itemId, chosen_idx: chosenIdx })
|
|
18094
|
+
});
|
|
18095
|
+
return response.ok;
|
|
18096
|
+
}
|
|
18097
|
+
function fixPollLines(poll, selected, width = 100) {
|
|
18098
|
+
const max = Math.max(32, width - 10);
|
|
18099
|
+
const choices = [...poll.candidates, "None of the above"];
|
|
18100
|
+
const scope = [poll.ruleId, poll.filePath].filter(Boolean).join(" \xB7 ");
|
|
18101
|
+
const lines = [
|
|
18102
|
+
"",
|
|
18103
|
+
S2.bold + S2.rule + " Synkro needs your decision" + S2.reset,
|
|
18104
|
+
...scope ? [S2.dim + " " + neutralizeTerminalControls(scope) + S2.reset] : [],
|
|
18105
|
+
""
|
|
18106
|
+
];
|
|
18107
|
+
choices.forEach((choice, index) => {
|
|
18108
|
+
const prefix = index === selected ? S2.user + " \u276F " : S2.dim + " ";
|
|
18109
|
+
const text = wrap(index + 1 + ". " + neutralizeTerminalControls(choice), max);
|
|
18110
|
+
lines.push(prefix + (text[0] || "") + S2.reset);
|
|
18111
|
+
for (const continuation of text.slice(1)) lines.push(" " + continuation);
|
|
18112
|
+
});
|
|
18113
|
+
lines.push("", S2.dim + " \u2191/\u2193 select \xB7 Enter confirm \xB7 1-" + choices.length + " choose" + S2.reset);
|
|
18114
|
+
return lines;
|
|
18115
|
+
}
|
|
18116
|
+
async function pickFixPoll(poll, io = {}) {
|
|
18117
|
+
const input = io.input || process.stdin;
|
|
18118
|
+
const output = io.output || process.stdout;
|
|
18119
|
+
if (!input.isTTY || !output.isTTY) return -1;
|
|
18120
|
+
let selected = 0;
|
|
18121
|
+
let rendered = 0;
|
|
18122
|
+
const draw = () => {
|
|
18123
|
+
if (rendered) output.write("\x1B[" + rendered + "A\x1B[J");
|
|
18124
|
+
const lines = fixPollLines(poll, selected, Number(output.columns || 100));
|
|
18125
|
+
output.write(lines.join("\n") + "\n");
|
|
18126
|
+
rendered = lines.length;
|
|
18127
|
+
};
|
|
18128
|
+
const choice = await new Promise((resolve8) => {
|
|
18129
|
+
const choices = poll.candidates.length + 1;
|
|
18130
|
+
const wasRaw = Boolean(input.isRaw);
|
|
18131
|
+
const done = (index) => {
|
|
18132
|
+
input.off("data", onData);
|
|
18133
|
+
if (input.setRawMode) input.setRawMode(wasRaw);
|
|
18134
|
+
resolve8(index === poll.candidates.length ? -1 : index);
|
|
18135
|
+
};
|
|
18136
|
+
const onData = (chunk) => {
|
|
18137
|
+
const key = chunk.toString("utf8");
|
|
18138
|
+
if (key === "") return done(poll.candidates.length);
|
|
18139
|
+
if (key === "\x1B" || key.toLowerCase() === "n") return done(poll.candidates.length);
|
|
18140
|
+
if (key === "\r" || key === "\n") return done(selected);
|
|
18141
|
+
if (key === "\x1B[A" || key === "k") selected = (selected - 1 + choices) % choices;
|
|
18142
|
+
else if (key === "\x1B[B" || key === "j") selected = (selected + 1) % choices;
|
|
18143
|
+
else if (/^[1-9]$/.test(key)) {
|
|
18144
|
+
const index = Number(key) - 1;
|
|
18145
|
+
if (index < choices) return done(index);
|
|
18146
|
+
} else return;
|
|
18147
|
+
draw();
|
|
18148
|
+
};
|
|
18149
|
+
if (input.setRawMode) input.setRawMode(true);
|
|
18150
|
+
input.resume();
|
|
18151
|
+
input.on("data", onData);
|
|
18152
|
+
draw();
|
|
18153
|
+
});
|
|
18154
|
+
return choice;
|
|
18155
|
+
}
|
|
18156
|
+
async function resolveFixPolls(events, io = {}) {
|
|
18157
|
+
const ids = Array.from(new Set(events.filter((event) => event.type === "tool-end").map((event) => event.pollId || "").filter(Boolean)));
|
|
18158
|
+
const recorded = [];
|
|
18159
|
+
for (const itemId of ids) {
|
|
18160
|
+
const poll = await loadFixPoll(itemId, io.fetchImpl).catch(() => null);
|
|
18161
|
+
if (!poll) continue;
|
|
18162
|
+
const chosen = await pickFixPoll(poll, io);
|
|
18163
|
+
const ok = await recordFixPoll(itemId, chosen, io.fetchImpl).catch(() => false);
|
|
18164
|
+
if (ok) recorded.push(chosen);
|
|
18165
|
+
else (io.output || process.stdout).write(S2.blocked + " Could not record that Synkro decision.\n" + S2.reset);
|
|
18166
|
+
}
|
|
18167
|
+
return recorded;
|
|
18168
|
+
}
|
|
18169
|
+
var init_fixPoll = __esm({
|
|
18170
|
+
"cli/harness/fixPoll.ts"() {
|
|
18171
|
+
"use strict";
|
|
18172
|
+
init_render2();
|
|
18173
|
+
}
|
|
18174
|
+
});
|
|
18175
|
+
|
|
18176
|
+
// cli/harness/identity.ts
|
|
18177
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
18178
|
+
import { homedir as homedir39 } from "os";
|
|
18179
|
+
function shortenPath(path, home = homedir39()) {
|
|
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() : "";
|
|
18188
|
+
}
|
|
18189
|
+
function cursorAccount() {
|
|
18190
|
+
try {
|
|
18191
|
+
const out = execFileSync6("cursor-agent", ["status"], {
|
|
18192
|
+
encoding: "utf8",
|
|
18193
|
+
timeout: 5e3,
|
|
18194
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18195
|
+
});
|
|
18196
|
+
return parseCursorAccount(out);
|
|
18197
|
+
} catch {
|
|
18198
|
+
return "";
|
|
18199
|
+
}
|
|
18200
|
+
}
|
|
18201
|
+
function synkroAccount() {
|
|
18202
|
+
let email = "";
|
|
18203
|
+
try {
|
|
18204
|
+
email = String(getUserInfo().email || "");
|
|
18205
|
+
} catch {
|
|
18206
|
+
return { email: "", needsLogin: false };
|
|
18207
|
+
}
|
|
18208
|
+
let needsLogin = false;
|
|
18209
|
+
try {
|
|
18210
|
+
needsLogin = isTokenExpired() && !loadCredentials()?.refresh_token;
|
|
18211
|
+
} catch {
|
|
18212
|
+
}
|
|
18213
|
+
return { email, needsLogin };
|
|
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
|
+
};
|
|
18222
|
+
}
|
|
18223
|
+
function identityHeader(opts) {
|
|
18224
|
+
const lines = ["", S2.bold + " " + shortenPath(opts.cwd, opts.home) + S2.reset];
|
|
18225
|
+
const label = (name, value, note = "") => S2.dim + " " + name.padEnd(7) + S2.reset + S2.think + value + S2.reset + note;
|
|
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;
|
|
18232
|
+
}
|
|
18233
|
+
var init_identity2 = __esm({
|
|
18234
|
+
"cli/harness/identity.ts"() {
|
|
18235
|
+
"use strict";
|
|
18236
|
+
init_auth();
|
|
18237
|
+
init_render2();
|
|
17035
18238
|
}
|
|
17036
18239
|
});
|
|
17037
18240
|
|
|
17038
18241
|
// cli/harness/session.ts
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
if (harness !== "cursor") {
|
|
18242
|
+
async function runOnce(harness, cwd, prompt, echoPrompt = true, showHeader = true, signal) {
|
|
18243
|
+
if (harness !== "cursor" && harness !== "codex") {
|
|
17042
18244
|
process.stdout.write(S2.dim + " " + harness + " sessions are not embedded yet\n" + S2.reset);
|
|
17043
18245
|
return 1;
|
|
17044
18246
|
}
|
|
17045
|
-
const result = await runCursorTurn({ prompt, cwd });
|
|
18247
|
+
const result = harness === "codex" ? await runCodexTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal }) : await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal });
|
|
18248
|
+
await resolveFixPolls(result.events);
|
|
17046
18249
|
if (result.blocked > 0) {
|
|
17047
18250
|
process.stdout.write(
|
|
17048
18251
|
S2.blocked + " " + result.blocked + " action" + (result.blocked === 1 ? "" : "s") + " blocked by policy" + S2.reset + "\n"
|
|
@@ -17052,31 +18255,52 @@ async function runOnce(harness, cwd, prompt) {
|
|
|
17052
18255
|
}
|
|
17053
18256
|
async function runGovernedSession(harness, cwd, prompt) {
|
|
17054
18257
|
if (prompt) return runOnce(harness, cwd, prompt);
|
|
17055
|
-
process.stdout.write(
|
|
17056
|
-
const
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
18258
|
+
process.stdout.write(identityHeader({ cwd, harness, identity: readIdentity(harness) }).join("\n") + "\n");
|
|
18259
|
+
const codex = harness === "codex" ? new CodexAppSession() : null;
|
|
18260
|
+
let turn = null;
|
|
18261
|
+
const interruptTurn = () => {
|
|
18262
|
+
turn?.abort();
|
|
18263
|
+
};
|
|
18264
|
+
process.on("SIGINT", interruptTurn);
|
|
18265
|
+
let first = true;
|
|
18266
|
+
try {
|
|
18267
|
+
for (; ; ) {
|
|
18268
|
+
const draft = await readPrompt(cwd);
|
|
18269
|
+
if (draft === null) break;
|
|
18270
|
+
const line = draft.text.trim();
|
|
18271
|
+
if (!line && draft.images.length === 0) continue;
|
|
18272
|
+
if (draft.images.length === 0 && (line === "exit" || line === "quit")) break;
|
|
18273
|
+
try {
|
|
18274
|
+
turn = new AbortController();
|
|
18275
|
+
const cursorPrompt = draft.images.length ? [draft.text, "", "Attached image files:", ...draft.images].join("\n") : draft.text;
|
|
18276
|
+
const result = codex ? await codex.turn({ prompt: draft.text, images: draft.images, cwd, showPrompt: false, showHeader: first, signal: turn.signal }) : await runCursorTurn({ prompt: cursorPrompt, cwd, showPrompt: false, showHeader: first, signal: turn.signal });
|
|
18277
|
+
await resolveFixPolls(result.events);
|
|
18278
|
+
if (result.blocked > 0) {
|
|
18279
|
+
process.stdout.write(
|
|
18280
|
+
S2.blocked + " " + result.blocked + " action" + (result.blocked === 1 ? "" : "s") + " blocked by policy" + S2.reset + "\n"
|
|
18281
|
+
);
|
|
18282
|
+
}
|
|
18283
|
+
first = false;
|
|
18284
|
+
} finally {
|
|
18285
|
+
cleanupPromptDraft(draft);
|
|
18286
|
+
turn = null;
|
|
18287
|
+
}
|
|
18288
|
+
}
|
|
18289
|
+
} finally {
|
|
18290
|
+
process.off("SIGINT", interruptTurn);
|
|
18291
|
+
codex?.close();
|
|
17063
18292
|
}
|
|
17064
|
-
rl.close();
|
|
17065
18293
|
return 0;
|
|
17066
18294
|
}
|
|
17067
|
-
var BANNER;
|
|
17068
18295
|
var init_session = __esm({
|
|
17069
18296
|
"cli/harness/session.ts"() {
|
|
17070
18297
|
"use strict";
|
|
18298
|
+
init_composer();
|
|
17071
18299
|
init_run();
|
|
18300
|
+
init_codex();
|
|
18301
|
+
init_fixPoll();
|
|
18302
|
+
init_identity2();
|
|
17072
18303
|
init_render2();
|
|
17073
|
-
BANNER = [
|
|
17074
|
-
"",
|
|
17075
|
-
S2.bold + " synkro" + S2.reset + S2.dim + " governed session" + S2.reset,
|
|
17076
|
-
S2.dim + " every tool call passes Synkro policy before it runs" + S2.reset,
|
|
17077
|
-
S2.dim + " ctrl-c to leave" + S2.reset,
|
|
17078
|
-
""
|
|
17079
|
-
];
|
|
17080
18304
|
}
|
|
17081
18305
|
});
|
|
17082
18306
|
|
|
@@ -17098,7 +18322,7 @@ async function takeover(kind, cwd) {
|
|
|
17098
18322
|
const harness = ["claude", "codex", "cursor"].includes(kind) ? kind : "claude";
|
|
17099
18323
|
const info = await detectContainerBackend();
|
|
17100
18324
|
const reachable = info.backend === "container" ? (await run(info.runner, ["test", "-d", cwd])).ok : false;
|
|
17101
|
-
const backend = reachable ? "container" : "host";
|
|
18325
|
+
const backend = harness === "codex" ? "host" : reachable ? "container" : "host";
|
|
17102
18326
|
const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
|
|
17103
18327
|
const spawned = await spawnAgent(info, {
|
|
17104
18328
|
name: spaceName + "-" + harness + "-" + String(process.pid % 1e4),
|
|
@@ -17126,7 +18350,7 @@ async function restoreSession(session) {
|
|
|
17126
18350
|
harness: record.harness,
|
|
17127
18351
|
spaceName: record.spaceName,
|
|
17128
18352
|
cwd: record.space,
|
|
17129
|
-
backend: record.backend,
|
|
18353
|
+
backend: record.harness === "codex" ? "host" : record.backend,
|
|
17130
18354
|
resume: true
|
|
17131
18355
|
});
|
|
17132
18356
|
}
|
|
@@ -17269,11 +18493,11 @@ __export(linear_exports, {
|
|
|
17269
18493
|
linearCommand: () => linearCommand
|
|
17270
18494
|
});
|
|
17271
18495
|
import { readFileSync as readFileSync33 } from "fs";
|
|
17272
|
-
import { homedir as
|
|
17273
|
-
import { join as
|
|
18496
|
+
import { homedir as homedir40 } from "os";
|
|
18497
|
+
import { join as join38 } from "path";
|
|
17274
18498
|
function mcpJwt() {
|
|
17275
18499
|
try {
|
|
17276
|
-
return readFileSync33(
|
|
18500
|
+
return readFileSync33(join38(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
|
|
17277
18501
|
} catch {
|
|
17278
18502
|
return "";
|
|
17279
18503
|
}
|
|
@@ -17312,7 +18536,7 @@ var SYNKRO_DIR14, PORT2, BASE;
|
|
|
17312
18536
|
var init_linear = __esm({
|
|
17313
18537
|
"cli/commands/linear.ts"() {
|
|
17314
18538
|
"use strict";
|
|
17315
|
-
SYNKRO_DIR14 =
|
|
18539
|
+
SYNKRO_DIR14 = join38(homedir40(), ".synkro");
|
|
17316
18540
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
17317
18541
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
17318
18542
|
}
|
|
@@ -17461,10 +18685,10 @@ var init_cveReachability = __esm({
|
|
|
17461
18685
|
});
|
|
17462
18686
|
|
|
17463
18687
|
// cli/reachability/reachabilityScan.ts
|
|
17464
|
-
import { spawnSync as
|
|
17465
|
-
import { readFileSync as readFileSync35, writeFileSync as
|
|
17466
|
-
import { join as
|
|
17467
|
-
import { homedir as
|
|
18688
|
+
import { spawnSync as spawnSync13, execFileSync as execFileSync7 } from "child_process";
|
|
18689
|
+
import { readFileSync as readFileSync35, writeFileSync as writeFileSync28, existsSync as existsSync39, readdirSync as readdirSync10 } from "fs";
|
|
18690
|
+
import { join as join39 } from "path";
|
|
18691
|
+
import { homedir as homedir41 } from "os";
|
|
17468
18692
|
import { createRequire } from "module";
|
|
17469
18693
|
function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
17470
18694
|
const SKIP2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -17481,7 +18705,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
17481
18705
|
}
|
|
17482
18706
|
for (const e of ents) {
|
|
17483
18707
|
if (files.length >= maxFiles) break;
|
|
17484
|
-
const full =
|
|
18708
|
+
const full = join39(dir, e.name);
|
|
17485
18709
|
if (e.isDirectory()) {
|
|
17486
18710
|
if (!SKIP2.has(e.name) && !e.name.startsWith(".")) stack.push(full);
|
|
17487
18711
|
continue;
|
|
@@ -17508,12 +18732,12 @@ function cleanVersion(spec) {
|
|
|
17508
18732
|
function gatherManifestVersions(repoRoot3) {
|
|
17509
18733
|
const out = {};
|
|
17510
18734
|
const dirs = [repoRoot3];
|
|
17511
|
-
const pkgsDir =
|
|
17512
|
-
if (
|
|
18735
|
+
const pkgsDir = join39(repoRoot3, "packages");
|
|
18736
|
+
if (existsSync39(pkgsDir)) {
|
|
17513
18737
|
try {
|
|
17514
18738
|
for (const d of readdirSync10(pkgsDir)) {
|
|
17515
|
-
const pd =
|
|
17516
|
-
if (
|
|
18739
|
+
const pd = join39(pkgsDir, d);
|
|
18740
|
+
if (existsSync39(join39(pd, "package.json"))) dirs.push(pd);
|
|
17517
18741
|
}
|
|
17518
18742
|
} catch {
|
|
17519
18743
|
}
|
|
@@ -17522,7 +18746,7 @@ function gatherManifestVersions(repoRoot3) {
|
|
|
17522
18746
|
for (const dir of dirs) {
|
|
17523
18747
|
let pkg;
|
|
17524
18748
|
try {
|
|
17525
|
-
pkg = JSON.parse(readFileSync35(
|
|
18749
|
+
pkg = JSON.parse(readFileSync35(join39(dir, "package.json"), "utf8"));
|
|
17526
18750
|
} catch {
|
|
17527
18751
|
continue;
|
|
17528
18752
|
}
|
|
@@ -17545,25 +18769,25 @@ function findJelly(repoRoot3) {
|
|
|
17545
18769
|
const pkg = JSON.parse(readFileSync35(pkgJson, "utf8"));
|
|
17546
18770
|
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
|
|
17547
18771
|
if (bin) {
|
|
17548
|
-
const p =
|
|
17549
|
-
if (
|
|
18772
|
+
const p = join39(dir, bin);
|
|
18773
|
+
if (existsSync39(p)) return p;
|
|
17550
18774
|
}
|
|
17551
18775
|
} catch {
|
|
17552
18776
|
}
|
|
17553
18777
|
for (const base of [repoRoot3, process.cwd()]) {
|
|
17554
|
-
const b =
|
|
17555
|
-
if (
|
|
18778
|
+
const b = join39(base, "node_modules", ".bin", "jelly");
|
|
18779
|
+
if (existsSync39(b)) return b;
|
|
17556
18780
|
}
|
|
17557
18781
|
return null;
|
|
17558
18782
|
}
|
|
17559
18783
|
function findEntries(repoRoot3) {
|
|
17560
18784
|
const dirs = [repoRoot3];
|
|
17561
|
-
const pkgsDir =
|
|
17562
|
-
if (
|
|
18785
|
+
const pkgsDir = join39(repoRoot3, "packages");
|
|
18786
|
+
if (existsSync39(pkgsDir)) {
|
|
17563
18787
|
try {
|
|
17564
18788
|
for (const d of readdirSync10(pkgsDir)) {
|
|
17565
|
-
const pd =
|
|
17566
|
-
if (
|
|
18789
|
+
const pd = join39(pkgsDir, d);
|
|
18790
|
+
if (existsSync39(join39(pd, "package.json"))) dirs.push(pd);
|
|
17567
18791
|
}
|
|
17568
18792
|
} catch {
|
|
17569
18793
|
}
|
|
@@ -17571,12 +18795,12 @@ function findEntries(repoRoot3) {
|
|
|
17571
18795
|
const entries = [];
|
|
17572
18796
|
for (const dir of dirs) {
|
|
17573
18797
|
try {
|
|
17574
|
-
const pkg = JSON.parse(readFileSync35(
|
|
18798
|
+
const pkg = JSON.parse(readFileSync35(join39(dir, "package.json"), "utf8"));
|
|
17575
18799
|
const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
|
|
17576
18800
|
for (const c of cands) {
|
|
17577
18801
|
if (typeof c !== "string") continue;
|
|
17578
|
-
const f =
|
|
17579
|
-
if (
|
|
18802
|
+
const f = join39(dir, c);
|
|
18803
|
+
if (existsSync39(f)) {
|
|
17580
18804
|
entries.push(f);
|
|
17581
18805
|
break;
|
|
17582
18806
|
}
|
|
@@ -17588,7 +18812,7 @@ function findEntries(repoRoot3) {
|
|
|
17588
18812
|
}
|
|
17589
18813
|
function currentCommit(repoRoot3) {
|
|
17590
18814
|
try {
|
|
17591
|
-
return
|
|
18815
|
+
return execFileSync7("git", ["rev-parse", "HEAD"], { cwd: repoRoot3, encoding: "utf8" }).trim();
|
|
17592
18816
|
} catch {
|
|
17593
18817
|
return "";
|
|
17594
18818
|
}
|
|
@@ -17609,7 +18833,7 @@ function parseApiUsage(log) {
|
|
|
17609
18833
|
}
|
|
17610
18834
|
function runReachabilityScan(repoRoot3, opts = {}) {
|
|
17611
18835
|
const commit = currentCommit(repoRoot3);
|
|
17612
|
-
if (!opts.force && commit &&
|
|
18836
|
+
if (!opts.force && commit && existsSync39(REACHABILITY_PATH)) {
|
|
17613
18837
|
try {
|
|
17614
18838
|
const prev = JSON.parse(readFileSync35(REACHABILITY_PATH, "utf8"));
|
|
17615
18839
|
if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
|
|
@@ -17662,7 +18886,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
|
|
|
17662
18886
|
if (jelly) {
|
|
17663
18887
|
const entries = findEntries(repoRoot3);
|
|
17664
18888
|
if (entries.length > 0) {
|
|
17665
|
-
const r =
|
|
18889
|
+
const r = spawnSync13(
|
|
17666
18890
|
process.execPath,
|
|
17667
18891
|
[jelly, "-b", repoRoot3, "--api-usage", ...entries],
|
|
17668
18892
|
{ encoding: "utf8", timeout: opts.timeoutMs ?? 18e4, maxBuffer: 2e8 }
|
|
@@ -17700,7 +18924,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
|
|
|
17700
18924
|
if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
|
|
17701
18925
|
const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot3) };
|
|
17702
18926
|
try {
|
|
17703
|
-
|
|
18927
|
+
writeFileSync28(REACHABILITY_PATH, JSON.stringify(file, null, 2));
|
|
17704
18928
|
} catch (e) {
|
|
17705
18929
|
return { ok: false, reason: "write failed: " + String(e.message || e) };
|
|
17706
18930
|
}
|
|
@@ -17712,7 +18936,7 @@ var init_reachabilityScan = __esm({
|
|
|
17712
18936
|
"use strict";
|
|
17713
18937
|
init_cveReachability();
|
|
17714
18938
|
require2 = createRequire(import.meta.url);
|
|
17715
|
-
REACHABILITY_PATH =
|
|
18939
|
+
REACHABILITY_PATH = join39(homedir41(), ".synkro", "reachability.json");
|
|
17716
18940
|
}
|
|
17717
18941
|
});
|
|
17718
18942
|
|
|
@@ -17721,13 +18945,13 @@ var reachabilityScan_exports = {};
|
|
|
17721
18945
|
__export(reachabilityScan_exports, {
|
|
17722
18946
|
reachabilityScanCommand: () => reachabilityScanCommand
|
|
17723
18947
|
});
|
|
17724
|
-
import { readFileSync as readFileSync36, existsSync as
|
|
17725
|
-
import { join as
|
|
17726
|
-
import { homedir as
|
|
17727
|
-
import { execFileSync as
|
|
18948
|
+
import { readFileSync as readFileSync36, existsSync as existsSync40 } from "fs";
|
|
18949
|
+
import { join as join40 } from "path";
|
|
18950
|
+
import { homedir as homedir42 } from "os";
|
|
18951
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
17728
18952
|
function readConfigEnv4() {
|
|
17729
|
-
const p =
|
|
17730
|
-
if (!
|
|
18953
|
+
const p = join40(SYNKRO_DIR15, "config.env");
|
|
18954
|
+
if (!existsSync40(p)) return {};
|
|
17731
18955
|
const out = {};
|
|
17732
18956
|
for (const line of readFileSync36(p, "utf-8").split("\n")) {
|
|
17733
18957
|
const t = line.trim();
|
|
@@ -17739,7 +18963,7 @@ function readConfigEnv4() {
|
|
|
17739
18963
|
}
|
|
17740
18964
|
function repoRoot2() {
|
|
17741
18965
|
try {
|
|
17742
|
-
return
|
|
18966
|
+
return execFileSync8("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
|
|
17743
18967
|
} catch {
|
|
17744
18968
|
return process.cwd();
|
|
17745
18969
|
}
|
|
@@ -17747,7 +18971,7 @@ function repoRoot2() {
|
|
|
17747
18971
|
function repoSlug(root) {
|
|
17748
18972
|
const run2 = (a) => {
|
|
17749
18973
|
try {
|
|
17750
|
-
return
|
|
18974
|
+
return execFileSync8("git", a, { encoding: "utf-8" }).trim();
|
|
17751
18975
|
} catch {
|
|
17752
18976
|
return "";
|
|
17753
18977
|
}
|
|
@@ -17761,10 +18985,10 @@ async function pushToCloud(cfg, repo) {
|
|
|
17761
18985
|
while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
|
|
17762
18986
|
let jwt2 = "";
|
|
17763
18987
|
try {
|
|
17764
|
-
jwt2 = readFileSync36(
|
|
18988
|
+
jwt2 = readFileSync36(join40(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
|
|
17765
18989
|
} catch {
|
|
17766
18990
|
}
|
|
17767
|
-
if (!jwt2 || !
|
|
18991
|
+
if (!jwt2 || !existsSync40(REACHABILITY_PATH)) return;
|
|
17768
18992
|
const body = readFileSync36(REACHABILITY_PATH, "utf-8");
|
|
17769
18993
|
try {
|
|
17770
18994
|
const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
|
|
@@ -17797,7 +19021,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
17797
19021
|
"cli/commands/reachabilityScan.ts"() {
|
|
17798
19022
|
"use strict";
|
|
17799
19023
|
init_reachabilityScan();
|
|
17800
|
-
SYNKRO_DIR15 =
|
|
19024
|
+
SYNKRO_DIR15 = join40(homedir42(), ".synkro");
|
|
17801
19025
|
}
|
|
17802
19026
|
});
|
|
17803
19027
|
|
|
@@ -17927,11 +19151,11 @@ var config_exports = {};
|
|
|
17927
19151
|
__export(config_exports, {
|
|
17928
19152
|
configCommand: () => configCommand
|
|
17929
19153
|
});
|
|
17930
|
-
import { readFileSync as readFileSync37, writeFileSync as
|
|
17931
|
-
import { join as
|
|
17932
|
-
import { homedir as
|
|
19154
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync29, existsSync as existsSync41 } from "fs";
|
|
19155
|
+
import { join as join41 } from "path";
|
|
19156
|
+
import { homedir as homedir43 } from "os";
|
|
17933
19157
|
function readConfigEnv5() {
|
|
17934
|
-
if (!
|
|
19158
|
+
if (!existsSync41(CONFIG_PATH9)) return {};
|
|
17935
19159
|
const out = {};
|
|
17936
19160
|
for (const line of readFileSync37(CONFIG_PATH9, "utf-8").split("\n")) {
|
|
17937
19161
|
const t = line.trim();
|
|
@@ -17942,7 +19166,7 @@ function readConfigEnv5() {
|
|
|
17942
19166
|
return out;
|
|
17943
19167
|
}
|
|
17944
19168
|
function updateConfigValue(key, value) {
|
|
17945
|
-
if (!
|
|
19169
|
+
if (!existsSync41(CONFIG_PATH9)) {
|
|
17946
19170
|
console.error("No config found. Run `synkro install` first.");
|
|
17947
19171
|
process.exit(1);
|
|
17948
19172
|
}
|
|
@@ -17957,7 +19181,7 @@ function updateConfigValue(key, value) {
|
|
|
17957
19181
|
return line;
|
|
17958
19182
|
});
|
|
17959
19183
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
17960
|
-
|
|
19184
|
+
writeFileSync29(CONFIG_PATH9, updated.join("\n"), "utf-8");
|
|
17961
19185
|
}
|
|
17962
19186
|
function resolveInferenceMode(cfg) {
|
|
17963
19187
|
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
@@ -18115,8 +19339,8 @@ var init_config = __esm({
|
|
|
18115
19339
|
"use strict";
|
|
18116
19340
|
init_stub();
|
|
18117
19341
|
init_optout();
|
|
18118
|
-
SYNKRO_DIR16 =
|
|
18119
|
-
CONFIG_PATH9 =
|
|
19342
|
+
SYNKRO_DIR16 = join41(homedir43(), ".synkro");
|
|
19343
|
+
CONFIG_PATH9 = join41(SYNKRO_DIR16, "config.env");
|
|
18120
19344
|
}
|
|
18121
19345
|
});
|
|
18122
19346
|
|
|
@@ -18206,12 +19430,12 @@ async function runExport(args2) {
|
|
|
18206
19430
|
}
|
|
18207
19431
|
function confirmYesNo(question) {
|
|
18208
19432
|
if (!process.stdin.isTTY) return Promise.resolve(false);
|
|
18209
|
-
return new Promise((
|
|
19433
|
+
return new Promise((resolve8) => {
|
|
18210
19434
|
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
18211
19435
|
rl.question(`${question} (y/N): `, (answer) => {
|
|
18212
19436
|
rl.close();
|
|
18213
19437
|
const t = answer.trim().toLowerCase();
|
|
18214
|
-
|
|
19438
|
+
resolve8(t === "y" || t === "yes");
|
|
18215
19439
|
});
|
|
18216
19440
|
});
|
|
18217
19441
|
}
|
|
@@ -18306,11 +19530,11 @@ Usage:
|
|
|
18306
19530
|
|
|
18307
19531
|
// cli/inventory/identity.ts
|
|
18308
19532
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
18309
|
-
import { existsSync as
|
|
18310
|
-
import { homedir as
|
|
18311
|
-
import { dirname as
|
|
19533
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync24, readFileSync as readFileSync38, renameSync as renameSync9, writeFileSync as writeFileSync30 } from "fs";
|
|
19534
|
+
import { homedir as homedir44 } from "os";
|
|
19535
|
+
import { dirname as dirname13, join as join42 } from "path";
|
|
18312
19536
|
function operationalIdentityPath() {
|
|
18313
|
-
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH ||
|
|
19537
|
+
return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join42(homedir44(), ".synkro", "installation.json");
|
|
18314
19538
|
}
|
|
18315
19539
|
function validIdentity(value) {
|
|
18316
19540
|
if (!value || typeof value !== "object") return false;
|
|
@@ -18318,15 +19542,15 @@ function validIdentity(value) {
|
|
|
18318
19542
|
return typeof row2.installation_id === "string" && UUID_RE.test(row2.installation_id) && typeof row2.created_at === "string" && Number.isFinite(Date.parse(row2.created_at));
|
|
18319
19543
|
}
|
|
18320
19544
|
function writeIdentity(path, identity) {
|
|
18321
|
-
mkdirSync24(
|
|
19545
|
+
mkdirSync24(dirname13(path), { recursive: true, mode: 448 });
|
|
18322
19546
|
const temp = `${path}.${process.pid}.${randomUUID5()}.tmp`;
|
|
18323
|
-
|
|
19547
|
+
writeFileSync30(temp, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
18324
19548
|
renameSync9(temp, path);
|
|
18325
19549
|
}
|
|
18326
19550
|
function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
18327
19551
|
const prior = cached4.get(path);
|
|
18328
19552
|
if (prior) return prior;
|
|
18329
|
-
if (
|
|
19553
|
+
if (existsSync42(path)) {
|
|
18330
19554
|
try {
|
|
18331
19555
|
const parsed = JSON.parse(readFileSync38(path, "utf8"));
|
|
18332
19556
|
if (validIdentity(parsed)) {
|
|
@@ -18342,7 +19566,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
|
|
|
18342
19566
|
return identity;
|
|
18343
19567
|
}
|
|
18344
19568
|
var UUID_RE, cached4;
|
|
18345
|
-
var
|
|
19569
|
+
var init_identity3 = __esm({
|
|
18346
19570
|
"cli/inventory/identity.ts"() {
|
|
18347
19571
|
"use strict";
|
|
18348
19572
|
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;
|
|
@@ -18353,13 +19577,13 @@ var init_identity2 = __esm({
|
|
|
18353
19577
|
// cli/inventory/collector.ts
|
|
18354
19578
|
import { createHash as createHash5 } from "crypto";
|
|
18355
19579
|
import {
|
|
18356
|
-
existsSync as
|
|
19580
|
+
existsSync as existsSync43,
|
|
18357
19581
|
readFileSync as readFileSync39,
|
|
18358
19582
|
readdirSync as readdirSync11,
|
|
18359
|
-
statSync as
|
|
19583
|
+
statSync as statSync6
|
|
18360
19584
|
} from "fs";
|
|
18361
|
-
import { arch, homedir as
|
|
18362
|
-
import { basename as
|
|
19585
|
+
import { arch, homedir as homedir45, hostname as hostname2, platform as platform6, release as release2 } from "os";
|
|
19586
|
+
import { basename as basename4, join as join43, relative, resolve as resolve6 } from "path";
|
|
18363
19587
|
import { fileURLToPath } from "url";
|
|
18364
19588
|
function sha256(value) {
|
|
18365
19589
|
return createHash5("sha256").update(value).digest("hex");
|
|
@@ -18369,14 +19593,14 @@ function pseudonymousHostnameHash(installationId, host) {
|
|
|
18369
19593
|
}
|
|
18370
19594
|
function cliVersion() {
|
|
18371
19595
|
try {
|
|
18372
|
-
return "1.10.
|
|
19596
|
+
return "1.10.8";
|
|
18373
19597
|
} catch {
|
|
18374
19598
|
return "0.0.0";
|
|
18375
19599
|
}
|
|
18376
19600
|
}
|
|
18377
19601
|
function readJson(path) {
|
|
18378
19602
|
try {
|
|
18379
|
-
if (!
|
|
19603
|
+
if (!existsSync43(path)) return null;
|
|
18380
19604
|
const parsed = JSON.parse(readFileSync39(path, "utf8"));
|
|
18381
19605
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
18382
19606
|
} catch {
|
|
@@ -18385,7 +19609,7 @@ function readJson(path) {
|
|
|
18385
19609
|
}
|
|
18386
19610
|
function readText(path) {
|
|
18387
19611
|
try {
|
|
18388
|
-
if (!
|
|
19612
|
+
if (!existsSync43(path)) return "";
|
|
18389
19613
|
return readFileSync39(path, "utf8");
|
|
18390
19614
|
} catch {
|
|
18391
19615
|
return "";
|
|
@@ -18406,7 +19630,7 @@ function canonical(raw) {
|
|
|
18406
19630
|
}
|
|
18407
19631
|
function safePackageName(command, args2) {
|
|
18408
19632
|
if (typeof command !== "string" || !command.trim()) return void 0;
|
|
18409
|
-
const runner =
|
|
19633
|
+
const runner = basename4(command.trim()).replace(/\.exe$/i, "");
|
|
18410
19634
|
if (Array.isArray(args2) && ["npx", "bunx", "uvx"].includes(runner)) {
|
|
18411
19635
|
const pkg = args2.find((arg) => typeof arg === "string" && !arg.startsWith("-"));
|
|
18412
19636
|
if (typeof pkg === "string") {
|
|
@@ -18414,7 +19638,7 @@ function safePackageName(command, args2) {
|
|
|
18414
19638
|
if (/^(?:@[a-z0-9_.-]+\/)?[a-z0-9_.-]+(?:@[a-z0-9_.+~-]+)?$/i.test(candidate)) {
|
|
18415
19639
|
return candidate;
|
|
18416
19640
|
}
|
|
18417
|
-
return
|
|
19641
|
+
return basename4(candidate);
|
|
18418
19642
|
}
|
|
18419
19643
|
}
|
|
18420
19644
|
return runner;
|
|
@@ -18472,16 +19696,16 @@ function mcpArtifactsFromJson(harness, config, configScope = "user") {
|
|
|
18472
19696
|
}
|
|
18473
19697
|
function claudeDesktopConfigCandidates(home, targetPlatform) {
|
|
18474
19698
|
if (targetPlatform === "darwin") {
|
|
18475
|
-
return [
|
|
19699
|
+
return [join43(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
|
|
18476
19700
|
}
|
|
18477
19701
|
if (targetPlatform === "linux") {
|
|
18478
19702
|
return [
|
|
18479
|
-
|
|
18480
|
-
|
|
19703
|
+
join43(home, ".config", "Claude", "claude_desktop_config.json"),
|
|
19704
|
+
join43(home, ".config", "claude", "claude_desktop_config.json")
|
|
18481
19705
|
];
|
|
18482
19706
|
}
|
|
18483
19707
|
if (targetPlatform === "win32" && process.env.APPDATA) {
|
|
18484
|
-
return [
|
|
19708
|
+
return [join43(process.env.APPDATA, "Claude", "claude_desktop_config.json")];
|
|
18485
19709
|
}
|
|
18486
19710
|
return [];
|
|
18487
19711
|
}
|
|
@@ -18489,7 +19713,7 @@ function claudeManagedMcpConfigCandidates(targetPlatform) {
|
|
|
18489
19713
|
if (targetPlatform === "darwin") return ["/Library/Application Support/ClaudeCode/managed-mcp.json"];
|
|
18490
19714
|
if (targetPlatform === "linux") return ["/etc/claude-code/managed-mcp.json"];
|
|
18491
19715
|
if (targetPlatform === "win32" && process.env.ProgramFiles) {
|
|
18492
|
-
return [
|
|
19716
|
+
return [join43(process.env.ProgramFiles, "ClaudeCode", "managed-mcp.json")];
|
|
18493
19717
|
}
|
|
18494
19718
|
return [];
|
|
18495
19719
|
}
|
|
@@ -18497,8 +19721,8 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
|
|
|
18497
19721
|
const roots = /* @__PURE__ */ new Set();
|
|
18498
19722
|
const add = (value) => {
|
|
18499
19723
|
if (typeof value !== "string" || !value.trim()) return;
|
|
18500
|
-
const path =
|
|
18501
|
-
if (
|
|
19724
|
+
const path = resolve6(value);
|
|
19725
|
+
if (existsSync43(path)) roots.add(path);
|
|
18502
19726
|
};
|
|
18503
19727
|
add(currentDirectory);
|
|
18504
19728
|
for (const path of explicit) add(path);
|
|
@@ -18509,17 +19733,17 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
|
|
|
18509
19733
|
return [...roots];
|
|
18510
19734
|
}
|
|
18511
19735
|
function cursorWorkspaceStorageCandidates(home, targetPlatform) {
|
|
18512
|
-
if (targetPlatform === "darwin") return [
|
|
18513
|
-
if (targetPlatform === "linux") return [
|
|
19736
|
+
if (targetPlatform === "darwin") return [join43(home, "Library", "Application Support", "Cursor", "User", "workspaceStorage")];
|
|
19737
|
+
if (targetPlatform === "linux") return [join43(home, ".config", "Cursor", "User", "workspaceStorage")];
|
|
18514
19738
|
if (targetPlatform === "win32" && process.env.APPDATA) {
|
|
18515
|
-
return [
|
|
19739
|
+
return [join43(process.env.APPDATA, "Cursor", "User", "workspaceStorage")];
|
|
18516
19740
|
}
|
|
18517
19741
|
return [];
|
|
18518
19742
|
}
|
|
18519
19743
|
function cursorWorkspaceRoots(home, targetPlatform) {
|
|
18520
19744
|
const roots = /* @__PURE__ */ new Set();
|
|
18521
19745
|
for (const storage of cursorWorkspaceStorageCandidates(home, targetPlatform)) {
|
|
18522
|
-
if (!
|
|
19746
|
+
if (!existsSync43(storage)) continue;
|
|
18523
19747
|
let entries = [];
|
|
18524
19748
|
try {
|
|
18525
19749
|
entries = readdirSync11(storage, { withFileTypes: true });
|
|
@@ -18528,12 +19752,12 @@ function cursorWorkspaceRoots(home, targetPlatform) {
|
|
|
18528
19752
|
}
|
|
18529
19753
|
for (const entry of entries) {
|
|
18530
19754
|
if (!entry.isDirectory() || entry.isSymbolicLink?.()) continue;
|
|
18531
|
-
const state = readJson(
|
|
19755
|
+
const state = readJson(join43(storage, entry.name, "workspace.json"));
|
|
18532
19756
|
const raw = state?.folder;
|
|
18533
19757
|
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
18534
19758
|
try {
|
|
18535
19759
|
const path = raw.startsWith("file:") ? fileURLToPath(raw) : raw;
|
|
18536
|
-
if (
|
|
19760
|
+
if (existsSync43(path)) roots.add(resolve6(path));
|
|
18537
19761
|
} catch {
|
|
18538
19762
|
}
|
|
18539
19763
|
}
|
|
@@ -18609,10 +19833,10 @@ function hookArtifacts(harness, config) {
|
|
|
18609
19833
|
harness,
|
|
18610
19834
|
type: "hook",
|
|
18611
19835
|
canonical_id: `${canonical(event)}:${commandHash.slice(0, 20)}`,
|
|
18612
|
-
display_name: `${event} \xB7 ${
|
|
19836
|
+
display_name: `${event} \xB7 ${basename4(String(entry.command).split(/\s+/)[0] || "hook")}`,
|
|
18613
19837
|
enabled: entry.enabled !== false,
|
|
18614
19838
|
config_scope: "user",
|
|
18615
|
-
package_name:
|
|
19839
|
+
package_name: basename4(String(entry.command).split(/\s+/)[0] || "") || void 0,
|
|
18616
19840
|
config_hash: commandHash,
|
|
18617
19841
|
metadata: { managed, events: [event] }
|
|
18618
19842
|
});
|
|
@@ -18627,7 +19851,7 @@ function parseFrontmatter(content) {
|
|
|
18627
19851
|
return { name: value("name"), version: value("version") };
|
|
18628
19852
|
}
|
|
18629
19853
|
function skillArtifacts(harness, root) {
|
|
18630
|
-
if (!
|
|
19854
|
+
if (!existsSync43(root)) return [];
|
|
18631
19855
|
const manifests = [];
|
|
18632
19856
|
const visit = (dir) => {
|
|
18633
19857
|
let entries;
|
|
@@ -18638,7 +19862,7 @@ function skillArtifacts(harness, root) {
|
|
|
18638
19862
|
}
|
|
18639
19863
|
for (const entry of entries) {
|
|
18640
19864
|
if (entry.isSymbolicLink?.()) continue;
|
|
18641
|
-
const path =
|
|
19865
|
+
const path = join43(dir, entry.name);
|
|
18642
19866
|
if (entry.isFile() && entry.name === "SKILL.md") manifests.push(path);
|
|
18643
19867
|
else if (entry.isDirectory()) visit(path);
|
|
18644
19868
|
}
|
|
@@ -18648,7 +19872,7 @@ function skillArtifacts(harness, root) {
|
|
|
18648
19872
|
const content = readText(path);
|
|
18649
19873
|
const frontmatter = parseFrontmatter(content);
|
|
18650
19874
|
const rel = relative(root, path).replaceAll("\\", "/");
|
|
18651
|
-
const name = frontmatter.name ||
|
|
19875
|
+
const name = frontmatter.name || basename4(join43(path, "..")) || "skill";
|
|
18652
19876
|
return {
|
|
18653
19877
|
harness,
|
|
18654
19878
|
type: "skill",
|
|
@@ -18663,7 +19887,7 @@ function skillArtifacts(harness, root) {
|
|
|
18663
19887
|
});
|
|
18664
19888
|
}
|
|
18665
19889
|
function cursorExtensionArtifacts(root) {
|
|
18666
|
-
if (!
|
|
19890
|
+
if (!existsSync43(root)) return [];
|
|
18667
19891
|
let dirs = [];
|
|
18668
19892
|
try {
|
|
18669
19893
|
dirs = readdirSync11(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.isSymbolicLink());
|
|
@@ -18672,7 +19896,7 @@ function cursorExtensionArtifacts(root) {
|
|
|
18672
19896
|
}
|
|
18673
19897
|
const artifacts = [];
|
|
18674
19898
|
for (const dir of dirs) {
|
|
18675
|
-
const pkg = readJson(
|
|
19899
|
+
const pkg = readJson(join43(root, dir.name, "package.json"));
|
|
18676
19900
|
if (!pkg) continue;
|
|
18677
19901
|
const publisher = typeof pkg.publisher === "string" ? pkg.publisher : void 0;
|
|
18678
19902
|
const name = typeof pkg.name === "string" ? pkg.name : dir.name;
|
|
@@ -18692,20 +19916,20 @@ function cursorExtensionArtifacts(root) {
|
|
|
18692
19916
|
return artifacts;
|
|
18693
19917
|
}
|
|
18694
19918
|
function deploymentMode2(home) {
|
|
18695
|
-
const raw = readText(
|
|
19919
|
+
const raw = readText(join43(home, ".synkro", "config.env"));
|
|
18696
19920
|
const value = (key) => raw.match(new RegExp(`^${key}=['"]?([^'"\\n]*)`, "m"))?.[1]?.toLowerCase();
|
|
18697
19921
|
if (value("SYNKRO_GRADING_MODE") === "byok") return "byok";
|
|
18698
19922
|
if (value("SYNKRO_STORAGE_MODE") === "cloud") return "cloud";
|
|
18699
19923
|
return "local";
|
|
18700
19924
|
}
|
|
18701
19925
|
function telemetryHealth(home) {
|
|
18702
|
-
const meta = readJson(
|
|
19926
|
+
const meta = readJson(join43(home, ".synkro", "telemetry-meta.json"));
|
|
18703
19927
|
const health = {};
|
|
18704
19928
|
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;
|
|
18705
19929
|
if (meta?.last_flush_error) health.telemetry_last_error = "flush_failed";
|
|
18706
|
-
const queue =
|
|
19930
|
+
const queue = join43(home, ".synkro", "telemetry-pending.jsonl");
|
|
18707
19931
|
try {
|
|
18708
|
-
const size =
|
|
19932
|
+
const size = statSync6(queue).size;
|
|
18709
19933
|
health.telemetry_backlog = size <= 5 * 1024 * 1024 ? readFileSync39(queue, "utf8").split("\n").filter(Boolean).length : Math.ceil(size / 1024);
|
|
18710
19934
|
} catch {
|
|
18711
19935
|
}
|
|
@@ -18747,7 +19971,7 @@ function harnessSnapshot(agent) {
|
|
|
18747
19971
|
}
|
|
18748
19972
|
const config = readJson(agent.settingsPath);
|
|
18749
19973
|
const coverage = inspectCodexHooks(agent.settingsPath);
|
|
18750
|
-
const toml = readText(
|
|
19974
|
+
const toml = readText(join43(agent.configDir, "config.toml"));
|
|
18751
19975
|
const permission = toml.match(/^\s*approval_policy\s*=\s*["']([^"']+)/m)?.[1];
|
|
18752
19976
|
return {
|
|
18753
19977
|
row: {
|
|
@@ -18764,11 +19988,11 @@ function harnessSnapshot(agent) {
|
|
|
18764
19988
|
};
|
|
18765
19989
|
}
|
|
18766
19990
|
function collectOperationalInventory(options = {}) {
|
|
18767
|
-
const home = options.homeDir ??
|
|
19991
|
+
const home = options.homeDir ?? homedir45();
|
|
18768
19992
|
const detected = options.detectedAgents ?? detectAgents();
|
|
18769
19993
|
const identity = getOperationalInstallationIdentity(options.identityPath);
|
|
18770
19994
|
const targetPlatform = options.platformName ?? platform6();
|
|
18771
|
-
const codexHome = options.homeDir ?
|
|
19995
|
+
const codexHome = options.homeDir ? join43(home, ".codex") : process.env.CODEX_HOME || join43(home, ".codex");
|
|
18772
19996
|
const harnesses = [];
|
|
18773
19997
|
const artifacts = [];
|
|
18774
19998
|
for (const agent of detected) {
|
|
@@ -18776,7 +20000,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
18776
20000
|
harnesses.push(row2);
|
|
18777
20001
|
artifacts.push(...hookArtifacts(row2.harness, config));
|
|
18778
20002
|
}
|
|
18779
|
-
const claudeJson = readJson(
|
|
20003
|
+
const claudeJson = readJson(join43(home, ".claude.json"));
|
|
18780
20004
|
artifacts.push(...mcpArtifactsFromJson("claude_code", claudeJson));
|
|
18781
20005
|
if (claudeJson?.projects && typeof claudeJson.projects === "object") {
|
|
18782
20006
|
for (const [projectPath, project] of Object.entries(claudeJson.projects)) {
|
|
@@ -18784,8 +20008,8 @@ function collectOperationalInventory(options = {}) {
|
|
|
18784
20008
|
artifacts.push(...mcpArtifactsFromJson("claude_code", project, `local:${sha256(projectPath).slice(0, 16)}`));
|
|
18785
20009
|
}
|
|
18786
20010
|
}
|
|
18787
|
-
artifacts.push(...mcpArtifactsFromJson("cursor", readJson(
|
|
18788
|
-
artifacts.push(...codexMcpArtifacts(readText(
|
|
20011
|
+
artifacts.push(...mcpArtifactsFromJson("cursor", readJson(join43(home, ".cursor", "mcp.json"))));
|
|
20012
|
+
artifacts.push(...codexMcpArtifacts(readText(join43(codexHome, "config.toml"))));
|
|
18789
20013
|
const projectRoots = discoveredProjectRoots(
|
|
18790
20014
|
claudeJson,
|
|
18791
20015
|
options.currentDirectory ?? process.cwd(),
|
|
@@ -18796,11 +20020,11 @@ function collectOperationalInventory(options = {}) {
|
|
|
18796
20020
|
const scopeHash = sha256(projectRoot).slice(0, 16);
|
|
18797
20021
|
artifacts.push(...mcpArtifactsFromJson(
|
|
18798
20022
|
"claude_code",
|
|
18799
|
-
readJson(
|
|
20023
|
+
readJson(join43(projectRoot, ".mcp.json")),
|
|
18800
20024
|
`project:${scopeHash}`
|
|
18801
20025
|
));
|
|
18802
|
-
const cursorProjectConfig =
|
|
18803
|
-
if (
|
|
20026
|
+
const cursorProjectConfig = join43(projectRoot, ".cursor", "mcp.json");
|
|
20027
|
+
if (resolve6(cursorProjectConfig) !== resolve6(join43(home, ".cursor", "mcp.json"))) {
|
|
18804
20028
|
artifacts.push(...mcpArtifactsFromJson(
|
|
18805
20029
|
"cursor",
|
|
18806
20030
|
readJson(cursorProjectConfig),
|
|
@@ -18811,7 +20035,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
18811
20035
|
for (const managedPath of claudeManagedMcpConfigCandidates(targetPlatform)) {
|
|
18812
20036
|
artifacts.push(...mcpArtifactsFromJson("claude_code", readJson(managedPath), "managed"));
|
|
18813
20037
|
}
|
|
18814
|
-
const desktopConfigPath = claudeDesktopConfigCandidates(home, targetPlatform).find((path) =>
|
|
20038
|
+
const desktopConfigPath = claudeDesktopConfigCandidates(home, targetPlatform).find((path) => existsSync43(path));
|
|
18815
20039
|
if (desktopConfigPath) {
|
|
18816
20040
|
const desktopConfig = readJson(desktopConfigPath);
|
|
18817
20041
|
harnesses.push({
|
|
@@ -18822,7 +20046,7 @@ function collectOperationalInventory(options = {}) {
|
|
|
18822
20046
|
});
|
|
18823
20047
|
artifacts.push(...mcpArtifactsFromJson("claude_desktop", desktopConfig));
|
|
18824
20048
|
}
|
|
18825
|
-
const claudeSettings = readJson(
|
|
20049
|
+
const claudeSettings = readJson(join43(home, ".claude", "settings.json"));
|
|
18826
20050
|
if (claudeSettings?.enabledPlugins && typeof claudeSettings.enabledPlugins === "object") {
|
|
18827
20051
|
for (const [name, enabled] of Object.entries(claudeSettings.enabledPlugins)) {
|
|
18828
20052
|
artifacts.push({
|
|
@@ -18836,10 +20060,10 @@ function collectOperationalInventory(options = {}) {
|
|
|
18836
20060
|
});
|
|
18837
20061
|
}
|
|
18838
20062
|
}
|
|
18839
|
-
artifacts.push(...skillArtifacts("claude_code",
|
|
18840
|
-
artifacts.push(...skillArtifacts("cursor",
|
|
18841
|
-
artifacts.push(...skillArtifacts("codex",
|
|
18842
|
-
artifacts.push(...cursorExtensionArtifacts(
|
|
20063
|
+
artifacts.push(...skillArtifacts("claude_code", join43(home, ".claude", "skills")));
|
|
20064
|
+
artifacts.push(...skillArtifacts("cursor", join43(home, ".cursor", "skills")));
|
|
20065
|
+
artifacts.push(...skillArtifacts("codex", join43(codexHome, "skills")));
|
|
20066
|
+
artifacts.push(...cursorExtensionArtifacts(join43(home, ".cursor", "extensions")));
|
|
18843
20067
|
const uniqueArtifacts = /* @__PURE__ */ new Map();
|
|
18844
20068
|
for (const artifact of artifacts) {
|
|
18845
20069
|
const key = `${artifact.harness || "global"}:${artifact.type}:${artifact.canonical_id}`;
|
|
@@ -18876,7 +20100,7 @@ var init_collector = __esm({
|
|
|
18876
20100
|
init_ccHookConfig();
|
|
18877
20101
|
init_cursorHookConfig();
|
|
18878
20102
|
init_codexHookConfig();
|
|
18879
|
-
|
|
20103
|
+
init_identity3();
|
|
18880
20104
|
}
|
|
18881
20105
|
});
|
|
18882
20106
|
|
|
@@ -18892,18 +20116,18 @@ __export(sync_exports2, {
|
|
|
18892
20116
|
syncOperationalInventoryDetached: () => syncOperationalInventoryDetached
|
|
18893
20117
|
});
|
|
18894
20118
|
import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
|
|
18895
|
-
import { spawn as
|
|
20119
|
+
import { spawn as spawn11 } from "child_process";
|
|
18896
20120
|
import {
|
|
18897
|
-
existsSync as
|
|
20121
|
+
existsSync as existsSync44,
|
|
18898
20122
|
mkdirSync as mkdirSync25,
|
|
18899
20123
|
readFileSync as readFileSync40,
|
|
18900
20124
|
renameSync as renameSync10,
|
|
18901
|
-
writeFileSync as
|
|
20125
|
+
writeFileSync as writeFileSync31
|
|
18902
20126
|
} from "fs";
|
|
18903
|
-
import { homedir as
|
|
18904
|
-
import { dirname as
|
|
20127
|
+
import { homedir as homedir46 } from "os";
|
|
20128
|
+
import { dirname as dirname14, join as join44 } from "path";
|
|
18905
20129
|
function syncStatePath() {
|
|
18906
|
-
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH ||
|
|
20130
|
+
return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join44(homedir46(), ".synkro", "inventory-sync.json");
|
|
18907
20131
|
}
|
|
18908
20132
|
function readState(path = syncStatePath()) {
|
|
18909
20133
|
try {
|
|
@@ -18915,9 +20139,9 @@ function readState(path = syncStatePath()) {
|
|
|
18915
20139
|
}
|
|
18916
20140
|
function writeState(state, path = syncStatePath()) {
|
|
18917
20141
|
try {
|
|
18918
|
-
mkdirSync25(
|
|
20142
|
+
mkdirSync25(dirname14(path), { recursive: true, mode: 448 });
|
|
18919
20143
|
const temp = `${path}.${process.pid}.tmp`;
|
|
18920
|
-
|
|
20144
|
+
writeFileSync31(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
18921
20145
|
renameSync10(temp, path);
|
|
18922
20146
|
} catch {
|
|
18923
20147
|
}
|
|
@@ -18930,7 +20154,7 @@ function shouldSyncInventory(state, now = Date.now(), target) {
|
|
|
18930
20154
|
return !Number.isFinite(lastAttempt) || lastAttempt <= 0 || now - lastAttempt >= FAILURE_RETRY_MS;
|
|
18931
20155
|
}
|
|
18932
20156
|
function readConfig() {
|
|
18933
|
-
const path =
|
|
20157
|
+
const path = join44(homedir46(), ".synkro", "config.env");
|
|
18934
20158
|
const out = {};
|
|
18935
20159
|
try {
|
|
18936
20160
|
for (const rawLine of readFileSync40(path, "utf8").split("\n")) {
|
|
@@ -18972,7 +20196,7 @@ function resolveInventoryGateway(raw) {
|
|
|
18972
20196
|
}
|
|
18973
20197
|
async function loadToken() {
|
|
18974
20198
|
try {
|
|
18975
|
-
const durable = readFileSync40(
|
|
20199
|
+
const durable = readFileSync40(join44(homedir46(), ".synkro", ".mcp-jwt"), "utf8").trim();
|
|
18976
20200
|
if (durable) return durable;
|
|
18977
20201
|
} catch {
|
|
18978
20202
|
}
|
|
@@ -19090,8 +20314,8 @@ function syncOperationalInventoryDetached() {
|
|
|
19090
20314
|
writeState({ ...state, last_attempt_at: (/* @__PURE__ */ new Date()).toISOString(), last_target: target }, path);
|
|
19091
20315
|
try {
|
|
19092
20316
|
const script = process.argv[1];
|
|
19093
|
-
if (!script || !
|
|
19094
|
-
const child =
|
|
20317
|
+
if (!script || !existsSync44(script)) return;
|
|
20318
|
+
const child = spawn11(process.execPath, [script, "inventory-sync", "--detached"], {
|
|
19095
20319
|
detached: true,
|
|
19096
20320
|
stdio: "ignore",
|
|
19097
20321
|
env: { ...process.env, SYNKRO_INVENTORY_DETACHED: "1" }
|
|
@@ -19114,14 +20338,14 @@ var init_sync2 = __esm({
|
|
|
19114
20338
|
});
|
|
19115
20339
|
|
|
19116
20340
|
// cli/bootstrap.js
|
|
19117
|
-
import { readFileSync as readFileSync41, existsSync as
|
|
19118
|
-
import { resolve as
|
|
20341
|
+
import { readFileSync as readFileSync41, existsSync as existsSync45 } from "fs";
|
|
20342
|
+
import { resolve as resolve7 } from "path";
|
|
19119
20343
|
process.title = "synkro";
|
|
19120
20344
|
var envCandidates = [
|
|
19121
|
-
|
|
20345
|
+
resolve7(process.env.HOME ?? "", ".synkro", "config.env")
|
|
19122
20346
|
];
|
|
19123
20347
|
for (const envPath of envCandidates) {
|
|
19124
|
-
if (!
|
|
20348
|
+
if (!existsSync45(envPath)) continue;
|
|
19125
20349
|
const envContent = readFileSync41(envPath, "utf-8");
|
|
19126
20350
|
for (const line of envContent.split("\n")) {
|
|
19127
20351
|
const trimmed = line.trim();
|
|
@@ -19139,7 +20363,7 @@ var subArgs = args.slice(1);
|
|
|
19139
20363
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
19140
20364
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
19141
20365
|
function printVersion() {
|
|
19142
|
-
console.log("1.10.
|
|
20366
|
+
console.log("1.10.8");
|
|
19143
20367
|
}
|
|
19144
20368
|
function printHelp2() {
|
|
19145
20369
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|