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.
Files changed (64) hide show
  1. package/README.md +11 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/install-helpers.js +8 -3
  5. package/dist/config/skills-manifest.json +58 -58
  6. package/dist/core/agents/agent-effort-policy.js +28 -17
  7. package/dist/core/agents/agent-schema.js +1 -1
  8. package/dist/core/codex/codex-config-guard.js +178 -7
  9. package/dist/core/codex/codex-config-readability.js +21 -8
  10. package/dist/core/codex/codex-config-toml.js +14 -11
  11. package/dist/core/codex-app/mcp-manager.js +679 -0
  12. package/dist/core/codex-app/sks-menubar.js +405 -6
  13. package/dist/core/codex-control/codex-lb-launch-recovery.js +15 -0
  14. package/dist/core/codex-native/core-skill-manifest.js +1 -1
  15. package/dist/core/commands/mad-sks-command.js +44 -3
  16. package/dist/core/commands/menubar-command.js +94 -0
  17. package/dist/core/commands/naruto-command.js +3 -1
  18. package/dist/core/commands/wiki-command.js +11 -5
  19. package/dist/core/fsx.js +1 -0
  20. package/dist/core/hooks-runtime/code-pack-freshness-preflight.js +14 -8
  21. package/dist/core/hooks-runtime/naruto-decision-gate.js +184 -0
  22. package/dist/core/hooks-runtime/stop-repeat-guard.js +89 -0
  23. package/dist/core/hooks-runtime.js +36 -105
  24. package/dist/core/init/skills.js +4 -4
  25. package/dist/core/init.js +1 -1
  26. package/dist/core/managed-assets/managed-assets-manifest.js +178 -29
  27. package/dist/core/preflight/parallel-preflight-engine.js +16 -3
  28. package/dist/core/proof/route-adapter.js +1 -1
  29. package/dist/core/proof/selftest-proof-fixtures.js +3 -5
  30. package/dist/core/provider/model-router.js +15 -6
  31. package/dist/core/release/package-size-budget.js +4 -6
  32. package/dist/core/research/research-plan-markdown.js +123 -0
  33. package/dist/core/research/research-super-search.js +8 -4
  34. package/dist/core/research.js +4 -119
  35. package/dist/core/retention.js +70 -2
  36. package/dist/core/routes/dollar-manifest-lite.js +1 -1
  37. package/dist/core/routes.js +75 -22
  38. package/dist/core/subagents/agent-catalog.js +87 -15
  39. package/dist/core/subagents/model-policy.js +189 -48
  40. package/dist/core/subagents/naruto-help-contract.js +11 -4
  41. package/dist/core/subagents/official-subagent-preparation.js +55 -9
  42. package/dist/core/subagents/official-subagent-prompt.js +45 -23
  43. package/dist/core/subagents/thread-budget.js +1 -1
  44. package/dist/core/subagents/triwiki-attention.js +117 -14
  45. package/dist/core/triwiki/code-pack-head-freshness.js +291 -0
  46. package/dist/core/version.js +1 -1
  47. package/dist/core/zellij/zellij-launcher.js +12 -2
  48. package/dist/core/zellij/zellij-pane-proof.js +9 -1
  49. package/dist/core/zellij/zellij-update.js +14 -1
  50. package/dist/scripts/canonical-test-runner.js +7 -1
  51. package/dist/scripts/codex-lb-fast-mode-truth-check.js +11 -1
  52. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +9 -2
  53. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +13 -2
  54. package/dist/scripts/codex-native-agent-role-content-check.js +13 -1
  55. package/dist/scripts/codex-sdk-backend-router-check.js +2 -0
  56. package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
  57. package/dist/scripts/official-subagent-workflow-check.js +1 -1
  58. package/dist/scripts/packlist-performance-check.js +1 -18
  59. package/dist/scripts/python-codex-sdk-all-pipelines-check.js +8 -0
  60. package/dist/scripts/sks-menubar-install-check.js +6 -1
  61. package/dist/scripts/super-search-provider-interface-check.js +31 -18
  62. package/package.json +3 -1
  63. package/dist/scripts/codex-0139-feature-probes-check.js +0 -30
  64. package/dist/scripts/codex-0139-marketplace-source-check.js +0 -13
@@ -2,41 +2,42 @@ import path from 'node:path';
2
2
  import { readJson } from '../fsx.js';
3
3
  export const BOUNDED_TRIWIKI_ATTENTION_SCHEMA = 'sks.subagent-triwiki-attention.v1';
4
4
  export const DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT = 8;
