sneakoscope 6.1.2 → 6.2.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 +11 -4
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/install-helpers.js +8 -3
- package/dist/config/skills-manifest.json +58 -58
- package/dist/core/agents/agent-effort-policy.js +28 -17
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/codex/codex-config-guard.js +178 -7
- package/dist/core/codex/codex-config-readability.js +21 -8
- package/dist/core/codex/codex-config-toml.js +14 -11
- package/dist/core/codex-app/mcp-manager.js +679 -0
- package/dist/core/codex-app/sks-menubar.js +405 -6
- package/dist/core/codex-control/codex-lb-launch-recovery.js +15 -0
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/commands/mad-sks-command.js +44 -3
- package/dist/core/commands/menubar-command.js +94 -0
- package/dist/core/commands/naruto-command.js +3 -1
- package/dist/core/commands/wiki-command.js +11 -5
- package/dist/core/fsx.js +1 -0
- package/dist/core/hooks-runtime/code-pack-freshness-preflight.js +14 -8
- package/dist/core/hooks-runtime/naruto-decision-gate.js +184 -0
- package/dist/core/hooks-runtime/stop-repeat-guard.js +89 -0
- package/dist/core/hooks-runtime.js +36 -105
- package/dist/core/init/skills.js +4 -4
- package/dist/core/init.js +1 -1
- package/dist/core/managed-assets/managed-assets-manifest.js +178 -29
- package/dist/core/preflight/parallel-preflight-engine.js +16 -3
- package/dist/core/proof/route-adapter.js +1 -1
- package/dist/core/proof/selftest-proof-fixtures.js +3 -5
- package/dist/core/provider/model-router.js +15 -6
- package/dist/core/release/package-size-budget.js +4 -6
- package/dist/core/research/research-plan-markdown.js +123 -0
- package/dist/core/research/research-super-search.js +8 -4
- package/dist/core/research.js +4 -119
- package/dist/core/retention.js +70 -2
- package/dist/core/routes/dollar-manifest-lite.js +1 -1
- package/dist/core/routes.js +75 -22
- package/dist/core/subagents/agent-catalog.js +87 -15
- package/dist/core/subagents/model-policy.js +189 -48
- package/dist/core/subagents/naruto-help-contract.js +11 -4
- package/dist/core/subagents/official-subagent-preparation.js +55 -9
- package/dist/core/subagents/official-subagent-prompt.js +45 -23
- package/dist/core/subagents/thread-budget.js +1 -1
- package/dist/core/subagents/triwiki-attention.js +117 -14
- package/dist/core/triwiki/code-pack-head-freshness.js +291 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +12 -2
- package/dist/core/zellij/zellij-pane-proof.js +9 -1
- package/dist/core/zellij/zellij-update.js +14 -1
- package/dist/scripts/canonical-test-runner.js +7 -1
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +11 -1
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +9 -2
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +13 -2
- package/dist/scripts/codex-native-agent-role-content-check.js +13 -1
- package/dist/scripts/codex-sdk-backend-router-check.js +2 -0
- package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
- package/dist/scripts/official-subagent-workflow-check.js +1 -1
- package/dist/scripts/packlist-performance-check.js +1 -18
- package/dist/scripts/python-codex-sdk-all-pipelines-check.js +8 -0
- package/dist/scripts/sks-menubar-install-check.js +6 -1
- package/dist/scripts/super-search-provider-interface-check.js +31 -18
- package/package.json +3 -1
- package/dist/scripts/codex-0139-feature-probes-check.js +0 -30
- package/dist/scripts/codex-0139-marketplace-source-check.js +0 -13
|
@@ -62,24 +62,31 @@ export function decideAgentEffort(input = {}) {
|
|
|
62
62
|
]
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
// Official Codex subagents
|
|
66
|
-
//
|
|
67
|
-
//
|
|
65
|
+
// Official Codex subagents use one of four fixed profiles: Luna Max for tiny
|
|
66
|
+
// mechanical work, Sol High for ordinary implementation, Sol Max for
|
|
67
|
+
// judgment, and Terra Medium for long-context or Codex-tool execution.
|
|
68
68
|
export function decideOfficialSubagentModel(input = {}) {
|
|
69
69
|
const persona = input.persona || {};
|
|
70
70
|
const prompt = String(input.prompt || '');
|
|
71
71
|
const role = String(persona.role || '');
|
|
72
72
|
const agentId = String(input.agentId || persona.id || 'subagent');
|
|
73
|
+
const routingRole = [agentId, persona.naruto_role, persona.work_kind, role].filter(Boolean).join(' ');
|
|
74
|
+
const taskClass = /(?:implementation_specialist|ui_implementer|native_app_specialist)/i.test(routingRole)
|
|
75
|
+
? 'implementation'
|
|
76
|
+
: /(?:explorer|docs_maintainer|long_context_analyst|computer_use_operator|browser_use_operator|image_generation_operator)/i.test(routingRole)
|
|
77
|
+
? 'context_tools'
|
|
78
|
+
: undefined;
|
|
73
79
|
const routed = decideSubagentModel({
|
|
74
|
-
title:
|
|
80
|
+
title: routingRole,
|
|
75
81
|
description: [prompt, persona.risk_focus, persona.write_policy].filter(Boolean).join(' '),
|
|
76
82
|
role,
|
|
77
|
-
expectedOutput: (persona.output_expectations || []).join(' ')
|
|
83
|
+
expectedOutput: (persona.output_expectations || []).join(' '),
|
|
84
|
+
...(taskClass ? { taskClass } : {})
|
|
78
85
|
});
|
|
79
|
-
const effort =
|
|
86
|
+
const effort = routed.modelReasoningEffort;
|
|
80
87
|
const modelCapability = codexModelEffortCapability({
|
|
81
88
|
model: routed.model,
|
|
82
|
-
advertisedEfforts: [
|
|
89
|
+
advertisedEfforts: [effort],
|
|
83
90
|
defaultEffort: effort
|
|
84
91
|
});
|
|
85
92
|
return {
|
|
@@ -99,11 +106,15 @@ export function decideOfficialSubagentModel(input = {}) {
|
|
|
99
106
|
reason: routed.reason,
|
|
100
107
|
dynamic: true,
|
|
101
108
|
escalation_triggers: [
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'requested model
|
|
109
|
+
'review, debugging, planning, architecture, integration, security, database, research, release, or ambiguity selects Sol Max',
|
|
110
|
+
'mixed tool-and-judgment work is split; an unsplittable slice selects Sol Max',
|
|
111
|
+
'requested model/effort profile unavailable blocks instead of silently falling back'
|
|
105
112
|
],
|
|
106
|
-
downshift_triggers: [
|
|
113
|
+
downshift_triggers: [
|
|
114
|
+
'ordinary UI, logic, backend, or native implementation selects Sol High',
|
|
115
|
+
'long-context, Browser/Chrome, Computer Use, or image-generation execution selects Terra Medium',
|
|
116
|
+
'tiny short-context mechanical work selects Luna Max'
|
|
117
|
+
]
|
|
107
118
|
};
|
|
108
119
|
}
|
|
109
120
|
/** @deprecated since 6.1.1. Use decideOfficialSubagentModel. */
|
|
@@ -129,20 +140,20 @@ export function buildAgentEffortPolicy(roster = {}) {
|
|
|
129
140
|
policy_version: 1,
|
|
130
141
|
dynamic: true,
|
|
131
142
|
service_tier: 'fast',
|
|
132
|
-
model_catalog_policy: narutoFamilyOnly ? '
|
|
133
|
-
model_constraint: narutoFamilyOnly ? ['gpt-5.6-luna', 'gpt-5.6-sol'] : null,
|
|
134
|
-
explicit_compatibility_models:
|
|
143
|
+
model_catalog_policy: narutoFamilyOnly ? 'official_subagent_four_profile_matrix' : 'codex_catalog_passthrough',
|
|
144
|
+
model_constraint: narutoFamilyOnly ? ['gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol'] : null,
|
|
145
|
+
explicit_compatibility_models: null,
|
|
135
146
|
model_tiers: narutoFamilyOnly
|
|
136
|
-
? ['gpt-5.6-luna-max', 'gpt-5.6-sol-max', 'gpt-5.6-terra-
|
|
147
|
+
? ['gpt-5.6-luna-max', 'gpt-5.6-sol-high', 'gpt-5.6-sol-max', 'gpt-5.6-terra-medium']
|
|
137
148
|
: ['codex-selected-low', 'codex-selected-medium', 'codex-selected-high', 'codex-selected-xhigh', 'glm-5.2-minimal', 'glm-5.2-low', 'glm-5.2-high', 'glm-5.2-xhigh'],
|
|
138
|
-
allowed_efforts: narutoFamilyOnly ? ['max'] : codexModelEffortCapability().advertised_efforts,
|
|
149
|
+
allowed_efforts: narutoFamilyOnly ? ['medium', 'high', 'max'] : codexModelEffortCapability().advertised_efforts,
|
|
139
150
|
model_effort_capability: codexModelEffortCapability(),
|
|
140
151
|
max_agents: roster.max_agents || 20,
|
|
141
152
|
agent_count: roster.agent_count || decisions.length,
|
|
142
153
|
concurrency: roster.concurrency || decisions.length,
|
|
143
154
|
decisions,
|
|
144
155
|
rule: narutoFamilyOnly
|
|
145
|
-
? 'Official Naruto subagents
|
|
156
|
+
? 'Official Naruto subagents use GPT-5.6 Luna Max only for tiny short-context mechanical work, GPT-5.6 Sol High for ordinary implementation, GPT-5.6 Sol Max for judgment-heavy work, and GPT-5.6 Terra Medium for long-context or Browser/Chrome, Computer Use, and image-generation execution. Judgment wins when one slice cannot be safely split.'
|
|
146
157
|
: 'Codex/OpenAI workers inherit the current Codex-selected model, including future catalog entries; SKS changes only advertised reasoning effort. Explicit non-Codex provider modes retain their provider model.'
|
|
147
158
|
};
|
|
148
159
|
}
|
|
@@ -12,7 +12,7 @@ export const MAX_AGENT_COUNT = 20;
|
|
|
12
12
|
export const DEFAULT_AGENT_CONCURRENCY = 4;
|
|
13
13
|
/** @deprecated Legacy process-swarm queue ceiling; never an official thread cap. */
|
|
14
14
|
export const MAX_NARUTO_AGENT_COUNT = 100;
|
|
15
|
-
/** @deprecated Legacy clone default; official workflows use
|
|
15
|
+
/** @deprecated Legacy clone default; official workflows use two default children unless --agents is explicit. */
|
|
16
16
|
export const DEFAULT_NARUTO_CLONES = 8;
|
|
17
17
|
export const AGENT_BACKENDS = ['fake', 'process', 'codex-sdk', 'zellij', 'ollama', 'local-llm'];
|
|
18
18
|
export function normalizeAgentBackend(input) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
2
4
|
import { appendJsonl, ensureDir, nowIso, readText, sha256, writeTextAtomic } from '../fsx.js';
|
|
3
5
|
import { diffCodexAppUiSnapshots, snapshotCodexAppUiState } from '../codex-app/codex-app-ui-state-snapshot.js';
|
|
4
6
|
import { cleanupCodexConfigBackups, validateCodexConfigRoundTrip } from './codex-config-toml.js';
|
|
@@ -38,10 +40,13 @@ export async function writeCodexConfigGuarded(input) {
|
|
|
38
40
|
return result;
|
|
39
41
|
}
|
|
40
42
|
const beforeSnapshot = await snapshotForConfig(root, configPath).catch(() => null);
|
|
41
|
-
|
|
43
|
+
const normalizeText = input.preserveTextFormatting === true
|
|
44
|
+
? (value) => String(value || '')
|
|
45
|
+
: ensureTrailingNewline;
|
|
46
|
+
let next = normalizeText(await input.mutate(before));
|
|
42
47
|
if (input.removeTopLevelModeLocks === true)
|
|
43
48
|
next = removeLegacyTopLevelCodexModeLocks(next);
|
|
44
|
-
const preserved = input.preserveFastUiKeys === false ? { text:
|
|
49
|
+
const preserved = input.preserveFastUiKeys === false ? { text: normalizeText(next), keys: [] } : mergeLostFastUiKeys(before, next);
|
|
45
50
|
next = preserved.text;
|
|
46
51
|
if (input.removeTopLevelModeLocks === true)
|
|
47
52
|
next = removeLegacyTopLevelCodexModeLocks(next);
|
|
@@ -63,8 +68,29 @@ export async function writeCodexConfigGuarded(input) {
|
|
|
63
68
|
});
|
|
64
69
|
return result;
|
|
65
70
|
}
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
const expectedBefore = normalizeText(before);
|
|
72
|
+
if (next === expectedBefore) {
|
|
73
|
+
if (input.verifyUnchangedBeforeWrite === true) {
|
|
74
|
+
const observed = await readConfigCommitSnapshot(configPath);
|
|
75
|
+
const expectedExists = input.expectedBeforeExists ?? true;
|
|
76
|
+
if (!configCommitSnapshotMatches(observed, expectedExists, before)) {
|
|
77
|
+
return recordConcurrentConfigChange({
|
|
78
|
+
root,
|
|
79
|
+
reportPath: input.reportPath,
|
|
80
|
+
cause,
|
|
81
|
+
configPath,
|
|
82
|
+
before,
|
|
83
|
+
expectedExists,
|
|
84
|
+
observed,
|
|
85
|
+
backupPath: null,
|
|
86
|
+
repairedKeys: preserved.keys,
|
|
87
|
+
forbiddenTopLevel
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
await writeTextAtomic(configPath, next, { mode: 0o600 });
|
|
93
|
+
}
|
|
68
94
|
const result = { ok: true, status: 'present', config_path: configPath, backup_path: null, changed: false, repaired_keys: preserved.keys, forbidden_top_level: forbiddenTopLevel };
|
|
69
95
|
if (preserved.keys.length || forbiddenTopLevel.length) {
|
|
70
96
|
await recordCodexConfigGuard(root, input.reportPath, {
|
|
@@ -79,9 +105,37 @@ export async function writeCodexConfigGuarded(input) {
|
|
|
79
105
|
}
|
|
80
106
|
return result;
|
|
81
107
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
let backupPath = null;
|
|
109
|
+
if (input.verifyUnchangedBeforeWrite === true) {
|
|
110
|
+
const expectedExists = input.expectedBeforeExists ?? true;
|
|
111
|
+
const commit = await commitCodexConfigIfUnchanged({
|
|
112
|
+
configPath,
|
|
113
|
+
before,
|
|
114
|
+
next,
|
|
115
|
+
expectedExists,
|
|
116
|
+
backupTag: input.backupTag || cause
|
|
117
|
+
});
|
|
118
|
+
if (!commit.ok) {
|
|
119
|
+
return recordConcurrentConfigChange({
|
|
120
|
+
root,
|
|
121
|
+
reportPath: input.reportPath,
|
|
122
|
+
cause,
|
|
123
|
+
configPath,
|
|
124
|
+
before,
|
|
125
|
+
expectedExists,
|
|
126
|
+
observed: commit.observed,
|
|
127
|
+
backupPath: commit.backupPath,
|
|
128
|
+
repairedKeys: preserved.keys,
|
|
129
|
+
forbiddenTopLevel
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
backupPath = commit.backupPath;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
backupPath = before.trim() ? await backupCodexConfig(configPath, before, input.backupTag || cause) : null;
|
|
136
|
+
await ensureDir(path.dirname(configPath));
|
|
137
|
+
await writeTextAtomic(configPath, next, { mode: 0o600 });
|
|
138
|
+
}
|
|
85
139
|
const afterSnapshot = await snapshotForConfig(root, configPath).catch(() => null);
|
|
86
140
|
const diff = beforeSnapshot && afterSnapshot ? diffCodexAppUiSnapshots(beforeSnapshot, afterSnapshot) : null;
|
|
87
141
|
const result = {
|
|
@@ -115,6 +169,123 @@ export async function writeCodexConfigGuarded(input) {
|
|
|
115
169
|
});
|
|
116
170
|
return { ...result, report_path: reportPath };
|
|
117
171
|
}
|
|
172
|
+
function configCommitSnapshotMatches(observed, expectedExists, expectedText) {
|
|
173
|
+
return observed.ok
|
|
174
|
+
&& observed.exists === expectedExists
|
|
175
|
+
&& sha256(observed.text) === sha256(expectedText);
|
|
176
|
+
}
|
|
177
|
+
async function recordConcurrentConfigChange(input) {
|
|
178
|
+
const result = {
|
|
179
|
+
ok: false,
|
|
180
|
+
status: 'concurrent_change_detected',
|
|
181
|
+
config_path: input.configPath,
|
|
182
|
+
backup_path: input.backupPath,
|
|
183
|
+
changed: false,
|
|
184
|
+
repaired_keys: input.repairedKeys,
|
|
185
|
+
forbidden_top_level: input.forbiddenTopLevel
|
|
186
|
+
};
|
|
187
|
+
await recordCodexConfigGuard(input.root, input.reportPath, {
|
|
188
|
+
cause: input.cause,
|
|
189
|
+
config_path: input.configPath,
|
|
190
|
+
ok: false,
|
|
191
|
+
status: result.status,
|
|
192
|
+
expected_exists: input.expectedExists,
|
|
193
|
+
observed_exists: input.observed.exists,
|
|
194
|
+
expected_sha256: sha256(input.before),
|
|
195
|
+
observed_sha256: input.observed.ok ? sha256(input.observed.text) : null,
|
|
196
|
+
observed_status: input.observed.status,
|
|
197
|
+
backup_path: input.backupPath,
|
|
198
|
+
changed: false
|
|
199
|
+
});
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
async function commitCodexConfigIfUnchanged(input) {
|
|
203
|
+
await ensureDir(path.dirname(input.configPath));
|
|
204
|
+
const token = `${Date.now().toString(36)}-${process.pid}-${randomBytes(6).toString('hex')}`;
|
|
205
|
+
const candidatePath = `${input.configPath}.sks-commit-${token}.tmp`;
|
|
206
|
+
let backupPath = null;
|
|
207
|
+
try {
|
|
208
|
+
await writeTextAtomic(candidatePath, input.next, { mode: 0o600 });
|
|
209
|
+
const observed = await readConfigCommitSnapshot(input.configPath);
|
|
210
|
+
if (!configCommitSnapshotMatches(observed, input.expectedExists, input.before)) {
|
|
211
|
+
return { ok: false, backupPath, observed };
|
|
212
|
+
}
|
|
213
|
+
if (input.expectedExists) {
|
|
214
|
+
backupPath = `${input.configPath}.sks-${safeBackupTag(input.backupTag)}-${token}.bak`;
|
|
215
|
+
try {
|
|
216
|
+
await fsp.rename(input.configPath, backupPath);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return { ok: false, backupPath: null, observed: await readConfigCommitSnapshot(input.configPath) };
|
|
220
|
+
}
|
|
221
|
+
const claimed = await readConfigCommitSnapshot(backupPath);
|
|
222
|
+
if (!configCommitSnapshotMatches(claimed, true, input.before)) {
|
|
223
|
+
await restoreClaimedConfigIfAbsent(backupPath, input.configPath, claimed);
|
|
224
|
+
return { ok: false, backupPath, observed: claimed };
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
await fsp.chmod(backupPath, 0o600);
|
|
228
|
+
const now = new Date();
|
|
229
|
+
await fsp.utimes(backupPath, now, now);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
await restoreClaimedConfigIfAbsent(backupPath, input.configPath, claimed);
|
|
233
|
+
return { ok: false, backupPath, observed: await readConfigCommitSnapshot(input.configPath) };
|
|
234
|
+
}
|
|
235
|
+
const afterClaim = await readConfigCommitSnapshot(input.configPath);
|
|
236
|
+
if (!afterClaim.ok || afterClaim.exists) {
|
|
237
|
+
if (!afterClaim.exists)
|
|
238
|
+
await restoreClaimedConfigIfAbsent(backupPath, input.configPath, claimed);
|
|
239
|
+
return { ok: false, backupPath, observed: await readConfigCommitSnapshot(input.configPath) };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await fsp.link(candidatePath, input.configPath);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
if (backupPath) {
|
|
247
|
+
const claimed = await readConfigCommitSnapshot(backupPath);
|
|
248
|
+
await restoreClaimedConfigIfAbsent(backupPath, input.configPath, claimed);
|
|
249
|
+
}
|
|
250
|
+
return { ok: false, backupPath, observed: await readConfigCommitSnapshot(input.configPath) };
|
|
251
|
+
}
|
|
252
|
+
await fsp.chmod(input.configPath, 0o600).catch(() => undefined);
|
|
253
|
+
await cleanupCodexConfigBackups(input.configPath, { keepPerTag: 3, maxAgeMs: 30 * 24 * 60 * 60 * 1000 }).catch(() => undefined);
|
|
254
|
+
return { ok: true, backupPath };
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
await fsp.rm(candidatePath, { force: true }).catch(() => undefined);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function restoreClaimedConfigIfAbsent(backupPath, configPath, claimed) {
|
|
261
|
+
if (!claimed.ok || !claimed.exists || claimed.status !== 'regular')
|
|
262
|
+
return;
|
|
263
|
+
await fsp.link(backupPath, configPath).catch(() => undefined);
|
|
264
|
+
}
|
|
265
|
+
function safeBackupTag(value) {
|
|
266
|
+
return String(value || 'codex-config').replace(/[^A-Za-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 96) || 'codex-config';
|
|
267
|
+
}
|
|
268
|
+
async function readConfigCommitSnapshot(configPath) {
|
|
269
|
+
let stat;
|
|
270
|
+
try {
|
|
271
|
+
stat = await fsp.lstat(configPath);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (error?.code === 'ENOENT')
|
|
275
|
+
return { ok: true, exists: false, text: '', status: 'missing' };
|
|
276
|
+
return { ok: false, exists: false, text: '', status: 'read_failed' };
|
|
277
|
+
}
|
|
278
|
+
if (stat.isSymbolicLink())
|
|
279
|
+
return { ok: false, exists: true, text: '', status: 'symlink' };
|
|
280
|
+
if (!stat.isFile())
|
|
281
|
+
return { ok: false, exists: true, text: '', status: 'non_regular' };
|
|
282
|
+
try {
|
|
283
|
+
return { ok: true, exists: true, text: await fsp.readFile(configPath, 'utf8'), status: 'regular' };
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return { ok: false, exists: true, text: '', status: 'read_failed' };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
118
289
|
export function extractTomlTable(text, tableName) {
|
|
119
290
|
const source = String(text || '');
|
|
120
291
|
const header = `[${tableName}]`;
|
|
@@ -50,16 +50,29 @@ export async function inspectCodexConfigReadability(rootInput = process.cwd(), o
|
|
|
50
50
|
}
|
|
51
51
|
add({ name: 'config_symlink', ok: !isSymlink || symlinkDetail.allowed === true, detail: symlinkDetail });
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const readChecksPromise = Promise.all([
|
|
54
|
+
nodeReadCheck(configPath),
|
|
55
|
+
childReadCheck(configPath, root),
|
|
56
|
+
codexCliConfigLoadCheck(root, configPath, opts)
|
|
57
|
+
]);
|
|
58
|
+
// These are independent read-only metadata probes. Running them serially
|
|
59
|
+
// multiplied a single TCC/ACL timeout by three on the MAD launch path.
|
|
60
|
+
const macChecksPromise = process.platform === 'darwin'
|
|
61
|
+
? Promise.all([
|
|
62
|
+
commandCheck('macos_acl_ls_le', 'ls', ['-le', configPath], root),
|
|
63
|
+
commandCheck('macos_flags_ls_lO', 'ls', ['-lO', configPath], root),
|
|
64
|
+
commandCheck('macos_xattr', 'xattr', ['-l', configPath], root, { allowExitCodes: [0, 1] })
|
|
65
|
+
])
|
|
66
|
+
: Promise.resolve(null);
|
|
67
|
+
const [[nodeRead, childRead, codexLoad], macChecks] = await Promise.all([readChecksPromise, macChecksPromise]);
|
|
68
|
+
if (macChecks) {
|
|
69
|
+
const [acl, flags, xattrs] = macChecks;
|
|
70
|
+
add(acl);
|
|
56
71
|
if (/\bdeny\b.*\b(read|readattr|readextattr|readsecurity|search)\b/i.test(String(acl?.detail?.stdout || '')))
|
|
57
72
|
blockers.add('acl_denied');
|
|
58
|
-
const flags = await commandCheck('macos_flags_ls_lO', 'ls', ['-lO', configPath], root);
|
|
59
73
|
add(flags);
|
|
60
74
|
if (/\b(uchg|schg|restricted)\b/.test(String(flags.detail?.stdout || '')))
|
|
61
75
|
blockers.add('flags_locked');
|
|
62
|
-
const xattrs = await commandCheck('macos_xattr', 'xattr', ['-l', configPath], root, { allowExitCodes: [0, 1] });
|
|
63
76
|
add(xattrs);
|
|
64
77
|
add({ name: 'macos_quarantine_xattr', ok: !/com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')), detail: { present: /com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')) } });
|
|
65
78
|
if (/com\.apple\.quarantine/.test(String(xattrs.detail?.stdout || '')))
|
|
@@ -68,9 +81,9 @@ export async function inspectCodexConfigReadability(rootInput = process.cwd(), o
|
|
|
68
81
|
else {
|
|
69
82
|
add({ name: 'macos_metadata', ok: true, status: 'skipped_non_macos' });
|
|
70
83
|
}
|
|
71
|
-
add(
|
|
72
|
-
add(
|
|
73
|
-
add(
|
|
84
|
+
add(nodeRead);
|
|
85
|
+
add(childRead);
|
|
86
|
+
add(codexLoad);
|
|
74
87
|
}
|
|
75
88
|
else {
|
|
76
89
|
add({ name: 'config_file_checks', ok: true, status: 'skipped_config_missing', detail: { config_path: configPath } });
|
|
@@ -52,9 +52,8 @@ export async function cleanupCodexConfigBackups(configPath, opts = {}) {
|
|
|
52
52
|
const now = Date.now();
|
|
53
53
|
const groups = new Map();
|
|
54
54
|
for (const file of entries) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!/(?:^|[.-])(?:sks-|bak-|struct-bak-)|\.bak$/.test(file))
|
|
55
|
+
const tag = ownedBackupTag(base, file);
|
|
56
|
+
if (!tag)
|
|
58
57
|
continue;
|
|
59
58
|
const abs = path.join(dir, file);
|
|
60
59
|
let stat;
|
|
@@ -66,7 +65,6 @@ export async function cleanupCodexConfigBackups(configPath, opts = {}) {
|
|
|
66
65
|
}
|
|
67
66
|
if (!stat.isFile())
|
|
68
67
|
continue;
|
|
69
|
-
const tag = backupTag(base, file);
|
|
70
68
|
const group = groups.get(tag) || [];
|
|
71
69
|
group.push({ file, abs, mtimeMs: stat.mtimeMs });
|
|
72
70
|
groups.set(tag, group);
|
|
@@ -87,16 +85,21 @@ export async function cleanupCodexConfigBackups(configPath, opts = {}) {
|
|
|
87
85
|
}
|
|
88
86
|
return { cleaned: removed.length, files: removed };
|
|
89
87
|
}
|
|
90
|
-
function
|
|
88
|
+
function ownedBackupTag(base, file) {
|
|
89
|
+
if (!file.startsWith(`${base}.`))
|
|
90
|
+
return null;
|
|
91
91
|
const suffix = file.slice(`${base}.`.length);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return `sks-${
|
|
95
|
-
|
|
92
|
+
if (/^sks-[A-Za-z0-9_.-]+\.bak$/.test(suffix)) {
|
|
93
|
+
const group = suffix.match(/^sks-([^-]+)-/)?.[1] || suffix.slice('sks-'.length, -'.bak'.length);
|
|
94
|
+
return `sks-${group}`;
|
|
95
|
+
}
|
|
96
|
+
if (/^struct-bak-[A-Za-z0-9_.-]+$/.test(suffix))
|
|
96
97
|
return 'struct-bak';
|
|
97
|
-
if (
|
|
98
|
+
if (/^bak-[A-Za-z0-9_.-]+$/.test(suffix))
|
|
98
99
|
return 'bak';
|
|
99
|
-
|
|
100
|
+
if (/^codex-app-ui-repair-[A-Za-z0-9_.-]+\.bak$/.test(suffix))
|
|
101
|
+
return 'codex-app-ui-repair';
|
|
102
|
+
return null;
|
|
100
103
|
}
|
|
101
104
|
function messageOf(err) {
|
|
102
105
|
return err instanceof Error ? err.message : String(err);
|