sneakoscope 8.0.2 → 8.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.
- package/README.md +47 -23
- package/config/skills-hash-ledger.v1.json +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +55 -13
- package/dist/bin/sks-dispatch.js +1 -1
- package/dist/cli/command-manifest-lite.js +4 -0
- package/dist/cli/command-registry.js +4 -0
- package/dist/cli/install-helpers-codex-lb-chain.js +187 -0
- package/dist/cli/install-helpers-codex-lb-config.js +74 -8
- package/dist/cli/install-helpers.js +89 -44
- package/dist/commands/codex-lb.js +117 -19
- package/dist/commands/doctor.js +697 -37
- package/dist/config/skills-manifest.json +3 -2
- package/dist/core/agents/agent-runner-codex-exec.js +5 -3
- package/dist/core/codex/codex-config-guard.js +238 -52
- package/dist/core/codex-adapter.js +5 -3
- package/dist/core/codex-app/menubar/constants.js +10 -1
- package/dist/core/codex-app/menubar/global-install.js +288 -0
- package/dist/core/codex-app/menubar/installer.js +79 -14
- package/dist/core/codex-app/menubar/launch-agent.js +267 -45
- package/dist/core/codex-app/menubar/paths.js +3 -0
- package/dist/core/codex-app/menubar/rollback.js +55 -7
- package/dist/core/codex-app/menubar/status.js +75 -4
- package/dist/core/codex-control/codex-0139-image-path-real-probe.js +3 -1
- package/dist/core/codex-control/codex-0139-web-search-probe.js +3 -1
- package/dist/core/codex-control/codex-app-server-runtime-env.js +13 -4
- package/dist/core/codex-control/codex-app-server-v2-client.js +3 -3
- package/dist/core/codex-exec-output-schema.js +4 -1
- package/dist/core/codex-lb/codex-lb-env.js +63 -72
- package/dist/core/codex-lb/codex-lb-setup.js +15 -2
- package/dist/core/codex-lb/desktop-center-credentials.js +517 -58
- package/dist/core/codex-lb/desktop-controller.js +153 -24
- package/dist/core/codex-lb/routing-truth.js +327 -0
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/commands/basic-cli.js +7 -1
- package/dist/core/commands/qa-loop-command.js +3 -1
- package/dist/core/commands/telegram-command.js +241 -0
- package/dist/core/config-adopt/config-adopt-command.js +35 -0
- package/dist/core/config-adopt/config-adopt.js +346 -0
- package/dist/core/config-adopt/index.js +3 -0
- package/dist/core/doctor/command-alias-cleanup.js +4 -1
- package/dist/core/doctor/current-project-guidance-nested.js +37 -6
- package/dist/core/doctor/current-project-guidance.js +11 -6
- package/dist/core/doctor/doctor-readiness-matrix.js +14 -0
- package/dist/core/doctor/retired-managed-residue-private.js +1 -1
- package/dist/core/doctor/sks-menubar-doctor.js +4 -0
- package/dist/core/feature-fixtures.js +8 -0
- package/dist/core/hooks-runtime/hook-context.js +9 -0
- package/dist/core/hooks-runtime/managed-skill-admission.js +54 -4
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +69 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +2 -2
- package/dist/core/hooks-runtime/subagent-skill-availability-evidence.js +3 -3
- package/dist/core/hooks-runtime/subagent-skill-availability-guards.js +43 -5
- package/dist/core/hooks-runtime/subagent-skill-availability.js +64 -30
- package/dist/core/hooks-runtime/subagent-stop-hook.js +15 -2
- package/dist/core/hooks-runtime.js +148 -4
- package/dist/core/imagegen/imagegen-capability.js +46 -0
- package/dist/core/imagegen/require-imagegen.js +2 -2
- package/dist/core/init/skills.js +17 -3
- package/dist/core/install/installed-package-smoke.js +8 -2
- package/dist/core/naruto/hardware-capacity-probe.js +9 -3
- package/dist/core/naruto/naruto-concurrency-governor.js +68 -24
- package/dist/core/perf/import-graph-budget.js +0 -1
- package/dist/core/product-design-app-server.js +13 -1
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-pack-receipt.js +1 -2
- package/dist/core/routes/dollar-manifest-lite.js +1 -1
- package/dist/core/routes.js +1 -1
- package/dist/core/security/private-credential-file.js +209 -0
- package/dist/core/subagents/agent-catalog.js +17 -32
- package/dist/core/subagents/naruto-help-contract.js +5 -5
- package/dist/core/subagents/official-subagent-config.js +16 -6
- package/dist/core/subagents/official-subagent-preparation.js +34 -15
- package/dist/core/subagents/official-subagent-prompt.js +4 -4
- package/dist/core/subagents/official-subagent-runner.js +27 -4
- package/dist/core/subagents/thread-budget.js +19 -12
- package/dist/core/subagents/wave-lifecycle.js +18 -6
- package/dist/core/telegram/access.js +54 -0
- package/dist/core/telegram/client.js +144 -0
- package/dist/core/telegram/confirmation.js +37 -0
- package/dist/core/telegram/doctor.js +151 -0
- package/dist/core/telegram/index.js +9 -0
- package/dist/core/telegram/keychain.js +265 -0
- package/dist/core/telegram/liveness.js +109 -0
- package/dist/core/telegram/poller.js +90 -0
- package/dist/core/telegram/redaction.js +15 -0
- package/dist/core/telegram/types.js +1 -0
- package/dist/core/update/update-diagnostics.js +52 -0
- package/dist/core/update/update-migration-state.js +5 -1
- package/dist/core/update/update-operation.js +2 -1
- package/dist/core/update/update-stage-diagnostics.js +38 -0
- package/dist/core/update/update-status.js +2 -9
- package/dist/core/update-check.js +147 -31
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +23 -4
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +13 -0
- package/dist/native/sks-menubar/Sources/AppIdentity.swift +7 -0
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +3 -2
- package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +115 -0
- package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +17 -0
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +190 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +100 -59
- package/dist/native/sks-menubar/Sources/SingletonInstanceGuard.swift +328 -0
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileStore.swift +320 -0
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileSupport.swift +303 -0
- package/dist/native/sks-menubar/Sources/TelegramProcessGateway.swift +238 -0
- package/dist/native/sks-menubar/Sources/TelegramRuntimeSupport.swift +56 -0
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
- package/dist/native/sks-menubar/Sources/TelegramTransport.swift +474 -0
- package/dist/native/sks-menubar/Sources/main.swift +15 -1
- package/dist/native/sks-menubar/Tests/OperationCoordinatorTests.swift +4 -3
- package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +129 -0
- package/dist/native/sks-menubar/Tests/SingletonInstanceGuardTests.swift +183 -0
- package/dist/native/sks-menubar/Tests/TelegramRuntimeSmokeTests.swift +267 -0
- package/dist/native/sks-menubar/Tests/TelegramRuntimeTests.swift +222 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +1 -1
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +1 -1
- package/dist/scripts/current-command-surface-check.js +2 -4
- package/dist/scripts/official-subagent-workflow-check.js +1 -1
- package/dist/scripts/packlist-performance-check.js +1 -2
- package/package.json +1 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { exists, nowIso, readJson, readText, runProcess, writeJsonAtomic } from '../../fsx.js';
|
|
3
|
+
import { ensureConfinedDirectory, inspectConfinedPath, removeManagedPathVerified } from '../../managed-path-safety.js';
|
|
4
|
+
const MENU_BAR_EXECUTABLE_SUFFIX = path.join('SKSMenuBar.app', 'Contents', 'MacOS', 'SKSMenuBar');
|
|
5
|
+
export function projectMenuBarDuplicateInstallDirs(input) {
|
|
6
|
+
const root = path.resolve(input.root);
|
|
7
|
+
const home = path.resolve(input.paths.home);
|
|
8
|
+
const canonical = path.resolve(input.paths.install_dir);
|
|
9
|
+
return [...new Set([
|
|
10
|
+
path.join(root, '.codex', 'sks-menubar'),
|
|
11
|
+
path.join(root, '.sneakoscope', 'sks-menubar'),
|
|
12
|
+
path.join(root, '.sneakoscope', 'codex-app', 'sks-menubar'),
|
|
13
|
+
path.join(home, '.sneakoscope', 'sks-menubar'),
|
|
14
|
+
path.join(home, '.sneakoscope', 'codex-app', 'sks-menubar')
|
|
15
|
+
].map((candidate) => path.resolve(candidate)).filter((candidate) => candidate !== canonical))];
|
|
16
|
+
}
|
|
17
|
+
export async function verifiedProjectMenuBarDuplicateExecutablePaths(input) {
|
|
18
|
+
const root = path.resolve(input.root);
|
|
19
|
+
const home = path.resolve(input.paths.home);
|
|
20
|
+
const executables = [];
|
|
21
|
+
const candidates = await discoverProjectMenuBarDuplicateCandidates(input);
|
|
22
|
+
for (const candidate of candidates.inspected) {
|
|
23
|
+
const confinementRoot = confinementRootForCandidate(root, home, candidate);
|
|
24
|
+
const state = await inspectConfinedPath(confinementRoot, candidate).catch(() => null);
|
|
25
|
+
if (!state?.exists || state.leafSymlink || !state.stat?.isDirectory())
|
|
26
|
+
continue;
|
|
27
|
+
const stamp = await readJson(path.join(candidate, 'build-stamp.json'), null);
|
|
28
|
+
const executable = path.join(candidate, 'SKSMenuBar.app', 'Contents', 'MacOS', 'SKSMenuBar');
|
|
29
|
+
if (stamp?.schema === 'sks.sks-menubar-build-stamp.v2'
|
|
30
|
+
&& stamp.codesign_identifier === 'com.sneakoscope.sks-menubar'
|
|
31
|
+
&& await exists(executable)) {
|
|
32
|
+
executables.push(executable);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return executables;
|
|
36
|
+
}
|
|
37
|
+
export async function inspectProjectMenuBarCanonicalState(input) {
|
|
38
|
+
const root = path.resolve(input.root);
|
|
39
|
+
const home = path.resolve(input.paths.home);
|
|
40
|
+
const discoveredCandidates = await discoverProjectMenuBarDuplicateCandidates(input);
|
|
41
|
+
const inspected = discoveredCandidates.inspected;
|
|
42
|
+
const verifiedDuplicates = [];
|
|
43
|
+
const unverifiedCollisions = [];
|
|
44
|
+
const blockers = [];
|
|
45
|
+
for (const candidate of inspected) {
|
|
46
|
+
const confinementRoot = confinementRootForCandidate(root, home, candidate);
|
|
47
|
+
const state = await inspectConfinedPath(confinementRoot, candidate).catch(() => null);
|
|
48
|
+
if (!state) {
|
|
49
|
+
blockers.push(`menubar_project_duplicate_inspection_failed:${candidate}`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!state.exists)
|
|
53
|
+
continue;
|
|
54
|
+
if (state.leafSymlink || !state.stat?.isDirectory()) {
|
|
55
|
+
unverifiedCollisions.push(candidate);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const stamp = await readJson(path.join(candidate, 'build-stamp.json'), null);
|
|
59
|
+
const executable = path.join(candidate, 'SKSMenuBar.app', 'Contents', 'MacOS', 'SKSMenuBar');
|
|
60
|
+
if (stamp?.schema === 'sks.sks-menubar-build-stamp.v2'
|
|
61
|
+
&& stamp.codesign_identifier === 'com.sneakoscope.sks-menubar'
|
|
62
|
+
&& await exists(executable)) {
|
|
63
|
+
verifiedDuplicates.push(candidate);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
unverifiedCollisions.push(candidate);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const candidatePaths = [...new Set([
|
|
70
|
+
...discoveredCandidates.discovered,
|
|
71
|
+
...verifiedDuplicates,
|
|
72
|
+
...unverifiedCollisions
|
|
73
|
+
])].sort();
|
|
74
|
+
const warnings = candidatePaths.map((candidate) => `menubar_duplicate_candidate_detected:${candidate}`);
|
|
75
|
+
return {
|
|
76
|
+
canonical_only: candidatePaths.length === 0 && blockers.length === 0,
|
|
77
|
+
inspected,
|
|
78
|
+
candidate_paths: candidatePaths,
|
|
79
|
+
verified_duplicates: verifiedDuplicates,
|
|
80
|
+
unverified_collisions: unverifiedCollisions,
|
|
81
|
+
blockers,
|
|
82
|
+
warnings
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export async function cleanupProjectMenuBarDuplicates(input) {
|
|
86
|
+
const root = path.resolve(input.root);
|
|
87
|
+
const canonical = path.resolve(input.paths.install_dir);
|
|
88
|
+
const discovered = await discoverProjectMenuBarDuplicateCandidates(input);
|
|
89
|
+
const retainedCandidates = (input.candidateExecutablePaths || [])
|
|
90
|
+
.map((executable) => duplicateInstallDirForExecutable(executable, canonical))
|
|
91
|
+
.filter((candidate) => Boolean(candidate));
|
|
92
|
+
const inspected = [...new Set([...discovered.inspected, ...retainedCandidates])];
|
|
93
|
+
const removed = [];
|
|
94
|
+
const preserved = [];
|
|
95
|
+
const blockers = [];
|
|
96
|
+
const warnings = [];
|
|
97
|
+
for (const candidate of inspected) {
|
|
98
|
+
const confinementRoot = confinementRootForCandidate(root, input.paths.home, candidate);
|
|
99
|
+
const state = await inspectConfinedPath(confinementRoot, candidate).catch(() => null);
|
|
100
|
+
if (!state) {
|
|
101
|
+
blockers.push(`menubar_project_duplicate_inspection_failed:${candidate}`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!state.exists)
|
|
105
|
+
continue;
|
|
106
|
+
if (state.leafSymlink || !state.stat?.isDirectory()) {
|
|
107
|
+
preserved.push(candidate);
|
|
108
|
+
warnings.push(`menubar_project_duplicate_unmanaged_collision_preserved:${candidate}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const stamp = await readJson(path.join(candidate, 'build-stamp.json'), null);
|
|
112
|
+
if (stamp?.schema !== 'sks.sks-menubar-build-stamp.v2'
|
|
113
|
+
|| stamp.codesign_identifier !== 'com.sneakoscope.sks-menubar'
|
|
114
|
+
|| !await exists(path.join(candidate, 'SKSMenuBar.app', 'Contents', 'MacOS', 'SKSMenuBar'))) {
|
|
115
|
+
preserved.push(candidate);
|
|
116
|
+
warnings.push(`menubar_project_duplicate_unverified_preserved:${candidate}`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (isProtectedDuplicateRemovalTarget(candidate, root, input.paths.home)) {
|
|
120
|
+
preserved.push(candidate);
|
|
121
|
+
warnings.push(`menubar_project_duplicate_protected_path_preserved:${candidate}`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
await removeManagedPathVerified(confinementRoot, candidate);
|
|
126
|
+
removed.push(candidate);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
blockers.push(`menubar_project_duplicate_remove_failed:${candidate}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (preserved.length > 0)
|
|
133
|
+
warnings.push('menubar_strict_canonical_only_unmet');
|
|
134
|
+
let receiptPath = null;
|
|
135
|
+
if (removed.length > 0 || preserved.length > 0 || blockers.length > 0) {
|
|
136
|
+
try {
|
|
137
|
+
await ensureConfinedDirectory(input.paths.install_dir, input.paths.duplicate_receipts_dir);
|
|
138
|
+
receiptPath = path.join(input.paths.duplicate_receipts_dir, `${Date.now()}-${process.pid}.json`);
|
|
139
|
+
await writeJsonAtomic(receiptPath, {
|
|
140
|
+
schema: 'sks.menubar-project-duplicate-cleanup-receipt.v1',
|
|
141
|
+
generated_at: nowIso(),
|
|
142
|
+
canonical_install_dir: canonical,
|
|
143
|
+
project_root: root,
|
|
144
|
+
inspected,
|
|
145
|
+
removed,
|
|
146
|
+
preserved,
|
|
147
|
+
blockers,
|
|
148
|
+
warnings
|
|
149
|
+
}, { mode: 0o600 });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
blockers.push('menubar_project_duplicate_receipt_write_failed');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const canonicalOnly = preserved.length === 0 && blockers.length === 0;
|
|
156
|
+
return {
|
|
157
|
+
schema: 'sks.menubar-project-duplicate-cleanup.v1',
|
|
158
|
+
ok: blockers.length === 0,
|
|
159
|
+
canonical_only: canonicalOnly,
|
|
160
|
+
canonical_install_dir: canonical,
|
|
161
|
+
inspected,
|
|
162
|
+
removed,
|
|
163
|
+
preserved,
|
|
164
|
+
receipt_path: receiptPath,
|
|
165
|
+
blockers,
|
|
166
|
+
warnings
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function discoverProjectMenuBarDuplicateCandidates(input) {
|
|
170
|
+
const canonical = path.resolve(input.paths.install_dir);
|
|
171
|
+
const fixed = projectMenuBarDuplicateInstallDirs(input);
|
|
172
|
+
const discovered = [...new Set([
|
|
173
|
+
...await runningMenuBarDuplicateInstallDirs(canonical, input.env || process.env),
|
|
174
|
+
...await launchAgentDuplicateInstallDirs(input.paths.launch_agent_path, canonical)
|
|
175
|
+
])].sort();
|
|
176
|
+
return {
|
|
177
|
+
inspected: [...new Set([...fixed, ...discovered])],
|
|
178
|
+
discovered
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async function runningMenuBarDuplicateInstallDirs(canonicalInstallDir, env) {
|
|
182
|
+
const pgrep = await processTool(env, 'SKS_MENUBAR_PGREP', '/usr/bin/pgrep');
|
|
183
|
+
if (!pgrep)
|
|
184
|
+
return [];
|
|
185
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
186
|
+
const result = await runProcess(pgrep, [
|
|
187
|
+
...(uid === null ? [] : ['-U', String(uid)]),
|
|
188
|
+
'-f',
|
|
189
|
+
'SKSMenuBar\\.app/Contents/MacOS/SKSMenuBar'
|
|
190
|
+
], { timeoutMs: 2_000, maxOutputBytes: 32 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
|
|
191
|
+
if (result.code !== 0)
|
|
192
|
+
return [];
|
|
193
|
+
const rows = String(result.stdout || '').split(/\r?\n/).filter(Boolean).map((line) => ({
|
|
194
|
+
line,
|
|
195
|
+
executable: runningExecutablePath(line)
|
|
196
|
+
}));
|
|
197
|
+
const executablePaths = rows.map((row) => row.executable).filter((value) => Boolean(value));
|
|
198
|
+
const unresolvedPids = rows
|
|
199
|
+
.filter((row) => !row.executable)
|
|
200
|
+
.map((row) => Number(row.line.trim()))
|
|
201
|
+
.filter((pid) => Number.isInteger(pid) && pid > 0)
|
|
202
|
+
.slice(0, 128);
|
|
203
|
+
if (unresolvedPids.length > 0) {
|
|
204
|
+
const ps = await processTool(env, 'SKS_MENUBAR_PS', '/bin/ps');
|
|
205
|
+
if (ps) {
|
|
206
|
+
const processList = await runProcess(ps, [
|
|
207
|
+
'-p',
|
|
208
|
+
unresolvedPids.join(','),
|
|
209
|
+
'-o',
|
|
210
|
+
'pid=,command='
|
|
211
|
+
], { timeoutMs: 2_000, maxOutputBytes: 64 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
|
|
212
|
+
if (processList.code === 0) {
|
|
213
|
+
executablePaths.push(...String(processList.stdout || '')
|
|
214
|
+
.split(/\r?\n/)
|
|
215
|
+
.map((line) => runningExecutablePath(line))
|
|
216
|
+
.filter((value) => Boolean(value)));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return [...new Set(executablePaths
|
|
221
|
+
.map((executable) => duplicateInstallDirForExecutable(executable, canonicalInstallDir))
|
|
222
|
+
.filter((candidate) => Boolean(candidate)))].sort();
|
|
223
|
+
}
|
|
224
|
+
async function launchAgentDuplicateInstallDirs(launchAgentPath, canonicalInstallDir) {
|
|
225
|
+
const source = await readText(launchAgentPath, '');
|
|
226
|
+
const block = source.match(/<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/i)?.[1] || '';
|
|
227
|
+
const encoded = block.match(/<string>([\s\S]*?)<\/string>/i)?.[1];
|
|
228
|
+
if (!encoded)
|
|
229
|
+
return [];
|
|
230
|
+
const executable = decodeXml(encoded.trim());
|
|
231
|
+
const candidate = duplicateInstallDirForExecutable(executable, canonicalInstallDir);
|
|
232
|
+
return candidate ? [candidate] : [];
|
|
233
|
+
}
|
|
234
|
+
function runningExecutablePath(line) {
|
|
235
|
+
const command = line.replace(/^\s*\d+\s+/, '').trim();
|
|
236
|
+
const suffixIndex = command.indexOf(MENU_BAR_EXECUTABLE_SUFFIX);
|
|
237
|
+
if (suffixIndex < 0)
|
|
238
|
+
return null;
|
|
239
|
+
const suffixEnd = suffixIndex + MENU_BAR_EXECUTABLE_SUFFIX.length;
|
|
240
|
+
const firstSlash = command.indexOf('/');
|
|
241
|
+
if (firstSlash < 0 || firstSlash > suffixIndex)
|
|
242
|
+
return null;
|
|
243
|
+
return command.slice(firstSlash, suffixEnd);
|
|
244
|
+
}
|
|
245
|
+
function duplicateInstallDirForExecutable(executablePath, canonicalInstallDir) {
|
|
246
|
+
if (!path.isAbsolute(executablePath))
|
|
247
|
+
return null;
|
|
248
|
+
const executable = path.resolve(executablePath);
|
|
249
|
+
const suffix = `${path.sep}${MENU_BAR_EXECUTABLE_SUFFIX}`;
|
|
250
|
+
if (!executable.endsWith(suffix))
|
|
251
|
+
return null;
|
|
252
|
+
if (isWithin(path.resolve(canonicalInstallDir), executable))
|
|
253
|
+
return null;
|
|
254
|
+
return executable.slice(0, -suffix.length);
|
|
255
|
+
}
|
|
256
|
+
function confinementRootForCandidate(rootInput, homeInput, candidate) {
|
|
257
|
+
const root = path.resolve(rootInput);
|
|
258
|
+
const home = path.resolve(homeInput);
|
|
259
|
+
if (isWithin(root, candidate))
|
|
260
|
+
return root;
|
|
261
|
+
if (isWithin(home, candidate))
|
|
262
|
+
return home;
|
|
263
|
+
return path.parse(path.resolve(candidate)).root;
|
|
264
|
+
}
|
|
265
|
+
function isProtectedDuplicateRemovalTarget(candidateInput, rootInput, homeInput) {
|
|
266
|
+
const candidate = path.resolve(candidateInput);
|
|
267
|
+
return candidate === path.resolve(rootInput)
|
|
268
|
+
|| candidate === path.resolve(homeInput)
|
|
269
|
+
|| candidate === path.parse(candidate).root;
|
|
270
|
+
}
|
|
271
|
+
async function processTool(env, key, fallback) {
|
|
272
|
+
const injected = env.SKS_MENUBAR_TEST_PROCESS_TOOLS === '1' ? env[key] : null;
|
|
273
|
+
if (injected)
|
|
274
|
+
return injected;
|
|
275
|
+
return exists(fallback).then((available) => available ? fallback : null);
|
|
276
|
+
}
|
|
277
|
+
function decodeXml(value) {
|
|
278
|
+
return value
|
|
279
|
+
.replace(/'/g, "'")
|
|
280
|
+
.replace(/"/g, '"')
|
|
281
|
+
.replace(/>/g, '>')
|
|
282
|
+
.replace(/</g, '<')
|
|
283
|
+
.replace(/&/g, '&');
|
|
284
|
+
}
|
|
285
|
+
function isWithin(root, candidate) {
|
|
286
|
+
const relative = path.relative(root, candidate);
|
|
287
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
288
|
+
}
|
|
@@ -8,7 +8,8 @@ import { aggregateFileHashes, createSksMenuBarBuildStamp, menuBarBuildStampsEqua
|
|
|
8
8
|
import { MENU_ITEMS, SKS_MENUBAR_LABEL } from './constants.js';
|
|
9
9
|
import { resolveCodexBundleId, writeDefaultMenuBarConfig } from './config.js';
|
|
10
10
|
import { applyMenuBarGenerationTransaction, commitMenuBarGenerationTransaction, installGenerationPairs, MenuBarGenerationTransactionError, recoverMenuBarGenerationTransaction, rollbackGenerationPairs } from './generation-transaction.js';
|
|
11
|
-
import { launchAgentSource, launchMenuBar, seedMenuBarPreferredPosition } from './launch-agent.js';
|
|
11
|
+
import { launchAgentSource, launchMenuBar, inspectRunningMenuBarProcess, seedMenuBarPreferredPosition, stopMenuBarForReplacement, terminateMenuBarProcesses } from './launch-agent.js';
|
|
12
|
+
import { cleanupProjectMenuBarDuplicates, verifiedProjectMenuBarDuplicateExecutablePaths } from './global-install.js';
|
|
12
13
|
import { cleanupMacLaunchSecretEnvironment, cleanupRetiredRemoteBridgeLaunchAgent, quarantineRetiredRemoteBridgeBindings } from './migration.js';
|
|
13
14
|
import { sksMenuBarPaths } from './paths.js';
|
|
14
15
|
import { infoPlistSource, inspectInstalledResources, loadNativeMenuBarSources, nativeResourceHashes } from './resources.js';
|
|
@@ -56,6 +57,12 @@ export async function installSksMenuBar(opts = {}) {
|
|
|
56
57
|
const result = baseResult(paths, false, 'planned', status?.ok !== false, status?.installed ? ['menubar_app_present'] : ['menubar_app_install_available'], status?.warnings || []);
|
|
57
58
|
result.codex_bundle_id = status?.codex_sync.bundle_id || null;
|
|
58
59
|
result.build_stamp = status?.build_stamp || null;
|
|
60
|
+
if (status) {
|
|
61
|
+
result.installed_version = status.installed_version;
|
|
62
|
+
result.running_process = status.running_process;
|
|
63
|
+
result.menubar_version_probe = status.menubar_version_probe;
|
|
64
|
+
}
|
|
65
|
+
result.duplicate_install_candidates = status?.duplicate_install_candidates || [];
|
|
59
66
|
result.blockers = status?.blockers || [];
|
|
60
67
|
result.launch = { requested: false, method: 'skipped', ok: true };
|
|
61
68
|
return result;
|
|
@@ -198,6 +205,19 @@ export async function installSksMenuBar(opts = {}) {
|
|
|
198
205
|
? await inspectSignature(paths.app_path, { ...env, SKS_MENUBAR_CODESIGN: codesign })
|
|
199
206
|
: { checked: false, identifier: null, ok: false, error: 'app_missing' };
|
|
200
207
|
const installedInfoPlist = await readText(paths.info_plist_path, '');
|
|
208
|
+
const restartDeferred = sksMenuBarRestartDeferred(env);
|
|
209
|
+
const launchWanted = opts.launch !== false
|
|
210
|
+
&& env.SKS_SKIP_SKS_MENUBAR_LAUNCH !== '1'
|
|
211
|
+
&& !restartDeferred;
|
|
212
|
+
if (restartDeferred && opts.launch !== false && env.SKS_SKIP_SKS_MENUBAR_LAUNCH !== '1') {
|
|
213
|
+
warnings.push('launch_deferred_until_parent_operation_completes');
|
|
214
|
+
}
|
|
215
|
+
const launchAllowed = path.resolve(paths.home) === realUserHome()
|
|
216
|
+
&& !isMenuBarInstallPathUnderTempDir(paths.executable_path, env);
|
|
217
|
+
if (launchWanted && path.resolve(paths.home) !== realUserHome())
|
|
218
|
+
warnings.push('launch_skipped_non_user_home');
|
|
219
|
+
if (launchWanted && isMenuBarInstallPathUnderTempDir(paths.executable_path, env))
|
|
220
|
+
warnings.push('launch_skipped_temp_install');
|
|
201
221
|
const upToDate = bundleExists
|
|
202
222
|
&& currentAction === actionScript
|
|
203
223
|
&& menuBarBuildStampsEqual(previous, stamp)
|
|
@@ -273,6 +293,50 @@ export async function installSksMenuBar(opts = {}) {
|
|
|
273
293
|
return blocked(error.blocker, error.message);
|
|
274
294
|
return blocked('menubar_build_failed', error instanceof Error ? error.message : String(error));
|
|
275
295
|
}
|
|
296
|
+
}
|
|
297
|
+
const duplicateExecutablePaths = await verifiedProjectMenuBarDuplicateExecutablePaths({
|
|
298
|
+
paths,
|
|
299
|
+
root: paths.root,
|
|
300
|
+
env
|
|
301
|
+
});
|
|
302
|
+
const mustStopCanonical = !upToDate || (launchWanted && launchAllowed);
|
|
303
|
+
let canonicalStopped = false;
|
|
304
|
+
if (mustStopCanonical) {
|
|
305
|
+
const stopped = await stopMenuBarForReplacement({
|
|
306
|
+
launchctl,
|
|
307
|
+
paths,
|
|
308
|
+
executablePaths: [paths.executable_path, ...duplicateExecutablePaths],
|
|
309
|
+
env
|
|
310
|
+
});
|
|
311
|
+
if (!stopped.ok) {
|
|
312
|
+
return blocked(stopped.error || 'menubar_stop_before_replace_failed', 'Menu Bar process shutdown could not be verified before replacement.');
|
|
313
|
+
}
|
|
314
|
+
canonicalStopped = true;
|
|
315
|
+
actions.push('booted out Menu Bar and verified all canonical/project duplicate processes stopped');
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const duplicateStop = await terminateMenuBarProcesses(duplicateExecutablePaths, env);
|
|
319
|
+
if (!duplicateStop.ok) {
|
|
320
|
+
return blocked('menubar_project_duplicate_stop_failed', `Remaining PIDs: ${duplicateStop.remaining_pids.join(',')}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const duplicateCleanup = await cleanupProjectMenuBarDuplicates({
|
|
324
|
+
paths,
|
|
325
|
+
root: paths.root,
|
|
326
|
+
env,
|
|
327
|
+
candidateExecutablePaths: duplicateExecutablePaths
|
|
328
|
+
});
|
|
329
|
+
warnings.push(...duplicateCleanup.warnings);
|
|
330
|
+
if (!duplicateCleanup.ok) {
|
|
331
|
+
return blocked(duplicateCleanup.blockers[0] || 'menubar_project_duplicate_cleanup_failed', 'Project-local Menu Bar duplicate cleanup could not be completed safely.');
|
|
332
|
+
}
|
|
333
|
+
if (duplicateCleanup.removed.length > 0) {
|
|
334
|
+
actions.push(`removed ${duplicateCleanup.removed.length} verified project-local Menu Bar duplicate(s)`);
|
|
335
|
+
}
|
|
336
|
+
if (duplicateCleanup.receipt_path)
|
|
337
|
+
actions.push(`wrote duplicate cleanup receipt ${duplicateCleanup.receipt_path}`);
|
|
338
|
+
if (!upToDate) {
|
|
339
|
+
const preservingPrevious = await exists(paths.app_path);
|
|
276
340
|
try {
|
|
277
341
|
transaction = await applyMenuBarGenerationTransaction({
|
|
278
342
|
purpose: 'install', journalPath: paths.install_transaction_path, pairs: installPairs, env
|
|
@@ -325,23 +389,19 @@ export async function installSksMenuBar(opts = {}) {
|
|
|
325
389
|
transaction = committed;
|
|
326
390
|
}
|
|
327
391
|
}
|
|
328
|
-
const restartDeferred = sksMenuBarRestartDeferred(env);
|
|
329
|
-
const launchWanted = opts.launch !== false
|
|
330
|
-
&& env.SKS_SKIP_SKS_MENUBAR_LAUNCH !== '1'
|
|
331
|
-
&& !restartDeferred;
|
|
332
|
-
if (restartDeferred && opts.launch !== false && env.SKS_SKIP_SKS_MENUBAR_LAUNCH !== '1') {
|
|
333
|
-
warnings.push('launch_deferred_until_parent_operation_completes');
|
|
334
|
-
}
|
|
335
|
-
const launchAllowed = path.resolve(paths.home) === realUserHome() && !isMenuBarInstallPathUnderTempDir(paths.executable_path, env);
|
|
336
|
-
if (launchWanted && path.resolve(paths.home) !== realUserHome())
|
|
337
|
-
warnings.push('launch_skipped_non_user_home');
|
|
338
|
-
if (launchWanted && isMenuBarInstallPathUnderTempDir(paths.executable_path, env))
|
|
339
|
-
warnings.push('launch_skipped_temp_install');
|
|
340
392
|
let launch = { requested: false, method: 'skipped', ok: true };
|
|
341
393
|
if (launchWanted && launchAllowed) {
|
|
342
394
|
if (await seedMenuBarPreferredPosition(env))
|
|
343
395
|
actions.push('seeded SKS menu bar preferred position');
|
|
344
|
-
launch = await launchMenuBar({
|
|
396
|
+
launch = await launchMenuBar({
|
|
397
|
+
launchctl,
|
|
398
|
+
open,
|
|
399
|
+
paths,
|
|
400
|
+
env,
|
|
401
|
+
alreadyStopped: canonicalStopped,
|
|
402
|
+
expectedVersion: stamp.package_version,
|
|
403
|
+
executablePaths: [paths.executable_path, ...duplicateExecutablePaths]
|
|
404
|
+
});
|
|
345
405
|
}
|
|
346
406
|
const rollbackCandidateExists = !upToDate && await exists(paths.backup_app_path);
|
|
347
407
|
const rollback = shouldAutoRollbackMenuBarLaunch({ launch, upToDate, rollbackCandidateExists })
|
|
@@ -362,6 +422,11 @@ export async function installSksMenuBar(opts = {}) {
|
|
|
362
422
|
: stamp;
|
|
363
423
|
result.secret_env_cleanup = cleanup;
|
|
364
424
|
result.launch = launch;
|
|
425
|
+
result.installed_version = result.build_stamp?.package_version || null;
|
|
426
|
+
if (launch.version_probe) {
|
|
427
|
+
result.menubar_version_probe = launch.version_probe;
|
|
428
|
+
result.running_process = await inspectRunningMenuBarProcess(paths, launch.version_probe.pid, env);
|
|
429
|
+
}
|
|
365
430
|
result.rollback = rollback;
|
|
366
431
|
result.transaction = transaction;
|
|
367
432
|
result.blockers = ok ? [] : [
|