sneakoscope 6.7.0 → 7.0.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 (34) hide show
  1. package/README.md +3 -3
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/install-helpers-codex-lb-config.js +71 -0
  5. package/dist/cli/install-helpers-codex-lb-selftest.js +11 -14
  6. package/dist/cli/install-helpers.js +305 -53
  7. package/dist/config/skills-manifest.json +52 -52
  8. package/dist/core/agents/agent-orchestrator.js +56 -5
  9. package/dist/core/agents/agent-roster.js +4 -2
  10. package/dist/core/codex-app/codex-app-fast-ui-repair.js +18 -1
  11. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +16 -0
  12. package/dist/core/codex-app.js +52 -6
  13. package/dist/core/hooks-runtime.js +77 -3
  14. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  15. package/dist/core/release/package-size-budget.js +1 -1
  16. package/dist/core/subagents/official-subagent-config.js +8 -5
  17. package/dist/core/subagents/official-subagent-preparation.js +24 -1
  18. package/dist/core/subagents/official-subagent-prompt.js +3 -2
  19. package/dist/core/subagents/wave-lifecycle.js +20 -0
  20. package/dist/core/subagents/wave-parent-guidance.js +42 -0
  21. package/dist/core/version.js +1 -1
  22. package/dist/native/sks-menubar/Sources/AppIdentity.swift +3 -1
  23. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +14 -8
  24. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +71 -7
  25. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +50 -9
  26. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +61 -8
  27. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +120 -29
  28. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +58 -23
  29. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +41 -7
  30. package/dist/native/sks-menubar/Sources/StatusItemController.swift +34 -3
  31. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +38 -8
  32. package/dist/scripts/docs-truthfulness-check.js +2 -2
  33. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +7 -3
  34. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import Cocoa
2
2
 
