sneakoscope 4.0.1 → 4.0.3

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.
Files changed (54) hide show
  1. package/README.md +7 -7
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -0
  7. package/dist/cli/global-mode-router.js +25 -0
  8. package/dist/cli/router.js +12 -0
  9. package/dist/commands/codex-app.js +10 -1
  10. package/dist/commands/codex.js +15 -1
  11. package/dist/core/build/build-once-runner.js +49 -16
  12. package/dist/core/codex-app/glm-model-profile.js +2 -0
  13. package/dist/core/codex-app/glm-profile-installer.js +61 -0
  14. package/dist/core/codex-app/glm-profile-schema.js +24 -0
  15. package/dist/core/codex-control/codex-0141-capability.js +95 -0
  16. package/dist/core/commands/daemon-command.js +1 -1
  17. package/dist/core/commands/glm-command.js +5 -0
  18. package/dist/core/daemon/sksd-client.js +7 -2
  19. package/dist/core/daemon/sksd-ipc.js +13 -3
  20. package/dist/core/daemon/sksd.js +62 -3
  21. package/dist/core/doctor/doctor-dirty-planner.js +97 -7
  22. package/dist/core/doctor/doctor-transaction.js +21 -5
  23. package/dist/core/fsx.js +1 -1
  24. package/dist/core/providers/glm/glm-52-profile.js +30 -0
  25. package/dist/core/providers/glm/glm-52-request.js +34 -0
  26. package/dist/core/providers/glm/glm-52-response-guard.js +34 -0
  27. package/dist/core/providers/glm/glm-52-settings.js +26 -0
  28. package/dist/core/providers/glm/glm-mad-mode.js +242 -0
  29. package/dist/core/providers/openrouter/openrouter-client.js +44 -0
  30. package/dist/core/providers/openrouter/openrouter-error.js +37 -0
  31. package/dist/core/providers/openrouter/openrouter-secret-store.js +113 -0
  32. package/dist/core/providers/openrouter/openrouter-types.js +2 -0
  33. package/dist/core/release/extreme-parallel-scheduler.js +124 -11
  34. package/dist/core/release/gate-pack-assertion.js +58 -0
  35. package/dist/core/release/gate-pack-fixture-cache.js +38 -2
  36. package/dist/core/release/gate-pack-manifest.js +2 -2
  37. package/dist/core/release/gate-pack-runner.js +9 -93
  38. package/dist/core/release/release-gate-cache-v2.js +71 -0
  39. package/dist/core/release/release-gate-dag.js +50 -5
  40. package/dist/core/release/release-gate-node.js +2 -0
  41. package/dist/core/release/release-gate-resource-governor.js +2 -0
  42. package/dist/core/release/resource-class-budget.js +1 -0
  43. package/dist/core/results.js +2 -0
  44. package/dist/core/secret-redaction.js +4 -0
  45. package/dist/core/security/redact-secrets.js +15 -0
  46. package/dist/core/triwiki/triwiki-affected-graph.js +35 -3
  47. package/dist/core/triwiki/triwiki-gate-impact-map.js +2 -0
  48. package/dist/core/triwiki/triwiki-proof-bank.js +12 -2
  49. package/dist/core/triwiki/triwiki-sla-certificate.js +3 -0
  50. package/dist/core/version.js +1 -1
  51. package/dist/scripts/release-4002-required-gates.js +14 -0
  52. package/dist/scripts/release-gate-dag-runner.js +2 -1
  53. package/dist/scripts/release-gate-existence-audit.js +1 -2
  54. package/package.json +16 -3
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { hashJson } from '../triwiki/triwiki-cache-key.js';
4
+ import { triWikiProofBankDir } from '../triwiki/triwiki-proof-bank.js';
4
5
  export const DOCTOR_DIRTY_PLAN_SCHEMA = 'sks.doctor-dirty-plan.v1';
