sneakoscope 2.0.18 → 3.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 +125 -71
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/commands/mad-sks.js +2 -0
- package/dist/commands/zellij.js +58 -1
- package/dist/core/agents/agent-scheduler.js +32 -24
- package/dist/core/agents/native-cli-session-swarm.js +22 -2
- package/dist/core/codex-app/codex-app-handoff.js +30 -9
- package/dist/core/codex-app/codex-app-launcher.js +103 -0
- package/dist/core/codex-control/codex-0138-capability.js +42 -4
- package/dist/core/codex-control/codex-model-capabilities.js +25 -4
- package/dist/core/codex-control/codex-model-metadata.js +91 -0
- package/dist/core/codex-plugins/codex-plugin-cache.js +38 -0
- package/dist/core/codex-plugins/codex-plugin-diff.js +73 -0
- package/dist/core/codex-plugins/codex-plugin-json.js +35 -11
- package/dist/core/commands/mad-sks-command.js +4 -0
- package/dist/core/commands/naruto-command.js +27 -0
- package/dist/core/commands/qa-loop-command.js +41 -6
- package/dist/core/fsx.js +1 -1
- package/dist/core/image/image-artifact-path-contract.js +2 -0
- package/dist/core/image/image-artifact-registry.js +33 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +27 -16
- package/dist/core/qa-loop/qa-loop-app-handoff-confirmation.js +51 -0
- package/dist/core/qa-loop/qa-loop-budget-policy.js +1 -1
- package/dist/core/qa-loop.js +44 -3
- package/dist/core/release/release-gate-cache-v2.js +47 -5
- package/dist/core/usage/codex-account-usage.js +77 -16
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-pane-renderer.js +5 -2
- package/dist/core/zellij/zellij-slot-telemetry.js +65 -12
- package/dist/core/zellij/zellij-ui-mode.js +8 -1
- package/dist/core/zellij/zellij-update.js +307 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +211 -145
- package/package.json +22 -2
- package/dist/core/naruto/naruto-work-stealing.js +0 -11
- package/dist/core/zellij/zellij-right-column-layout-proof.js +0 -42
|
@@ -17,11 +17,13 @@ import { runCodexAppHandoff, qaLoopShouldRequestAppHandoff } from '../codex-app/
|
|
|
17
17
|
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
18
18
|
import { writeCodexAccountUsageArtifacts } from '../usage/codex-account-usage.js';
|
|
19
19
|
import { buildQaLoopBudgetPolicy, selectQaLoopEscalatedEffort } from '../qa-loop/qa-loop-budget-policy.js';
|
|
20
|
+
import { writeCodexModelEffortCapabilityArtifact } from '../codex-control/codex-model-capabilities.js';
|
|
20
21
|
import { discoverImageArtifactsInDir, writeImageArtifactPathContract } from '../image/image-artifact-path-contract.js';
|
|
21
22
|
import { pluginAppTemplatePolicy } from '../codex-plugins/codex-plugin-json.js';
|
|
23
|
+
import { confirmQaLoopAppHandoff } from '../qa-loop/qa-loop-app-handoff-confirmation.js';
|
|
22
24
|
import fsp from 'node:fs/promises';
|
|
23
25
|
export async function qaLoopCommand(sub, args = []) {
|
|
24
|
-
const known = new Set(['prepare', 'answer', 'run', 'status', 'help', '--help', '-h']);
|
|
26
|
+
const known = new Set(['prepare', 'answer', 'run', 'status', 'app-confirm', 'help', '--help', '-h']);
|
|
25
27
|
const action = known.has(sub) ? sub : 'prepare';
|
|
26
28
|
const actionArgs = action === 'prepare' && sub && !known.has(sub) ? [sub, ...args] : args;
|
|
27
29
|
if (action === 'prepare')
|
|
@@ -32,12 +34,15 @@ export async function qaLoopCommand(sub, args = []) {
|
|
|
32
34
|
return qaLoopRun(actionArgs);
|
|
33
35
|
if (action === 'status')
|
|
34
36
|
return qaLoopStatus(actionArgs);
|
|
37
|
+
if (action === 'app-confirm')
|
|
38
|
+
return qaLoopAppConfirm(actionArgs);
|
|
35
39
|
console.log(`SKS QA-LOOP
|
|
36
40
|
|
|
37
41
|
Usage:
|
|
38
42
|
sks qa-loop prepare "target"
|
|
39
43
|
sks qa-loop answer <mission-id|latest> <answers.json>
|
|
40
|
-
sks qa-loop run <mission-id|latest> [--mock] [--max-cycles N] [--app-handoff] [--app-handoff-required]
|
|
44
|
+
sks qa-loop run <mission-id|latest> [--mock] [--max-cycles N] [--app-handoff] [--app-handoff-required] [--app-handoff-launch] [--app-handoff-artifact-only]
|
|
45
|
+
sks qa-loop app-confirm <mission-id|latest> --verdict pass|fail --notes "..."
|
|
41
46
|
sks qa-loop status <mission-id|latest> [--desktop]
|
|
42
47
|
`);
|
|
43
48
|
}
|
|
@@ -146,9 +151,11 @@ async function qaLoopRun(args) {
|
|
|
146
151
|
const usageArtifact = await writeCodexAccountUsageArtifacts(root, { missionId: id }).catch((err) => ({ error: err?.message || String(err), snapshot: null }));
|
|
147
152
|
const budgetPolicy = buildQaLoopBudgetPolicy({ usage: usageArtifact?.snapshot || null, provider: 'codex-sdk' });
|
|
148
153
|
await writeJsonAtomic(path.join(dir, 'qa-loop', 'qa-loop-budget-policy.json'), budgetPolicy);
|
|
154
|
+
const effortCapabilityArtifact = await writeCodexModelEffortCapabilityArtifact(root, { missionId: id }).catch((err) => ({ error: err?.message || String(err), capability: null }));
|
|
149
155
|
const effortEscalation = selectQaLoopEscalatedEffort({
|
|
150
156
|
failureCount: Number(qaGate.safe_fix_attempts || qaGate.failure_count || 0),
|
|
151
|
-
currentEffort: String(profile || 'high').replace(/^sks-(?:logic|agent)-/, '').replace(/-fast$/, '') || 'high'
|
|
157
|
+
currentEffort: String(profile || 'high').replace(/^sks-(?:logic|agent)-/, '').replace(/-fast$/, '') || 'high',
|
|
158
|
+
capability: effortCapabilityArtifact?.capability || undefined
|
|
152
159
|
});
|
|
153
160
|
await writeJsonAtomic(path.join(dir, 'qa-loop', 'qa-loop-effort-escalation.json'), effortEscalation);
|
|
154
161
|
const discoveredImages = await discoverImageArtifactsInDir(dir).catch(() => []);
|
|
@@ -158,6 +165,9 @@ async function qaLoopRun(args) {
|
|
|
158
165
|
const pluginInventory = await readJson(path.join(root, '.sneakoscope', 'codex-plugin-inventory.json'), null);
|
|
159
166
|
const pluginPolicy = pluginInventory?.schema === 'sks.codex-plugin-inventory.v1' ? pluginAppTemplatePolicy(pluginInventory) : null;
|
|
160
167
|
const appHandoffRequired = flag(args, '--app-handoff-required') || process.env.SKS_QA_LOOP_APP_HANDOFF_REQUIRED === '1';
|
|
168
|
+
const launchMode = flag(args, '--app-handoff-launch') || process.env.SKS_QA_LOOP_APP_HANDOFF_LAUNCH === '1'
|
|
169
|
+
? 'attempt-launch'
|
|
170
|
+
: 'artifact-only';
|
|
161
171
|
const appHandoffRequested = qaLoopShouldRequestAppHandoff({
|
|
162
172
|
args,
|
|
163
173
|
uiRequired,
|
|
@@ -183,13 +193,15 @@ async function qaLoopRun(args) {
|
|
|
183
193
|
].filter(Boolean),
|
|
184
194
|
prompt: mission.prompt || 'QA-LOOP desktop handoff',
|
|
185
195
|
require_desktop: appHandoffRequired,
|
|
186
|
-
capability_required: 'codex-0.138'
|
|
196
|
+
capability_required: 'codex-0.138',
|
|
197
|
+
launch_mode: flag(args, '--app-handoff-artifact-only') ? 'artifact-only' : launchMode
|
|
187
198
|
}).catch((err) => ({
|
|
188
199
|
ok: false,
|
|
189
200
|
status: 'blocked_for_desktop_review',
|
|
190
201
|
artifact_path: path.join(dir, 'qa-loop', 'app-handoff.json'),
|
|
191
202
|
blockers: [`codex_app_handoff_failed:${err?.message || String(err)}`],
|
|
192
|
-
desktop_handoff_supported: false
|
|
203
|
+
desktop_handoff_supported: false,
|
|
204
|
+
launch_attempt: null
|
|
193
205
|
}))
|
|
194
206
|
: null;
|
|
195
207
|
if (appHandoff || imagePathContract) {
|
|
@@ -200,6 +212,9 @@ async function qaLoopRun(args) {
|
|
|
200
212
|
desktop_app_handoff_status: appHandoff ? appHandoff.status : 'not_requested',
|
|
201
213
|
desktop_app_handoff_artifact: appHandoff ? path.relative(dir, appHandoff.artifact_path) : null,
|
|
202
214
|
desktop_app_handoff_supported: appHandoff ? appHandoff.desktop_handoff_supported === true : false,
|
|
215
|
+
desktop_app_handoff_confirmed: latestGate.desktop_app_handoff_confirmed === true,
|
|
216
|
+
desktop_app_handoff_verdict: latestGate.desktop_app_handoff_verdict || null,
|
|
217
|
+
desktop_app_handoff_launch_attempt: appHandoff ? appHandoff.launch_attempt || null : null,
|
|
203
218
|
desktop_app_handoff_is_web_ui_evidence: false,
|
|
204
219
|
image_artifact_path_contract_present: Boolean(imagePathContract),
|
|
205
220
|
image_artifact_path_contract_artifact: imagePathContract ? 'qa-loop/image-artifact-path-contract.json' : null,
|
|
@@ -207,6 +222,7 @@ async function qaLoopRun(args) {
|
|
|
207
222
|
blockers: Array.from(new Set([
|
|
208
223
|
...(latestGate.blockers || []),
|
|
209
224
|
...(appHandoffRequired && appHandoff && appHandoff.ok !== true ? ['blocked_for_desktop_review'] : []),
|
|
225
|
+
...(appHandoffRequired && latestGate.desktop_app_handoff_confirmed !== true ? ['desktop_app_handoff_confirmation_missing'] : []),
|
|
210
226
|
...(imagePathContract?.contract?.blockers || [])
|
|
211
227
|
])),
|
|
212
228
|
notes: [
|
|
@@ -367,8 +383,10 @@ async function qaLoopStatus(args) {
|
|
|
367
383
|
const nativeAgentPlan = await readJson(path.join(dir, 'qa-agent-plan.json'), null);
|
|
368
384
|
const agentSessions = await readJson(path.join(dir, 'agents', 'agent-sessions.json'), null);
|
|
369
385
|
const desktop = await readJson(path.join(dir, 'qa-loop', 'app-handoff.json'), null);
|
|
386
|
+
const desktopConfirmation = await readJson(path.join(dir, 'qa-loop', 'app-handoff-confirmation.json'), null);
|
|
387
|
+
const desktopReviewComplete = desktopConfirmation?.verdict === 'pass';
|
|
370
388
|
if (flag(args, '--json'))
|
|
371
|
-
return console.log(JSON.stringify({ mission, state, qa: status, desktop_app_handoff: desktop, native_agent_plan: nativeAgentPlan, agent_sessions: agentSessions?.sessions || null }, null, 2));
|
|
389
|
+
return console.log(JSON.stringify({ mission, state, qa: status, desktop_app_handoff: desktop, desktop_app_confirmation: desktopConfirmation, desktop_review_complete: desktopReviewComplete, native_agent_plan: nativeAgentPlan, agent_sessions: agentSessions?.sessions || null }, null, 2));
|
|
372
390
|
console.log('SKS QA-LOOP Status\n');
|
|
373
391
|
console.log(`Mission: ${id}`);
|
|
374
392
|
console.log(`Phase: ${state.phase || mission.phase}`);
|
|
@@ -380,11 +398,28 @@ async function qaLoopStatus(args) {
|
|
|
380
398
|
if (flag(args, '--desktop')) {
|
|
381
399
|
console.log('Desktop:');
|
|
382
400
|
console.log(` /app handoff: ${desktop?.status || 'not_requested'}`);
|
|
401
|
+
console.log(` launch: ${desktop?.launch_attempt?.attempted ? desktop?.launch_attempt?.launched ? 'launched' : 'attempted_fallback' : 'not_attempted'}`);
|
|
402
|
+
console.log(` confirmation: ${desktopConfirmation?.verdict || 'missing'}`);
|
|
403
|
+
console.log(` complete: ${desktopReviewComplete ? 'yes' : 'no'}`);
|
|
383
404
|
if (desktop?.operator_instruction?.prompt_artifact)
|
|
384
405
|
console.log(` prompt: ${desktop.operator_instruction.prompt_artifact}`);
|
|
385
406
|
console.log(' web evidence: not a substitute for Codex Chrome Extension web UI verification');
|
|
386
407
|
}
|
|
387
408
|
}
|
|
409
|
+
async function qaLoopAppConfirm(args) {
|
|
410
|
+
const root = await sksRoot();
|
|
411
|
+
const id = await resolveMissionId(root, args[0]);
|
|
412
|
+
const verdict = String(readFlagValue(args, '--verdict', '') || '').trim();
|
|
413
|
+
const notes = String(readFlagValue(args, '--notes', '') || '');
|
|
414
|
+
if (!id || !['pass', 'fail'].includes(verdict))
|
|
415
|
+
throw new Error('Usage: sks qa-loop app-confirm <mission-id|latest> --verdict pass|fail --notes "..."');
|
|
416
|
+
const result = await confirmQaLoopAppHandoff(root, { missionId: id, verdict: verdict, notes });
|
|
417
|
+
const evaluated = await evaluateQaGate(path.join(root, '.sneakoscope', 'missions', id));
|
|
418
|
+
if (flag(args, '--json'))
|
|
419
|
+
return console.log(JSON.stringify({ schema: 'sks.qa-loop-app-confirm.v1', ok: verdict === 'pass', mission_id: id, confirmation: result.confirmation, artifact_path: result.artifact_path, gate: result.gate, evaluated }, null, 2));
|
|
420
|
+
console.log(`QA-LOOP Desktop app handoff confirmation recorded: ${id} (${verdict})`);
|
|
421
|
+
console.log(path.relative(root, result.artifact_path));
|
|
422
|
+
}
|
|
388
423
|
async function writeQaLoopImagePathContract(root, dir, missionId, images) {
|
|
389
424
|
const primary = await writeImageArtifactPathContract(root, {
|
|
390
425
|
missionId,
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '
|
|
8
|
+
export const PACKAGE_VERSION = '3.0.0';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -16,6 +16,8 @@ export async function buildImageArtifactPathContract(root, input) {
|
|
|
16
16
|
kind: image.kind,
|
|
17
17
|
file_path: filePath,
|
|
18
18
|
relative_path: path.relative(root, filePath),
|
|
19
|
+
route: image.route || null,
|
|
20
|
+
stage: image.stage || null,
|
|
19
21
|
exists,
|
|
20
22
|
mime_type: mimeForPath(filePath),
|
|
21
23
|
width: dims?.width ?? null,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { buildImageArtifactPathContract } from './image-artifact-path-contract.js';
|
|
4
|
+
export async function registerImageArtifact(root, input) {
|
|
5
|
+
const artifactPath = imageArtifactRegistryPath(root, input.missionId);
|
|
6
|
+
const existing = await readJson(artifactPath, null);
|
|
7
|
+
const id = input.id || path.basename(input.filePath).replace(/[^0-9A-Za-z._-]/g, '_');
|
|
8
|
+
const rows = [
|
|
9
|
+
...(existing?.images || [])
|
|
10
|
+
.filter((image) => image.id !== id)
|
|
11
|
+
.map((image) => ({
|
|
12
|
+
id: image.id,
|
|
13
|
+
kind: image.kind,
|
|
14
|
+
filePath: image.file_path,
|
|
15
|
+
route: image.route || null,
|
|
16
|
+
stage: image.stage || null
|
|
17
|
+
})),
|
|
18
|
+
{
|
|
19
|
+
id,
|
|
20
|
+
kind: input.kind,
|
|
21
|
+
filePath: input.filePath,
|
|
22
|
+
route: input.route,
|
|
23
|
+
stage: input.stage
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
const contract = await buildImageArtifactPathContract(root, { missionId: input.missionId, images: rows });
|
|
27
|
+
await writeJsonAtomic(artifactPath, contract);
|
|
28
|
+
return contract;
|
|
29
|
+
}
|
|
30
|
+
export function imageArtifactRegistryPath(root, missionId) {
|
|
31
|
+
return path.join(root, '.sneakoscope', 'missions', missionId, 'image-artifact-path-contract.json');
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=image-artifact-registry.js.map
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
3
|
import { parseShellEnvValue } from '../codex-lb/codex-lb-env.js';
|
|
4
|
-
import { ensureDir, exists, nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { ensureDir, exists, nowIso, projectRoot, readJson, readText, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { sha256File, imageDimensions } from '../wiki-image/image-hash.js';
|
|
6
6
|
import { detectImagegenCapability } from '../imagegen/imagegen-capability.js';
|
|
7
7
|
import { validateGptImage2Request } from '../imagegen/gpt-image-2-request-validator.js';
|
|
8
8
|
import { withResponsesRetry } from '../responses-retry-policy.js';
|
|
9
9
|
import { discoverCodexAppGeneratedImage } from './codex-app-generated-image-discovery.js';
|
|
10
10
|
import { writeImageArtifactPathContract } from '../image/image-artifact-path-contract.js';
|
|
11
|
+
import { registerImageArtifact } from '../image/image-artifact-registry.js';
|
|
11
12
|
const DEFAULT_OPENAI_IMAGE_EDITS_ENDPOINT = 'https://api.openai.com/v1/images/edits';
|
|
12
13
|
export function buildCalloutPrompt(sourceScreenId, context = {}) {
|
|
13
14
|
return [
|
|
@@ -479,16 +480,36 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
479
480
|
};
|
|
480
481
|
}
|
|
481
482
|
async function writeGeneratedImagePathContract(input, outputPath, provider) {
|
|
482
|
-
|
|
483
|
+
const root = await resolveImageArtifactRoot(input);
|
|
484
|
+
if (input.mission_id) {
|
|
485
|
+
await registerImageArtifact(root, {
|
|
486
|
+
missionId: input.mission_id,
|
|
487
|
+
id: `${provider}-${input.source_screen_id || 'screen'}`,
|
|
488
|
+
kind: 'generated_image',
|
|
489
|
+
filePath: outputPath,
|
|
490
|
+
route: '$Image-UX-Review',
|
|
491
|
+
stage: provider
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return writeImageArtifactPathContract(root, {
|
|
483
495
|
missionId: input.mission_id || 'unassigned',
|
|
484
496
|
images: [{
|
|
485
497
|
id: `${provider}-${input.source_screen_id || 'screen'}`,
|
|
486
498
|
kind: 'generated_image',
|
|
487
|
-
filePath: outputPath
|
|
499
|
+
filePath: outputPath,
|
|
500
|
+
route: '$Image-UX-Review',
|
|
501
|
+
stage: provider
|
|
488
502
|
}],
|
|
489
503
|
artifactPath: path.join(input.output_dir, 'image-artifact-path-contract.json')
|
|
490
504
|
});
|
|
491
505
|
}
|
|
506
|
+
async function resolveImageArtifactRoot(input) {
|
|
507
|
+
const cwdRoot = await projectRoot(process.cwd()).catch(() => process.cwd());
|
|
508
|
+
const resolvedCwd = path.resolve(process.cwd());
|
|
509
|
+
if (path.resolve(cwdRoot) !== resolvedCwd)
|
|
510
|
+
return cwdRoot;
|
|
511
|
+
return projectRoot(input.output_dir || process.cwd()).catch(() => cwdRoot);
|
|
512
|
+
}
|
|
492
513
|
export async function generateGptImage2CalloutReview(input, opts = {}) {
|
|
493
514
|
if (opts.fake === true || process.env.SKS_TEST_FAKE_IMAGEGEN === '1') {
|
|
494
515
|
return createFakeImagegenAdapter(opts.fakeAdapter || {}).generateCalloutReview(input);
|
|
@@ -500,21 +521,11 @@ export async function generateGptImage2CalloutReview(input, opts = {}) {
|
|
|
500
521
|
// allowApiFallback:false or SKS_IMAGEGEN_ALLOW_API_FALLBACK=0.
|
|
501
522
|
const openAiKeyPresent = Boolean(opts.openai?.apiKey || process.env.OPENAI_API_KEY);
|
|
502
523
|
const explicitDisableApiFallback = opts.allowApiFallback === false || process.env.SKS_IMAGEGEN_ALLOW_API_FALLBACK === '0';
|
|
503
|
-
// codex-lb imagegen
|
|
504
|
-
//
|
|
505
|
-
// image_generation tool call is just another Responses request). When codex-lb
|
|
506
|
-
// is the active, fully-configured auth (selected provider + key + base_url) and
|
|
507
|
-
// there is no direct OPENAI_API_KEY, enable it BY DEFAULT so image generation
|
|
508
|
-
// works for users authenticated only through codex-lb — that is the common case
|
|
509
|
-
// and a hard block here is the bug the user hit. It still never overrides a real
|
|
510
|
-
// OpenAI key, and SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK=0 (or
|
|
511
|
-
// allowCodexLbApiFallback:false) opts out for callers that require Codex App
|
|
512
|
-
// built-in evidence only.
|
|
513
|
-
const codexLbAuthActive = capability?.codex_lb?.available === true;
|
|
524
|
+
// codex-lb imagegen is a direct API fallback, not Codex App imagegen evidence.
|
|
525
|
+
// It must be explicitly enabled by the caller or environment.
|
|
514
526
|
const explicitDisableCodexLbFallback = opts.allowCodexLbApiFallback === false || process.env.SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK === '0';
|
|
515
527
|
const allowCodexLbApiFallback = !explicitDisableCodexLbFallback && (opts.allowCodexLbApiFallback === true
|
|
516
|
-
|| process.env.SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK === '1'
|
|
517
|
-
|| (codexLbAuthActive && !openAiKeyPresent));
|
|
528
|
+
|| process.env.SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK === '1');
|
|
518
529
|
const allowApiFallback = !explicitDisableApiFallback && (opts.allowApiFallback === true
|
|
519
530
|
|| process.env.SKS_IMAGEGEN_ALLOW_API_FALLBACK === '1'
|
|
520
531
|
|| openAiKeyPresent
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
export async function confirmQaLoopAppHandoff(root, input) {
|
|
4
|
+
const missionDir = path.join(root, '.sneakoscope', 'missions', input.missionId);
|
|
5
|
+
const qaLoopDir = path.join(missionDir, 'qa-loop');
|
|
6
|
+
const handoffArtifact = path.join(qaLoopDir, 'app-handoff.json');
|
|
7
|
+
const confirmationArtifact = path.join(qaLoopDir, 'app-handoff-confirmation.json');
|
|
8
|
+
const handoff = await readJson(handoffArtifact, null);
|
|
9
|
+
if (handoff?.schema !== 'sks.codex-app-handoff-result.v1') {
|
|
10
|
+
throw new Error(`Cannot confirm Desktop app handoff before app-handoff.json exists for mission ${input.missionId}`);
|
|
11
|
+
}
|
|
12
|
+
if (input.verdict === 'pass' && handoff.status === 'blocked_for_desktop_review') {
|
|
13
|
+
throw new Error(`Cannot pass-confirm blocked Desktop app handoff for mission ${input.missionId}`);
|
|
14
|
+
}
|
|
15
|
+
const confirmation = {
|
|
16
|
+
schema: 'sks.qa-loop-app-handoff-confirmation.v1',
|
|
17
|
+
mission_id: input.missionId,
|
|
18
|
+
confirmed_at: nowIso(),
|
|
19
|
+
verdict: input.verdict,
|
|
20
|
+
notes: String(input.notes || ''),
|
|
21
|
+
operator: input.operator || process.env.USER || null,
|
|
22
|
+
related_handoff_artifact: path.relative(missionDir, handoffArtifact).split(path.sep).join('/')
|
|
23
|
+
};
|
|
24
|
+
await writeJsonAtomic(confirmationArtifact, confirmation);
|
|
25
|
+
const gatePath = path.join(missionDir, 'qa-gate.json');
|
|
26
|
+
const previousGate = await readJson(gatePath, {});
|
|
27
|
+
const previousBlockers = Array.isArray(previousGate.blockers) ? previousGate.blockers : [];
|
|
28
|
+
const failedBlocker = 'desktop_app_handoff_failed';
|
|
29
|
+
const blockers = input.verdict === 'pass'
|
|
30
|
+
? previousBlockers.filter((blocker) => blocker !== failedBlocker && blocker !== 'desktop_app_handoff_confirmation_missing')
|
|
31
|
+
: Array.from(new Set([...previousBlockers, failedBlocker]));
|
|
32
|
+
const gate = {
|
|
33
|
+
...previousGate,
|
|
34
|
+
desktop_app_handoff_required: previousGate.desktop_app_handoff_required === true,
|
|
35
|
+
desktop_app_handoff_status: input.verdict === 'pass' ? 'completed' : previousGate.desktop_app_handoff_status || 'pending',
|
|
36
|
+
desktop_app_handoff_confirmed: input.verdict === 'pass',
|
|
37
|
+
desktop_app_handoff_verdict: input.verdict,
|
|
38
|
+
desktop_app_handoff_confirmation_artifact: path.relative(missionDir, confirmationArtifact).split(path.sep).join('/'),
|
|
39
|
+
desktop_app_handoff_confirmation_notes: confirmation.notes,
|
|
40
|
+
blockers,
|
|
41
|
+
notes: Array.from(new Set([
|
|
42
|
+
...(Array.isArray(previousGate.notes) ? previousGate.notes : []),
|
|
43
|
+
input.verdict === 'pass'
|
|
44
|
+
? 'Codex Desktop /app review was explicitly confirmed by operator artifact.'
|
|
45
|
+
: 'Codex Desktop /app review failed and remains a QA blocker.'
|
|
46
|
+
]))
|
|
47
|
+
};
|
|
48
|
+
await writeJsonAtomic(gatePath, gate);
|
|
49
|
+
return { confirmation, artifact_path: confirmationArtifact, gate };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=qa-loop-app-handoff-confirmation.js.map
|
|
@@ -9,7 +9,7 @@ export function buildQaLoopBudgetPolicy(input = {}) {
|
|
|
9
9
|
const baseBudget = defaultModelCallBudget(String(input.provider || 'codex-sdk'));
|
|
10
10
|
return {
|
|
11
11
|
schema: 'sks.qa-loop-budget-policy.v1',
|
|
12
|
-
ok:
|
|
12
|
+
ok: available,
|
|
13
13
|
account_usage_source: usage?.source || 'unavailable',
|
|
14
14
|
token_usage_available: available,
|
|
15
15
|
near_limit: nearLimit,
|
package/dist/core/qa-loop.js
CHANGED
|
@@ -313,6 +313,9 @@ export function defaultQaGate(contract = {}, opts = {}) {
|
|
|
313
313
|
desktop_app_handoff_status: 'not_requested',
|
|
314
314
|
desktop_app_handoff_artifact: null,
|
|
315
315
|
desktop_app_handoff_supported: false,
|
|
316
|
+
desktop_app_handoff_confirmed: false,
|
|
317
|
+
desktop_app_handoff_verdict: null,
|
|
318
|
+
desktop_app_handoff_confirmation_artifact: null,
|
|
316
319
|
desktop_app_handoff_is_web_ui_evidence: false,
|
|
317
320
|
image_artifact_path_contract_present: false,
|
|
318
321
|
image_artifact_path_contract_artifact: null,
|
|
@@ -383,8 +386,14 @@ export async function evaluateQaGate(dir) {
|
|
|
383
386
|
reasons.push('computer_use_web_evidence_forbidden');
|
|
384
387
|
}
|
|
385
388
|
if (gate.desktop_app_handoff_required === true) {
|
|
386
|
-
if (
|
|
389
|
+
if (!['pending', 'launched_pending_confirmation', 'completed'].includes(String(gate.desktop_app_handoff_status || '')))
|
|
387
390
|
reasons.push('desktop_app_handoff_missing');
|
|
391
|
+
if (gate.desktop_app_handoff_confirmed !== true)
|
|
392
|
+
reasons.push('desktop_app_handoff_confirmation_missing');
|
|
393
|
+
if (gate.desktop_app_handoff_verdict !== 'pass')
|
|
394
|
+
reasons.push('desktop_app_handoff_verdict_not_pass');
|
|
395
|
+
if (gate.desktop_app_handoff_status !== 'completed')
|
|
396
|
+
reasons.push('desktop_app_handoff_not_completed');
|
|
388
397
|
if (gate.desktop_app_handoff_is_web_ui_evidence === true)
|
|
389
398
|
reasons.push('desktop_app_handoff_misused_as_web_evidence');
|
|
390
399
|
}
|
|
@@ -410,7 +419,38 @@ export async function writeMockQaResult(dir, mission, contract) {
|
|
|
410
419
|
const reportFile = isQaReportFilename(previousReportFile) ? previousReportFile : qaReportFilename();
|
|
411
420
|
const uiRequired = qaUiRequired(contract.answers || {});
|
|
412
421
|
await writeTextAtomic(path.join(dir, reportFile), `# QA-LOOP Report\n\nMission: ${mission.id}\nMode: mock verification\n\nMock QA-LOOP completed. No live UI/API actions were executed.\n\n## Honest Mode\n\nThis is a mock smoke run for command verification, not production QA evidence.\n`);
|
|
413
|
-
await writeJsonAtomic(path.join(dir, 'qa-gate.json'), {
|
|
422
|
+
await writeJsonAtomic(path.join(dir, 'qa-gate.json'), {
|
|
423
|
+
...defaultQaGate(contract, { reportFile }),
|
|
424
|
+
desktop_app_handoff_required: previousGate.desktop_app_handoff_required === true,
|
|
425
|
+
desktop_app_handoff_status: previousGate.desktop_app_handoff_status || 'not_requested',
|
|
426
|
+
desktop_app_handoff_artifact: previousGate.desktop_app_handoff_artifact || null,
|
|
427
|
+
desktop_app_handoff_supported: previousGate.desktop_app_handoff_supported === true,
|
|
428
|
+
desktop_app_handoff_confirmed: previousGate.desktop_app_handoff_confirmed === true,
|
|
429
|
+
desktop_app_handoff_verdict: previousGate.desktop_app_handoff_verdict || null,
|
|
430
|
+
desktop_app_handoff_confirmation_artifact: previousGate.desktop_app_handoff_confirmation_artifact || null,
|
|
431
|
+
desktop_app_handoff_is_web_ui_evidence: false,
|
|
432
|
+
image_artifact_path_contract_present: previousGate.image_artifact_path_contract_present === true,
|
|
433
|
+
image_artifact_path_contract_artifact: previousGate.image_artifact_path_contract_artifact || null,
|
|
434
|
+
image_artifact_path_contract_blockers: previousGate.image_artifact_path_contract_blockers || [],
|
|
435
|
+
blockers: previousGate.blockers || [],
|
|
436
|
+
passed: !uiRequired,
|
|
437
|
+
qa_report_written: true,
|
|
438
|
+
qa_ledger_complete: true,
|
|
439
|
+
checklist_completed: true,
|
|
440
|
+
safety_reviewed: true,
|
|
441
|
+
credentials_not_persisted: true,
|
|
442
|
+
chrome_extension_preflight_passed: !uiRequired,
|
|
443
|
+
ui_chrome_extension_evidence: !uiRequired,
|
|
444
|
+
ui_computer_use_evidence: false,
|
|
445
|
+
ui_evidence_source: uiRequired ? null : 'not_required',
|
|
446
|
+
unresolved_findings: 0,
|
|
447
|
+
unresolved_fixable_findings: 0,
|
|
448
|
+
unsafe_or_deferred_findings: 0,
|
|
449
|
+
post_fix_verification_complete: true,
|
|
450
|
+
honest_mode_complete: true,
|
|
451
|
+
evidence: ['mock QA-LOOP smoke completed'],
|
|
452
|
+
notes: ['No live UI/API verification was claimed.']
|
|
453
|
+
});
|
|
414
454
|
return evaluateQaGate(dir);
|
|
415
455
|
}
|
|
416
456
|
export function buildQaLoopPrompt({ id, mission, contract, cycle, previous, reportFile, imagePathContract, appHandoff }) {
|
|
@@ -442,10 +482,11 @@ export async function qaStatus(dir) {
|
|
|
442
482
|
const gate = await readJson(path.join(dir, 'qa-gate.evaluated.json'), await readJson(path.join(dir, 'qa-gate.json'), null));
|
|
443
483
|
const ledger = await readJson(path.join(dir, 'qa-ledger.json'), null);
|
|
444
484
|
const appHandoff = await readJson(path.join(dir, 'qa-loop', 'app-handoff.json'), null);
|
|
485
|
+
const appConfirmation = await readJson(path.join(dir, 'qa-loop', 'app-handoff-confirmation.json'), null);
|
|
445
486
|
const imagePathContract = await readJson(path.join(dir, 'qa-loop', 'image-artifact-path-contract.json'), null);
|
|
446
487
|
const reportFile = qaReportFileFromGate(gate?.gate || gate || {}) || ledger?.qa_report_file || null;
|
|
447
488
|
const report = reportFile && isQaReportFilename(reportFile) ? await readText(path.join(dir, reportFile), '') : '';
|
|
448
|
-
return { gate, checklist_count: ledger?.checklist?.length ?? null, report_file: reportFile, report_written: Boolean(report.trim()), desktop_app_handoff: appHandoff, image_path_contract: imagePathContract };
|
|
489
|
+
return { gate, checklist_count: ledger?.checklist?.length ?? null, report_file: reportFile, report_written: Boolean(report.trim()), desktop_app_handoff: appHandoff, desktop_app_confirmation: appConfirmation, desktop_review_complete: appConfirmation?.verdict === 'pass', image_path_contract: imagePathContract };
|
|
449
490
|
}
|
|
450
491
|
function qaChecklist(a) {
|
|
451
492
|
const cases = [
|
|
@@ -5,19 +5,44 @@ export const RELEASE_GATE_CACHE_V2_SCHEMA = 'sks.release-gate-cache.v2';
|
|
|
5
5
|
export function releaseGateCacheFile(root) {
|
|
6
6
|
return path.join(root, '.sneakoscope', 'reports', 'release-gates', 'cache-v2.json');
|
|
7
7
|
}
|
|
8
|
+
// Files whose only release-to-release difference is the version literal.
|
|
9
|
+
// Hashing them version-neutrally keeps a pure `sks versioning bump` from
|
|
10
|
+
// invalidating every behavior gate: bumping the version rewrites
|
|
11
|
+
// package.json, package-lock.json, and the three PACKAGE_VERSION constant
|
|
12
|
+
// sources, which are inputs of ~280 gates (via `package.json` and `src/**`).
|
|
13
|
+
// Before this normalization every publish re-ran the entire DAG from zero
|
|
14
|
+
// (test:blackbox alone is ~11 minutes) even when no behavior changed.
|
|
15
|
+
// Version-CORRECTNESS gates (release:version-truth, release:metadata, ...)
|
|
16
|
+
// are declared with `cache.enabled: false`, so they always re-run and still
|
|
17
|
+
// catch version drift. Set SKS_RELEASE_CACHE_VERSION_SENSITIVE=1 to restore
|
|
18
|
+
// the old fully version-sensitive hashing.
|
|
19
|
+
const VERSION_NEUTRAL_CACHE_FILES = new Set([
|
|
20
|
+
'package.json',
|
|
21
|
+
'package-lock.json',
|
|
22
|
+
'src/core/version.ts',
|
|
23
|
+
'src/core/fsx.ts',
|
|
24
|
+
'src/bin/sks.ts'
|
|
25
|
+
]);
|
|
8
26
|
export function releaseGateCacheKey(root, gate) {
|
|
9
27
|
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
28
|
+
const releaseVersion = String(pkg.version || '');
|
|
29
|
+
const versionSensitive = process.env.SKS_RELEASE_CACHE_VERSION_SENSITIVE === '1';
|
|
10
30
|
const hash = crypto.createHash('sha256');
|
|
11
31
|
hash.update(gate.id);
|
|
12
32
|
hash.update(gate.command);
|
|
13
|
-
|
|
33
|
+
if (versionSensitive)
|
|
34
|
+
hash.update(releaseVersion);
|
|
14
35
|
hash.update(process.version);
|
|
15
36
|
hash.update(String(process.env.npm_config_user_agent || ''));
|
|
16
37
|
hash.update(JSON.stringify(gate.resource || []));
|
|
17
38
|
hash.update(JSON.stringify(gate.preset || []));
|
|
18
39
|
hashFileIfPresent(hash, path.join(root, 'release-gates.v2.json'));
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
if (versionSensitive || !gate.cache.inputs.length) {
|
|
41
|
+
// No declared inputs (or explicitly version-sensitive mode): fall back to
|
|
42
|
+
// the conservative global digests so such a gate cannot cache-hit forever.
|
|
43
|
+
hashFileIfPresent(hash, path.join(root, 'package.json'));
|
|
44
|
+
hashFileIfPresent(hash, path.join(root, 'dist', 'build-manifest.json'));
|
|
45
|
+
}
|
|
21
46
|
for (const input of gate.cache.inputs) {
|
|
22
47
|
const expanded = expandGlob(root, input);
|
|
23
48
|
hash.update(`input:${input}`);
|
|
@@ -26,12 +51,29 @@ export function releaseGateCacheKey(root, gate) {
|
|
|
26
51
|
continue;
|
|
27
52
|
}
|
|
28
53
|
for (const file of expanded) {
|
|
29
|
-
|
|
30
|
-
|
|
54
|
+
const rel = path.relative(root, file);
|
|
55
|
+
hash.update(rel);
|
|
56
|
+
if (!versionSensitive && VERSION_NEUTRAL_CACHE_FILES.has(rel))
|
|
57
|
+
hashVersionNeutralFile(hash, file, releaseVersion);
|
|
58
|
+
else
|
|
59
|
+
hashFileIfPresent(hash, file);
|
|
31
60
|
}
|
|
32
61
|
}
|
|
33
62
|
return hash.digest('hex');
|
|
34
63
|
}
|
|
64
|
+
function hashVersionNeutralFile(hash, file, releaseVersion) {
|
|
65
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
66
|
+
return;
|
|
67
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
68
|
+
if (!releaseVersion) {
|
|
69
|
+
hash.update(text);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Replace exact occurrences of the current release version literal so a
|
|
73
|
+
// version-only bump hashes identically. Any other content change in these
|
|
74
|
+
// files still alters the key.
|
|
75
|
+
hash.update(text.split(releaseVersion).join('__SKS_RELEASE_VERSION__'));
|
|
76
|
+
}
|
|
35
77
|
export function expandGlob(root, input) {
|
|
36
78
|
const absolute = path.join(root, input);
|
|
37
79
|
if (!/[*!?[\]{}]/.test(input)) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
|
+
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
3
4
|
export async function collectCodexAccountUsage() {
|
|
4
5
|
if (process.env.SKS_CODEX_ACCOUNT_USAGE_FAKE === '1') {
|
|
5
6
|
return {
|
|
@@ -15,22 +16,42 @@ export async function collectCodexAccountUsage() {
|
|
|
15
16
|
reset_at: null
|
|
16
17
|
},
|
|
17
18
|
usage_limit_tokens: 100000,
|
|
19
|
+
attempted_sources: ['fake'],
|
|
18
20
|
blockers: []
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
const attemptedSources = [];
|
|
24
|
+
const urls = [
|
|
25
|
+
['CODEX_APP_SERVER_USAGE_URL', process.env.CODEX_APP_SERVER_USAGE_URL],
|
|
26
|
+
['SKS_CODEX_APP_SERVER_USAGE_URL', process.env.SKS_CODEX_APP_SERVER_USAGE_URL],
|
|
27
|
+
...localWellKnownUsageUrls().map((url) => [`local:${url}`, url])
|
|
28
|
+
];
|
|
29
|
+
const blockers = [];
|
|
30
|
+
for (const [label, rawUrl] of urls) {
|
|
31
|
+
const url = String(rawUrl || '').trim();
|
|
32
|
+
if (!url)
|
|
33
|
+
continue;
|
|
34
|
+
attemptedSources.push(label);
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(label.startsWith('local:') ? 800 : 5000) });
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
blockers.push(`codex_app_server_usage_http_${response.status}:${label}`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const payload = await response.json();
|
|
42
|
+
return normalizeUsagePayload(payload, 'app-server', attemptedSources);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
blockers.push(`codex_app_server_usage_fetch_failed:${label}:${err?.message || String(err)}`);
|
|
46
|
+
}
|
|
33
47
|
}
|
|
48
|
+
const cli = await collectUsageFromCodexCli(attemptedSources).catch((err) => {
|
|
49
|
+
blockers.push(`codex_cli_usage_probe_failed:${err?.message || String(err)}`);
|
|
50
|
+
return null;
|
|
51
|
+
});
|
|
52
|
+
if (cli)
|
|
53
|
+
return cli;
|
|
54
|
+
return unavailable(attemptedSources.length ? blockers : ['codex_app_server_usage_endpoint_unavailable'], attemptedSources);
|
|
34
55
|
}
|
|
35
56
|
export async function writeCodexAccountUsageArtifacts(root, input = {}) {
|
|
36
57
|
const snapshot = await collectCodexAccountUsage();
|
|
@@ -43,7 +64,7 @@ export async function writeCodexAccountUsageArtifacts(root, input = {}) {
|
|
|
43
64
|
}
|
|
44
65
|
return { snapshot, root_artifact: rootArtifact, mission_artifact: missionArtifact };
|
|
45
66
|
}
|
|
46
|
-
function normalizeUsagePayload(payload, source) {
|
|
67
|
+
function normalizeUsagePayload(payload, source, attemptedSources) {
|
|
47
68
|
const usage = payload?.token_usage || payload?.usage || payload;
|
|
48
69
|
const input = Number(usage?.input_tokens || usage?.inputTokens || 0);
|
|
49
70
|
const output = Number(usage?.output_tokens || usage?.outputTokens || 0);
|
|
@@ -61,18 +82,58 @@ function normalizeUsagePayload(payload, source) {
|
|
|
61
82
|
reset_at: usage?.reset_at || usage?.resetAt || null
|
|
62
83
|
},
|
|
63
84
|
usage_limit_tokens: Number.isFinite(Number(payload?.usage_limit_tokens || payload?.usageLimitTokens)) ? Number(payload?.usage_limit_tokens || payload?.usageLimitTokens) : null,
|
|
85
|
+
attempted_sources: attemptedSources,
|
|
64
86
|
blockers: []
|
|
65
87
|
};
|
|
66
88
|
}
|
|
67
|
-
function unavailable(blockers) {
|
|
89
|
+
function unavailable(blockers, attemptedSources = []) {
|
|
68
90
|
return {
|
|
69
91
|
schema: 'sks.codex-account-usage.v1',
|
|
70
92
|
generated_at: nowIso(),
|
|
71
|
-
ok:
|
|
93
|
+
ok: false,
|
|
72
94
|
source: 'unavailable',
|
|
73
95
|
token_usage: null,
|
|
74
96
|
usage_limit_tokens: null,
|
|
97
|
+
attempted_sources: attemptedSources,
|
|
75
98
|
blockers
|
|
76
99
|
};
|
|
77
100
|
}
|
|
101
|
+
function localWellKnownUsageUrls() {
|
|
102
|
+
const ports = [
|
|
103
|
+
process.env.CODEX_APP_SERVER_PORT,
|
|
104
|
+
process.env.SKS_CODEX_APP_SERVER_PORT,
|
|
105
|
+
1455,
|
|
106
|
+
1456,
|
|
107
|
+
3000
|
|
108
|
+
].map((value) => Number(value)).filter((value, index, rows) => Number.isFinite(value) && value > 0 && rows.indexOf(value) === index);
|
|
109
|
+
return ports.flatMap((port) => [
|
|
110
|
+
`http://127.0.0.1:${port}/usage`,
|
|
111
|
+
`http://127.0.0.1:${port}/api/usage`,
|
|
112
|
+
`http://127.0.0.1:${port}/.well-known/codex/usage`
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
async function collectUsageFromCodexCli(attemptedSources) {
|
|
116
|
+
const bin = await findCodexBinary();
|
|
117
|
+
if (!bin)
|
|
118
|
+
return null;
|
|
119
|
+
const commands = [
|
|
120
|
+
['account', 'usage', '--json'],
|
|
121
|
+
['usage', '--json'],
|
|
122
|
+
['app-server', 'status', '--json']
|
|
123
|
+
];
|
|
124
|
+
for (const args of commands) {
|
|
125
|
+
const label = `codex-cli:${args.join(' ')}`;
|
|
126
|
+
attemptedSources.push(label);
|
|
127
|
+
const result = await runProcess(bin, args, { timeoutMs: 3000, maxOutputBytes: 64 * 1024 }).catch(() => null);
|
|
128
|
+
if (!result || result.code !== 0)
|
|
129
|
+
continue;
|
|
130
|
+
try {
|
|
131
|
+
const payload = JSON.parse(`${result.stdout || ''}${result.stderr || ''}`.trim() || '{}');
|
|
132
|
+
const normalized = normalizeUsagePayload(payload, 'app-server', attemptedSources);
|
|
133
|
+
return { ...normalized, source: 'app-server' };
|
|
134
|
+
}
|
|
135
|
+
catch { }
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
78
139
|
//# sourceMappingURL=codex-account-usage.js.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '
|
|
1
|
+
export const PACKAGE_VERSION = '3.0.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|