sneakoscope 10.2.0 → 10.3.0

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 (43) hide show
  1. package/README.md +6 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/command-manifest-lite.js +3 -1
  5. package/dist/cli/command-registry.js +9 -1
  6. package/dist/cli/install-helpers-menubar.js +57 -0
  7. package/dist/cli/install-helpers.js +10 -1
  8. package/dist/commands/decision.js +7 -0
  9. package/dist/config/skills-manifest.json +1 -1
  10. package/dist/core/agents/native-worker-backend-router.js +7 -2
  11. package/dist/core/codex-app/menubar/constants.js +2 -0
  12. package/dist/core/decisions/cli.js +324 -0
  13. package/dist/core/decisions/config.js +109 -0
  14. package/dist/core/decisions/evaluation.js +62 -0
  15. package/dist/core/decisions/integration.js +262 -0
  16. package/dist/core/decisions/openrouter.js +270 -0
  17. package/dist/core/decisions/policy.js +393 -0
  18. package/dist/core/decisions/questions.js +215 -0
  19. package/dist/core/decisions/receipt.js +41 -0
  20. package/dist/core/decisions/recovery.js +39 -0
  21. package/dist/core/decisions/routing.js +58 -0
  22. package/dist/core/decisions/state.js +173 -0
  23. package/dist/core/decisions/types.js +69 -0
  24. package/dist/core/feature-fixtures.js +1 -0
  25. package/dist/core/feature-registry.js +1 -0
  26. package/dist/core/hooks-runtime/subagent-context.js +5 -3
  27. package/dist/core/hooks-runtime/subagent-spawn-policy.js +9 -3
  28. package/dist/core/provider/model-router.js +3 -3
  29. package/dist/core/routes.js +2 -1
  30. package/dist/core/subagents/model-policy.js +15 -0
  31. package/dist/core/subagents/official-subagent-lock.js +15 -0
  32. package/dist/core/subagents/official-subagent-preparation.js +265 -24
  33. package/dist/core/subagents/official-subagent-prompt.js +56 -13
  34. package/dist/core/update/update-migration-state/retired-local-decision.js +203 -0
  35. package/dist/core/update/update-migration-state.js +6 -0
  36. package/dist/core/version.js +1 -1
  37. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +6 -3
  38. package/dist/native/sks-menubar/Sources/LocalDecisionModels.swift +166 -0
  39. package/dist/native/sks-menubar/Sources/LocalDecisionViewController.swift +200 -0
  40. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +4 -0
  41. package/dist/native/sks-menubar/Sources/SidebarItem.swift +2 -0
  42. package/dist/scripts/current-surface-update-e2e-check.js +1 -0
  43. package/package.json +1 -1
@@ -0,0 +1,203 @@
1
+ import fsp from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { runProcess } from '../../fsx.js';
5
+ import { inspectConfinedPath, ManagedPathSafetyError } from '../../managed-path-safety.js';
6
+ const RUNTIME_DIRNAME = 'local-decision';
7
+ const MAX_MARKER_BYTES = 256 * 1024;
8
+ export async function runRetiredLocalDecisionStage(env = process.env) {
9
+ const cleanup = await removeRetiredLocalDecisionRuntime(env);
10
+ return {
11
+ ok: cleanup.ok,
12
+ status: cleanup.ok ? 'ok' : 'failed',
13
+ actions: cleanup.actions,
14
+ blockers: cleanup.blockers,
15
+ warnings: cleanup.warnings,
16
+ detail: cleanup.detail
17
+ };
18
+ }
19
+ export async function removeRetiredLocalDecisionRuntime(env = process.env, opts = {}) {
20
+ const sksHome = path.resolve(env.SKS_HOME || path.join(env.HOME || os.homedir(), '.sneakoscope'));
21
+ const runtimeRoot = path.join(sksHome, RUNTIME_DIRNAME);
22
+ const actions = [];
23
+ const blockers = [];
24
+ const warnings = [];
25
+ const detail = {
26
+ runtime_root: runtimeRoot,
27
+ removed_runtime: false,
28
+ removed_socket_dir: false,
29
+ stopped_worker: false
30
+ };
31
+ if (sksHome === path.parse(sksHome).root) {
32
+ blockers.push('retired_local_decision_home_root_refused');
33
+ return { ok: false, actions, blockers, warnings, detail };
34
+ }
35
+ const stopped = await stopRecordedWorker(runtimeRoot).catch(() => 'unreadable');
36
+ if (stopped === 'stopped') {
37
+ detail.stopped_worker = true;
38
+ actions.push('stopped_retired_local_decision_worker');
39
+ }
40
+ else if (stopped === 'not_ours') {
41
+ warnings.push('retired_local_decision_worker_pid_not_owned');
42
+ }
43
+ const removedDefault = await removeConfinedTree(sksHome, runtimeRoot);
44
+ if (removedDefault === 'removed') {
45
+ detail.removed_runtime = true;
46
+ actions.push('removed_retired_local_decision_runtime');
47
+ }
48
+ else if (removedDefault === 'absent') {
49
+ actions.push('retired_local_decision_runtime_absent');
50
+ }
51
+ else {
52
+ blockers.push(`retired_local_decision_runtime_${removedDefault}`);
53
+ }
54
+ const explicit = explicitRuntimeRoot(env, runtimeRoot);
55
+ if (explicit) {
56
+ const removedExplicit = await removeMarkedExplicitRoot(explicit);
57
+ if (removedExplicit === 'removed')
58
+ actions.push('removed_explicit_retired_local_decision_root');
59
+ else if (removedExplicit === 'preserved')
60
+ warnings.push('retired_local_decision_explicit_root_preserved');
61
+ else if (removedExplicit !== 'absent')
62
+ blockers.push(`retired_local_decision_explicit_root_${removedExplicit}`);
63
+ }
64
+ const socketDir = opts.socketDir || defaultSocketDir();
65
+ const removedSocket = await removeSocketDir(socketDir, Boolean(opts.socketDir));
66
+ if (removedSocket === 'removed') {
67
+ detail.removed_socket_dir = true;
68
+ actions.push('removed_retired_local_decision_socket_dir');
69
+ }
70
+ else if (removedSocket !== 'absent') {
71
+ blockers.push(`retired_local_decision_socket_${removedSocket}`);
72
+ }
73
+ return { ok: blockers.length === 0, actions, blockers, warnings, detail };
74
+ }
75
+ function explicitRuntimeRoot(env, defaultRoot) {
76
+ const raw = String(env.SKS_LOCAL_DECISION_ROOT || '').trim();
77
+ if (!raw)
78
+ return null;
79
+ const resolved = path.resolve(raw);
80
+ if (resolved === defaultRoot)
81
+ return null;
82
+ return resolved;
83
+ }
84
+ async function removeMarkedExplicitRoot(target) {
85
+ const parent = path.dirname(target);
86
+ if (parent === target || parent === path.parse(parent).root)
87
+ return 'preserved';
88
+ const marked = await isRetiredRuntimeMarker(target);
89
+ if (marked === 'absent')
90
+ return 'absent';
91
+ if (marked !== 'marked')
92
+ return 'preserved';
93
+ return removeConfinedTree(parent, target);
94
+ }
95
+ async function isRetiredRuntimeMarker(root) {
96
+ const configPath = path.join(root, 'config.json');
97
+ const receiptPath = path.join(root, 'install-receipt.json');
98
+ const configStat = await fsp.lstat(configPath).catch(() => null);
99
+ const receiptStat = await fsp.lstat(receiptPath).catch(() => null);
100
+ if (!configStat && !receiptStat)
101
+ return 'absent';
102
+ if (!configStat?.isFile() || configStat.isSymbolicLink() || !receiptStat?.isFile() || receiptStat.isSymbolicLink())
103
+ return 'unmarked';
104
+ if (configStat.size > MAX_MARKER_BYTES || receiptStat.size > MAX_MARKER_BYTES)
105
+ return 'unmarked';
106
+ try {
107
+ const config = JSON.parse(await fsp.readFile(configPath, 'utf8'));
108
+ if (config.schemaVersion !== 1)
109
+ return 'unmarked';
110
+ return 'marked';
111
+ }
112
+ catch {
113
+ return 'unmarked';
114
+ }
115
+ }
116
+ async function removeConfinedTree(boundary, target) {
117
+ try {
118
+ const inspected = await inspectConfinedPath(boundary, target);
119
+ if (!inspected.exists)
120
+ return 'absent';
121
+ await removeExistingTree(inspected.path);
122
+ const after = await inspectConfinedPath(boundary, target);
123
+ return after.exists ? 'refused' : 'removed';
124
+ }
125
+ catch (error) {
126
+ if (error instanceof ManagedPathSafetyError)
127
+ return 'refused';
128
+ return 'refused';
129
+ }
130
+ }
131
+ async function removeSocketDir(socketDir, trustedParent) {
132
+ const resolved = path.resolve(socketDir);
133
+ const parent = path.dirname(resolved);
134
+ const name = path.basename(resolved);
135
+ if (!/^sks-ld-\d+$/.test(name))
136
+ return 'refused';
137
+ const stat = await fsp.lstat(resolved).catch((error) => error.code === 'ENOENT' ? null : Promise.reject(error));
138
+ if (!stat)
139
+ return 'absent';
140
+ if (stat.isSymbolicLink() || !stat.isDirectory())
141
+ return 'refused';
142
+ if (!trustedParent && !(await parentIsTemp(parent)))
143
+ return 'refused';
144
+ await removeExistingTree(resolved);
145
+ return 'removed';
146
+ }
147
+ async function parentIsTemp(parent) {
148
+ const parentReal = await fsp.realpath(parent).catch(() => null);
149
+ const tempReal = await fsp.realpath('/tmp').catch(() => null);
150
+ return Boolean(parentReal && tempReal && parentReal === tempReal);
151
+ }
152
+ async function removeExistingTree(target) {
153
+ await fsp.rm(target, { recursive: true, force: false });
154
+ }
155
+ function defaultSocketDir() {
156
+ const uid = typeof process.getuid === 'function' ? process.getuid() : 0;
157
+ const base = process.platform === 'win32' ? os.tmpdir() : '/tmp';
158
+ return path.join(base, `sks-ld-${uid}`);
159
+ }
160
+ async function stopRecordedWorker(runtimeRoot) {
161
+ const file = path.join(runtimeRoot, 'runtime', 'service.json');
162
+ const stat = await fsp.lstat(file).catch(() => null);
163
+ if (!stat)
164
+ return 'absent';
165
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_MARKER_BYTES)
166
+ return 'unreadable';
167
+ let pid = null;
168
+ try {
169
+ const parsed = JSON.parse(await fsp.readFile(file, 'utf8'));
170
+ pid = typeof parsed.pid === 'number' && Number.isInteger(parsed.pid) ? parsed.pid : null;
171
+ }
172
+ catch {
173
+ return 'unreadable';
174
+ }
175
+ if (!pid || pid === process.pid || pid <= 1)
176
+ return 'unreadable';
177
+ const command = await processCommand(pid);
178
+ if (!command)
179
+ return 'absent';
180
+ if (!command.includes('sks_local_decision'))
181
+ return 'not_ours';
182
+ if (!signal(pid, 'SIGTERM'))
183
+ return 'absent';
184
+ return 'stopped';
185
+ }
186
+ async function processCommand(pid) {
187
+ const result = await runProcess('ps', ['-p', String(pid), '-o', 'command='], {
188
+ timeoutMs: 2_000,
189
+ maxOutputBytes: 16 * 1024
190
+ }).catch(() => null);
191
+ if (!result || result.code !== 0)
192
+ return null;
193
+ return String(result.stdout || '').trim() || null;
194
+ }
195
+ function signal(pid, sig) {
196
+ try {
197
+ process.kill(pid, sig);
198
+ return true;
199
+ }
200
+ catch {
201
+ return false;
202
+ }
203
+ }
@@ -14,6 +14,7 @@ import { runCurrentPublicSurfaceReconcileStage } from './update-migration-state/
14
14
  import { runDesktopBridgeCatalogRepairStage } from './update-migration-state/desktop-bridge-catalog-repair-stage.js';