5
6
  export function planDoctorDirtyRepair(root, phaseIds) {
6
7
  const phases = phaseIds.map((id) => {
@@ -13,6 +14,9 @@ export function planDoctorDirtyRepair(root, phaseIds) {
13
14
  if (markerState.input_hash !== inputHash) {
14
15
  return { id, status: 'dirty', reason: 'input_hash_changed', input_hash: inputHash, last_clean_proof_id: markerState.proof_id, postcheck_required: postcheckRequired };
15
16
  }
17
+ if (markerState.proof_id && !proofExists(root, markerState.proof_id)) {
18
+ return { id, status: 'dirty', reason: 'clean_proof_missing', input_hash: inputHash, last_clean_proof_id: markerState.proof_id, postcheck_required: postcheckRequired };
19
+ }
16
20
  if (postcheckRequired && !markerState.postcheck_passed) {
17
21
  return { id, status: 'dirty', reason: 'postcheck_required', input_hash: inputHash, last_clean_proof_id: markerState.proof_id, postcheck_required: true };
18
22
  }
@@ -23,15 +27,17 @@ export function planDoctorDirtyRepair(root, phaseIds) {
23
27
  root,
24
28
  phases,
25
29
  dirty_count: phases.filter((phase) => phase.status === 'dirty').length,
26
- clean_count: phases.filter((phase) => phase.status === 'clean').length
30
+ clean_count: phases.filter((phase) => phase.status === 'clean').length,
31
+ semantic_dirty_plan_path: dirtyPlanPath(root)
27
32
  };
28
33
  writeDirtyPlan(root, plan);
29
34
  return plan;
30
35
  }
31
- export function markDoctorPhaseClean(root, id) {
36
+ export function markDoctorPhaseClean(root, id, proofId = `doctor-${id}-${Date.now()}`, postcheckPassed = true) {
32
37
  const file = markerPath(root, id);
33
38
  fs.mkdirSync(path.dirname(file), { recursive: true });
34
- fs.writeFileSync(file, `${JSON.stringify({ schema: 'sks.doctor-dirty-clean-proof.v1', proof_id: `doctor-${id}-${Date.now()}`, cleaned_at: new Date().toISOString(), input_hash: phaseInputHash(root, id), postcheck_passed: true }, null, 2)}\n`);
39
+ fs.writeFileSync(file, `${JSON.stringify({ schema: 'sks.doctor-dirty-clean-proof.v1', proof_id: proofId, cleaned_at: new Date().toISOString(), input_hash: phaseInputHash(root, id), postcheck_passed: postcheckPassed }, null, 2)}\n`);
40
+ return proofId;
35
41
  }
36
42
  export function isDoctorPhaseClean(plan, id) {
37
43
  return plan?.phases.find((phase) => phase.id === id)?.status === 'clean';
@@ -58,10 +64,9 @@ function phaseInputHash(root, id) {
58
64
  const file = path.join(root, rel);
59
65
  if (!fs.existsSync(file))
60
66
  return { rel, hash: 'missing' };
61
- const stat = fs.statSync(file);
62
- return { rel, hash: stat.isDirectory() ? `dir:${stat.mtimeMs}` : hashJson({ size: stat.size, mtimeMs: stat.mtimeMs, text: stat.size < 512_000 ? fs.readFileSync(file, 'utf8') : '' }) };
67
+ return hashSemanticPath(root, rel);
63
68
  });
64
- return hashJson({ id, files, env: phaseEnvPresence(id) });
69
+ return hashJson({ id, files, env: phaseEnvPresence(id), semantic_state: phaseSemanticState(root, id) });
65
70
  }
66
71
  function phaseInputFiles(id) {
67
72
  if (id.includes('zellij'))
@@ -84,12 +89,97 @@ function phaseEnvPresence(id) {
84
89
  const keys = id.includes('supabase') ? ['SUPABASE_ACCESS_TOKEN'] : id.includes('context7') ? ['CONTEXT7_API_KEY'] : [];
85
90
  return Object.fromEntries(keys.map((key) => [key, process.env[key] !== undefined]));
86
91
  }
92
+ function phaseSemanticState(root, id) {
93
+ const config = readTextIfSmall(path.join(root, '.codex', 'config.toml'));
94
+ return {
95
+ zellij_capability_present: id.includes('zellij') ? fs.existsSync(path.join(root, 'src', 'core', 'zellij')) : undefined,
96
+ context7_transport: id.includes('context7') ? parseMcpTransport(config, 'context7') : undefined,
97
+ startup_config_targets: id.includes('startup') ? parseConfigTargets(config) : undefined,
98
+ supabase_env_present: id.includes('supabase') ? process.env.SUPABASE_ACCESS_TOKEN !== undefined : undefined,
99
+ skill_registry_hash: id.includes('skill') ? hashSemanticPath(root, '.agents/skills').hash : undefined,
100
+ native_capability_hash: id.includes('native') ? hashSemanticPath(root, 'src/core/codex-native').hash : undefined,
101
+ secret_fingerprint_hash: id.includes('secret') ? hashJson({ has_allowlist: fs.existsSync(path.join(root, 'safety-mutation-allowlist.json')) }) : undefined
102
+ };
103
+ }
104
+ function hashSemanticPath(root, rel) {
105
+ const absolute = path.join(root, rel);
106
+ if (!fs.existsSync(absolute))
107
+ return { rel, hash: 'missing' };
108
+ const stat = fs.lstatSync(absolute);
109
+ if (stat.isDirectory()) {
110
+ const records = walkFiles(absolute).map((file) => {
111
+ const fileStat = fs.lstatSync(file);
112
+ const relative = path.relative(root, file).replace(/\\/g, '/');
113
+ return fileStat.isSymbolicLink()
114
+ ? { path: relative, mode: 'symlink', target: fs.readlinkSync(file) }
115
+ : { path: relative, mode: 'file', hash: hashFile(file), size: fileStat.size };
116
+ });
117
+ return { rel, hash: hashJson(records) };
118
+ }
119
+ if (stat.isSymbolicLink())
120
+ return { rel, hash: hashJson({ mode: 'symlink', target: fs.readlinkSync(absolute) }) };
121
+ return { rel, hash: hashFile(absolute) };
122
+ }
123
+ function walkFiles(dir) {
124
+ const out = [];
125
+ const stack = [dir];
126
+ while (stack.length) {
127
+ const current = stack.pop();
128
+ if (!current)
129
+ continue;
130
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
131
+ const absolute = path.join(current, entry.name);
132
+ if (entry.isDirectory())
133
+ stack.push(absolute);
134
+ else if (entry.isFile() || entry.isSymbolicLink())
135
+ out.push(absolute);
136
+ }
137
+ }
138
+ return out.sort();
139
+ }
140
+ function hashFile(file) {
141
+ return hashJson({ text: fs.readFileSync(file, 'utf8') });
142
+ }
143
+ function readTextIfSmall(file) {
144
+ try {
145
+ const stat = fs.statSync(file);
146
+ if (stat.size > 512_000)
147
+ return '';
148
+ return fs.readFileSync(file, 'utf8');
149
+ }
150
+ catch {
151
+ return '';
152
+ }
153
+ }
154
+ function parseMcpTransport(text, name) {
155
+ const block = text.match(new RegExp(`\\[mcp_servers\\.${name}\\]([\\s\\S]*?)(?:\\n\\[|$)`));
156
+ if (!block?.[1])
157
+ return null;
158
+ const transport = block[1].match(/transport\s*=\s*["']?([^"'\n]+)["']?/);
159
+ const command = block[1].match(/command\s*=\s*["']?([^"'\n]+)["']?/);
160
+ return transport?.[1]?.trim() || (command ? 'stdio' : 'unknown');
161
+ }
162
+ function parseConfigTargets(text) {
163
+ return [...text.matchAll(/config_file\s*=\s*["']([^"']+)["']/g)].map((match) => match[1] || '').filter(Boolean).sort();
164
+ }
165
+ function proofExists(root, proofId) {
166
+ const transaction = path.join(root, '.sneakoscope', 'reports', 'doctor-fix-transaction.json');
167
+ if (fs.existsSync(transaction) && fs.readFileSync(transaction, 'utf8').includes(proofId))
168
+ return true;
169
+ const bank = triWikiProofBankDir(root);
170
+ if (!fs.existsSync(bank))
171
+ return false;
172
+ return walkFiles(bank).some((file) => path.basename(file) === `${proofId}.json` || fs.readFileSync(file, 'utf8').includes(proofId));
173
+ }
87
174
  function phaseRequiresPostcheck(id) {
88
175
  return /zellij|context7|startup|supabase|native|secret/i.test(id);
89
176
  }
90
177
  function writeDirtyPlan(root, plan) {
91
- const file = path.join(root, '.sneakoscope', 'reports', 'doctor-dirty-plan.json');
178
+ const file = dirtyPlanPath(root);
92
179
  fs.mkdirSync(path.dirname(file), { recursive: true });
93
180
  fs.writeFileSync(file, `${JSON.stringify(plan, null, 2)}\n`);
94
181
  }
182
+ function dirtyPlanPath(root) {
183
+ return path.join(root, '.sneakoscope', 'reports', 'doctor-dirty-plan.json');
184
+ }
95
185
  //# sourceMappingURL=doctor-dirty-planner.js.map
@@ -4,6 +4,7 @@ import { isDoctorPhaseClean, markDoctorPhaseClean } from './doctor-dirty-planner
4
4
  export async function runDoctorFixTransaction(input) {
5
5
  const startedAt = nowIso();
6
6
  const phases = [];
7
+ const proofIdsUsed = [];
7
8
  let rollbackPerformed = false;
8
9
  for (const definition of input.phases) {
9
10
  const phaseStarted = nowIso();
@@ -20,10 +21,13 @@ export async function runDoctorFixTransaction(input) {
20
21
  started_at: phaseStarted
21
22
  };
22
23
  if (isDoctorPhaseClean(input.dirtyPlan, definition.id)) {
24
+ const proofId = input.dirtyPlan?.phases.find((row) => row.id === definition.id)?.last_clean_proof_id;
25
+ if (proofId)
26
+ proofIdsUsed.push(proofId);
23
27
  phases.push({
24
28
  ...phase,
25
29
  ok: true,
26
- warnings: ['dirty_plan_skipped_clean_phase'],
30
+ warnings: [`dirty_plan_skipped_clean_phase${proofId ? `:${proofId}` : ''}`],
27
31
  completed_at: nowIso(),
28
32
  duration_ms: Math.max(0, Date.now() - startedMs)
29
33
  });
@@ -59,8 +63,11 @@ export async function runDoctorFixTransaction(input) {
59
63
  }
60
64
  phase.completed_at = phase.completed_at || nowIso();
61
65
  phase.duration_ms = phase.duration_ms ?? Math.max(0, Date.now() - startedMs);
62
- if (phase.ok)
63
- markDoctorPhaseClean(input.root, definition.id);
66
+ if (phase.ok) {
67
+ const proofId = `doctor-${definition.id}-${Date.now()}`;
68
+ markDoctorPhaseClean(input.root, definition.id, proofId, true);
69
+ proofIdsUsed.push(proofId);
70
+ }
64
71
  phases.push(phase);
65
72
  }
66
73
  const writeInput = {
@@ -71,7 +78,11 @@ export async function runDoctorFixTransaction(input) {
71
78
  };
72
79
  if (input.reportPath !== undefined)
73
80
  writeInput.reportPath = input.reportPath;
74
- return writeDoctorFixTransaction(writeInput);
81
+ return writeDoctorFixTransaction({
82
+ ...writeInput,
83
+ dirtyPlan: input.dirtyPlan || null,
84
+ proofIdsUsed
85
+ });
75
86
  }
76
87
  export async function writeDoctorFixTransaction(input) {
77
88
  const root = path.resolve(input.root);
@@ -99,7 +110,12 @@ export async function writeDoctorFixTransaction(input) {
99
110
  phases,
100
111
  postcheck_ok: postcheckOk,
101
112
  rollback_performed: input.rollbackPerformed === true,
102
- raw_secret_values_recorded: false
113
+ raw_secret_values_recorded: false,
114
+ skipped_clean_phases: phases.filter((phase) => phase.warnings.some((warning) => warning.startsWith('dirty_plan_skipped_clean_phase'))).map((phase) => phase.id),
115
+ dirty_phases: input.dirtyPlan?.phases.filter((phase) => phase.status === 'dirty').map((phase) => phase.id) || phases.filter((phase) => !phase.warnings.some((warning) => warning.startsWith('dirty_plan_skipped_clean_phase'))).map((phase) => phase.id),
116
+ proof_ids_used: [...new Set(input.proofIdsUsed || [])].sort(),
117
+ saved_ms_estimate: phases.filter((phase) => phase.warnings.some((warning) => warning.startsWith('dirty_plan_skipped_clean_phase'))).length * 1000,
118
+ semantic_dirty_plan_path: input.dirtyPlan?.semantic_dirty_plan_path || null
103
119
  };
104
120
  if (input.reportPath !== null)
105
121
  await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', 'doctor-fix-transaction.json'), report).catch(() => undefined);
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '4.0.1';
8
+ export const PACKAGE_VERSION = '4.0.3';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -0,0 +1,30 @@
1
+ import { GLM_52_DEFAULT_REQUEST_SETTINGS, GLM_52_OPENROUTER_MODEL, GLM_MAD_MODE } from './glm-52-settings.js';
2
+ export const GLM_CODEX_APP_PROFILE_ID = 'sks/glm-5.2-mad';
3
+ export const GLM_CODEX_APP_PROFILE_LABEL = 'GLM 5.2 (MAD / OpenRouter)';
4
+ export function buildGlmCodexAppModelProfile() {
5
+ return {
6
+ schema: 'sks.codex-app-model-profile.v1',
7
+ id: GLM_CODEX_APP_PROFILE_ID,
8
+ label: GLM_CODEX_APP_PROFILE_LABEL,
9
+ provider: 'openrouter',
10
+ model: GLM_52_OPENROUTER_MODEL,
11
+ mode: GLM_MAD_MODE,
12
+ strictModelLock: true,
13
+ gptFallbackAllowed: false,
14
+ requiresSecret: 'openrouter-api-key',
15
+ defaultSettings: {
16
+ temperature: GLM_52_DEFAULT_REQUEST_SETTINGS.temperature,
17
+ top_p: GLM_52_DEFAULT_REQUEST_SETTINGS.top_p,
18
+ reasoning_effort: 'high',
19
+ tool_choice: 'auto',
20
+ parallel_tool_calls: false
21
+ },
22
+ codexCompatibility: {
23
+ target: 'rust-v0.141.0',
24
+ selectedExecutorPluginMcp: 'defer-to-codex-native',
25
+ duplicateAppMcpDeclarations: 'dedupe-by-codex',
26
+ cwdShellPathSemantics: 'preserve-codex-native'
27
+ }
28
+ };
29
+ }
30
+ //# sourceMappingURL=glm-52-profile.js.map
@@ -0,0 +1,34 @@
1
+ import { GLM_52_DEFAULT_REQUEST_SETTINGS, GLM_52_OPENROUTER_MODEL, clampGlm52MaxTokens } from './glm-52-settings.js';
2
+ export function buildGlm52Request(input) {
3
+ const request = {
4
+ model: GLM_52_OPENROUTER_MODEL,
5
+ messages: input.messages,
6
+ stream: input.stream ?? GLM_52_DEFAULT_REQUEST_SETTINGS.stream,
7
+ temperature: GLM_52_DEFAULT_REQUEST_SETTINGS.temperature,
8
+ top_p: GLM_52_DEFAULT_REQUEST_SETTINGS.top_p,
9
+ reasoning: { effort: input.reasoningEffort ?? 'high' },
10
+ max_tokens: clampGlm52MaxTokens(input.maxTokens),
11
+ tool_choice: input.toolChoice ?? 'auto',
12
+ parallel_tool_calls: input.parallelToolCalls ?? false,
13
+ provider: {
14
+ allow_fallbacks: false,
15
+ require_parameters: true,
16
+ sort: input.providerSort ?? 'throughput'
17
+ }
18
+ };
19
+ return {
20
+ ...request,
21
+ ...(input.tools ? { tools: input.tools } : {}),
22
+ ...(input.responseFormat ? { response_format: input.responseFormat } : {})
23
+ };
24
+ }
25
+ export function buildGlm52KeyValidationRequest() {
26
+ return buildGlm52Request({
27
+ messages: [{ role: 'user', content: 'Reply with OK.' }],
28
+ stream: false,
29
+ maxTokens: 1,
30
+ toolChoice: 'none',
31
+ parallelToolCalls: false
32
+ });
33
+ }
34
+ //# sourceMappingURL=glm-52-request.js.map
@@ -0,0 +1,34 @@
1
+ import { GLM_52_OPENROUTER_MODEL } from './glm-52-settings.js';
2
+ export function assertGlm52ActualModel(responseModel) {
3
+ if (!responseModel) {
4
+ return {
5
+ ok: false,
6
+ code: 'glm_model_missing',
7
+ requestedModel: GLM_52_OPENROUTER_MODEL,
8
+ strictModelLock: true,
9
+ gptFallbackAllowed: false
10
+ };
11
+ }
12
+ const normalized = responseModel.toLowerCase();
13
+ if (normalized === GLM_52_OPENROUTER_MODEL ||
14
+ normalized.startsWith(`${GLM_52_OPENROUTER_MODEL}-`) ||
15
+ normalized.includes('glm-5.2')) {
16
+ return {
17
+ ok: true,
18
+ code: 'ok',
19
+ actualModel: responseModel,
20
+ requestedModel: GLM_52_OPENROUTER_MODEL,
21
+ strictModelLock: true,
22
+ gptFallbackAllowed: false
23
+ };
24
+ }
25
+ return {
26
+ ok: false,
27
+ code: 'glm_model_mismatch',
28
+ actualModel: responseModel,
29
+ requestedModel: GLM_52_OPENROUTER_MODEL,
30
+ strictModelLock: true,
31
+ gptFallbackAllowed: false
32
+ };
33
+ }
34
+ //# sourceMappingURL=glm-52-response-guard.js.map
@@ -0,0 +1,26 @@
1
+ export { OPENROUTER_CHAT_COMPLETIONS_URL } from '../openrouter/openrouter-types.js';
2
+ export const GLM_52_OPENROUTER_MODEL = 'z-ai/glm-5.2';
3
+ export const GLM_MAD_MODE = 'mad-glm';
4
+ export const GLM_52_MAX_TOKENS_DEFAULT = 32768;
5
+ export const GLM_52_MAX_TOKENS_LONG = 65536;
6
+ export const GLM_52_MAX_TOKENS_XLONG = 131072;
7
+ export const GLM_52_TOP_PROVIDER_MAX_COMPLETION_TOKENS = 262144;
8
+ export const GLM_52_DEFAULT_REQUEST_SETTINGS = {
9
+ model: GLM_52_OPENROUTER_MODEL,
10
+ temperature: 1,
11
+ top_p: 0.95,
12
+ reasoning_effort: 'high',
13
+ stream: true,
14
+ provider: {
15
+ allow_fallbacks: false,
16
+ require_parameters: true
17
+ },
18
+ tool_choice: 'auto',
19
+ parallel_tool_calls: false,
20
+ max_tokens: GLM_52_MAX_TOKENS_DEFAULT
21
+ };
22
+ export function clampGlm52MaxTokens(value) {
23
+ const numeric = Number.isFinite(value) ? Math.floor(Number(value)) : GLM_52_MAX_TOKENS_DEFAULT;
24
+ return Math.max(1, Math.min(numeric, GLM_52_TOP_PROVIDER_MAX_COMPLETION_TOKENS));
25
+ }
26
+ //# sourceMappingURL=glm-52-settings.js.map
@@ -0,0 +1,242 @@
1
+ import readline from 'node:readline/promises';
2
+ import path from 'node:path';
3
+ import { stdin as input, stdout as output } from 'node:process';
4
+ import { printJson } from '../../../cli/output.js';
5
+ import { flag } from '../../../cli/args.js';
6
+ import { nowIso, writeJsonAtomic } from '../../fsx.js';
7
+ import {} from '../openrouter/openrouter-types.js';
8
+ import { sendOpenRouterChatCompletion } from '../openrouter/openrouter-client.js';
9
+ import { resolveOpenRouterApiKey, writeStoredOpenRouterKey } from '../openrouter/openrouter-secret-store.js';
10
+ import { redactOpenRouterKey } from '../../security/redact-secrets.js';
11
+ import { buildGlmCodexAppModelProfile } from './glm-52-profile.js';
12
+ import { buildGlm52KeyValidationRequest, buildGlm52Request } from './glm-52-request.js';
13
+ import { assertGlm52ActualModel } from './glm-52-response-guard.js';
14
+ import { GLM_52_OPENROUTER_MODEL, GLM_MAD_MODE, OPENROUTER_CHAT_COMPLETIONS_URL } from './glm-52-settings.js';
15
+ export async function runMadGlmMode(args = [], adapters = {}) {
16
+ const runtime = buildDefaultAdapters(adapters);
17
+ const repair = flag(args, '--repair');
18
+ const noSaveKey = flag(args, '--no-save-key');
19
+ const skipValidation = flag(args, '--skip-validation');
20
+ const json = flag(args, '--json');
21
+ const profile = buildGlmCodexAppModelProfile();
22
+ let result;
23
+ if (repair) {
24
+ const key = await runtime.promptSecret('OpenRouter API key is required for GLM 5.2 mode.\nEnter OpenRouter API key: ');
25
+ if (!key) {
26
+ result = baseResult({
27
+ status: 'blocked',
28
+ blockers: ['glm_key_prompt_cancelled'],
29
+ warnings: []
30
+ });
31
+ }
32
+ else {
33
+ if (!noSaveKey)
34
+ await runtime.writeSecret(key);
35
+ const validation = skipValidation
36
+ ? { ok: true, value: validationValue(null) }
37
+ : await runtime.validateOpenRouterKey(key);
38
+ result = validation.ok
39
+ ? baseResult({
40
+ status: 'ready',
41
+ ...(validation.value.actual_model ? { actual_model: validation.value.actual_model } : {}),
42
+ openrouter_key_source: noSaveKey ? 'prompt' : 'user-secret-store',
43
+ key_preview: redactOpenRouterKey(key),
44
+ blockers: [],
45
+ warnings: noSaveKey ? ['openrouter_key_not_saved'] : []
46
+ })
47
+ : baseResult({
48
+ status: 'blocked',
49
+ openrouter_key_source: noSaveKey ? 'prompt' : 'user-secret-store',
50
+ key_preview: redactOpenRouterKey(key),
51
+ blockers: [validation.error.code],
52
+ warnings: []
53
+ });
54
+ }
55
+ }
56
+ else {
57
+ const resolved = await resolveOpenRouterApiKey({ env: runtime.env });
58
+ if (!resolved.key && process.stdin.isTTY) {
59
+ const key = await runtime.promptSecret('OpenRouter API key is required for GLM 5.2 mode.\nEnter OpenRouter API key: ');
60
+ if (!key) {
61
+ result = baseResult({ status: 'blocked', blockers: ['glm_key_prompt_cancelled'], warnings: [] });
62
+ }
63
+ else {
64
+ const save = noSaveKey ? false : await runtime.promptConfirm('Save this key for future SKS GLM runs? [Y/n] ', true);
65
+ if (save)
66
+ await runtime.writeSecret(key);
67
+ result = baseResult({
68
+ status: 'ready',
69
+ openrouter_key_source: save ? 'user-secret-store' : 'prompt',
70
+ key_preview: redactOpenRouterKey(key),
71
+ blockers: [],
72
+ warnings: save ? [] : ['openrouter_key_not_saved']
73
+ });
74
+ }
75
+ }
76
+ else if (!resolved.key) {
77
+ result = baseResult({
78
+ status: 'blocked',
79
+ blockers: resolved.blockers,
80
+ warnings: ['set_OPENROUTER_API_KEY_or_run_sks_--mad_--glm_--repair']
81
+ });
82
+ }
83
+ else {
84
+ result = baseResult({
85
+ status: 'ready',
86
+ ...(resolved.source ? { openrouter_key_source: resolved.source } : {}),
87
+ key_preview: resolved.key_preview,
88
+ blockers: [],
89
+ warnings: resolved.warnings
90
+ });
91
+ }
92
+ }
93
+ await writeGlmModeArtifacts(runtime.cwd, result, profile, runtime.nowIso()).catch(() => undefined);
94
+ if (json)
95
+ printJson(result);
96
+ else
97
+ printHumanGlmResult(result, runtime.log);
98
+ if (!result.ok)
99
+ process.exitCode = 1;
100
+ return result;
101
+ }
102
+ function baseResult(input) {
103
+ const result = {
104
+ schema: 'sks.glm-mode-result.v1',
105
+ ok: input.blockers.length === 0 && input.status !== 'failed',
106
+ status: input.status,
107
+ mode: GLM_MAD_MODE,
108
+ provider: 'openrouter',
109
+ model: GLM_52_OPENROUTER_MODEL,
110
+ requested_model: GLM_52_OPENROUTER_MODEL,
111
+ strict_model_lock: true,
112
+ gpt_fallback_allowed: false,
113
+ codex_app_profile_id: 'sks/glm-5.2-mad',
114
+ blockers: input.blockers,
115
+ warnings: input.warnings
116
+ };
117
+ return {
118
+ ...result,
119
+ ...(input.actual_model ? { actual_model: input.actual_model } : {}),
120
+ ...(input.openrouter_key_source ? { openrouter_key_source: input.openrouter_key_source } : {}),
121
+ ...(input.key_preview !== undefined ? { key_preview: input.key_preview } : {})
122
+ };
123
+ }
124
+ function buildDefaultAdapters(overrides) {
125
+ return {
126
+ nowIso: overrides.nowIso || nowIso,
127
+ env: overrides.env || process.env,
128
+ cwd: overrides.cwd || process.cwd(),
129
+ promptSecret: overrides.promptSecret || promptLine,
130
+ promptConfirm: overrides.promptConfirm || promptConfirmLine,
131
+ writeSecret: overrides.writeSecret || (async (value) => {
132
+ await writeStoredOpenRouterKey(value);
133
+ }),
134
+ validateOpenRouterKey: overrides.validateOpenRouterKey || validateOpenRouterKey,
135
+ sendOpenRouterRequest: overrides.sendOpenRouterRequest || (async (request, key) => sendOpenRouterChatCompletion({ request, apiKey: key })),
136
+ log: overrides.log || ((message) => console.log(message))
137
+ };
138
+ }
139
+ async function validateOpenRouterKey(key) {
140
+ const response = await sendOpenRouterChatCompletion({
141
+ apiKey: key,
142
+ request: buildGlm52KeyValidationRequest()
143
+ });
144
+ if (!response.ok)
145
+ return response;
146
+ const guard = assertGlm52ActualModel(response.value.model);
147
+ if (!guard.ok) {
148
+ return {
149
+ ok: false,
150
+ error: {
151
+ code: guard.code,
152
+ message: 'GLM model lock violated.',
153
+ severity: 'blocked'
154
+ }
155
+ };
156
+ }
157
+ return { ok: true, value: validationValue(response.value.model || null) };
158
+ }
159
+ function validationValue(actualModel) {
160
+ return {
161
+ schema: 'sks.openrouter-key-validation.v1',
162
+ ok: true,
163
+ requested_model: GLM_52_OPENROUTER_MODEL,
164
+ actual_model: actualModel,
165
+ strict_model_lock: true,
166
+ gpt_fallback_allowed: false
167
+ };
168
+ }
169
+ async function writeGlmModeArtifacts(cwd, result, profile, generatedAt) {
170
+ const dir = path.join(cwd, '.sneakoscope', 'glm');
171
+ await writeJsonAtomic(path.join(dir, 'mad-glm-session.json'), {
172
+ schema: 'sks.glm-mad-session.v1',
173
+ generated_at: generatedAt,
174
+ result,
175
+ profile_id: profile.id
176
+ });
177
+ await writeJsonAtomic(path.join(dir, 'openrouter-request-summary.json'), {
178
+ schema: 'sks.openrouter-request-summary.v1',
179
+ generated_at: generatedAt,
180
+ endpoint: OPENROUTER_CHAT_COMPLETIONS_URL,
181
+ model: GLM_52_OPENROUTER_MODEL,
182
+ temperature: 1,
183
+ top_p: 0.95,
184
+ reasoning_effort: 'high',
185
+ stream: true,
186
+ provider_allow_fallbacks: false,
187
+ require_parameters: true,
188
+ key_source: result.openrouter_key_source || null,
189
+ key_preview: result.key_preview || null
190
+ });
191
+ await writeJsonAtomic(path.join(dir, 'model-guard.json'), {
192
+ schema: 'sks.glm-model-guard.v1',
193
+ generated_at: generatedAt,
194
+ requested_model: GLM_52_OPENROUTER_MODEL,
195
+ actual_model: result.actual_model || null,
196
+ accepted: result.actual_model ? assertGlm52ActualModel(result.actual_model).ok : result.ok,
197
+ strict_model_lock: true,
198
+ gpt_fallback_allowed: false,
199
+ blockers: result.blockers
200
+ });
201
+ }
202
+ function printHumanGlmResult(result, log) {
203
+ log(`GLM 5.2 MAD mode: ${result.ok ? result.status : 'blocked'}`);
204
+ log(`Model: ${result.model}`);
205
+ log(`GPT fallback: ${result.gpt_fallback_allowed ? 'allowed' : 'blocked'}`);
206
+ if (result.openrouter_key_source)
207
+ log(`OpenRouter key: ${result.openrouter_key_source} ${result.key_preview || ''}`.trim());
208
+ for (const blocker of result.blockers)
209
+ log(`- blocker: ${blocker}`);
210
+ for (const warning of result.warnings)
211
+ log(`- warning: ${warning}`);
212
+ }
213
+ async function promptLine(prompt) {
214
+ if (!process.stdin.isTTY)
215
+ return null;
216
+ const rl = readline.createInterface({ input, output });
217
+ try {
218
+ const answer = await rl.question(prompt);
219
+ return answer.trim() || null;
220
+ }
221
+ finally {
222
+ rl.close();
223
+ }
224
+ }
225
+ async function promptConfirmLine(prompt, defaultYes) {
226
+ if (!process.stdin.isTTY)
227
+ return defaultYes;
228
+ const answer = await promptLine(prompt);
229
+ if (!answer)
230
+ return defaultYes;
231
+ return !/^n(o)?$/i.test(answer);
232
+ }
233
+ export function buildGlmModeDryRunRequest() {
234
+ return buildGlm52Request({
235
+ messages: [{ role: 'user', content: 'SKS GLM dry run.' }],
236
+ stream: false,
237
+ maxTokens: 1,
238
+ toolChoice: 'none',
239
+ parallelToolCalls: false
240
+ });
241
+ }
242
+ //# sourceMappingURL=glm-mad-mode.js.map
@@ -0,0 +1,44 @@
1
+ import { OPENROUTER_CHAT_COMPLETIONS_URL } from './openrouter-types.js';
2
+ import { invalidOpenRouterResponseIssue, normalizeOpenRouterError } from './openrouter-error.js';
3
+ import { redactOpenRouterString } from '../../security/redact-secrets.js';
4
+ export async function sendOpenRouterChatCompletion(input) {
5
+ try {
6
+ const doFetch = input.fetchImpl || fetch;
7
+ const response = await doFetch(input.endpoint || OPENROUTER_CHAT_COMPLETIONS_URL, {
8
+ method: 'POST',
9
+ headers: {
10
+ Authorization: `Bearer ${input.apiKey}`,
11
+ 'Content-Type': 'application/json',
12
+ 'X-OpenRouter-Title': 'Sneakoscope-Codex'
13
+ },
14
+ body: JSON.stringify(input.request)
15
+ });
16
+ const text = await response.text();
17
+ if (!response.ok)
18
+ return { ok: false, error: normalizeOpenRouterError(response.status, text) };
19
+ return parseOpenRouterResponse(text);
20
+ }
21
+ catch (err) {
22
+ return {
23
+ ok: false,
24
+ error: {
25
+ code: 'glm_openrouter_request_failed',
26
+ message: redactOpenRouterString(err instanceof Error ? err.message : String(err)),
27
+ severity: 'failed'
28
+ }
29
+ };
30
+ }
31
+ }
32
+ export function parseOpenRouterResponse(text) {
33
+ try {
34
+ const parsed = JSON.parse(text);
35
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
36
+ return { ok: false, error: invalidOpenRouterResponseIssue('OpenRouter response was not an object.', text) };
37
+ }
38
+ return { ok: true, value: parsed };
39
+ }
40
+ catch {
41
+ return { ok: false, error: invalidOpenRouterResponseIssue('OpenRouter response was not valid JSON.', text) };
42
+ }
43
+ }
44
+ //# sourceMappingURL=openrouter-client.js.map
@@ -0,0 +1,37 @@
1
+ import { redactOpenRouterString } from '../../security/redact-secrets.js';
2
+ export function normalizeOpenRouterError(status, body) {
3
+ const code = status === 401 || status === 403
4
+ ? 'glm_openrouter_unauthorized'
5
+ : status === 429
6
+ ? 'glm_openrouter_rate_limited'
7
+ : status >= 500
8
+ ? 'glm_openrouter_provider_unavailable'
9
+ : 'glm_openrouter_request_failed';
10
+ return {
11
+ code,
12
+ message: statusMessage(code),
13
+ severity: status >= 500 ? 'failed' : 'blocked',
14
+ status,
15
+ redacted_body_tail: redactOpenRouterString(body).slice(-2000)
16
+ };
17
+ }
18
+ export function invalidOpenRouterResponseIssue(message, body) {
19
+ const issue = {
20
+ code: 'glm_openrouter_invalid_response',
21
+ message,
22
+ severity: 'failed'
23
+ };
24
+ return body ? { ...issue, redacted_body_tail: redactOpenRouterString(body).slice(-2000) } : issue;
25
+ }
26
+ function statusMessage(code) {
27
+ if (code === 'glm_openrouter_unauthorized')
28
+ return 'OpenRouter rejected the GLM API key.';
29
+ if (code === 'glm_openrouter_rate_limited')
30
+ return 'OpenRouter rate limited the GLM request.';
31
+ if (code === 'glm_openrouter_provider_unavailable')
32
+ return 'OpenRouter provider is unavailable for GLM 5.2.';
33
+ if (code === 'glm_openrouter_invalid_response')
34
+ return 'OpenRouter returned an invalid response.';
35
+ return 'OpenRouter request failed.';
36
+ }
37
+ //# sourceMappingURL=openrouter-error.js.map