5
- export async function readBoundedTriwikiAttention(root, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT) {
5
+ export async function readBoundedTriwikiAttention(root, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT, query = '') {
6
6
  const pack = await readJson(path.join(root, '.sneakoscope', 'wiki', 'context-pack.json'), null);
7
- return extractBoundedTriwikiAttention(pack, limit);
7
+ return extractBoundedTriwikiAttention(pack, limit, query);
8
8
  }
9
- export function extractBoundedTriwikiAttention(value, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT) {
9
+ export function extractBoundedTriwikiAttention(value, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT, query = '') {
10
10
  const pack = asRecord(value);
11
11
  const attention = asRecord(pack.attention);
12
12
  const anchorLimit = normalizeLimit(limit);
13
13
  const hydrateHints = new Map();
14
- for (const row of Array.isArray(attention.hydrate_first) ? attention.hydrate_first : []) {
14
+ const hydrateOrder = new Map();
15
+ for (const [index, row] of (Array.isArray(attention.hydrate_first) ? attention.hydrate_first : []).entries()) {
15
16
  if (!Array.isArray(row))
16
17
  continue;
17
18
  const id = text(row[0]);
18
19
  const hint = text(row[1]);
19
- if (id && hint)
20
+ if (id && hint) {
20
21
  hydrateHints.set(id, hint.slice(0, 240));
22
+ hydrateOrder.set(id, index);
23
+ }
21
24
  }
22
- const anchors = [];
23
- const seen = new Set();
24
- for (const row of Array.isArray(attention.use_first) ? attention.use_first : []) {
25
+ const useFirst = [];
26
+ for (const [index, row] of (Array.isArray(attention.use_first) ? attention.use_first : []).entries()) {
25
27
  if (!Array.isArray(row))
26
28
  continue;
27
29
  const id = text(row[0]);
28
- if (!id || seen.has(id))
30
+ if (!id || useFirst.some((anchor) => anchor.id === id))
29
31
  continue;
30
- seen.add(id);
31
- anchors.push({
32
+ useFirst.push({
32
33
  id,
33
34
  claim_hash: text(row[1]) || null,
34
35
  source_hash: text(row[2]) || null,
35
- hydrate_hint: hydrateHints.get(id) || null
36
+ hydrate_hint: hydrateHints.get(id) || null,
37
+ order: index
36
38
  });
37
- if (anchors.length >= anchorLimit)
38
- break;
39
39
  }
40
+ const anchors = selectAnchors(useFirst, hydrateHints, hydrateOrder, anchorLimit, query);
40
41
  return {
41
42
  schema: BOUNDED_TRIWIKI_ATTENTION_SCHEMA,
42
43
  source: '.sneakoscope/wiki/context-pack.json',
@@ -48,6 +49,108 @@ export function extractBoundedTriwikiAttention(value, limit = DEFAULT_TRIWIKI_AT
48
49
  full_pack_injected: false
49
50
  };
50
51
  }
52
+ function selectAnchors(useFirst, hydrateHints, hydrateOrder, limit, query) {
53
+ const tokens = attentionQueryTokens(query);
54
+ if (!tokens.length)
55
+ return useFirst.slice(0, limit).map(stripOrder);
56
+ const selected = [];
57
+ const seen = new Set();
58
+ // Keep the leading high-trust policy anchors, then spend the remaining
59
+ // budget on query-relevant use_first or hydrate-first candidates. Hydrate-
60
+ // only rows stay hints: workers must open the cited source before relying on
61
+ // them, so relevance improves without treating lower-trust summaries as fact.
62
+ for (const anchor of useFirst.slice(0, Math.min(3, limit))) {
63
+ selected.push(stripOrder(anchor));
64
+ seen.add(anchor.id);
65
+ }
66
+ const candidates = [];
67
+ for (const anchor of useFirst) {
68
+ if (seen.has(anchor.id))
69
+ continue;
70
+ candidates.push({
71
+ ...stripOrder(anchor),
72
+ score: attentionRelevance(anchor.id, anchor.hydrate_hint, tokens),
73
+ order: anchor.order,
74
+ priority: 0
75
+ });
76
+ }
77
+ for (const [id, hint] of hydrateHints.entries()) {
78
+ if (seen.has(id) || useFirst.some((anchor) => anchor.id === id))
79
+ continue;
80
+ const score = attentionRelevance(id, hint, tokens);
81
+ if (score <= 0)
82
+ continue;
83
+ candidates.push({
84
+ id,
85
+ claim_hash: null,
86
+ source_hash: null,
87
+ hydrate_hint: hint,
88
+ score,
89
+ order: hydrateOrder.get(id) ?? Number.MAX_SAFE_INTEGER,
90
+ priority: 1
91
+ });
92
+ }
93
+ candidates.sort((left, right) => right.score - left.score || left.priority - right.priority || left.order - right.order);
94
+ for (const candidate of candidates) {
95
+ if (selected.length >= limit)
96
+ break;
97
+ if (candidate.score <= 0 || seen.has(candidate.id))
98
+ continue;
99
+ selected.push(stripRank(candidate));
100
+ seen.add(candidate.id);
101
+ }
102
+ for (const anchor of useFirst) {
103
+ if (selected.length >= limit)
104
+ break;
105
+ if (seen.has(anchor.id))
106
+ continue;
107
+ selected.push(stripOrder(anchor));
108
+ seen.add(anchor.id);
109
+ }
110
+ return selected;
111
+ }
112
+ function attentionQueryTokens(value) {
113
+ const stop = new Set([
114
+ 'and', 'the', 'for', 'with', 'from', 'into', 'this', 'that', 'sks', 'src', 'core',
115
+ '작업', '구현', '개선', '추가', '변경', '기능', '모든', '최대한'
116
+ ]);
117
+ return [...new Set(String(value || '')
118
+ .normalize('NFKC')
119
+ .toLowerCase()
120
+ .replace(/[^\p{L}\p{N}]+/gu, ' ')
121
+ .split(/\s+/)
122
+ .map((token) => token.trim())
123
+ .filter((token) => token.length >= 2 && !stop.has(token)))]
124
+ .slice(0, 64);
125
+ }
126
+ function attentionRelevance(id, hint, tokens) {
127
+ const haystack = `${id} ${hint || ''}`
128
+ .normalize('NFKC')
129
+ .toLowerCase()
130
+ .replace(/[^\p{L}\p{N}]+/gu, ' ');
131
+ const compact = haystack.replace(/\s+/g, '');
132
+ return tokens.reduce((score, token) => {
133
+ if (haystack.includes(token))
134
+ return score + (token.length >= 5 ? 4 : 3);
135
+ return compact.includes(token) ? score + 2 : score;
136
+ }, 0);
137
+ }
138
+ function stripOrder(anchor) {
139
+ return {
140
+ id: anchor.id,
141
+ claim_hash: anchor.claim_hash,
142
+ source_hash: anchor.source_hash,
143
+ hydrate_hint: anchor.hydrate_hint
144
+ };
145
+ }
146
+ function stripRank(anchor) {
147
+ return {
148
+ id: anchor.id,
149
+ claim_hash: anchor.claim_hash,
150
+ source_hash: anchor.source_hash,
151
+ hydrate_hint: anchor.hydrate_hint
152
+ };
153
+ }
51
154
  function normalizeLimit(value) {
52
155
  const parsed = Number(value);
53
156
  if (!Number.isFinite(parsed))
@@ -0,0 +1,291 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readJson, runProcess, writeJsonAtomic } from '../fsx.js';
4
+ const CODE_PACK_METADATA_PATHS = new Set([
5
+ '.sneakoscope/wiki/code-pack.json',
6
+ '.sneakoscope/wiki/code-pack.prev.json'
7
+ ]);
8
+ const COMMIT_MARKER = '@SKS-CODE-PACK';
9
+ const ADVISORY_CACHE_SCHEMA = 'sks.code-pack-head-freshness-cache.v1';
10
+ const ADVISORY_CACHE_PATH = path.join('.sneakoscope', 'cache', 'code-pack-head-freshness.json');
11
+ export async function inspectCodePackHeadFreshness(root, packShaInput, opts = {}) {
12
+ const timeoutMs = Math.max(1, Math.floor(opts.timeoutMs ?? 5_000));
13
+ const startedAt = Date.now();
14
+ const packSha = normalizeGitSha(packShaInput);
15
+ const fastHead = await readGitHeadFromAdminFiles(root);
16
+ if (!packSha)
17
+ return staleWithCurrentHead(root, null, timeoutMs, fastHead, false, 'invalid_pack_sha');
18
+ if (fastHead === packSha)
19
+ return freshness(true, true, 'exact_head', fastHead, packSha, false, []);
20
+ // Only the non-blocking hook consumes this cache. Authoritative wiki
21
+ // validation always replays Git history and never trusts advisory state.
22
+ if (opts.advisoryCache && fastHead) {
23
+ const cached = await readAdvisoryCache(root, packSha, fastHead);
24
+ if (cached)
25
+ return cached;
26
+ }
27
+ // One common-path Git process returns HEAD plus every committed path after
28
+ // the pack was generated. Excluding all parents of packSha keeps packSha in
29
+ // the walk (including a root commit), so its presence proves ancestry. We
30
+ // keep scanning after the pack marker because a later merge can introduce
31
+ // sibling-branch commits that Git orders after packSha. `-m` exposes merge
32
+ // resolution paths and `--no-renames` makes touched paths auditable.
33
+ const history = await runProcess('git', [
34
+ '-c',
35
+ 'core.quotepath=true',
36
+ 'log',
37
+ '--topo-order',
38
+ '-m',
39
+ '--no-ext-diff',
40
+ `--format=${COMMIT_MARKER}%x09%H`,
41
+ '--name-status',
42
+ '--no-renames',
43
+ 'HEAD',
44
+ '--not',
45
+ `${packSha}^@`,
46
+ '--'
47
+ ], {
48
+ cwd: root,
49
+ timeoutMs,
50
+ maxOutputBytes: 64 * 1024,
51
+ env: gitEnvironment({
52
+ GIT_NO_REPLACE_OBJECTS: '1',
53
+ GIT_OPTIONAL_LOCKS: '0',
54
+ LC_ALL: 'C'
55
+ })
56
+ }).catch(() => null);
57
+ if (!history || history.code !== 0 || history.timedOut) {
58
+ return staleWithCurrentHead(root, packSha, remainingMs(startedAt, timeoutMs), fastHead, false, history?.timedOut ? 'git_timeout' : 'git_failed');
59
+ }
60
+ const parsed = parseHistory(history.stdout, packSha);
61
+ if (history.truncated || !parsed.currentSha || parsed.invalid) {
62
+ const currentSha = parsed.currentSha
63
+ || fastHead
64
+ || await readCurrentHead(root, remainingMs(startedAt, timeoutMs));
65
+ return freshness(false, false, history.truncated ? 'history_truncated' : 'history_parse_invalid', currentSha, packSha, false, parsed.changedPaths);
66
+ }
67
+ if (!parsed.sawPack) {
68
+ return freshness(false, true, 'pack_not_ancestor', parsed.currentSha, packSha, false, parsed.changedPaths);
69
+ }
70
+ const metadataOnly = parsed.changedPaths.every((value) => CODE_PACK_METADATA_PATHS.has(value));
71
+ const exactHead = parsed.currentSha === packSha;
72
+ const result = freshness(metadataOnly, true, metadataOnly ? 'metadata_only_history' : 'source_change_history', parsed.currentSha, packSha, metadataOnly && !exactHead, parsed.changedPaths);
73
+ if (opts.advisoryCache && fastHead === parsed.currentSha) {
74
+ await writeAdvisoryCache(root, result).catch(() => undefined);
75
+ }
76
+ return result;
77
+ }
78
+ function parseHistory(stdout, packSha) {
79
+ let currentSha = null;
80
+ let activeCommit = null;
81
+ let sawPack = false;
82
+ let invalid = false;
83
+ const changedPaths = [];
84
+ const seenPaths = new Set();
85
+ for (const line of String(stdout || '').split(/\r?\n/)) {
86
+ if (line === '')
87
+ continue;
88
+ const marker = line.match(/^@SKS-CODE-PACK\t([0-9a-f]{40,64})$/i);
89
+ if (marker) {
90
+ activeCommit = String(marker[1]).toLowerCase();
91
+ currentSha ||= activeCommit;
92
+ if (activeCommit === packSha)
93
+ sawPack = true;
94
+ continue;
95
+ }
96
+ if (!activeCommit) {
97
+ invalid = true;
98
+ break;
99
+ }
100
+ const separator = line.indexOf('\t');
101
+ const status = separator > 0 ? line.slice(0, separator) : '';
102
+ const pathText = separator > 0 ? line.slice(separator + 1) : '';
103
+ if (!/^[A-Z][0-9]*$/.test(status) || !pathText) {
104
+ invalid = true;
105
+ break;
106
+ }
107
+ if (activeCommit === packSha)
108
+ continue;
109
+ // Do not trim Git paths: leading/trailing whitespace is meaningful and
110
+ // must never be normalized into an allowlisted metadata path.
111
+ for (const changedPath of pathText.split('\t')) {
112
+ if (!changedPath || seenPaths.has(changedPath))
113
+ continue;
114
+ seenPaths.add(changedPath);
115
+ changedPaths.push(changedPath);
116
+ }
117
+ }
118
+ return { currentSha, sawPack, invalid, changedPaths };
119
+ }
120
+ async function readAdvisoryCache(root, packSha, currentSha) {
121
+ const cached = await readJson(path.join(root, ADVISORY_CACHE_PATH), null).catch(() => null);
122
+ if (cached?.schema !== ADVISORY_CACHE_SCHEMA)
123
+ return null;
124
+ if (normalizeGitSha(cached.pack_sha) !== packSha || normalizeGitSha(cached.current_sha) !== currentSha)
125
+ return null;
126
+ const changedPaths = Array.isArray(cached.changed_paths)
127
+ ? cached.changed_paths.filter((value) => typeof value === 'string').slice(0, 64)
128
+ : [];
129
+ const fresh = cached.fresh === true;
130
+ if (fresh && (cached.metadata_only_drift !== true || changedPaths.some((value) => !CODE_PACK_METADATA_PATHS.has(value)))) {
131
+ return null;
132
+ }
133
+ return freshness(fresh, true, 'advisory_cache', currentSha, packSha, fresh, changedPaths);
134
+ }
135
+ async function writeAdvisoryCache(root, result) {
136
+ if (!result.pack_sha || !result.current_sha)
137
+ return;
138
+ await writeJsonAtomic(path.join(root, ADVISORY_CACHE_PATH), {
139
+ schema: ADVISORY_CACHE_SCHEMA,
140
+ pack_sha: result.pack_sha,
141
+ current_sha: result.current_sha,
142
+ fresh: result.fresh,
143
+ reason: result.reason,
144
+ metadata_only_drift: result.metadata_only_drift,
145
+ changed_paths: result.changed_paths.slice(0, 64),
146
+ checked_at: new Date().toISOString()
147
+ });
148
+ }
149
+ async function staleWithCurrentHead(root, packSha, timeoutMs, knownCurrentSha = null, conclusive = true, reason = 'git_failed') {
150
+ const currentSha = knownCurrentSha || await readCurrentHead(root, timeoutMs);
151
+ return freshness(false, conclusive, reason, currentSha, packSha, false, []);
152
+ }
153
+ async function readCurrentHead(root, timeoutMs) {
154
+ if (timeoutMs <= 0)
155
+ return null;
156
+ const head = await runProcess('git', ['rev-parse', 'HEAD'], {
157
+ cwd: root,
158
+ timeoutMs,
159
+ maxOutputBytes: 4 * 1024,
160
+ env: gitEnvironment({
161
+ GIT_NO_REPLACE_OBJECTS: '1',
162
+ GIT_OPTIONAL_LOCKS: '0',
163
+ LC_ALL: 'C'
164
+ })
165
+ }).catch(() => null);
166
+ return head && head.code === 0 && !head.truncated ? normalizeGitSha(head.stdout) : null;
167
+ }
168
+ async function readGitHeadFromAdminFiles(root) {
169
+ const gitDir = await resolveGitDir(root);
170
+ if (!gitDir)
171
+ return null;
172
+ const headText = await readSmallText(path.join(gitDir, 'HEAD'), 4 * 1024);
173
+ if (!headText)
174
+ return null;
175
+ const detached = normalizeGitSha(headText);
176
+ if (detached)
177
+ return detached;
178
+ const symbolic = headText.match(/^ref:\s*(refs\/[^\r\n]+)\s*$/);
179
+ const ref = symbolic ? String(symbolic[1]) : '';
180
+ if (!safeGitRef(ref))
181
+ return null;
182
+ const commonDir = await resolveCommonDir(gitDir);
183
+ for (const base of [...new Set([gitDir, commonDir])]) {
184
+ const loose = await readLooseRef(base, ref);
185
+ if (loose)
186
+ return loose;
187
+ }
188
+ return readPackedRef(commonDir, ref);
189
+ }
190
+ async function resolveGitDir(root) {
191
+ const dotGit = path.join(root, '.git');
192
+ const stat = await fsp.stat(dotGit).catch(() => null);
193
+ if (!stat)
194
+ return null;
195
+ if (stat.isDirectory())
196
+ return dotGit;
197
+ if (!stat.isFile())
198
+ return null;
199
+ const text = await readSmallText(dotGit, 4 * 1024);
200
+ const match = text?.match(/^gitdir:\s*(.+?)\s*$/m);
201
+ return match ? path.resolve(path.dirname(dotGit), String(match[1])) : null;
202
+ }
203
+ async function resolveCommonDir(gitDir) {
204
+ const text = await readSmallText(path.join(gitDir, 'commondir'), 4 * 1024);
205
+ return text?.trim() ? path.resolve(gitDir, text.trim()) : gitDir;
206
+ }
207
+ async function readLooseRef(base, ref) {
208
+ const file = safeRefPath(base, ref);
209
+ if (!file)
210
+ return null;
211
+ return normalizeGitSha(await readSmallText(file, 4 * 1024));
212
+ }
213
+ async function readPackedRef(commonDir, ref) {
214
+ const text = await readSmallText(path.join(commonDir, 'packed-refs'), 4 * 1024 * 1024);
215
+ if (!text)
216
+ return null;
217
+ for (const line of text.split(/\r?\n/)) {
218
+ if (!line || line.startsWith('#') || line.startsWith('^'))
219
+ continue;
220
+ const separator = line.indexOf(' ');
221
+ if (separator <= 0 || line.slice(separator + 1) !== ref)
222
+ continue;
223
+ return normalizeGitSha(line.slice(0, separator));
224
+ }
225
+ return null;
226
+ }
227
+ async function readSmallText(file, maxBytes) {
228
+ const stat = await fsp.stat(file).catch(() => null);
229
+ if (!stat?.isFile() || stat.size > maxBytes)
230
+ return null;
231
+ return fsp.readFile(file, 'utf8').catch(() => null);
232
+ }
233
+ function safeGitRef(ref) {
234
+ return /^refs\/[A-Za-z0-9._/-]+$/.test(ref)
235
+ && !ref.includes('..')
236
+ && !ref.includes('//')
237
+ && !ref.includes('@{')
238
+ && !ref.endsWith('/')
239
+ && !ref.endsWith('.')
240
+ && !ref.endsWith('.lock');
241
+ }
242
+ function safeRefPath(base, ref) {
243
+ if (!safeGitRef(ref))
244
+ return null;
245
+ const candidate = path.resolve(base, ...ref.split('/'));
246
+ const relative = path.relative(path.resolve(base), candidate);
247
+ return relative && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative) ? candidate : null;
248
+ }
249
+ function remainingMs(startedAt, timeoutMs) {
250
+ return Math.max(0, timeoutMs - (Date.now() - startedAt));
251
+ }
252
+ function gitEnvironment(extra = {}) {
253
+ return {
254
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: undefined,
255
+ GIT_COMMON_DIR: undefined,
256
+ GIT_CONFIG: undefined,
257
+ GIT_CONFIG_COUNT: '0',
258
+ GIT_CONFIG_GLOBAL: undefined,
259
+ GIT_CONFIG_NOSYSTEM: undefined,
260
+ GIT_CONFIG_PARAMETERS: undefined,
261
+ GIT_CONFIG_SYSTEM: undefined,
262
+ GIT_DIR: undefined,
263
+ GIT_DISCOVERY_ACROSS_FILESYSTEM: undefined,
264
+ GIT_EXTERNAL_DIFF: undefined,
265
+ GIT_GRAFT_FILE: undefined,
266
+ GIT_INDEX_FILE: undefined,
267
+ GIT_NAMESPACE: undefined,
268
+ GIT_OBJECT_DIRECTORY: undefined,
269
+ GIT_QUARANTINE_PATH: undefined,
270
+ GIT_REPLACE_REF_BASE: undefined,
271
+ GIT_SHALLOW_FILE: undefined,
272
+ GIT_WORK_TREE: undefined,
273
+ ...extra
274
+ };
275
+ }
276
+ function freshness(fresh, conclusive, reason, currentSha, packSha, metadataOnlyDrift, changedPaths) {
277
+ return {
278
+ fresh,
279
+ conclusive,
280
+ reason,
281
+ current_sha: currentSha,
282
+ pack_sha: packSha,
283
+ metadata_only_drift: metadataOnlyDrift,
284
+ changed_paths: changedPaths
285
+ };
286
+ }
287
+ function normalizeGitSha(value) {
288
+ const text = String(value || '').trim();
289
+ return /^[0-9a-f]{40,64}$/i.test(text) && !/^0+$/.test(text) ? text.toLowerCase() : null;
290
+ }
291
+ //# sourceMappingURL=code-pack-head-freshness.js.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '6.1.2';
1
+ export const PACKAGE_VERSION = '6.2.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -29,7 +29,8 @@ export async function launchZellijLayout(opts = {}) {
29
29
  if (opts.codexBin)
30
30
  layoutInput.codexBin = opts.codexBin;
31
31
  const layout = await writeZellijLayout(root, layoutInput);
32
- const capability = await checkZellijCapability({ root, require: opts.requireZellij === true });
32
+ const capability = reusableZellijCapability(opts.zellijCapability, opts.requireZellij === true)
33
+ || await checkZellijCapability({ root, require: opts.requireZellij === true });
33
34
  // Configure the clipboard pipeline so selections inside the session reach the OS
34
35
  // clipboard (Zellij's default OSC-52 copy is dropped by Terminal.app etc.). The
35
36
  // copy option flags are appended AFTER `--default-layout <path>` so the launch
@@ -46,6 +47,7 @@ export async function launchZellijLayout(opts = {}) {
46
47
  root: opts.cwd || root,
47
48
  env: launchProcessEnv,
48
49
  cliArgs: layout.codex_args,
50
+ ...(opts.verifiedCodexLbToolOutputRecovery ? { verifiedProbe: opts.verifiedCodexLbToolOutputRecovery } : {}),
49
51
  ...(typeof opts.recoveryFetch === 'function' ? { fetchImpl: opts.recoveryFetch } : {}),
50
52
  ...(opts.recoveryTimeoutMs === undefined ? {} : { timeoutMs: opts.recoveryTimeoutMs }),
51
53
  ...(opts.recoveryAllowUnverified === true ? { allowUnverified: true } : {})
@@ -75,7 +77,8 @@ export async function launchZellijLayout(opts = {}) {
75
77
  ledgerRoot: path.join(root, '.sneakoscope', 'missions', missionId),
76
78
  sessionName,
77
79
  expectedLaneCount: 0,
78
- expectedCwd: opts.cwd || root
80
+ expectedCwd: opts.cwd || root,
81
+ zellijCapability: capability
79
82
  };
80
83
  if (layout.main_pane_kind === 'codex_interactive')
81
84
  paneProofOpts.expectedMainCommandIncludes = 'codex';
@@ -169,6 +172,13 @@ export async function launchZellijLayout(opts = {}) {
169
172
  ]);
170
173
  return report;
171
174
  }
175
+ function reusableZellijCapability(value, requireZellij) {
176
+ if (!value || value.schema !== 'sks.zellij-capability.v1')
177
+ return null;
178
+ if (requireZellij && value.status !== 'ok')
179
+ return null;
180
+ return value;
181
+ }
172
182
  function launchRecoveryEnv(launchEnv) {
173
183
  const env = { ...process.env };
174
184
  for (const [key, value] of Object.entries(launchEnv || {})) {
@@ -9,7 +9,8 @@ export async function writeZellijPaneProof(root, opts = {}) {
9
9
  const session = await readJson(path.join(outRoot, 'zellij-session.json'), null);
10
10
  const sessionName = opts.sessionName || session?.session_name || null;
11
11
  const expectedMainCommand = opts.expectedMainCommandIncludes || (session?.codex_pane?.enabled === true ? String(session.codex_pane.bin || 'codex') : '');
12
- const capability = await checkZellijCapability({ root, require: opts.require === true, writeReport: true });
12
+ const capability = reusableZellijCapability(opts.zellijCapability, opts.require === true)
13
+ || await checkZellijCapability({ root, require: opts.require === true, writeReport: true });
13
14
  const command = sessionName
14
15
  ? ['--session', sessionName, 'action', 'list-panes', '--json', '--all']
15
16
  : ['action', 'list-panes', '--json', '--all'];
@@ -93,6 +94,13 @@ export async function writeZellijPaneProof(root, opts = {}) {
93
94
  await writeJsonAtomic(path.join(outRoot, 'zellij-pane-proof.json'), report);
94
95
  return report;
95
96
  }
97
+ function reusableZellijCapability(value, requireZellij) {
98
+ if (!value || value.schema !== 'sks.zellij-capability.v1')
99
+ return null;
100
+ if (requireZellij && value.status !== 'ok')
101
+ return null;
102
+ return value;
103
+ }
96
104
  export async function readZellijPaneProof(root) {
97
105
  return readJson(path.join(root, 'zellij-pane-proof.json'), null);
98
106
  }
@@ -206,6 +206,7 @@ export async function upgradeZellijToLatest(input = {}) {
206
206
  export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
207
207
  const env = opts.env || process.env;
208
208
  const list = (args || []).map((arg) => String(arg));
209
+ const autoYes = list.includes('--yes') || list.includes('-y');
209
210
  const mode = resolveZellijUpdatePromptMode({
210
211
  env,
211
212
  skipFlag: list.includes('--json') || list.includes('--skip-cli-tools') || list.includes('--skip-zellij-update'),
@@ -215,6 +216,19 @@ export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
215
216
  if (mode === 'skip') {
216
217
  return { status: 'skipped', current: null, latest: null, command: null };
217
218
  }
219
+ if (opts.deferUpdateCheck === true && !autoYes) {
220
+ const localCapability = await checkZellijCapability({ require: false, writeReport: false, env }).catch(() => null);
221
+ if (localCapability?.status === 'ok') {
222
+ return {
223
+ status: 'current',
224
+ current: localCapability.version,
225
+ latest: null,
226
+ command: null,
227
+ deferred: true,
228
+ capability: localCapability
229
+ };
230
+ }
231
+ }
218
232
  const noticeInput = { env };
219
233
  if (opts.missionDir !== undefined)
220
234
  noticeInput.missionDir = opts.missionDir;
@@ -264,7 +278,6 @@ export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
264
278
  return { status: 'current', current: notice.current_version, latest: notice.latest_version, command: null, error: notice.error || null };
265
279
  }
266
280
  const label = opts.label || 'Zellij launch';
267
- const autoYes = list.includes('--yes') || list.includes('-y');
268
281
  if (mode === 'nonblocking-notice') {
269
282
  console.log(`Zellij update available: ${notice.current_version} -> ${notice.latest_version}. Run: ${notice.upgrade_command}`);
270
283
  return { status: 'available', current: notice.current_version, latest: notice.latest_version, command: notice.upgrade_command };
@@ -2,7 +2,7 @@
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
- import { tmpdir } from '../core/fsx.js';
5
+ import { SKS_TEMP_LEASE_FILE, tmpdir, writeJsonAtomic } from '../core/fsx.js';
6
6
  const root = process.cwd();
7
7
  const compiled = discover(path.join(root, 'dist'), (file) => file.endsWith('.test.js') && file.includes(`${path.sep}__tests__${path.sep}`));
8
8
  const unit = discover(path.join(root, 'test', 'unit'), (file) => file.endsWith('.test.mjs'));
@@ -60,6 +60,12 @@ process.once('exit', () => {
60
60
  if (error)
61
61
  console.error(`canonical test cleanup failed during exit: ${error.message}`);
62
62
  });
63
+ await writeJsonAtomic(path.join(scratch, SKS_TEMP_LEASE_FILE), {
64
+ schema: 'sks.temp-lease.v1',
65
+ kind: 'canonical-test-runner',
66
+ pid: process.pid,
67
+ created_at: new Date().toISOString()
68
+ });
63
69
  const isolatedProcessGroup = process.platform !== 'win32';
64
70
  const child = spawn(process.execPath, ['--test', '--test-concurrency=1', ...files, ...process.argv.slice(2)], {
65
71
  cwd: root,
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { run } from '../commands/codex-lb.js';
6
+ import { CODEX_LB_TOOL_OUTPUT_RECOVERY_MIN_VERSION } from '../core/codex-lb/codex-lb-tool-output-recovery.js';
6
7
  const calls = [];
7
8
  const home = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-lb-fast-truth-'));
8
9
  await fs.mkdir(path.join(home, '.codex'), { recursive: true });
@@ -58,7 +59,16 @@ async function runFastCheck(env) {
58
59
  try {
59
60
  Object.assign(process.env, env);
60
61
  process.exitCode = 0;
61
- globalThis.fetch = async (_url, init) => {
62
+ globalThis.fetch = async (url, init = {}) => {
63
+ if (new URL(String(url)).pathname === '/health') {
64
+ return new Response('{}', {
65
+ status: 200,
66
+ headers: {
67
+ 'content-type': 'application/json',
68
+ 'x-app-version': CODEX_LB_TOOL_OUTPUT_RECOVERY_MIN_VERSION
69
+ }
70
+ });
71
+ }
62
72
  const body = JSON.parse(String(init.body || '{}'));
63
73
  calls.push({ body });
64
74
  const id = calls.length % 2 === 1 ? 'resp_fast_1' : 'resp_fast_2';
@@ -41,8 +41,15 @@ await fs.writeFile(configPath, [
41
41
  ].join('\n'));
42
42
  await fs.writeFile(envPath, 'export CODEX_LB_BASE_URL="https://lb.example.test/backend-api/codex"\nexport CODEX_LB_API_KEY="sk-test-fast-ui"\n');
43
43
  await fs.writeFile(authPath, `${oauthAuth}\n`);
44
+ const toolOutputRecoveryFetch = async () => new Response('{}', {
45
+ status: 200,
46
+ headers: {
47
+ 'content-type': 'application/json',
48
+ 'x-app-version': '1.21.0-beta.3'
49
+ }
50
+ });
44
51
  const install = await ensureGlobalCodexFastModeDuringInstall({ home, configPath, forceFastMode: true });
45
- const firstRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb' });
52
+ const firstRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb', toolOutputRecoveryFetch });
46
53
  const firstConfig = await fs.readFile(configPath, 'utf8');
47
54
  const firstAssert = assertConfig(firstConfig, 'first_use_codex_lb');
48
55
  const release = await releaseCodexLbAuthHold({ home, configPath, authPath, backupPath: oauthBackupPath });
@@ -55,7 +62,7 @@ const releaseAssert = {
55
62
  ...(hasLegacyFastModeTables(releasedConfig) ? ['legacy_fast_mode_tables_survived_use_oauth'] : [])
56
63
  ]
57
64
  };
58
- const secondRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb' });
65
+ const secondRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb', toolOutputRecoveryFetch });
59
66
  const secondConfig = await fs.readFile(configPath, 'utf8');
60
67
  const secondAssert = assertConfig(secondConfig, 'second_use_codex_lb');
61
68
  const report = {