@synkro-sh/cli 1.6.86 → 1.6.88
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 +114 -19
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -2143,10 +2143,12 @@ export function appendSessionAction(sessionId: string, entry: SessionAction): vo
|
|
|
2143
2143
|
appendFileSync(logPath, JSON.stringify(entry) + '\\n', 'utf-8');
|
|
2144
2144
|
} catch {}
|
|
2145
2145
|
|
|
2146
|
-
// Cloud:
|
|
2147
|
-
//
|
|
2146
|
+
// Cloud: stream the action to the session_actions hypertable (salience tagged
|
|
2147
|
+
// server-side, indexed) so the grade path pulls a BOUNDED indexed slice instead
|
|
2148
|
+
// of re-reading a multi-GB log \u2014 what lets us scale to million-tool-call
|
|
2149
|
+
// sessions. Fire-and-forget, idempotent by (session, step).
|
|
2148
2150
|
if (deployIsCloud()) {
|
|
2149
|
-
|
|
2151
|
+
shipCloud(loadJwt(), '/api/v1/cli/session-action', { session_id: sessionId, step, tool: entry.tool, summary: entry.summary, file: entry.file, outcome: entry.outcome });
|
|
2150
2152
|
return;
|
|
2151
2153
|
}
|
|
2152
2154
|
const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';
|
|
@@ -2173,8 +2175,68 @@ export function readSessionLog(sessionId: string): SessionAction[] {
|
|
|
2173
2175
|
} catch { return []; }
|
|
2174
2176
|
}
|
|
2175
2177
|
|
|
2176
|
-
|
|
2178
|
+
const RETRIEVAL_RECENT_N = 40;
|
|
2179
|
+
const PROCESS_KW = ['test', 'typecheck', 'tsc', 'lint', 'build', 'migrate', 'migration', 'deploy', 'publish', 'push', 'seed'];
|
|
2180
|
+
|
|
2181
|
+
// Is this session action security- or rule-relevant (i.e. can it participate in a
|
|
2182
|
+
// violation)? Conservative net \u2014 uses substring checks (no regex, to stay safe
|
|
2183
|
+
// inside the materialized-hook template). Plus rule-awareness: an action that
|
|
2184
|
+
// performs a process keyword an active rule actually mentions (e.g. a "pnpm test"
|
|
2185
|
+
// when a rule is about tests) is kept so precondition rules stay resolvable.
|
|
2186
|
+
function actionSalience(a: SessionAction, ruleText: string): string | null {
|
|
2187
|
+
const x = ((a.tool || '') + ' ' + (a.file || '') + ' ' + (a.summary || '')).toLowerCase();
|
|
2188
|
+
const has = (arr: string[]): boolean => { for (const k of arr) if (x.indexOf(k) >= 0) return true; return false; };
|
|
2189
|
+
if (has(['.env', 'credential', 'secret', 'token', '.pem', '.key', 'password', 'api key', 'api_key', 'api-key', 'authorization', 'bearer', 'oauth', '.aws'])) return 'secret';
|
|
2190
|
+
if (has(['curl ', 'wget ', 'ssh ', 'scp ', 'netcat', 'http://', 'https://', 'fetch('])) return 'network';
|
|
2191
|
+
if (has(['rm -rf', 'rm -r ', 'drop table', 'drop database', 'truncate', 'chmod', 'chown', 'sudo', 'kill ', 'wrangler', '--force', 'deploy', 'publish', 'git push'])) return 'destructive';
|
|
2192
|
+
for (const kw of PROCESS_KW) if (ruleText.indexOf(kw) >= 0 && x.indexOf(kw) >= 0) return 'rule';
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// Cloud session log \u2014 retrieval style (validated 4/4 vs the last-200 dump in the
|
|
2197
|
+
// shadow corpus). Keeps EVERY security-salient + rule-relevant action in order
|
|
2198
|
+
// regardless of age (so cross-turn violations the dump's 200-window drops are
|
|
2199
|
+
// still seen), a recent verbatim window, and a COMPLETENESS-asserting summary of
|
|
2200
|
+
// the routine remainder (the assertion is what lets the grader trust the collapsed
|
|
2201
|
+
// part is benign \u2014 safe only because the salience net above is conservative).
|
|
2202
|
+
function compressSessionLogRetrieval(actions: SessionAction[], rules: Rule[]): string {
|
|
2203
|
+
if (actions.length === 0) return '';
|
|
2204
|
+
const ruleText = (rules || []).map(r => String((r as any).text || '').toLowerCase()).join(' ');
|
|
2205
|
+
const total = actions.length;
|
|
2206
|
+
const recentStart = Math.max(0, total - RETRIEVAL_RECENT_N);
|
|
2207
|
+
const salient: Array<{ a: SessionAction; s: string; i: number }> = [];
|
|
2208
|
+
const noise: Record<string, number> = {};
|
|
2209
|
+
for (let i = 0; i < recentStart; i++) {
|
|
2210
|
+
const a = actions[i];
|
|
2211
|
+
const s = actionSalience(a, ruleText);
|
|
2212
|
+
if (s) salient.push({ a, s, i });
|
|
2213
|
+
else noise[a.tool] = (noise[a.tool] || 0) + 1;
|
|
2214
|
+
}
|
|
2215
|
+
const lines: string[] = ['SESSION HISTORY (' + total + ' actions; security-salient + recent, in order):'];
|
|
2216
|
+
if (salient.length) {
|
|
2217
|
+
lines.push(' --- relevant (any point this session) ---');
|
|
2218
|
+
for (const r of salient) {
|
|
2219
|
+
const target = r.a.file || (r.a.summary || '').slice(0, 80);
|
|
2220
|
+
lines.push(' ' + (r.i + 1) + '. [' + r.s + '] ' + r.a.tool + ' ' + target + (r.a.outcome ? ' -> ' + r.a.outcome : ''));
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
const noiseN = Object.values(noise).reduce((n, c) => n + c, 0);
|
|
2224
|
+
if (noiseN) {
|
|
2225
|
+
lines.push(' [' + noiseN + ' earlier routine actions (' + Object.entries(noise).map(([t, c]) => c + ' ' + t).join(', ') + '). The relevant list above is COMPLETE: every credential/secret read, outbound network call, and destructive or rule-gated action this session is listed there; these ' + noiseN + ' are non-salient in-repo reads/edits/builds only.]');
|
|
2226
|
+
}
|
|
2227
|
+
lines.push(' --- recent ---');
|
|
2228
|
+
for (let i = recentStart; i < total; i++) {
|
|
2229
|
+
const a = actions[i];
|
|
2230
|
+
lines.push(' ' + (i + 1) + '. ' + (a.summary || a.tool) + (a.outcome ? ' -> ' + a.outcome : ''));
|
|
2231
|
+
}
|
|
2232
|
+
return lines.join('\\n');
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
export function compressSessionLog(actions: SessionAction[], rules?: Rule[]): string {
|
|
2177
2236
|
if (actions.length === 0) return '';
|
|
2237
|
+
// Cloud grade path passes the active rules \u2192 retrieval-style context. Local
|
|
2238
|
+
// (no rules arg, or local storage mode) keeps the dump below, UNTOUCHED.
|
|
2239
|
+
if (rules !== undefined && !isLocalStorageMode()) return compressSessionLogRetrieval(actions, rules);
|
|
2178
2240
|
const total = actions.length;
|
|
2179
2241
|
const lines: string[] = [];
|
|
2180
2242
|
|
|
@@ -2214,6 +2276,39 @@ export function compressSessionLog(actions: SessionAction[]): string {
|
|
|
2214
2276
|
return 'SESSION HISTORY (' + total + ' actions):\\n' + lines.join('\\n');
|
|
2215
2277
|
}
|
|
2216
2278
|
|
|
2279
|
+
// Cloud: fetch the BOUNDED, indexed session context from Timescale (salient +
|
|
2280
|
+
// recent + complete summary) \u2014 flat cost at any session length. Returns null if
|
|
2281
|
+
// the session isn't streamed yet (brand-new / streaming lagging) so the caller
|
|
2282
|
+
// falls back to the local path.
|
|
2283
|
+
async function fetchSessionContext(sessionId: string): Promise<string | null> {
|
|
2284
|
+
const jwt = loadJwt();
|
|
2285
|
+
if (!jwt) return null;
|
|
2286
|
+
try {
|
|
2287
|
+
const r = await fetch(GATEWAY_URL + '/api/v1/hook/session-context', {
|
|
2288
|
+
method: 'POST',
|
|
2289
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
2290
|
+
body: JSON.stringify({ session_id: sessionId }),
|
|
2291
|
+
signal: AbortSignal.timeout(3000),
|
|
2292
|
+
});
|
|
2293
|
+
if (!r.ok) return null;
|
|
2294
|
+
const d = await r.json() as { context?: string };
|
|
2295
|
+
return (d && typeof d.context === 'string' && d.context) ? d.context : null;
|
|
2296
|
+
} catch {
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// Grade-time session log. CLOUD \u2192 Timescale-backed bounded context (this is the
|
|
2302
|
+
// million-tool-call path: no full-history read); falls back to the local
|
|
2303
|
+
// retrieval compressor only if the session isn't streamed yet. LOCAL \u2192 the
|
|
2304
|
+
// on-device dump, completely UNTOUCHED.
|
|
2305
|
+
export async function sessionLogForGrade(sessionId: string, rules: Rule[]): Promise<string> {
|
|
2306
|
+
if (isLocalStorageMode()) return compressSessionLog(readSessionLog(sessionId));
|
|
2307
|
+
const ctx = await fetchSessionContext(sessionId);
|
|
2308
|
+
if (ctx) return ctx;
|
|
2309
|
+
return compressSessionLog(readSessionLog(sessionId), rules);
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2217
2312
|
export function cleanupSessionLog(sessionId: string): void {
|
|
2218
2313
|
const logPath = sessionLogPath(sessionId);
|
|
2219
2314
|
if (!logPath) return;
|
|
@@ -3961,7 +4056,7 @@ import {
|
|
|
3961
4056
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
3962
4057
|
parseVerdict, parseCombinedVerdict, combinedEditGradeEnabled, dispatchCapture, dispatchFinding, ruleMode, reconstructContent, isPathUnder, postWithRetry,
|
|
3963
4058
|
readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput,
|
|
3964
|
-
appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
4059
|
+
appendSessionAction, readSessionLog, compressSessionLog, sessionLogForGrade, log,
|
|
3965
4060
|
outputJson, outputEmpty, setupCursorHookSignals, installHookWatchdog, isEditTool, hookSessionId, GATEWAY_URL,
|
|
3966
4061
|
logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, countEditLineDelta,
|
|
3967
4062
|
captureLineMetrics, cursorModelFromPayload, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
@@ -4088,7 +4183,7 @@ async function main() {
|
|
|
4088
4183
|
if (rt === 'local') {
|
|
4089
4184
|
// \u2500\u2500\u2500 Local grading: org rules ONLY (channel 1, port 18929) \u2500\u2500\u2500
|
|
4090
4185
|
const proposedShort = proposed.slice(0, 4000);
|
|
4091
|
-
const sessionLog =
|
|
4186
|
+
const sessionLog = await sessionLogForGrade(sessionId, config.rules);
|
|
4092
4187
|
const graderContent = 'file=' + filePath + ' content=' + proposedShort;
|
|
4093
4188
|
const relevantRules = await filterRules(
|
|
4094
4189
|
ruleFilterText(graderContent, transcript.userIntent || lastPrompt),
|
|
@@ -4281,7 +4376,7 @@ async function main() {
|
|
|
4281
4376
|
recent_user_messages: transcript.recentUserMessages,
|
|
4282
4377
|
recent_messages: transcript.recentMessages,
|
|
4283
4378
|
recent_actions: transcript.recentActions,
|
|
4284
|
-
session_history:
|
|
4379
|
+
session_history: await sessionLogForGrade(sessionId, config.rules),
|
|
4285
4380
|
session_id: sessionId || null,
|
|
4286
4381
|
tool_use_id: toolUseId || null,
|
|
4287
4382
|
cwd: cwd || null,
|
|
@@ -5352,7 +5447,7 @@ import process from 'node:process';
|
|
|
5352
5447
|
import {
|
|
5353
5448
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
5354
5449
|
parseVerdict, dispatchCapture, dispatchFinding, ruleMode, postWithRetry, readStdin,
|
|
5355
|
-
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
5450
|
+
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, sessionLogForGrade, log,
|
|
5356
5451
|
outputJson, outputEmpty, setupCursorHookSignals, installHookWatchdog, isShellTool, hookSessionId, GATEWAY_URL,
|
|
5357
5452
|
logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, appendLocalTelemetry, isSafeInRepoRead,
|
|
5358
5453
|
loadSynkroFile, effectiveGraderPool, synkroFilePresent, noSynkroSkipMessage,
|
|
@@ -5520,7 +5615,7 @@ async function main() {
|
|
|
5520
5615
|
// mode was also checked up there.
|
|
5521
5616
|
|
|
5522
5617
|
if (rt === 'local') {
|
|
5523
|
-
const sessionLog =
|
|
5618
|
+
const sessionLog = await sessionLogForGrade(sessionId, config.rules);
|
|
5524
5619
|
const relevantRules = await filterRules(
|
|
5525
5620
|
ruleFilterText(command, transcript.userIntent || lastPrompt),
|
|
5526
5621
|
config.rules,
|
|
@@ -5602,7 +5697,7 @@ async function main() {
|
|
|
5602
5697
|
recent_user_messages: transcript.recentUserMessages,
|
|
5603
5698
|
recent_messages: transcript.recentMessages,
|
|
5604
5699
|
recent_actions: transcript.recentActions,
|
|
5605
|
-
session_history:
|
|
5700
|
+
session_history: await sessionLogForGrade(sessionId, config.rules),
|
|
5606
5701
|
session_id: sessionId || null,
|
|
5607
5702
|
tool_use_id: toolUseId || null,
|
|
5608
5703
|
cwd: cwd || null,
|
|
@@ -5653,7 +5748,7 @@ main();
|
|
|
5653
5748
|
import {
|
|
5654
5749
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
5655
5750
|
parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
|
|
5656
|
-
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
5751
|
+
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, sessionLogForGrade, log,
|
|
5657
5752
|
outputJson, outputEmpty, setupCursorHookSignals, installHookWatchdog, isAgentTool, hookSessionId, GATEWAY_URL,
|
|
5658
5753
|
logGraderUnavailable, graderUnavailableMessage, filterRules, normalizeMode, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
5659
5754
|
loadSynkroFile, effectiveGraderPool, synkroFilePresent, noSynkroSkipMessage,
|
|
@@ -5735,7 +5830,7 @@ async function main() {
|
|
|
5735
5830
|
}
|
|
5736
5831
|
|
|
5737
5832
|
if (rt === 'local') {
|
|
5738
|
-
const sessionLog =
|
|
5833
|
+
const sessionLog = await sessionLogForGrade(sessionId, config.rules);
|
|
5739
5834
|
const agentText = 'agent=' + subagentType + ' description=' + description + ' prompt=' + prompt.slice(0, 2000);
|
|
5740
5835
|
const relevantRules = await filterRules(agentText, config.rules);
|
|
5741
5836
|
const graderPrompt = [
|
|
@@ -5808,7 +5903,7 @@ async function main() {
|
|
|
5808
5903
|
recent_user_messages: transcript.recentUserMessages,
|
|
5809
5904
|
recent_messages: transcript.recentMessages,
|
|
5810
5905
|
recent_actions: transcript.recentActions,
|
|
5811
|
-
session_history:
|
|
5906
|
+
session_history: await sessionLogForGrade(sessionId, config.rules),
|
|
5812
5907
|
session_id: sessionId || null,
|
|
5813
5908
|
tool_use_id: toolUseId || null,
|
|
5814
5909
|
cwd: cwd || null,
|
|
@@ -5846,7 +5941,7 @@ main();
|
|
|
5846
5941
|
PLAN_JUDGE_TS = `#!/usr/bin/env bun
|
|
5847
5942
|
import {
|
|
5848
5943
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
5849
|
-
parseVerdict, dispatchCapture, appendSessionAction, readSessionLog, compressSessionLog, postWithRetry, readStdin, log,
|
|
5944
|
+
parseVerdict, dispatchCapture, appendSessionAction, readSessionLog, compressSessionLog, sessionLogForGrade, postWithRetry, readStdin, log,
|
|
5850
5945
|
outputJson, outputEmpty, setupCursorHookSignals, installHookWatchdog, isPlanTool, hookSessionId, GATEWAY_URL,
|
|
5851
5946
|
filterRules, graderUnavailableMessage, logGraderUnavailable, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
5852
5947
|
loadSynkroFile, effectiveGraderPool, synkroFilePresent, noSynkroSkipMessage,
|
|
@@ -5953,7 +6048,7 @@ async function main() {
|
|
|
5953
6048
|
}
|
|
5954
6049
|
|
|
5955
6050
|
if (rt === 'local') {
|
|
5956
|
-
const sessionLog =
|
|
6051
|
+
const sessionLog = await sessionLogForGrade(sessionId, config.rules);
|
|
5957
6052
|
const relevantRules = await filterRules(plan.slice(0, 2000), config.rules);
|
|
5958
6053
|
const graderPrompt = [
|
|
5959
6054
|
'Working directory: ' + (cwd || '.'),
|
|
@@ -6386,7 +6481,7 @@ import {
|
|
|
6386
6481
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
6387
6482
|
parseVerdict, dispatchCapture, dispatchFinding, ruleMode, normalizeMode, filterRules, ruleFilterText,
|
|
6388
6483
|
isSafeInRepoRead, resolveTranscriptPath, postWithRetry, readStdin, hashCommand,
|
|
6389
|
-
extractTranscript, readLastPrompt, readSessionLog, compressSessionLog,
|
|
6484
|
+
extractTranscript, readLastPrompt, readSessionLog, compressSessionLog, sessionLogForGrade,
|
|
6390
6485
|
appendLocalTelemetry, logGraderUnavailable, graderUnavailableMessage, log, GATEWAY_URL,
|
|
6391
6486
|
loadSynkroFile, effectiveGraderPool, synkroFilePresent,
|
|
6392
6487
|
type Rule,
|
|
@@ -6559,7 +6654,7 @@ async function main() {
|
|
|
6559
6654
|
}
|
|
6560
6655
|
|
|
6561
6656
|
if (rt === 'local') {
|
|
6562
|
-
const sessionLog =
|
|
6657
|
+
const sessionLog = await sessionLogForGrade(sessionId, config.rules);
|
|
6563
6658
|
const relevantRules = await filterRules(
|
|
6564
6659
|
ruleFilterText(command, transcript.userIntent || lastPrompt),
|
|
6565
6660
|
config.rules,
|
|
@@ -10856,7 +10951,7 @@ function writeConfigEnv(opts) {
|
|
|
10856
10951
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
10857
10952
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
10858
10953
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
10859
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
10954
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.88")}`
|
|
10860
10955
|
];
|
|
10861
10956
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
10862
10957
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -14899,7 +14994,7 @@ var args = process.argv.slice(2);
|
|
|
14899
14994
|
var cmd = args[0] || "";
|
|
14900
14995
|
var subArgs = args.slice(1);
|
|
14901
14996
|
function printVersion() {
|
|
14902
|
-
console.log("1.6.
|
|
14997
|
+
console.log("1.6.88");
|
|
14903
14998
|
}
|
|
14904
14999
|
function printHelp2() {
|
|
14905
15000
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|