3
- final class SettingsViewController: NSViewController {
3
+ final class SettingsViewController: NSViewController, ControlCenterPage {
4
4
  private let notifications: NotificationCoordinator
5
5
  private let quitWithCodex = NSButton(checkboxWithTitle: "Quit SKS Menu when Codex quits (otherwise hide)", target: nil, action: nil)
6
6
  private let status = NativeView.detail("Settings use the native app configuration file.")
@@ -10,26 +10,60 @@ final class SettingsViewController: NSViewController {
10
10
  override func loadView() {
11
11
  quitWithCodex.target = self; quitWithCodex.action = #selector(save)
12
12
  quitWithCodex.state = readConfig()["quit_with_codex"] as? Bool == true ? .on : .off
13
+ quitWithCodex.setAccessibilityLabel("Quit SKS Menu when Codex quits")
13
14
  let authorize = NativeView.button("Enable Notifications", target: self, action: #selector(enableNotifications))
14
15
  view = NativeView.stack([
15
16
  NativeView.title("Settings"),
16
- NativeView.detail("Use system typography, focus behavior, notification authorization, and explicit consequential changes."),
17
+ NativeView.detail("These options stay on this Mac. Notification permission and Codex lifecycle behavior never leave the machine."),
17
18
  quitWithCodex, authorize, status
18
19
  ])
20
+ refreshOnAppear()
19
21
  }
20
22
 
21
- @objc private func enableNotifications() { notifications.requestAuthorizationFromSettings(); status.stringValue = "Notification authorization was requested from macOS." }
23
+ func refreshOnAppear() {
24
+ quitWithCodex.state = readConfig()["quit_with_codex"] as? Bool == true ? .on : .off
25
+ if notifications.authorizationDenied {
26
+ status.stringValue = "Notifications are denied in macOS Settings. Operation results still appear in Control Center."
27
+ } else if FileManager.default.fileExists(atPath: AppRuntime.configPath) {
28
+ status.stringValue = "Settings loaded from the native app configuration file."
29
+ } else {
30
+ status.stringValue = "No settings file yet. Changing an option creates one with owner-only permissions."
31
+ }
32
+ }
33
+
34
+ @objc private func enableNotifications() {
35
+ notifications.requestAuthorizationFromSettings()
36
+ status.stringValue = "Notification authorization was requested from macOS."
37
+ }
22
38
 
23
39
  @objc private func save() {
24
40
  var config = readConfig()
25
41
  config["schema"] = "sks.sks-menubar-config.v1"
26
42
  config["codex_bundle_id"] = AppRuntime.codexBundleId as Any
27
43
  config["quit_with_codex"] = quitWithCodex.state == .on
28
- guard JSONSerialization.isValidJSONObject(config), let data = try? JSONSerialization.data(withJSONObject: config, options: [.prettyPrinted]) else { return }
44
+ guard JSONSerialization.isValidJSONObject(config), let data = try? JSONSerialization.data(withJSONObject: config, options: [.prettyPrinted]) else {
45
+ status.stringValue = "Settings could not be encoded."
46
+ return
47
+ }
29
48
  let target = URL(fileURLWithPath: AppRuntime.configPath)
30
- let temporary = target.deletingLastPathComponent().appendingPathComponent(".config.\(UUID().uuidString).tmp")
31
- do { try data.write(to: temporary, options: .atomic); _ = try FileManager.default.replaceItemAt(target, withItemAt: temporary); status.stringValue = "Settings saved." }
32
- catch { status.stringValue = "Settings could not be saved." }
49
+ let directory = target.deletingLastPathComponent()
50
+ let temporary = directory.appendingPathComponent(".config.\(UUID().uuidString).tmp")
51
+ do {
52
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
53
+ try data.write(to: temporary, options: .atomic)
54
+ if FileManager.default.fileExists(atPath: target.path) {
55
+ _ = try FileManager.default.replaceItemAt(target, withItemAt: temporary)
56
+ } else {
57
+ try FileManager.default.moveItem(at: temporary, to: target)
58
+ }
59
+ try FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: target.path)
60
+ status.stringValue = quitWithCodex.state == .on
61
+ ? "Saved. SKS Menu will quit when Codex quits."
62
+ : "Saved. SKS Menu will hide when Codex quits."
63
+ } catch {
64
+ try? FileManager.default.removeItem(at: temporary)
65
+ status.stringValue = "Settings could not be saved. Confirm \(directory.path) is writable."
66
+ }
33
67
  }
34
68
 
35
69
  private func readConfig() -> [String: Any] {
@@ -93,6 +93,7 @@ final class StatusItemController: NSObject, NSMenuDelegate {
93
93
  }
94
94
 
95
95
  private func refreshLocalState() {
96
+ hydrateFromLatestOperation()
96
97
  let update = readJson(path: FileManager.default.homeDirectoryForCurrentUser
97
98
  .appendingPathComponent(".sneakoscope-global/cache/update-status.json").path)
98
99
  let sksUpdate = ((update?["sks"] as? [String: Any])?["update_available"] as? Bool) == true
@@ -101,18 +102,45 @@ final class StatusItemController: NSObject, NSMenuDelegate {
101
102
  let signatureBroken = menuBar?["signature_ok"] as? Bool == false
102
103
  let resourcesBroken = menuBar?["resources_ok"] as? Bool == false
103
104
  let integrityBroken = signatureBroken || resourcesBroken || !FileManager.default.fileExists(atPath: AppRuntime.buildStampPath)
105
+ let pendingCount = operations.latestSnapshot()?.state == .waitingForConfirmation ? 1 : 0
106
+ pendingLine.title = "Pending approvals (\(pendingCount))"
104
107
  let icon: SKSStatusIcon
105
108
  let summary: String
106
109
  if integrityBroken { icon = .warning; summary = "Needs attention — Menu Bar integrity" }
107
110
  else if operationFailed { icon = .warning; summary = "Needs attention — last operation failed" }
108
- else if actionRequired || notificationAuthorizationDenied { icon = .attention; summary = notificationAuthorizationDenied ? "Notifications require attention" : "Input or approval required" }
111
+ else if actionRequired || notificationAuthorizationDenied || pendingCount > 0 {
112
+ icon = .attention
113
+ summary = notificationAuthorizationDenied ? "Notifications require attention" : pendingCount > 0 ? "Input or approval required" : "Input or approval required"
114
+ }
109
115
  else if sksUpdate || codexUpdate { icon = .updateAvailable; summary = "Update available" }
110
116
  else if operationRunning { icon = .working; summary = "Working" }
111
117
  else { icon = .healthy; summary = "Healthy" }
112
- apply(icon)
118
+ apply(icon, summary: summary)
113
119
  statusLine.title = "Status: \(summary)"
114
120
  }
115
121
 
122
+ private func hydrateFromLatestOperation() {
123
+ guard let latest = operations.latestSnapshot() else { return }
124
+ switch latest.state {
125
+ case .queued, .running:
126
+ operationRunning = true
127
+ operationFailed = false
128
+ actionRequired = false
129
+ case .waitingForConfirmation:
130
+ operationRunning = false
131
+ operationFailed = false
132
+ actionRequired = true
133
+ case .failed, .terminalUncertain:
134
+ operationRunning = false
135
+ operationFailed = true
136
+ actionRequired = false
137
+ case .succeeded, .cancelled:
138
+ operationRunning = false
139
+ operationFailed = false
140
+ actionRequired = false
141
+ }
142
+ }
143
+
116
144
  private func refreshExpiredUpdateStatusIfNeeded() {
117
145
  let update = readJson(path: FileManager.default.homeDirectoryForCurrentUser
118
146
  .appendingPathComponent(".sneakoscope-global/cache/update-status.json").path)
@@ -190,7 +218,7 @@ final class StatusItemController: NSObject, NSMenuDelegate {
190
218
  return expiry <= now
191
219
  }
192
220
 
193
- private func apply(_ state: SKSStatusIcon) {
221
+ private func apply(_ state: SKSStatusIcon, summary: String) {
194
222
  let pair: (String, String)
195
223
  switch state {
196
224
  case .healthy: pair = ("SKSStatusTemplate", "checkmark.circle")
@@ -201,6 +229,9 @@ final class StatusItemController: NSObject, NSMenuDelegate {
201
229
  }
202
230
  statusItem.button?.image = AppIdentity.statusImage(resource: pair.0, symbol: pair.1)
203
231
  statusItem.button?.imagePosition = .imageOnly
232
+ statusItem.button?.setAccessibilityLabel("SKS status")
233
+ statusItem.button?.setAccessibilityValue(summary)
234
+ statusItem.button?.toolTip = "SKS Control Center — \(summary)"
204
235
  }
205
236
 
206
237
  private func run(_ arguments: [String], kind: String, mutationGroup: String?, summary: String, completion: (() -> Void)? = nil) {
@@ -1,6 +1,6 @@
1
1
  import Cocoa
2
2
 
3
- final class UpdatesViewController: NSViewController {
3
+ final class UpdatesViewController: NSViewController, ControlCenterPage {
4
4
  private static let controlCenterUpdateEnvironment = ["SKS_UPDATE_DEFER_MENUBAR_RESTART": "1"]
5
5
  private let processClient: ProcessClient
6
6
  private let operations: OperationCoordinator
@@ -11,6 +11,9 @@ final class UpdatesViewController: NSViewController {
11
11
  private let progress = NSProgressIndicator()
12
12
  private var receiptTimer: Timer?
13
13
  private var activeReceiptId: String?
14
+ private var checkButton: NSButton!
15
+ private var reviewButton: NSButton!
16
+ private var busy = false
14
17
 
15
18
  init(processClient: ProcessClient, operations: OperationCoordinator, notifications: NotificationCoordinator) {
16
19
  self.processClient = processClient; self.operations = operations; self.notifications = notifications
@@ -22,9 +25,9 @@ final class UpdatesViewController: NSViewController {
22
25
  progress.isIndeterminate = !NSWorkspace.shared.accessibilityDisplayShouldReduceMotion
23
26
  progress.controlSize = .small
24
27
  progress.isHidden = true
25
- let check = NativeView.button("Check Now", target: self, action: #selector(checkNow))
26
- let review = NativeView.button("Review and Update", target: self, action: #selector(reviewAndUpdate))
27
- let buttons = NSStackView(views: [check, review, progress])
28
+ checkButton = NativeView.button("Check Now", target: self, action: #selector(checkNow))
29
+ reviewButton = NativeView.button("Review and Update", target: self, action: #selector(reviewAndUpdate))
30
+ let buttons = NSStackView(views: [checkButton, reviewButton, progress])
28
31
  buttons.orientation = .horizontal; buttons.spacing = 8
29
32
  view = NativeView.stack([
30
33
  NativeView.title("Updates"),
@@ -37,17 +40,36 @@ final class UpdatesViewController: NSViewController {
37
40
 
38
41
  deinit { receiptTimer?.invalidate() }
39
42
 
43
+ func refreshOnAppear() { reloadSnapshot() }
44
+
40
45
  private func loadCached() {
41
46
  reloadSnapshot()
42
47
  }
43
48
 
49
+ private func setBusy(_ value: Bool) {
50
+ busy = value
51
+ checkButton?.isEnabled = !value
52
+ reviewButton?.isEnabled = !value
53
+ }
54
+
44
55
  @objc private func checkNow() { run(["update", "status", "--refresh", "--json"], kind: "update-status", group: nil) }
45
56
 
46
57
  @objc private func reviewAndUpdate() {
47
58
  run(["update", "review", "--json"], kind: "update-review", group: nil) { [weak self] result in
48
- guard result.code == 0, let self = self, let window = self.view.window else { return }
59
+ guard let self = self else { return }
60
+ guard result.code == 0, let window = self.view.window else {
61
+ self.status.stringValue = "Update review failed. Open Diagnostics for the redacted log."
62
+ return
63
+ }
64
+ let pending = self.operations.latestSnapshot()
49
65
  AlertFactory.confirmSheet(window: window, title: "Update SKS?", message: "Review completed. Continue with the verified staged update?", destructive: false) { approved in
50
- if approved { self.run(["update", "now", "--json"], kind: "update", group: "update") }
66
+ if approved {
67
+ self.run(["update", "now", "--json"], kind: "update", group: "update")
68
+ } else if let pending = pending, pending.kind == "update-review" {
69
+ _ = self.operations.update(pending, state: .cancelled, stage: "confirmation", progress: 1, summary: "Update review cancelled")
70
+ self.status.stringValue = "Update review cancelled. No staged update was applied."
71
+ self.reloadSnapshot()
72
+ }
51
73
  }
52
74
  }
53
75
  }
@@ -62,6 +84,7 @@ final class UpdatesViewController: NSViewController {
62
84
  ))
63
85
  return
64
86
  }
87
+ setBusy(true)
65
88
  progress.isHidden = false
66
89
  if NSWorkspace.shared.accessibilityDisplayShouldReduceMotion {
67
90
  progress.isIndeterminate = false
@@ -76,10 +99,12 @@ final class UpdatesViewController: NSViewController {
76
99
  _ = operations.update(operation, state: .running, stage: "running", progress: nil, summary: status.stringValue)
77
100
  if kind == "update" { startReceiptPolling(for: operation) }
78
101
  let environment = kind == "update" ? Self.controlCenterUpdateEnvironment : [:]
79
- processClient.run(args, environment: environment) { [weak self] result in
102
+ let timeout: TimeInterval? = kind == "update" ? nil : NativeView.mutationTimeout
103
+ processClient.run(args, environment: environment, timeout: timeout) { [weak self] result in
80
104
  guard let self = self else { return }
81
105
  self.stopReceiptPolling()
82
106
  self.progress.stopAnimation(nil); self.progress.isHidden = true
107
+ self.setBusy(false)
83
108
  var authoritativeState: OperationState?
84
109
  var restartMenuBarAfterCompletion = false
85
110
  if kind == "update" {
@@ -106,6 +131,11 @@ final class UpdatesViewController: NSViewController {
106
131
  } else {
107
132
  let state: OperationState = kind == "update-review" && result.code == 0 ? .waitingForConfirmation : result.code == 0 ? .succeeded : .failed
108
133
  _ = self.operations.update(operation, state: state, stage: state == .waitingForConfirmation ? "confirmation" : "complete", progress: 1, summary: state == .waitingForConfirmation ? "Update review is waiting for confirmation" : result.code == 0 ? "Update operation completed" : "Update operation failed")
134
+ if result.code != 0 {
135
+ self.status.stringValue = "\(kind.replacingOccurrences(of: "-", with: " ").capitalized) failed · \(NativeView.redactPreview(result.output))"
136
+ } else if state == .waitingForConfirmation {
137
+ self.status.stringValue = "Review ready. Confirm to continue, or cancel to leave the current install unchanged."
138
+ }
109
139
  }
110
140
  let updateOutput = kind == "update-status" ? result.output : nil
111
141
  let approvalRequired = kind == "update-review" && result.code == 0
@@ -162,7 +192,7 @@ final class UpdatesViewController: NSViewController {
162
192
  }
163
193
 
164
194
  private func reloadSnapshot() {
165
- processClient.run(["update", "status", "--json"]) { [weak self] result in self?.render(statusResult: result) }
195
+ processClient.run(["update", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in self?.render(statusResult: result) }
166
196
  }
167
197
 
168
198
  private func render(statusResult result: ProcessResult) {
@@ -33,7 +33,7 @@ const required = {
33
33
  'docs/goal-to-loop-migration.md': ['only persisted goal owner', 'creates no SKS mission', '--legacy-goal-runtime', 'fail with an instruction'],
34
34
  'docs/known-gaps.md': ['No P0', 'P1'],
35
35
  'docs/release-readiness.md': [
36
- 'SKS 6.7.0 Release Readiness',
36
+ 'SKS 7.0.0 Release Readiness',
37
37
  '$sks-naruto',
38
38
  '$sks-work',
39
39
  'sks doctor --fix',
@@ -46,7 +46,7 @@ const required = {
46
46
  'proof-aware fleet control',
47
47
  'npm stage publish',
48
48
  'npm stage approve <stage-id>',
49
- '6.2.0 to 6.7.0 upgrade smoke'
49
+ '6.2.0 to 7.0.0 upgrade smoke'
50
50
  ]
51
51
  };
52
52
  const forbidden = [
@@ -38,15 +38,19 @@ const ok = plan.fast_selector === 'manual_action_required'
38
38
  && plan.provider_actions.includes('sks codex-lb setup --host <domain> --api-key-stdin --yes')
39
39
  && plan.safe_auto_apply === true
40
40
  && repaired.fast_selector === 'repaired'
41
- && repaired.provider_selector === 'selected_provider_ready'
42
- && repaired.selected_provider_blockers.length === 0
41
+ // Contract-valid codex-lb selection is preserved through Fast UI repair; without
42
+ // hermetic credentials/catalog the selected provider stays setup-required.
43
+ && repaired.provider_selector === 'manual_action_required'
44
+ && repaired.selected_provider_blockers.includes('codex_lb_api_key_missing')
45
+ && repaired.selected_provider_blockers.includes('codex_lb_base_url_missing')
46
+ && repaired.selected_provider_blockers.includes('codex_lb_model_catalog_json_unselected')
43
47
  && repaired.safe_auto_apply === true
44
48
  && backups.length >= 1
45
49
  && /^model\s*=\s*"future-codex-model"$/m.test(projectAfter.split(/\n\s*\[/)[0] || '')
46
50
  && /^model_reasoning_effort\s*=\s*"medium"$/m.test(projectAfter.split(/\n\s*\[/)[0] || '')
47
51
  && !/^model\s*=/m.test(homeTopLevel)
48
52
  && !/^model_reasoning_effort\s*=/m.test(homeTopLevel)
49
- && !/^model_provider\s*=/m.test(homeTopLevel)
53
+ && /^model_provider\s*=\s*"codex-lb"$/m.test(homeTopLevel)
50
54
  && /model_provider\s*=\s*"codex-lb"/.test(projectAfter)
51
55
  && /^service_tier\s*=\s*"fast"$/m.test(homeTopLevel)
52
56
  && /fast_mode = false/.test(homeAfter)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "6.7.0",
4
+ "version": "7.0.0",
5
5
  "description": "Proof-first Codex CLI and ChatGPT Desktop orchestration for AI coding agents, release verification, and macOS menu bar control.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",