@synkro-sh/cli 1.6.95 → 1.6.97
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 +134 -23
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1750,7 +1750,7 @@ async function cloudGrade(surface: string, prompt: string, jwt: string, timeoutM
|
|
|
1750
1750
|
// to the org's dispatcher /submit), but to the hosted ingress with the gateway
|
|
1751
1751
|
// JWT (the Worker derives the org from the token and routes to that org's
|
|
1752
1752
|
// container). Any non-2xx throws so the caller's catch fails open.
|
|
1753
|
-
const CONTAINERS_URL = process.env.SYNKRO_CONTAINERS_URL || 'https://
|
|
1753
|
+
const CONTAINERS_URL = process.env.SYNKRO_CONTAINERS_URL || 'https://api.synkro.sh';
|
|
1754
1754
|
// Cloud grades cross the network and can hit a cold/queued worker, so they get a
|
|
1755
1755
|
// longer leash than on-device channel grades. The hook chain is sized around it:
|
|
1756
1756
|
// grade abort (45s) < watchdog (48s) < CC hook budget (50s) \u2014 the grade aborts
|
|
@@ -1781,7 +1781,21 @@ async function hostedGrade(role: GradeRole, prompt: string, jwt: string, timeout
|
|
|
1781
1781
|
// break grading. The payload IS a ScanEnvelope; the container runs getActiveTask
|
|
1782
1782
|
// (resolved per (org,user,repo) via active_tasks) + taskDrift and returns the
|
|
1783
1783
|
// decision. Secrets are scrubbed before the envelope crosses the network.
|
|
1784
|
-
|
|
1784
|
+
// Cloud task-drift returns BOTH channels so the hook can STEER the agent, not just
|
|
1785
|
+
// narrate to the user: systemMessage \u2192 user terminal; additionalContext \u2192 the agent;
|
|
1786
|
+
// decision='deny' \u2192 a contradiction the agent must fix (hard block, matching local).
|
|
1787
|
+
// The container ships formatDecision()'s shape ({ systemMessage, hookSpecificOutput:
|
|
1788
|
+
// { additionalContext, permissionDecision } }); we surface all three.
|
|
1789
|
+
export interface TaskDriftResult { systemMessage: string; additionalContext: string; decision: 'allow' | 'deny' }
|
|
1790
|
+
const TASK_DRIFT_NONE: TaskDriftResult = { systemMessage: '', additionalContext: '', decision: 'allow' };
|
|
1791
|
+
|
|
1792
|
+
export async function cloudTaskDrift(envelope: Record<string, unknown>, jwt: string, timeoutMs = CLOUD_GRADE_TIMEOUT_MS): Promise<TaskDriftResult> {
|
|
1793
|
+
const sid = typeof envelope.sessionId === 'string' ? envelope.sessionId : undefined;
|
|
1794
|
+
const repo = typeof envelope.cwd === 'string' ? envelope.cwd : undefined;
|
|
1795
|
+
// What was being graded (the action), captured for the failure record so a dead call
|
|
1796
|
+
// still shows WHAT timed out. The fully-assembled grader prompt is captured separately
|
|
1797
|
+
// server-side (grader_failures.grade_input); this is the client's view.
|
|
1798
|
+
const gradeInput = scrubSecrets(JSON.stringify(envelope.payload ?? envelope)).slice(0, 6000);
|
|
1785
1799
|
try {
|
|
1786
1800
|
const body = JSON.stringify({ role: 'task-drift', payload: scrubSecrets(JSON.stringify(envelope)), content: '' });
|
|
1787
1801
|
const resp = await fetch(CONTAINERS_URL + '/grade/submit', {
|
|
@@ -1790,10 +1804,65 @@ export async function cloudTaskDrift(envelope: Record<string, unknown>, jwt: str
|
|
|
1790
1804
|
body,
|
|
1791
1805
|
signal: AbortSignal.timeout(timeoutMs),
|
|
1792
1806
|
});
|
|
1793
|
-
if (!resp.ok)
|
|
1807
|
+
if (!resp.ok) {
|
|
1808
|
+
// Non-2xx \u2014 record it (rule grades log the same way) so the task message
|
|
1809
|
+
// silently not firing is observable, not invisible. Fire-and-forget per
|
|
1810
|
+
// logGraderUnavailable's contract; still fail-OPEN.
|
|
1811
|
+
void logGraderUnavailable('taskGuard', 'task-drift', 'http ' + resp.status, gradeInput, { sessionId: sid, repo }).catch(() => {});
|
|
1812
|
+
return TASK_DRIFT_NONE;
|
|
1813
|
+
}
|
|
1814
|
+
const j = await resp.json() as { systemMessage?: unknown; hookSpecificOutput?: { additionalContext?: unknown; permissionDecision?: unknown } };
|
|
1815
|
+
const hso = j?.hookSpecificOutput || {};
|
|
1816
|
+
return {
|
|
1817
|
+
systemMessage: typeof j?.systemMessage === 'string' ? j.systemMessage : '',
|
|
1818
|
+
additionalContext: typeof hso.additionalContext === 'string' ? hso.additionalContext : '',
|
|
1819
|
+
decision: hso.permissionDecision === 'deny' ? 'deny' : 'allow',
|
|
1820
|
+
};
|
|
1821
|
+
} catch (e) {
|
|
1822
|
+
// Track the failure \u2014 ESP. the 45s timeout, the most likely reason a task message
|
|
1823
|
+
// doesn't fire. AbortSignal.timeout throws TimeoutError on expiry; everything else
|
|
1824
|
+
// (network, parse) carries its own message. Fire-and-forget (don't await \u2014 would
|
|
1825
|
+
// add ~3s on the timeout path and blow the watchdog); fail-open: never block.
|
|
1826
|
+
const err = e as { name?: string; message?: string };
|
|
1827
|
+
const reason = err?.name === 'TimeoutError'
|
|
1828
|
+
? 'task-drift timed out after ' + timeoutMs + 'ms'
|
|
1829
|
+
: (err?.message || 'task-drift failed');
|
|
1830
|
+
void logGraderUnavailable('taskGuard', 'task-drift', reason, gradeInput, { sessionId: sid, repo }).catch(() => {});
|
|
1831
|
+
return TASK_DRIFT_NONE;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// Cloud task COMPLETION sweep \u2014 the end-of-turn FINAL CHECK (fired from the Stop hook).
|
|
1836
|
+
// Hits the org container's 'task-complete' surface: verify EVERY requirement against the
|
|
1837
|
+
// actual written code (file snapshots + transcript), mark met/open, and retire if all
|
|
1838
|
+
// met. Returns the result systemMessage ("complete \u2713 N/N retired" or "N/M \u2014 missing: \u2026").
|
|
1839
|
+
// payload.stop_hook_active MUST be falsey or handleTaskComplete bails (stop-loop guard).
|
|
1840
|
+
// Fail-silent: any hiccup returns '' so a flaky sweep never blocks the agent stopping.
|
|
1841
|
+
export async function cloudTaskComplete(envelope: Record<string, unknown>, jwt: string, timeoutMs = CLOUD_GRADE_TIMEOUT_MS): Promise<string> {
|
|
1842
|
+
const sid = typeof envelope.sessionId === 'string' ? envelope.sessionId : undefined;
|
|
1843
|
+
const repo = typeof envelope.cwd === 'string' ? envelope.cwd : undefined;
|
|
1844
|
+
try {
|
|
1845
|
+
const body = JSON.stringify({ role: 'task-complete', payload: scrubSecrets(JSON.stringify(envelope)), content: '' });
|
|
1846
|
+
const resp = await fetch(CONTAINERS_URL + '/grade/submit', {
|
|
1847
|
+
method: 'POST',
|
|
1848
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1849
|
+
body,
|
|
1850
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1851
|
+
});
|
|
1852
|
+
if (!resp.ok) {
|
|
1853
|
+
void logGraderUnavailable('taskComplete', 'task-complete', 'http ' + resp.status, undefined, { sessionId: sid, repo }).catch(() => {});
|
|
1854
|
+
return '';
|
|
1855
|
+
}
|
|
1794
1856
|
const j = await resp.json() as { systemMessage?: unknown };
|
|
1795
1857
|
return typeof j?.systemMessage === 'string' ? j.systemMessage : '';
|
|
1796
|
-
} catch {
|
|
1858
|
+
} catch (e) {
|
|
1859
|
+
const err = e as { name?: string; message?: string };
|
|
1860
|
+
const reason = err?.name === 'TimeoutError'
|
|
1861
|
+
? 'task-complete timed out after ' + timeoutMs + 'ms'
|
|
1862
|
+
: (err?.message || 'task-complete failed');
|
|
1863
|
+
void logGraderUnavailable('taskComplete', 'task-complete', reason, undefined, { sessionId: sid, repo }).catch(() => {});
|
|
1864
|
+
return '';
|
|
1865
|
+
}
|
|
1797
1866
|
}
|
|
1798
1867
|
|
|
1799
1868
|
// Option B: when a cloud grade fails (esp. a timeout), the REAL reason lives
|
|
@@ -4454,6 +4523,13 @@ async function main() {
|
|
|
4454
4523
|
if (rt === 'local') {
|
|
4455
4524
|
// \u2500\u2500\u2500 Local grading: org rules ONLY (channel 1, port 18929) \u2500\u2500\u2500
|
|
4456
4525
|
const proposedShort = proposed.slice(0, 4000);
|
|
4526
|
+
// The TASK grade sees the DIFF for large files, so a contradiction or drift DEEP
|
|
4527
|
+
// in a big file is visible \u2014 the old "first 4KB of the whole file" window missed
|
|
4528
|
+
// any change past ~2KB. Small files keep full content (cleaner for the grader);
|
|
4529
|
+
// editDiff is the already-computed changed-lines summary (- old / + new).
|
|
4530
|
+
const taskGradeContent = (proposed.length <= 3500 || !editDiff)
|
|
4531
|
+
? proposedShort
|
|
4532
|
+
: ('Changed lines of ' + filePath + ' (- old / + new):\\n' + editDiff).slice(0, 4000);
|
|
4457
4533
|
const sessionLog = await sessionLogForGrade(sessionId, config.rules);
|
|
4458
4534
|
const graderContent = 'file=' + filePath + ' content=' + proposedShort;
|
|
4459
4535
|
const relevantRules = await filterRules(
|
|
@@ -4478,9 +4554,9 @@ async function main() {
|
|
|
4478
4554
|
// message into the pass output. Fail-silent ('' on any issue). Sent as a
|
|
4479
4555
|
// synthetic Write of the reconstructed content so the container needs no base
|
|
4480
4556
|
// bytes. cwd = the repo identifier the container keys active_tasks on.
|
|
4481
|
-
const taskDriftP
|
|
4482
|
-
? cloudTaskDrift({ payload: { tool_name: 'Write', tool_input: { file_path: filePath, content:
|
|
4483
|
-
: Promise.resolve('');
|
|
4557
|
+
const taskDriftP = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud'
|
|
4558
|
+
? cloudTaskDrift({ payload: { tool_name: 'Write', tool_input: { file_path: filePath, content: taskGradeContent } }, harness: agentKind, cwd: gitRepo, sessionId }, jwt)
|
|
4559
|
+
: Promise.resolve({ systemMessage: '', additionalContext: '', decision: 'allow' as const });
|
|
4484
4560
|
|
|
4485
4561
|
// \u2500\u2500\u2500 Combined org-rules + CWE in ONE inference (SYNKRO_COMBINED_EDIT_GRADE) \u2500\u2500\u2500
|
|
4486
4562
|
// Self-contained early-return branch \u2014 the default two-grade path below is
|
|
@@ -4548,6 +4624,15 @@ async function main() {
|
|
|
4548
4624
|
dispatchFinding(jwt, { session_id: sessionId, file_path: filePath, finding_type: 'cwe', finding_id: f.id, status: 'open' }, config.captureDepth);
|
|
4549
4625
|
}
|
|
4550
4626
|
|
|
4627
|
+
// Resolve the parallel task grade NOW so a CONTRADICTION folds into the rule/CWE
|
|
4628
|
+
// block too \u2014 the agent gets rules + CWE + task drift in ONE deny instead of
|
|
4629
|
+
// discovering the contradiction a retry later. taskDriftP has been running in
|
|
4630
|
+
// parallel, so this rarely adds wall-clock. Only a contradiction (decision==='deny')
|
|
4631
|
+
// is folded into a block; progress/off-spec messages stay on the pass path below.
|
|
4632
|
+
const cTask = await taskDriftP;
|
|
4633
|
+
const cTaskSys = cTask.systemMessage;
|
|
4634
|
+
const taskDeny = cTask.decision === 'deny' ? (cTask.additionalContext || cTaskSys) : '';
|
|
4635
|
+
|
|
4551
4636
|
// Org-rule violation \u2192 ask/fix enforcement (CWE findings appended to the deny).
|
|
4552
4637
|
if (!ruleVerdict.ok) {
|
|
4553
4638
|
const mode = normalizeMode(ruleVerdict.ruleMode || ruleMode(ruleVerdict.ruleId, config.rules));
|
|
@@ -4556,6 +4641,7 @@ async function main() {
|
|
|
4556
4641
|
? 'Guard: ' + guardReason + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \u2014 resolve the violation in code yourself.'
|
|
4557
4642
|
: 'Guard: ' + guardReason + '\\nAsk the user for explicit consent before retrying.';
|
|
4558
4643
|
if (cweBlock) denyReason += '\\nAlso resolve these CWE weaknesses in code:\\n' + cweBlock;
|
|
4644
|
+
if (taskDeny) denyReason += '\\nThis edit ALSO contradicts the active task \u2014 fix that too:\\n' + taskDeny;
|
|
4559
4645
|
dispatchCapture(jwt, 'edit', 'block', ruleVerdict.severity || 'critical', ruleVerdict.category || 'security',
|
|
4560
4646
|
toolName, gitRepo, sessionId, config.captureDepth, {
|
|
4561
4647
|
command: editContent, reasoning: guardReason,
|
|
@@ -4563,17 +4649,19 @@ async function main() {
|
|
|
4563
4649
|
ccModel: captureModel, codeChange: editDiff, ...lineMetrics,
|
|
4564
4650
|
});
|
|
4565
4651
|
outputJson({
|
|
4566
|
-
systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 blocked: ' + guardReason + (cweFindings.length ? ' (+' + cweFindings.length + ' CWE)' : ''),
|
|
4652
|
+
systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 blocked: ' + guardReason + (cweFindings.length ? ' (+' + cweFindings.length + ' CWE)' : '') + (taskDeny ? ' (+task)' : ''),
|
|
4567
4653
|
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },
|
|
4568
4654
|
});
|
|
4569
4655
|
return;
|
|
4570
4656
|
}
|
|
4571
4657
|
|
|
4572
|
-
// Rules clean but CWE weakness(es) \u2192 fix-mode block (resolve in code
|
|
4658
|
+
// Rules clean but CWE weakness(es) \u2192 fix-mode block (resolve in code; task drift
|
|
4659
|
+
// folded in too if this edit also contradicts the active task).
|
|
4573
4660
|
if (cweFindings.length > 0) {
|
|
4574
|
-
|
|
4661
|
+
let ctx = 'CWE: ' + cweBlock + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \u2014 resolve the weakness in code yourself.';
|
|
4662
|
+
if (taskDeny) ctx += '\\nThis edit ALSO contradicts the active task \u2014 fix that too:\\n' + taskDeny;
|
|
4575
4663
|
outputJson({
|
|
4576
|
-
systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 blocked (' + cweFindings.map(f => f.id).join(', ') + ')',
|
|
4664
|
+
systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 blocked (' + cweFindings.map(f => f.id).join(', ') + ')' + (taskDeny ? ' (+task)' : ''),
|
|
4577
4665
|
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },
|
|
4578
4666
|
});
|
|
4579
4667
|
return;
|
|
@@ -4587,8 +4675,15 @@ async function main() {
|
|
|
4587
4675
|
ccModel: captureModel, codeChange: editDiff, ...lineMetrics,
|
|
4588
4676
|
});
|
|
4589
4677
|
const cPassLine = tagStr + ' editGuard ' + fileShort + ' \u2192 pass: ' + (ruleVerdict.reason || 'clean (rules + CWE)');
|
|
4590
|
-
|
|
4591
|
-
|
|
4678
|
+
if (cTask.decision === 'deny') {
|
|
4679
|
+
// Cloud parity with local: a task CONTRADICTION hard-blocks the edit and
|
|
4680
|
+
// tells the AGENT (additionalContext) to fix it before proceeding.
|
|
4681
|
+
outputJson({ systemMessage: cPassLine + (cTaskSys ? '\\n' + cTaskSys : ''), hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: cTask.additionalContext || cTaskSys, additionalContext: cTask.additionalContext || cTaskSys } });
|
|
4682
|
+
} else {
|
|
4683
|
+
// On-track / off-spec: pass, but pipe any task additionalContext to the AGENT
|
|
4684
|
+
// (not just systemMessage to the user) so drift/regression actually steers.
|
|
4685
|
+
outputJson({ systemMessage: cPassLine + (cTaskSys ? '\\n' + cTaskSys : ''), hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro combined edit judge (rules + CWE). ' + (ruleVerdict.reason || 'clean') + (cTask.additionalContext ? '\\n[task] ' + cTask.additionalContext : '') } });
|
|
4686
|
+
}
|
|
4592
4687
|
return;
|
|
4593
4688
|
}
|
|
4594
4689
|
|
|
@@ -4634,8 +4729,13 @@ async function main() {
|
|
|
4634
4729
|
ccModel: captureModel, codeChange: editDiff, ...lineMetrics,
|
|
4635
4730
|
});
|
|
4636
4731
|
const passLine = tagStr + ' editGuard ' + fileShort + ' \u2192 pass: ' + (verdict.reason || 'no policy violations detected');
|
|
4637
|
-
const
|
|
4638
|
-
|
|
4732
|
+
const rTask = await taskDriftP;
|
|
4733
|
+
const rTaskSys = rTask.systemMessage;
|
|
4734
|
+
if (rTask.decision === 'deny') {
|
|
4735
|
+
outputJson({ systemMessage: passLine + (rTaskSys ? '\\n' + rTaskSys : ''), hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: rTask.additionalContext || rTaskSys, additionalContext: rTask.additionalContext || rTaskSys } });
|
|
4736
|
+
} else {
|
|
4737
|
+
outputJson({ systemMessage: passLine + (rTaskSys ? '\\n' + rTaskSys : ''), hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local edit judge. ' + (verdict.reason || 'no policy violations detected') + (rTask.additionalContext ? '\\n[task] ' + rTask.additionalContext : '') } });
|
|
4738
|
+
}
|
|
4639
4739
|
return;
|
|
4640
4740
|
}
|
|
4641
4741
|
|
|
@@ -6466,6 +6566,7 @@ import {
|
|
|
6466
6566
|
loadJwt, detectRepo, loadConfig, tag, readStdin, aggregateUsage, cleanupSessionLog,
|
|
6467
6567
|
outputJson, outputEmpty, shipCloud, setupCursorHookSignals, hookSessionId, GATEWAY_URL,
|
|
6468
6568
|
resolveTranscriptPath, emitUsageTick, cursorModelFromPayload, log, synkroFilePresent,
|
|
6569
|
+
cloudTaskComplete,
|
|
6469
6570
|
} from './_synkro-common.ts';
|
|
6470
6571
|
|
|
6471
6572
|
async function main() {
|
|
@@ -6517,10 +6618,20 @@ async function main() {
|
|
|
6517
6618
|
|
|
6518
6619
|
cleanupSessionLog(sessionId);
|
|
6519
6620
|
|
|
6621
|
+
// Cloud FINAL CHECK: the agent stopped after editing \u2014 sweep the active task,
|
|
6622
|
+
// verify EVERY requirement against the actual written code, and retire if all met.
|
|
6623
|
+
// Returns '' when there's no active task (cheap getActiveTask short-circuit) or in
|
|
6624
|
+
// local mode. stop_hook_active passes through so handleTaskComplete's stop-loop guard
|
|
6625
|
+
// works (it bails when already inside a stop loop).
|
|
6626
|
+
const taskMsg = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud'
|
|
6627
|
+
? await cloudTaskComplete({ payload: { stop_hook_active: payload.stop_hook_active === true }, harness: 'claude_code', cwd: gitRepo, sessionId }, jwt)
|
|
6628
|
+
: '';
|
|
6629
|
+
const taskSuffix = taskMsg ? '\\n' + taskMsg : '';
|
|
6630
|
+
|
|
6520
6631
|
if (!findings) {
|
|
6521
|
-
outputJson({ systemMessage: tagStr + ' stop \u2192 0 issues across ' + edits + ' edit(s), session complete' });
|
|
6632
|
+
outputJson({ systemMessage: tagStr + ' stop \u2192 0 issues across ' + edits + ' edit(s), session complete' + taskSuffix });
|
|
6522
6633
|
} else {
|
|
6523
|
-
outputJson({ systemMessage: tagStr + ' stop \u2192 ' + findings + ' finding(s): ' + autoFixed + ' auto-fixed, ' + open + ' open' });
|
|
6634
|
+
outputJson({ systemMessage: tagStr + ' stop \u2192 ' + findings + ' finding(s): ' + autoFixed + ' auto-fixed, ' + open + ' open' + taskSuffix });
|
|
6524
6635
|
}
|
|
6525
6636
|
} catch (err) {
|
|
6526
6637
|
log('stopSummary error: ' + String(err));
|
|
@@ -6592,7 +6703,7 @@ async function main() {
|
|
|
6592
6703
|
|
|
6593
6704
|
const fakeConfig: HookConfig = { captureDepth: 'local_only', tier: 'standard', silent, policyName, rules: [], scanExemptions: [], gradingMode, storageMode: process.env.SYNKRO_STORAGE_MODE || 'local' };
|
|
6594
6705
|
const tagStr = tag(rt, fakeConfig);
|
|
6595
|
-
const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : deployCloud ? 'cloud container
|
|
6706
|
+
const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : deployCloud ? 'cloud container' : isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');
|
|
6596
6707
|
|
|
6597
6708
|
if (!jwt) {
|
|
6598
6709
|
outputJson({ systemMessage: routeLine });
|
|
@@ -11299,7 +11410,7 @@ function writeConfigEnv(opts) {
|
|
|
11299
11410
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11300
11411
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11301
11412
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11302
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
11413
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.97")}`
|
|
11303
11414
|
];
|
|
11304
11415
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11305
11416
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -11472,7 +11583,7 @@ async function printWorkerDebug(base, jwt2) {
|
|
|
11472
11583
|
}
|
|
11473
11584
|
}
|
|
11474
11585
|
async function verifyCloudGrader(jwt2) {
|
|
11475
|
-
const base = (process.env.SYNKRO_CONTAINERS_URL || "https://
|
|
11586
|
+
const base = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
11476
11587
|
console.log(" Warming the cloud grader (booting container + checking workers)...");
|
|
11477
11588
|
const deadline = Date.now() + 18e4;
|
|
11478
11589
|
let healthy = 0;
|
|
@@ -11676,7 +11787,7 @@ async function recycleCloudContainer() {
|
|
|
11676
11787
|
console.error("No access token \u2014 run `synkro login`.");
|
|
11677
11788
|
return;
|
|
11678
11789
|
}
|
|
11679
|
-
const base = (process.env.SYNKRO_CONTAINERS_URL || "https://
|
|
11790
|
+
const base = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
11680
11791
|
console.log("Synkro: recycling the cloud grader container (destroy \u2192 fresh boot)\u2026");
|
|
11681
11792
|
try {
|
|
11682
11793
|
const r = await fetch(`${base}/recycle`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(3e4) });
|
|
@@ -15327,7 +15438,7 @@ To change:`);
|
|
|
15327
15438
|
console.log(" BYOK grading uses your own provider key \u2014 register one in the");
|
|
15328
15439
|
console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.");
|
|
15329
15440
|
} else if (inferenceValue === "cloud") {
|
|
15330
|
-
console.log(" Grading runs on the Synkro cloud worker pool
|
|
15441
|
+
console.log(" Grading runs on the Synkro cloud worker pool.");
|
|
15331
15442
|
}
|
|
15332
15443
|
if (inferenceValue !== "cloud") await reconcileContainer();
|
|
15333
15444
|
}
|
|
@@ -15364,7 +15475,7 @@ var args = process.argv.slice(2);
|
|
|
15364
15475
|
var cmd = args[0] || "";
|
|
15365
15476
|
var subArgs = args.slice(1);
|
|
15366
15477
|
function printVersion() {
|
|
15367
|
-
console.log("1.6.
|
|
15478
|
+
console.log("1.6.97");
|
|
15368
15479
|
}
|
|
15369
15480
|
function printHelp2() {
|
|
15370
15481
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|