15
15
  import { runDesktopBridgeRestageStage } from './update-migration-state/desktop-bridge-restage.js';
16
16
  import { runSessionStateSplitStage } from './update-migration-state/session-state-split.js';
17
+ import { runRetiredLocalDecisionStage } from './update-migration-state/retired-local-decision.js';
17
18
  import { runHookTrustRefreshStage, runOtherHarnessCleanupStage } from './update-migration-state/simple-stages.js';
18
19
  import { compareSemVer } from './semver.js';
19
20
  export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
@@ -227,6 +228,11 @@ const UPDATE_MIGRATION_STAGES = [
227
228
  min_from_version: '0.0.0',
228
229
  run: runOtherHarnessCleanupStage
229
230
  },
231
+ {
232
+ id: 'retired-local-decision',
233
+ min_from_version: '0.0.0',
234
+ run: () => runRetiredLocalDecisionStage()
235
+ },
230
236
  {
231
237
  id: 'skills-reconcile',
232
238
  min_from_version: '0.0.0',
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '10.2.0';
1
+ export const PACKAGE_VERSION = '10.3.0';
@@ -16,12 +16,14 @@ final class ControlCenterWindowController: NSWindowController, NSTableViewDataSo
16
16
  let overview = OverviewViewController(processClient: processClient, operations: operations)
17
17
  overviewController = overview
18
18
  defer {
19
- // Overview quick navigation: section names only, so the standalone
20
- // Overview compile harness never needs the SidebarItem type.
21
- overview.openSection = { [weak self] name in
19
+ // Overview / Decisions quick navigation: section names only, so the
20
+ // standalone Overview compile harness never needs the SidebarItem type.
21
+ let open: (String) -> Void = { [weak self] name in
22
22
  guard let item = SidebarItem.allCases.first(where: { $0.rawValue == name }) else { return }
23
23
  self?.show(section: item)
24
24
  }
25
+ overview.openSection = open
26
+ (controllers[.localDecision] as? LocalDecisionViewController)?.openSection = open
25
27
  }
26
28
  controllers = [
27
29
  .overview: overview,
@@ -29,6 +31,7 @@ final class ControlCenterWindowController: NSWindowController, NSTableViewDataSo
29
31
  .mcpServers: MCPServersViewController(processClient: processClient, operations: operations, notifications: notifications),
30
32
  .providers: ProvidersViewController(processClient: processClient, operations: operations),
31
33
  .remoteCoding: RemoteCodingViewController(),
34
+ .localDecision: LocalDecisionViewController(processClient: processClient, operations: operations),
32
35
  .diagnostics: DiagnosticsViewController(processClient: processClient, operations: operations),
33
36
  .settings: SettingsViewController(processClient: processClient, operations: operations, notifications: notifications)
34
37
  ]
@@ -0,0 +1,166 @@
1
+ import Foundation
2
+
3
+ /// Exact `sks decision` argv the Control Center page runs. Tests lock this
4
+ /// against the CLI contract so enable/disable cannot silently drift.
5
+ enum LocalDecisionCommand {
6
+ static let provider = "openrouter"
7
+ static let pinnedModel = "typesafe/jev-1.13"
8
+ static let statusSchema = "sks.jev-decision-status.v1"
9
+ static let enableSchema = "sks.jev-decision-enable.v1"
10
+ static let disableSchema = "sks.jev-decision-disable.v1"
11
+
12
+ static var status: [String] { ["decision", "status", "--json"] }
13
+ static var enable: [String] {
14
+ [
15
+ "decision", "enable",
16
+ "--provider", provider,
17
+ "--model", pinnedModel,
18
+ "--consent-cloud",
19
+ "--json"
20
+ ]
21
+ }
22
+ static var disable: [String] { ["decision", "disable", "--json"] }
23
+
24
+ static func mutationSucceeded(_ payload: [String: Any]?, schema: String) -> Bool {
25
+ payload?["ok"] as? Bool == true && payload?["schema"] as? String == schema
26
+ }
27
+ }
28
+
29
+ /// stdout+stderr are merged by ProcessClient. Prefer the first complete object
30
+ /// whose schema is a Jev decision payload, not the last nested `{`.
31
+ enum LocalDecisionJSON {
32
+ static func object(from text: String) -> [String: Any]? {
33
+ let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
34
+ if let object = parseEntire(trimmed) { return object }
35
+ var search = trimmed.startIndex
36
+ var fallback: [String: Any]?
37
+ while search < trimmed.endIndex, let start = trimmed[search...].firstIndex(of: "{") {
38
+ if let slice = completeObject(in: trimmed, from: start), let object = parseEntire(slice) {
39
+ if let schema = object["schema"] as? String, schema.hasPrefix("sks.jev-decision") {
40
+ return object
41
+ }
42
+ if fallback == nil, object["ok"] != nil { fallback = object }
43
+ }
44
+ search = trimmed.index(after: start)
45
+ }
46
+ return fallback
47
+ }
48
+
49
+ static func statusFailureReason(code: Int32, output: String) -> String {
50
+ let text = output.lowercased()
51
+ if code == 127 || text.contains("sks command not found") {
52
+ return "SKS is not on this Menu Bar path. Update SKS, then reopen Decisions."
53
+ }
54
+ if text.contains("unknown subcommand") || text.contains("unknown command")
55
+ || (text.contains("usage: sks") && !text.contains("jev-decision") && !text.contains("sks decision")) {
56
+ return "This SKS build does not include `decision`. Update SKS, then reopen Decisions."
57
+ }
58
+ return "Status unavailable · update SKS, then reopen this page."
59
+ }
60
+
61
+ private static func parseEntire(_ text: String) -> [String: Any]? {
62
+ guard let data = text.data(using: .utf8) else { return nil }
63
+ return try? JSONSerialization.jsonObject(with: data) as? [String: Any]
64
+ }
65
+
66
+ private static func completeObject(in text: String, from start: String.Index) -> String? {
67
+ var depth = 0
68
+ var inString = false
69
+ var escape = false
70
+ var index = start
71
+ while index < text.endIndex {
72
+ let ch = text[index]
73
+ if inString {
74
+ if escape { escape = false }
75
+ else if ch == "\\" { escape = true }
76
+ else if ch == "\"" { inString = false }
77
+ } else if ch == "\"" {
78
+ inString = true
79
+ } else if ch == "{" {
80
+ depth += 1
81
+ } else if ch == "}" {
82
+ depth -= 1
83
+ if depth == 0 { return String(text[start...index]) }
84
+ }
85
+ index = text.index(after: index)
86
+ }
87
+ return nil
88
+ }
89
+ }
90
+
91
+ /// Public projection of `sks decision status --json`. Pure parsing so the
92
+ /// Control Center page and its tests share one reading of the CLI contract.
93
+ struct LocalDecisionStatus: Equatable {
94
+ enum NextStep: String { case missingKey = "missing_key", enable, ready }
95
+
96
+ let mode: String
97
+ let provider: String
98
+ let model: String
99
+ let consentCloud: Bool
100
+ let credentialPresent: Bool
101
+ let credentialSource: String?
102
+ let recoverySupported: Bool
103
+ let recoveryReason: String?
104
+ let nextStep: NextStep
105
+
106
+ static let schema = LocalDecisionCommand.statusSchema
107
+
108
+ static func decode(from payload: [String: Any]) -> LocalDecisionStatus? {
109
+ guard payload["schema"] as? String == schema, payload["ok"] as? Bool == true,
110
+ let mode = payload["mode"] as? String,
111
+ let provider = payload["provider"] as? String,
112
+ let model = payload["model"] as? String,
113
+ let consentCloud = payload["consentCloud"] as? Bool,
114
+ let credential = payload["credential"] as? [String: Any],
115
+ let present = credential["present"] as? Bool else { return nil }
116
+ let recovery = payload["recovery"] as? [String: Any]
117
+ let step = NextStep(rawValue: payload["nextStep"] as? String ?? "") ?? .enable
118
+ return LocalDecisionStatus(
119
+ mode: mode,
120
+ provider: provider,
121
+ model: model,
122
+ consentCloud: consentCloud,
123
+ credentialPresent: present,
124
+ credentialSource: credential["source"] as? String,
125
+ recoverySupported: recovery?["supported"] as? Bool == true,
126
+ recoveryReason: recovery?["reason"] as? String,
127
+ nextStep: step
128
+ )
129
+ }
130
+
131
+ var modeTitle: String { mode == "jev" ? "Jev" : "Off" }
132
+ var canEnable: Bool { mode != "jev" }
133
+ var canDisable: Bool { mode == "jev" }
134
+
135
+ var badgeText: String {
136
+ if mode == "jev", consentCloud {
137
+ return credentialPresent ? "Ready · Jev via OpenRouter" : "Jev on · add OpenRouter key"
138
+ }
139
+ return "Off · deterministic baseline"
140
+ }
141
+
142
+ var badgeReady: Bool { mode == "jev" && consentCloud && credentialPresent }
143
+
144
+ var modelLabel: String {
145
+ let credential = credentialPresent ? (credentialSource ?? "present") : "missing"
146
+ return "\(model) via \(provider) · credential \(credential)"
147
+ }
148
+
149
+ var guidance: String {
150
+ switch nextStep {
151
+ case .missingKey:
152
+ return "Add an OpenRouter key in Connections first. SKS reuses that credential; there is no Jev-specific secret. You can still turn the mode on from this page; preparation stays on the deterministic baseline until a key is present."
153
+ case .enable:
154
+ return "Turn Jev on here to use it in official-subagent preparation. Off makes no network call. Jev may choose an automatic plan variant or optional context; recovery is unsupported."
155
+ case .ready:
156
+ return "Jev is on. Eligible Naruto/official preparation may send one bounded Decisions request and compile the answer into the SKS plan. Recovery remains unsupported, and a saved plan is not proof that Codex spawned a child."
157
+ }
158
+ }
159
+
160
+ static let enableConsentBody = "SKS will send bounded task evidence to OpenRouter's Decisions endpoint using your existing OpenRouter credential. A valid answer is compiled into an existing plan or context selection. This is not a measured performance claim, and recovery stays unsupported."
161
+
162
+ var enableConsentMessage: String {
163
+ if credentialPresent { return Self.enableConsentBody }
164
+ return "\(Self.enableConsentBody) No OpenRouter key is saved yet — add one in Connections or Jev stays on the deterministic baseline."
165
+ }
166
+ }
@@ -0,0 +1,200 @@
1
+ import Cocoa
2
+
3
+ /// Control Center page for optional Jev decisions. Every action is an explicit
4
+ /// `sks decision …` call. Status never contacts OpenRouter.
5
+ final class LocalDecisionViewController: NSViewController, ControlCenterPage {
6
+ private let processClient: ProcessClient
7
+ private let operations: OperationCoordinator
8
+ private let badge = NativeView.badge("Checking…", color: .secondaryLabelColor)
9
+ private let modelDetail = NativeView.detail("Checking configuration…")
10
+ private let guidance = NativeView.detail("")
11
+ private let actionStatus = NativeView.detail("")
12
+ private let modePopup = NSPopUpButton()
13
+ private let modeStatus = NativeView.detail("Jev compiles a typed Choice into SKS-owned plan or context selection.")
14
+ private var enableButton: NSButton!
15
+ private var disableButton: NSButton!
16
+ private var refreshButton: NSButton!
17
+ private var connectionsButton: NSButton!
18
+ private var status: LocalDecisionStatus?
19
+ private var pendingMode: String?
20
+ private var busy = false
21
+ private var generation = 0
22
+ /// Section navigation by sidebar title, wired by ControlCenterWindowController.
23
+ var openSection: ((String) -> Void)?
24
+
25
+ init(processClient: ProcessClient, operations: OperationCoordinator) {
26
+ self.processClient = processClient
27
+ self.operations = operations
28
+ super.init(nibName: nil, bundle: nil)
29
+ }
30
+ required init?(coder: NSCoder) { nil }
31
+
32
+ override func loadView() {
33
+ enableButton = NativeView.button("Enable Jev…", target: self, action: #selector(enableJev))
34
+ disableButton = NativeView.button("Disable", target: self, action: #selector(disableJev))
35
+ refreshButton = NativeView.button("Refresh", target: self, action: #selector(refresh))
36
+ connectionsButton = NativeView.button("Connections…", target: self, action: #selector(openConnections))
37
+ connectionsButton.setAccessibilityIdentifier("sks-decision-connections")
38
+ enableButton.isEnabled = false
39
+ disableButton.isEnabled = false
40
+ modePopup.addItems(withTitles: ["Off", "Jev"])
41
+ modePopup.target = self
42
+ modePopup.action = #selector(applyMode)
43
+ modePopup.isEnabled = false
44
+ modePopup.setAccessibilityLabel("Decision mode")
45
+ modePopup.setAccessibilityIdentifier("sks-decision-mode")
46
+ badge.setAccessibilityIdentifier("sks-decision-badge")
47
+ actionStatus.setAccessibilityIdentifier("sks-decision-action-status")
48
+ let statusCard = NativeView.card(
49
+ title: "Decisions",
50
+ subtitle: "Optional Jev judgments through the existing OpenRouter credential. Off by default. A valid answer is compiled into an existing SKS candidate; there is no advisory text and no second LLM judge.",
51
+ views: [badge, modelDetail, guidance, NativeView.row([refreshButton, connectionsButton])]
52
+ )
53
+ let modeCard = NativeView.card(
54
+ title: "Mode",
55
+ subtitle: "Off: no network call. Jev: send bounded evidence to OpenRouter after explicit cloud consent. Recovery is unsupported.",
56
+ views: [NativeView.row([modePopup, enableButton, disableButton]), modeStatus, actionStatus]
57
+ )
58
+ view = NativeView.page([
59
+ ControlKit.header("Decisions", "Optional Jev selection through OpenRouter."),
60
+ statusCard, modeCard
61
+ ])
62
+ }
63
+
64
+ func refreshOnAppear() { refresh() }
65
+
66
+ @objc private func openConnections() { openSection?("Providers") }
67
+
68
+ @objc private func refresh() {
69
+ guard !busy else { return }
70
+ generation += 1
71
+ let requestGeneration = generation
72
+ processClient.run(LocalDecisionCommand.status, timeout: NativeView.statusTimeout) { [weak self] result in
73
+ guard let self, requestGeneration == self.generation, !self.busy else { return }
74
+ guard result.code == 0, let payload = LocalDecisionJSON.object(from: result.output),
75
+ let status = LocalDecisionStatus.decode(from: payload) else {
76
+ self.status = nil
77
+ self.render(failure: LocalDecisionJSON.statusFailureReason(code: result.code, output: result.output))
78
+ return
79
+ }
80
+ self.status = status
81
+ self.pendingMode = nil
82
+ self.render(failure: nil)
83
+ }
84
+ }
85
+
86
+ private func render(failure: String?) {
87
+ let mode = pendingMode ?? status?.mode
88
+ if let status {
89
+ let color: NSColor = status.badgeReady ? .systemGreen : (status.mode == "jev" ? .systemOrange : .secondaryLabelColor)
90
+ NativeView.setBadge(badge, text: status.badgeText, color: color)
91
+ modelDetail.stringValue = status.modelLabel
92
+ guidance.stringValue = status.guidance
93
+ if !status.recoverySupported {
94
+ modeStatus.stringValue = "Recovery is unsupported: no SKS-owned ambiguous-failure handler exists."
95
+ modeStatus.textColor = .secondaryLabelColor
96
+ }
97
+ } else {
98
+ NativeView.setBadge(badge, text: failure ?? "Unavailable", color: .systemOrange)
99
+ modelDetail.stringValue = ""
100
+ guidance.stringValue = "Enable and Disable still call `sks decision` so a stale status read cannot trap this page."
101
+ }
102
+ enableButton.isEnabled = mode != "jev" || mode == nil
103
+ disableButton.isEnabled = mode == "jev" || mode == nil
104
+ connectionsButton.isEnabled = true
105
+ modePopup.isEnabled = true
106
+ if let mode { modePopup.selectItem(withTitle: mode == "jev" ? "Jev" : "Off") }
107
+ }
108
+
109
+ @objc private func applyMode() {
110
+ guard !busy else { return }
111
+ let desired = modePopup.titleOfSelectedItem == "Jev" ? "jev" : "off"
112
+ let current = pendingMode ?? status?.mode
113
+ guard desired != current else { return }
114
+ if desired == "jev" {
115
+ enableJev()
116
+ return
117
+ }
118
+ disableJev()
119
+ }
120
+
121
+ @objc private func enableJev() {
122
+ guard !busy else { return }
123
+ guard let window = hostWindow() else {
124
+ revertModePopup()
125
+ actionStatus.stringValue = "Open SKS Control Center to confirm cloud consent."
126
+ return
127
+ }
128
+ AlertFactory.confirmSheet(
129
+ window: window,
130
+ title: "Enable Jev through OpenRouter?",
131
+ message: status?.enableConsentMessage ?? LocalDecisionStatus.enableConsentBody,
132
+ destructive: false,
133
+ actionTitle: "Consent and Enable"
134
+ ) { [weak self] confirmed in
135
+ guard let self, confirmed else {
136
+ self?.revertModePopup()
137
+ return
138
+ }
139
+ guard let operation = self.operations.begin(kind: "decision-enable", mutationGroup: "decision", summary: "Enable Jev via OpenRouter") else {
140
+ self.revertModePopup()
141
+ self.actionStatus.stringValue = "Another configuration change is running. Try again when it finishes."
142
+ return
143
+ }
144
+ self.setBusy(true, message: "Enabling Jev…")
145
+ self.processClient.run(LocalDecisionCommand.enable, timeout: NativeView.mutationTimeout) { [weak self] result in
146
+ guard let self else { return }
147
+ let payload = LocalDecisionJSON.object(from: result.output)
148
+ let ok = result.code == 0 && LocalDecisionCommand.mutationSucceeded(payload, schema: LocalDecisionCommand.enableSchema)
149
+ if ok { self.pendingMode = "jev" }
150
+ else { self.revertModePopup() }
151
+ let summary = ok ? "Jev enabled through OpenRouter." : "Enable failed · \(NativeView.redactPreview(result.output))"
152
+ _ = self.operations.update(operation, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: summary)
153
+ self.setBusy(false, message: summary)
154
+ }
155
+ }
156
+ }
157
+
158
+ @objc private func disableJev() {
159
+ guard !busy else { return }
160
+ guard let operation = operations.begin(kind: "decision-disable", mutationGroup: "decision", summary: "Disable Jev") else {
161
+ revertModePopup()
162
+ actionStatus.stringValue = "Another configuration change is running. Try again when it finishes."
163
+ return
164
+ }
165
+ setBusy(true, message: "Returning to the deterministic baseline…")
166
+ processClient.run(LocalDecisionCommand.disable, timeout: NativeView.mutationTimeout) { [weak self] result in
167
+ guard let self else { return }
168
+ let payload = LocalDecisionJSON.object(from: result.output)
169
+ let ok = result.code == 0 && LocalDecisionCommand.mutationSucceeded(payload, schema: LocalDecisionCommand.disableSchema)
170
+ if ok { self.pendingMode = "off" }
171
+ else { self.revertModePopup() }
172
+ let summary = ok ? "Mode set to off." : "Disable could not be confirmed · \(NativeView.redactPreview(result.output))"
173
+ _ = self.operations.update(operation, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: summary)
174
+ self.setBusy(false, message: summary)
175
+ }
176
+ }
177
+
178
+ private func setBusy(_ value: Bool, message: String) {
179
+ busy = value
180
+ actionStatus.stringValue = message
181
+ actionStatus.textColor = value ? .secondaryLabelColor : .labelColor
182
+ if value {
183
+ for button in [enableButton, disableButton, refreshButton, connectionsButton] { button?.isEnabled = false }
184
+ modePopup.isEnabled = false
185
+ } else {
186
+ refreshButton.isEnabled = true
187
+ connectionsButton.isEnabled = true
188
+ refresh()
189
+ }
190
+ }
191
+
192
+ private func revertModePopup() {
193
+ let mode = pendingMode ?? status?.mode
194
+ modePopup.selectItem(withTitle: mode == "jev" ? "Jev" : "Off")
195
+ }
196
+
197
+ private func hostWindow() -> NSWindow? {
198
+ view.window ?? NSApp.keyWindow ?? NSApp.windows.first(where: { $0.title == "SKS Control Center" })
199
+ }
200
+ }
@@ -38,6 +38,7 @@ final class OverviewViewController: NSViewController, ControlCenterPage {
38
38
  let shortcuts = NativeView.row([
39
39
  NativeView.button("Remote Coding…", target: self, action: #selector(openRemoteCoding)),
40
40
  connectionsShortcut(),
41
+ NativeView.button("Decisions…", target: self, action: #selector(openDecisions)),
41
42
  NativeView.button("Updates…", target: self, action: #selector(openUpdates)),
42
43
  NativeView.button("Diagnostics…", target: self, action: #selector(openDiagnostics))
43
44
  ])
@@ -73,6 +74,7 @@ final class OverviewViewController: NSViewController, ControlCenterPage {
73
74
 
74
75
  @objc private func openRemoteCoding() { openSection?("Remote Coding") }
75
76
  @objc private func openProviders() { openSection?("Providers") }
77
+ @objc private func openDecisions() { openSection?("Decisions") }
76
78
  @objc private func openUpdates() { openSection?("Updates") }
77
79
  @objc private func openDiagnostics() { openSection?("Diagnostics") }
78
80
 
@@ -89,6 +91,8 @@ final class OverviewViewController: NSViewController, ControlCenterPage {
89
91
  || operation.kind.localizedCaseInsensitiveContains("openrouter")
90
92
  || operation.kind.localizedCaseInsensitiveContains("codex-lb") {
91
93
  section = "Providers"
94
+ } else if operation.kind.localizedCaseInsensitiveContains("decision") {
95
+ section = "Decisions"
92
96
  } else if operation.kind.localizedCaseInsensitiveContains("update") {
93
97
  section = "Updates"
94
98
  } else {
@@ -8,6 +8,7 @@ enum SidebarItem: String, CaseIterable {
8
8
  case remoteCoding = "Remote Coding"
9
9
  case updates = "Updates"
10
10
  case mcpServers = "MCP Servers"
11
+ case localDecision = "Decisions"
11
12
  case diagnostics = "Diagnostics"
12
13
  case settings = "Settings"
13
14
 
@@ -19,6 +20,7 @@ enum SidebarItem: String, CaseIterable {
19
20
  case .providers: return "network"
20
21
  case .updates: return "arrow.down.circle"
21
22
  case .mcpServers: return "server.rack"
23
+ case .localDecision: return "cpu"
22
24
  case .remoteCoding: return "laptopcomputer.and.iphone"
23
25
  case .diagnostics: return "stethoscope"
24
26
  case .settings: return "gearshape"
@@ -9,6 +9,7 @@ import { runSksUpdateNow } from '../core/update-check.js';
9
9
  import { ensureCurrentMigrationBeforeCommand, projectUpdateMigrationReceiptPath, runPackageLocalDoctor } from '../core/update/update-migration-state.js';
10
10
  const REQUIRED_MIGRATION_STAGES = [
11
11
  'other-harness-cleanup',
12
+ 'retired-local-decision',
12
13
  'current-public-surface-reconcile',
13
14
  'session-state-split',
14
15
  'skills-reconcile',