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.
- package/README.md +3 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/install-helpers-codex-lb-config.js +71 -0
- package/dist/cli/install-helpers-codex-lb-selftest.js +11 -14
- package/dist/cli/install-helpers.js +305 -53
- package/dist/config/skills-manifest.json +52 -52
- package/dist/core/agents/agent-orchestrator.js +56 -5
- package/dist/core/agents/agent-roster.js +4 -2
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +18 -1
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +16 -0
- package/dist/core/codex-app.js +52 -6
- package/dist/core/hooks-runtime.js +77 -3
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
- package/dist/core/release/package-size-budget.js +1 -1
- package/dist/core/subagents/official-subagent-config.js +8 -5
- package/dist/core/subagents/official-subagent-preparation.js +24 -1
- package/dist/core/subagents/official-subagent-prompt.js +3 -2
- package/dist/core/subagents/wave-lifecycle.js +20 -0
- package/dist/core/subagents/wave-parent-guidance.js +42 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AppIdentity.swift +3 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +14 -8
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +71 -7
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +50 -9
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +61 -8
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +120 -29
- package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +58 -23
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +41 -7
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +34 -3
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +38 -8
- package/dist/scripts/docs-truthfulness-check.js +2 -2
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +7 -3
- 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("
|
|
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
|
-
|
|
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 {
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
let buttons = NSStackView(views: [
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
&&
|
|
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": "
|
|
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",
|