@synkro-sh/cli 1.6.96 → 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 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://containers.synkro.sh';
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
@@ -1832,6 +1832,39 @@ export async function cloudTaskDrift(envelope: Record<string, unknown>, jwt: str
1832
1832
  }
1833
1833
  }
1834
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
+ }
1856
+ const j = await resp.json() as { systemMessage?: unknown };
1857
+ return typeof j?.systemMessage === 'string' ? j.systemMessage : '';
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
+ }
1866
+ }
1867
+
1835
1868
  // Option B: when a cloud grade fails (esp. a timeout), the REAL reason lives
1836
1869
  // inside the container, not on the wire \u2014 the client only ever sees "timed out".
1837
1870
  // Pull the org's per-worker claude/cursor sick reasons + log tails from the
@@ -6533,6 +6566,7 @@ import {
6533
6566
  loadJwt, detectRepo, loadConfig, tag, readStdin, aggregateUsage, cleanupSessionLog,
6534
6567
  outputJson, outputEmpty, shipCloud, setupCursorHookSignals, hookSessionId, GATEWAY_URL,
6535
6568
  resolveTranscriptPath, emitUsageTick, cursorModelFromPayload, log, synkroFilePresent,
6569
+ cloudTaskComplete,
6536
6570
  } from './_synkro-common.ts';
6537
6571
 
6538
6572
  async function main() {
@@ -6584,10 +6618,20 @@ async function main() {
6584
6618
 
6585
6619
  cleanupSessionLog(sessionId);
6586
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
+
6587
6631
  if (!findings) {
6588
- 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 });
6589
6633
  } else {
6590
- 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 });
6591
6635
  }
6592
6636
  } catch (err) {
6593
6637
  log('stopSummary error: ' + String(err));
@@ -6659,7 +6703,7 @@ async function main() {
6659
6703
 
6660
6704
  const fakeConfig: HookConfig = { captureDepth: 'local_only', tier: 'standard', silent, policyName, rules: [], scanExemptions: [], gradingMode, storageMode: process.env.SYNKRO_STORAGE_MODE || 'local' };
6661
6705
  const tagStr = tag(rt, fakeConfig);
6662
- const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : deployCloud ? 'cloud container (containers.synkro.sh)' : isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');
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)');
6663
6707
 
6664
6708
  if (!jwt) {
6665
6709
  outputJson({ systemMessage: routeLine });
@@ -11366,7 +11410,7 @@ function writeConfigEnv(opts) {
11366
11410
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
11367
11411
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
11368
11412
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
11369
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.96")}`
11413
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.97")}`
11370
11414
  ];
11371
11415
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
11372
11416
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -11539,7 +11583,7 @@ async function printWorkerDebug(base, jwt2) {
11539
11583
  }
11540
11584
  }
11541
11585
  async function verifyCloudGrader(jwt2) {
11542
- const base = (process.env.SYNKRO_CONTAINERS_URL || "https://containers.synkro.sh").replace(/\/$/, "");
11586
+ const base = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
11543
11587
  console.log(" Warming the cloud grader (booting container + checking workers)...");
11544
11588
  const deadline = Date.now() + 18e4;
11545
11589
  let healthy = 0;
@@ -11743,7 +11787,7 @@ async function recycleCloudContainer() {
11743
11787
  console.error("No access token \u2014 run `synkro login`.");
11744
11788
  return;
11745
11789
  }
11746
- const base = (process.env.SYNKRO_CONTAINERS_URL || "https://containers.synkro.sh").replace(/\/$/, "");
11790
+ const base = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
11747
11791
  console.log("Synkro: recycling the cloud grader container (destroy \u2192 fresh boot)\u2026");
11748
11792
  try {
11749
11793
  const r = await fetch(`${base}/recycle`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(3e4) });
@@ -15394,7 +15438,7 @@ To change:`);
15394
15438
  console.log(" BYOK grading uses your own provider key \u2014 register one in the");
15395
15439
  console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.");
15396
15440
  } else if (inferenceValue === "cloud") {
15397
- console.log(" Grading runs on the Synkro cloud worker pool (containers.synkro.sh).");
15441
+ console.log(" Grading runs on the Synkro cloud worker pool.");
15398
15442
  }
15399
15443
  if (inferenceValue !== "cloud") await reconcileContainer();
15400
15444
  }
@@ -15431,7 +15475,7 @@ var args = process.argv.slice(2);
15431
15475
  var cmd = args[0] || "";
15432
15476
  var subArgs = args.slice(1);
15433
15477
  function printVersion() {
15434
- console.log("1.6.96");
15478
+ console.log("1.6.97");
15435
15479
  }
15436
15480
  function printHelp2() {
15437
15481
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents