sneakoscope 2.0.16 → 2.0.18
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 +23 -30
- 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/cli/command-registry.js +1 -1
- package/dist/commands/doctor.js +39 -1
- package/dist/commands/proof.js +21 -0
- package/dist/commands/zellij-slot-pane.js +7 -1
- package/dist/core/agents/agent-effort-policy.js +7 -1
- package/dist/core/agents/agent-orchestrator.js +3 -1
- package/dist/core/agents/agent-scheduler.js +14 -1
- package/dist/core/agents/native-cli-session-swarm.js +11 -7
- package/dist/core/agents/native-cli-worker.js +56 -7
- package/dist/core/agents/parallel-runtime-proof.js +68 -9
- package/dist/core/agents/runtime-proof-summary.js +75 -0
- package/dist/core/codex-app/codex-app-handoff.js +77 -0
- package/dist/core/codex-control/codex-0138-capability.js +64 -0
- package/dist/core/codex-control/codex-model-capabilities.js +41 -0
- package/dist/core/codex-control/codex-sdk-config-policy.js +1 -1
- package/dist/core/codex-control/codex-task-runner.js +1 -1
- package/dist/core/codex-plugins/codex-plugin-json.js +152 -0
- package/dist/core/commands/mad-sks-command.js +4 -0
- package/dist/core/commands/naruto-command.js +20 -4
- package/dist/core/commands/qa-loop-command.js +111 -4
- package/dist/core/commands/team-command.js +6 -311
- package/dist/core/commands/team-legacy-observe-command.js +182 -0
- package/dist/core/db-safety.js +15 -0
- package/dist/core/doctor/codex-0138-doctor.js +104 -0
- package/dist/core/doctor/doctor-readiness-matrix.js +11 -0
- package/dist/core/effort-orchestrator.js +9 -0
- package/dist/core/feature-registry.js +4 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +38 -4
- package/dist/core/image/image-artifact-path-contract.js +99 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +24 -3
- package/dist/core/init.js +1 -0
- package/dist/core/mad-db/mad-db-capability.js +9 -1
- package/dist/core/mad-db/mad-db-result-lifecycle.js +207 -0
- package/dist/core/mcp/mcp-plugin-inventory.js +29 -0
- package/dist/core/mcp/mcp-server-policy.js +24 -0
- package/dist/core/qa-loop/qa-loop-budget-policy.js +37 -0
- package/dist/core/qa-loop.js +28 -2
- package/dist/core/release/release-gate-affected-selector.js +47 -5
- package/dist/core/release/release-gate-dag.js +5 -1
- package/dist/core/release/release-gate-scheduler.js +2 -1
- package/dist/core/routes.js +3 -1
- package/dist/core/usage/codex-account-usage.js +78 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-column-anchor.js +16 -7
- package/dist/core/zellij/zellij-slot-pane-renderer.js +92 -1
- package/dist/core/zellij/zellij-slot-telemetry.js +29 -6
- package/dist/core/zellij/zellij-ui-mode.js +12 -2
- package/dist/scripts/prepublish-release-check-or-fast.js +3 -3
- package/dist/scripts/release-gate-existence-audit.js +5 -1
- package/dist/scripts/release-speed-summary.js +22 -2
- package/package.json +38 -4
- package/schemas/agents/parallel-runtime-proof.schema.json +31 -0
- package/schemas/codex-app/codex-app-handoff.schema.json +20 -0
- package/schemas/codex-plugins/codex-plugin-inventory.schema.json +32 -0
- package/schemas/image/image-artifact-path-contract.schema.json +32 -0
- package/schemas/usage/codex-account-usage.schema.json +27 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ARTIFACT_FILES } from '../artifact-schemas.js';
|
|
3
|
+
import { readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { loadMission } from '../mission.js';
|
|
5
|
+
import { MIN_TEAM_REVIEWER_LANES } from '../team-review-policy.js';
|
|
6
|
+
import { renderTeamDashboardState, writeTeamDashboardState } from '../team-dashboard-renderer.js';
|
|
7
|
+
import { appendTeamEvent, formatRoleCounts, isTerminalTeamAgentStatus, readTeamControl, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamCleanupSummary, renderTeamWatch, requestTeamSessionCleanup, teamCleanupRequested } from '../team-live.js';
|
|
8
|
+
import { attachZellijSessionInteractive, launchTeamZellijView } from '../zellij/zellij-launcher.js';
|
|
9
|
+
import { flag, readFlagValue } from './command-utils.js';
|
|
10
|
+
export const teamLegacySubcommands = new Set([
|
|
11
|
+
'log',
|
|
12
|
+
'tail',
|
|
13
|
+
'watch',
|
|
14
|
+
'lane',
|
|
15
|
+
'status',
|
|
16
|
+
'dashboard',
|
|
17
|
+
'event',
|
|
18
|
+
'message',
|
|
19
|
+
'open-zellij',
|
|
20
|
+
'attach-zellij',
|
|
21
|
+
'cleanup-zellij',
|
|
22
|
+
'open-tmux',
|
|
23
|
+
'attach-tmux',
|
|
24
|
+
'cleanup-tmux'
|
|
25
|
+
]);
|
|
26
|
+
export async function teamLegacyObserveCommand(sub, args = []) {
|
|
27
|
+
const root = await sksRoot();
|
|
28
|
+
const missionArg = args[0] && !String(args[0]).startsWith('--') ? args[0] : 'latest';
|
|
29
|
+
const { resolveMissionId } = await import('./command-utils.js');
|
|
30
|
+
const id = await resolveMissionId(root, missionArg);
|
|
31
|
+
if (!id) {
|
|
32
|
+
console.error(`Usage: sks team ${sub} [mission-id|latest]`);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const { dir } = await loadMission(root, id);
|
|
37
|
+
if (sub === 'open-tmux' || sub === 'attach-tmux' || sub === 'cleanup-tmux') {
|
|
38
|
+
const result = { ok: false, status: 'removed_runtime', runtime: 'tmux', replacement: 'zellij', operator_actions: ['Use `sks team open-zellij`, `attach-zellij`, or `cleanup-zellij`.'] };
|
|
39
|
+
if (flag(args, '--json'))
|
|
40
|
+
return console.log(JSON.stringify(result, null, 2));
|
|
41
|
+
console.error('tmux runtime has been removed from SKS Team. Use Zellij commands instead.');
|
|
42
|
+
process.exitCode = 2;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (sub === 'open-zellij' || sub === 'attach-zellij') {
|
|
46
|
+
const plan = await readJson(path.join(dir, 'team-plan.json'), null);
|
|
47
|
+
if (!plan) {
|
|
48
|
+
console.error(`Team plan missing for ${id}; cannot open Zellij Team view.`);
|
|
49
|
+
process.exitCode = 2;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const slotCount = await inferTeamZellijSlotCount(dir, plan);
|
|
53
|
+
const zellij = await launchTeamZellijView({ root, missionId: id, ledgerRoot: path.join(dir, 'agents'), slotCount, dryRun: flag(args, '--json'), attach: false });
|
|
54
|
+
if (flag(args, '--json'))
|
|
55
|
+
return console.log(JSON.stringify(zellij, null, 2));
|
|
56
|
+
if (!zellij.ok) {
|
|
57
|
+
console.error(`Zellij Team view blocked for ${id}: ${(zellij.blockers || []).join('; ') || 'Zellij launch failed'}`);
|
|
58
|
+
process.exitCode = 2;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (zellij.capability?.status === 'ok')
|
|
62
|
+
console.log(`Zellij: prepared Team lane(s) in ${zellij.session_name}`);
|
|
63
|
+
else
|
|
64
|
+
console.log(`Zellij: optional live panes unavailable (${(zellij.warnings || []).join('; ') || zellij.capability?.status || 'unknown'})`);
|
|
65
|
+
if (zellij.capability?.status === 'ok' && (sub === 'attach-zellij' || shouldAutoAttachTeamZellij(args))) {
|
|
66
|
+
attachZellijSessionInteractive(zellij.session_name, { cwd: root, configPath: zellij.clipboard_config_path });
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (sub === 'event') {
|
|
71
|
+
const message = readFlagValue(args, '--message', '');
|
|
72
|
+
if (!message) {
|
|
73
|
+
console.error('Usage: sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."');
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const phase = readFlagValue(args, '--phase', 'general');
|
|
78
|
+
const record = await appendTeamEvent(dir, { agent: readFlagValue(args, '--agent', 'parent_orchestrator'), phase, type: readFlagValue(args, '--type', 'status'), artifact: readFlagValue(args, '--artifact', ''), message });
|
|
79
|
+
if (flag(args, '--json'))
|
|
80
|
+
return console.log(JSON.stringify(record, null, 2));
|
|
81
|
+
console.log(`${record.ts} [${record.phase}] ${record.agent}: ${record.message}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (sub === 'message') {
|
|
85
|
+
const message = readFlagValue(args, '--message', '');
|
|
86
|
+
if (!message) {
|
|
87
|
+
console.error('Usage: sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."');
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const record = await appendTeamEvent(dir, { agent: readFlagValue(args, '--from', readFlagValue(args, '--agent', 'parent_orchestrator')), to: readFlagValue(args, '--to', 'all'), phase: readFlagValue(args, '--phase', 'communication'), type: 'message', message });
|
|
92
|
+
if (flag(args, '--json'))
|
|
93
|
+
return console.log(JSON.stringify(record, null, 2));
|
|
94
|
+
console.log(`${record.ts} [${record.phase}] ${record.agent} -> ${record.to}: ${record.message}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (sub === 'cleanup-zellij') {
|
|
98
|
+
const control = await requestTeamSessionCleanup(dir, { missionId: id, agent: readFlagValue(args, '--agent', 'parent_orchestrator'), reason: readFlagValue(args, '--reason', 'Team session ended; clean up live follow panes.'), finalMessage: 'Team session ended.' });
|
|
99
|
+
await appendTeamEvent(dir, { agent: readFlagValue(args, '--agent', 'parent_orchestrator'), phase: 'session_cleanup', type: 'cleanup', message: control.cleanup_reason || 'Team session cleanup requested.' });
|
|
100
|
+
const cleanup = { ok: true, runtime: 'zellij', mission_id: id, control, close_requested: flag(args, '--close-session') || flag(args, '--close') };
|
|
101
|
+
await writeJsonAtomic(path.join(dir, 'zellij-session-cleanup.json'), cleanup);
|
|
102
|
+
if (flag(args, '--json'))
|
|
103
|
+
return console.log(JSON.stringify(cleanup, null, 2));
|
|
104
|
+
console.log('Zellij cleanup: marked complete.');
|
|
105
|
+
console.log(renderTeamCleanupSummary(control));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (sub === 'status') {
|
|
109
|
+
const dashboard = await readTeamDashboard(dir);
|
|
110
|
+
if (flag(args, '--json'))
|
|
111
|
+
return console.log(JSON.stringify(dashboard || {}, null, 2));
|
|
112
|
+
if (!dashboard) {
|
|
113
|
+
console.error(`Team dashboard missing for ${id}.`);
|
|
114
|
+
process.exitCode = 2;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.log(`Team mission: ${id}`);
|
|
118
|
+
console.log(`Updated: ${dashboard.updated_at || 'unknown'}`);
|
|
119
|
+
console.log(`Agent sessions: ${dashboard.agent_session_count || MIN_TEAM_REVIEWER_LANES}`);
|
|
120
|
+
if (dashboard.role_counts)
|
|
121
|
+
console.log(`Role counts: ${formatRoleCounts(dashboard.role_counts)}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (sub === 'dashboard') {
|
|
125
|
+
await writeTeamDashboardState(dir, { missionId: id });
|
|
126
|
+
const state = await readJson(path.join(dir, ARTIFACT_FILES.team_dashboard_state), {});
|
|
127
|
+
if (flag(args, '--json'))
|
|
128
|
+
return console.log(JSON.stringify(state, null, 2));
|
|
129
|
+
console.log(renderTeamDashboardState(state));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (sub === 'log')
|
|
133
|
+
return console.log(await readTeamLive(dir));
|
|
134
|
+
if (sub === 'lane') {
|
|
135
|
+
const agent = readFlagValue(args, '--agent', 'parent_orchestrator');
|
|
136
|
+
const phase = readFlagValue(args, '--phase', '');
|
|
137
|
+
const lines = Number(readFlagValue(args, '--lines', '12'));
|
|
138
|
+
const text = await renderTeamAgentLane(dir, { missionId: id, agent, phase, lines });
|
|
139
|
+
if (flag(args, '--json'))
|
|
140
|
+
return console.log(JSON.stringify({ mission_id: id, agent, phase, lane: text }, null, 2));
|
|
141
|
+
console.log(text);
|
|
142
|
+
if (flag(args, '--follow') && !teamCleanupRequested(await readTeamControl(dir)) && !isTerminalTeamAgentStatus((await readTeamDashboard(dir).catch(() => null))?.agents?.[agent]?.status || '')) {
|
|
143
|
+
// Follow mode intentionally falls through only for interactive terminals in the full Zellij lane.
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (sub === 'tail' || sub === 'watch') {
|
|
148
|
+
const lines = readFlagValue(args, '--lines', '20');
|
|
149
|
+
if (sub === 'watch' && !flag(args, '--raw'))
|
|
150
|
+
console.log(await renderTeamWatch(dir, { missionId: id, lines: Number(lines) }));
|
|
151
|
+
else
|
|
152
|
+
for (const line of await readTeamTranscriptTail(dir, Number(lines)))
|
|
153
|
+
console.log(line);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function inferTeamZellijSlotCount(dir, plan = {}) {
|
|
157
|
+
const scheduler = await readJson(path.join(dir, 'agents', 'agent-scheduler-state.json'), null);
|
|
158
|
+
const lanes = await readJson(path.join(dir, 'agents', 'agent-zellij-lanes.json'), null);
|
|
159
|
+
const candidates = [
|
|
160
|
+
plan?.bundle_size,
|
|
161
|
+
plan?.agent_session_count,
|
|
162
|
+
lanes?.lane_count,
|
|
163
|
+
plan?.target_active_slots,
|
|
164
|
+
scheduler?.target_active_slots
|
|
165
|
+
].map((value) => Number(value)).filter((value) => Number.isFinite(value) && value > 0);
|
|
166
|
+
return Math.max(1, Math.min(100, Math.floor(candidates[0] || 5)));
|
|
167
|
+
}
|
|
168
|
+
function shouldAutoAttachTeamZellij(args = []) {
|
|
169
|
+
const list = (args || []).map((arg) => String(arg));
|
|
170
|
+
if (list.includes('--no-attach'))
|
|
171
|
+
return false;
|
|
172
|
+
if (list.includes('--json'))
|
|
173
|
+
return false;
|
|
174
|
+
if (process.env.SKS_NO_ZELLIJ_ATTACH === '1')
|
|
175
|
+
return false;
|
|
176
|
+
if (process.env.ZELLIJ)
|
|
177
|
+
return false;
|
|
178
|
+
if (list.includes('--attach'))
|
|
179
|
+
return true;
|
|
180
|
+
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=team-legacy-observe-command.js.map
|
package/dist/core/db-safety.js
CHANGED
|
@@ -5,6 +5,7 @@ import { evaluateMadSksPermissionGate, isMadSksRouteState } from './permission-g
|
|
|
5
5
|
import { resolveMadDbMutationPolicy } from './mad-db/mad-db-policy-resolver.js';
|
|
6
6
|
import { recordMadDbOperation } from './mad-db/mad-db-capability.js';
|
|
7
7
|
import { appendMadDbLedgerEvent, appendMadDbOperationLifecycle } from './mad-db/mad-db-ledger.js';
|
|
8
|
+
import { lifecycleHookFromUnknown, recordPendingMadDbLifecycleHook } from './mad-db/mad-db-result-lifecycle.js';
|
|
8
9
|
export const DEFAULT_DB_SAFETY_POLICY = Object.freeze({
|
|
9
10
|
schema_version: 1,
|
|
10
11
|
mode: 'read_only_default',
|
|
@@ -520,6 +521,14 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
|
|
|
520
521
|
sqlHash,
|
|
521
522
|
destructive: classification.level === 'destructive'
|
|
522
523
|
});
|
|
524
|
+
const lifecycleHook = {
|
|
525
|
+
mission_id: String(state.mission_id),
|
|
526
|
+
operation_id: operationId,
|
|
527
|
+
cycle_id: madDbDecision.cycle_id || null,
|
|
528
|
+
tool_name: classification.toolName || null,
|
|
529
|
+
sql_hash: sqlHash,
|
|
530
|
+
destructive: classification.level === 'destructive'
|
|
531
|
+
};
|
|
523
532
|
const decision = {
|
|
524
533
|
allowed: true,
|
|
525
534
|
action: 'allow',
|
|
@@ -534,6 +543,8 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
|
|
|
534
543
|
capability_file: 'mad-db-capability.json',
|
|
535
544
|
consumed: false,
|
|
536
545
|
operation_id: operationId,
|
|
546
|
+
lifecycle_result_pending: true,
|
|
547
|
+
ledger_result_hook: lifecycleHook,
|
|
537
548
|
operation_count: Number(madDbDecision.operation_count || 0) + 1,
|
|
538
549
|
max_operations: madDbDecision.max_operations || 20
|
|
539
550
|
}
|
|
@@ -555,6 +566,7 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
|
|
|
555
566
|
});
|
|
556
567
|
decision.mad_db.consumed = updatedCapability?.consumed === true;
|
|
557
568
|
decision.mad_db.operation_count = updatedCapability?.operation_count ?? decision.mad_db.operation_count;
|
|
569
|
+
await recordPendingMadDbLifecycleHook(root, state.mission_id, lifecycleHook);
|
|
558
570
|
await appendJsonlBounded(path.join(missionDir(root, state.mission_id), 'db-safety.jsonl'), { ts: nowIso(), decision });
|
|
559
571
|
return decision;
|
|
560
572
|
}
|
|
@@ -567,6 +579,9 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
|
|
|
567
579
|
}
|
|
568
580
|
return decision;
|
|
569
581
|
}
|
|
582
|
+
export function madDbLifecycleHookFromDecision(decision) {
|
|
583
|
+
return lifecycleHookFromUnknown(decision);
|
|
584
|
+
}
|
|
570
585
|
export async function checkSqlFile(file) {
|
|
571
586
|
const sql = await readText(file);
|
|
572
587
|
return classifySql(sql);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
5
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
6
|
+
export async function runCodex0138Doctor(root, input = {}) {
|
|
7
|
+
const capability = await detectCodex0138Capability();
|
|
8
|
+
const fixed = [];
|
|
9
|
+
const checks = {
|
|
10
|
+
bash_fallback: await bashFallbackCheck(),
|
|
11
|
+
linux_proxy_socket_path: linuxProxySocketCheck(root),
|
|
12
|
+
oauth_mcp_prerefresh: oauthMcpPrerefreshCheck(capability),
|
|
13
|
+
agents_logical_path: await agentsLogicalPathCheck(root),
|
|
14
|
+
plugin_discovery_cache: await pluginDiscoveryCacheCheck(root, input.fix === true, fixed)
|
|
15
|
+
};
|
|
16
|
+
const warnings = [
|
|
17
|
+
...(capability.ok ? [] : ['codex_0_138_not_detected']),
|
|
18
|
+
...Object.values(checks).flatMap((check) => Array.isArray(check.warnings) ? check.warnings : [])
|
|
19
|
+
];
|
|
20
|
+
const blockers = Object.values(checks).flatMap((check) => Array.isArray(check.blockers) ? check.blockers : []);
|
|
21
|
+
const report = {
|
|
22
|
+
schema: 'sks.codex-0138-doctor.v1',
|
|
23
|
+
generated_at: nowIso(),
|
|
24
|
+
ok: blockers.length === 0,
|
|
25
|
+
codex_0138_capability: capability,
|
|
26
|
+
checks,
|
|
27
|
+
fixed,
|
|
28
|
+
warnings,
|
|
29
|
+
blockers
|
|
30
|
+
};
|
|
31
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'codex-0138-doctor.json'), report);
|
|
32
|
+
return report;
|
|
33
|
+
}
|
|
34
|
+
async function bashFallbackCheck() {
|
|
35
|
+
const candidates = ['/bin/bash', '/usr/bin/bash'];
|
|
36
|
+
const existing = [];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
await fs.access(candidate);
|
|
40
|
+
existing.push(candidate);
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
ok: existing.length > 0,
|
|
46
|
+
candidates,
|
|
47
|
+
existing,
|
|
48
|
+
blockers: existing.length ? [] : ['bash_fallback_missing'],
|
|
49
|
+
warnings: []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function linuxProxySocketCheck(root) {
|
|
53
|
+
if (process.platform !== 'linux')
|
|
54
|
+
return { ok: true, status: 'not_linux', warnings: [], blockers: [] };
|
|
55
|
+
const candidate = path.join(os.tmpdir(), 'sks-proxy', path.basename(root), 'proxy.sock');
|
|
56
|
+
return {
|
|
57
|
+
ok: candidate.length < 100,
|
|
58
|
+
candidate,
|
|
59
|
+
length: candidate.length,
|
|
60
|
+
warnings: candidate.length < 100 ? [] : ['linux_proxy_socket_path_long'],
|
|
61
|
+
blockers: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function oauthMcpPrerefreshCheck(capability) {
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
supported: capability.supports_oauth_mcp_prerefresh === true,
|
|
68
|
+
warnings: capability.supports_oauth_mcp_prerefresh ? [] : ['oauth_mcp_prerefresh_requires_codex_0_138'],
|
|
69
|
+
blockers: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function agentsLogicalPathCheck(root) {
|
|
73
|
+
const agents = path.join(root, 'AGENTS.md');
|
|
74
|
+
const realRoot = await fs.realpath(root).catch(() => root);
|
|
75
|
+
const exists = await fs.stat(agents).then((st) => st.isFile()).catch(() => false);
|
|
76
|
+
return {
|
|
77
|
+
ok: exists,
|
|
78
|
+
logical_path: agents,
|
|
79
|
+
real_root: realRoot,
|
|
80
|
+
warnings: exists ? [] : ['agents_md_missing_or_unreadable'],
|
|
81
|
+
blockers: []
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function pluginDiscoveryCacheCheck(root, fix, fixed) {
|
|
85
|
+
const cacheDir = path.join(root, '.sneakoscope', 'cache', 'codex-plugin-discovery');
|
|
86
|
+
const exists = await fs.stat(cacheDir).then((st) => st.isDirectory()).catch(() => false);
|
|
87
|
+
if (!exists && fix) {
|
|
88
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
89
|
+
await writeJsonAtomic(path.join(cacheDir, 'README.json'), {
|
|
90
|
+
schema: 'sks.codex-plugin-discovery-cache.v1',
|
|
91
|
+
repaired_at: nowIso(),
|
|
92
|
+
purpose: 'Codex 0.138 plugin discovery cache placeholder; safe to refresh from codex plugin list --json.'
|
|
93
|
+
});
|
|
94
|
+
fixed.push('plugin_discovery_cache');
|
|
95
|
+
}
|
|
96
|
+
const after = exists || fix;
|
|
97
|
+
return {
|
|
98
|
+
ok: after,
|
|
99
|
+
path: cacheDir,
|
|
100
|
+
warnings: after ? [] : ['plugin_discovery_cache_missing_repair_available'],
|
|
101
|
+
blockers: []
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=codex-0138-doctor.js.map
|
|
@@ -52,6 +52,14 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
52
52
|
blockers.add('codex_app_fast_ui_repair_requires_confirmation');
|
|
53
53
|
if (input.codex_app_ui?.fast_selector === 'repaired')
|
|
54
54
|
warnings.add('codex_app_fast_selector_repaired_restart_app_if_needed');
|
|
55
|
+
const codex0138Doctor = input.codex_0138_doctor || null;
|
|
56
|
+
if (codex0138Doctor?.ok === false)
|
|
57
|
+
for (const blocker of normalizeList(codex0138Doctor.blockers))
|
|
58
|
+
warnings.add(blocker);
|
|
59
|
+
for (const warning of normalizeList(codex0138Doctor?.warnings))
|
|
60
|
+
warnings.add(warning);
|
|
61
|
+
for (const warning of normalizeList(input.codex_plugin_app_template_policy?.doctor_warnings))
|
|
62
|
+
warnings.add(warning);
|
|
55
63
|
if (input.codex_lb?.ok === false)
|
|
56
64
|
warnings.add(`codex_lb_${input.codex_lb?.circuit?.state || 'blocked'}`);
|
|
57
65
|
const localModel = input.local_model || {};
|
|
@@ -107,6 +115,9 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
107
115
|
replacement: 'zellij'
|
|
108
116
|
},
|
|
109
117
|
codex_doctor: codexDoctor || null,
|
|
118
|
+
codex_0138_doctor: codex0138Doctor,
|
|
119
|
+
codex_plugin_inventory: input.codex_plugin_inventory || null,
|
|
120
|
+
codex_plugin_app_template_policy: input.codex_plugin_app_template_policy || null,
|
|
110
121
|
fast_mode_ready: input.fast_mode_ready !== false,
|
|
111
122
|
codex_app_ui: input.codex_app_ui || null,
|
|
112
123
|
hooks_ready: input.hooks_ready !== false,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { nowIso, writeJsonAtomic } from './fsx.js';
|
|
3
3
|
import { ARTIFACT_FILES } from './artifact-schemas.js';
|
|
4
|
+
import { codexModelEffortCapability, modelEffortAtLeast } from './codex-control/codex-model-capabilities.js';
|
|
4
5
|
export const EFFORT_POLICY_VERSION = 1;
|
|
5
6
|
export function selectEffort(task = {}) {
|
|
6
7
|
const route = String(task.route || task.command || '').toLowerCase();
|
|
@@ -39,11 +40,19 @@ export function selectEffort(task = {}) {
|
|
|
39
40
|
selected = 'medium';
|
|
40
41
|
reasonCodes.push('default_medium');
|
|
41
42
|
}
|
|
43
|
+
const modelCapability = codexModelEffortCapability({
|
|
44
|
+
model: task.model || task.model_id,
|
|
45
|
+
advertisedEfforts: task.advertised_efforts || task.model_advertised_efforts,
|
|
46
|
+
defaultEffort: task.model_reasoning_effort || task.default_effort
|
|
47
|
+
});
|
|
48
|
+
const modelReasoningEffort = modelEffortAtLeast(selected, modelCapability);
|
|
42
49
|
return {
|
|
43
50
|
schema_version: EFFORT_POLICY_VERSION,
|
|
44
51
|
mission_id: task.mission_id || 'unassigned',
|
|
45
52
|
task_id: task.task_id || 'TASK-001',
|
|
46
53
|
selected_effort: selected,
|
|
54
|
+
model_reasoning_effort: modelReasoningEffort,
|
|
55
|
+
model_effort_capability: modelCapability,
|
|
47
56
|
reason_codes: reasonCodes,
|
|
48
57
|
risk_scores: risks,
|
|
49
58
|
demotion_allowed_after: demotionPolicy(selected),
|
|
@@ -830,7 +830,7 @@ function commandMaturity(name) {
|
|
|
830
830
|
function routeMaturity(command) {
|
|
831
831
|
if (['$Answer', '$DFix', '$SKS', '$Fast-Mode', '$Wiki', '$Help'].includes(command))
|
|
832
832
|
return 'stable';
|
|
833
|
-
if (['$Team', '$Goal', '$DB', '$Computer-Use', '$CU', '$QA-LOOP', '$MAD-SKS'].includes(command))
|
|
833
|
+
if (['$Team', '$Goal', '$DB', '$Computer-Use', '$CU', '$QA-LOOP', '$MAD-SKS', '$MAD-DB'].includes(command))
|
|
834
834
|
return 'beta';
|
|
835
835
|
return 'labs';
|
|
836
836
|
}
|
|
@@ -858,7 +858,7 @@ function knownGapsForCommand(name) {
|
|
|
858
858
|
function routeVoxelContract(command) {
|
|
859
859
|
if (['$Image-UX-Review', '$UX-Review', '$PPT', '$From-Chat-IMG', '$GX'].includes(command))
|
|
860
860
|
return 'image/source/bbox voxel required';
|
|
861
|
-
if (command === '$DB' || command === '$MAD-SKS')
|
|
861
|
+
if (command === '$DB' || command === '$MAD-SKS' || command === '$MAD-DB')
|
|
862
862
|
return 'DB policy voxel required';
|
|
863
863
|
return 'TriWiki anchors required';
|
|
864
864
|
}
|
|
@@ -867,6 +867,8 @@ function routeKnownGaps(command) {
|
|
|
867
867
|
return ['live imagegen/CU evidence required'];
|
|
868
868
|
if (command === '$MAD-SKS')
|
|
869
869
|
return ['permission closed by owning gate'];
|
|
870
|
+
if (command === '$MAD-DB')
|
|
871
|
+
return ['one-cycle capability must be explicitly enabled, consumed, or revoked'];
|
|
870
872
|
return [];
|
|
871
873
|
}
|
|
872
874
|
function checkRow(id, ok, blockers = []) {
|
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 = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.18';
|
|
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() {
|
|
@@ -3,6 +3,7 @@ import { projectRoot, readJson, readText, writeJsonAtomic, appendJsonl, readStdi
|
|
|
3
3
|
import { looksInteractiveCommand, interactiveCommandReason } from './no-question-guard.js';
|
|
4
4
|
import { missionDir, setCurrent, stateFile } from './mission.js';
|
|
5
5
|
import { checkDbOperation, dbBlockReason, handleMadSksUserConfirmation } from './db-safety.js';
|
|
6
|
+
import { maybeRecordMadDbToolResultFromToolUse } from './mad-db/mad-db-result-lifecycle.js';
|
|
6
7
|
import { checkHarnessModification, harnessGuardBlockReason, isHarnessSourceProject } from './harness-guard.js';
|
|
7
8
|
import { isMadSksRouteState } from './permission-gates.js';
|
|
8
9
|
import { classifyMadSksShellCommand } from './mad-sks/write-guard.js';
|
|
@@ -162,6 +163,8 @@ function toolFailed(payload = {}) {
|
|
|
162
163
|
if (Number.isFinite(n))
|
|
163
164
|
return n !== 0;
|
|
164
165
|
}
|
|
166
|
+
if (payload.isError === true || payload.tool_response?.isError === true || payload.toolResponse?.isError === true || payload.result?.isError === true)
|
|
167
|
+
return true;
|
|
165
168
|
if (payload.success === false || payload.tool_response?.success === false || payload.toolResponse?.success === false || payload.result?.success === false)
|
|
166
169
|
return true;
|
|
167
170
|
if (payload.executed === false)
|
|
@@ -422,10 +425,7 @@ function agentWorkerHookContext(state = {}, payload = {}) {
|
|
|
422
425
|
|| payload.agentWorker === true);
|
|
423
426
|
}
|
|
424
427
|
async function hookPostTool(root, state, payload, noQuestion) {
|
|
425
|
-
|
|
426
|
-
if (dbDecision.action === 'block' || dbDecision.action === 'confirm') {
|
|
427
|
-
return { decision: 'block', reason: dbBlockReason(dbDecision) };
|
|
428
|
-
}
|
|
428
|
+
await recordMadDbPostToolLifecycle(root, state, payload).catch(() => null);
|
|
429
429
|
await recordContext7Evidence(root, state, payload).catch(() => null);
|
|
430
430
|
await recordSubagentEvidence(root, state, payload).catch(() => null);
|
|
431
431
|
if (toolFailed(payload))
|
|
@@ -449,6 +449,40 @@ async function hookPostTool(root, state, payload, noQuestion) {
|
|
|
449
449
|
? { continue: true, additionalContext: teamDigest.context, systemMessage: joinSystemMessages(visibleHookMessage('post-tool'), teamDigest.system) }
|
|
450
450
|
: { continue: true };
|
|
451
451
|
}
|
|
452
|
+
async function recordMadDbPostToolLifecycle(root, state = {}, payload = {}) {
|
|
453
|
+
if (!state?.mission_id)
|
|
454
|
+
return null;
|
|
455
|
+
return maybeRecordMadDbToolResultFromToolUse({
|
|
456
|
+
root,
|
|
457
|
+
missionId: String(state.mission_id),
|
|
458
|
+
toolCallPayload: payload,
|
|
459
|
+
toolResult: payload
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function extractRowCount(payload = {}) {
|
|
463
|
+
const candidates = [
|
|
464
|
+
payload.row_count,
|
|
465
|
+
payload.rowCount,
|
|
466
|
+
payload.tool_response?.row_count,
|
|
467
|
+
payload.tool_response?.rowCount,
|
|
468
|
+
payload.toolResponse?.rowCount,
|
|
469
|
+
payload.result?.row_count,
|
|
470
|
+
payload.result?.rowCount,
|
|
471
|
+
payload.result?.rows_affected,
|
|
472
|
+
payload.tool_response?.rows_affected
|
|
473
|
+
];
|
|
474
|
+
for (const candidate of candidates) {
|
|
475
|
+
if (candidate === undefined || candidate === null || candidate === '')
|
|
476
|
+
continue;
|
|
477
|
+
const parsed = Number(candidate);
|
|
478
|
+
if (Number.isFinite(parsed))
|
|
479
|
+
return parsed;
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
function extractToolError(payload = {}) {
|
|
484
|
+
return String(payload.error || payload.message || payload.stderr || payload.tool_response?.stderr || payload.toolResponse?.stderr || payload.result?.stderr || payload.result?.error || 'tool_failed');
|
|
485
|
+
}
|
|
452
486
|
async function recordToolErrorTaxonomy(root, state = {}, payload = {}) {
|
|
453
487
|
if (!state?.mission_id)
|
|
454
488
|
return null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { imageDimensions } from '../wiki-image/image-hash.js';
|
|
5
|
+
export async function buildImageArtifactPathContract(root, input) {
|
|
6
|
+
const images = [];
|
|
7
|
+
const blockers = [];
|
|
8
|
+
for (const [index, image] of input.images.entries()) {
|
|
9
|
+
const filePath = path.resolve(root, image.filePath || '');
|
|
10
|
+
const exists = await fileExists(filePath);
|
|
11
|
+
if (!exists)
|
|
12
|
+
blockers.push(`${image.kind}_file_path_missing:${image.id || index + 1}`);
|
|
13
|
+
const dims = exists ? await imageDimensions(filePath).catch(() => null) : null;
|
|
14
|
+
images.push({
|
|
15
|
+
id: image.id || `image-${index + 1}`,
|
|
16
|
+
kind: image.kind,
|
|
17
|
+
file_path: filePath,
|
|
18
|
+
relative_path: path.relative(root, filePath),
|
|
19
|
+
exists,
|
|
20
|
+
mime_type: mimeForPath(filePath),
|
|
21
|
+
width: dims?.width ?? null,
|
|
22
|
+
height: dims?.height ?? null,
|
|
23
|
+
model_visible_path: filePath,
|
|
24
|
+
followup_edit_hint: exists
|
|
25
|
+
? `Use this saved local path for follow-up image edits: ${filePath}`
|
|
26
|
+
: 'Image file path missing; do not run visual QA until a real saved file path exists.'
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (images.some((image) => image.kind === 'generated_image' && !image.exists))
|
|
30
|
+
blockers.push('image_generated_file_path_missing');
|
|
31
|
+
return {
|
|
32
|
+
schema: 'sks.image-artifact-path-contract.v1',
|
|
33
|
+
mission_id: input.missionId,
|
|
34
|
+
generated_at: nowIso(),
|
|
35
|
+
images,
|
|
36
|
+
blockers: [...new Set(blockers)]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export async function writeImageArtifactPathContract(root, input) {
|
|
40
|
+
const contract = await buildImageArtifactPathContract(root, input);
|
|
41
|
+
const artifactPath = input.artifactPath || path.join(root, '.sneakoscope', 'missions', input.missionId, 'image-artifact-path-contract.json');
|
|
42
|
+
await writeJsonAtomic(artifactPath, contract);
|
|
43
|
+
return { contract, artifact_path: artifactPath };
|
|
44
|
+
}
|
|
45
|
+
export async function discoverImageArtifactsInDir(dir) {
|
|
46
|
+
const out = [];
|
|
47
|
+
await walk(dir, async (file) => {
|
|
48
|
+
if (!/\.(png|jpe?g|webp|gif)$/i.test(file))
|
|
49
|
+
return;
|
|
50
|
+
out.push({
|
|
51
|
+
id: path.basename(file).replace(/[^0-9A-Za-z._-]/g, '_'),
|
|
52
|
+
kind: /generated|gpt-image|callout/i.test(file) ? 'generated_image' : 'visual_qa_snapshot',
|
|
53
|
+
filePath: file
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function mimeForPath(file) {
|
|
59
|
+
const ext = path.extname(file).toLowerCase();
|
|
60
|
+
if (ext === '.png')
|
|
61
|
+
return 'image/png';
|
|
62
|
+
if (ext === '.jpg' || ext === '.jpeg')
|
|
63
|
+
return 'image/jpeg';
|
|
64
|
+
if (ext === '.webp')
|
|
65
|
+
return 'image/webp';
|
|
66
|
+
if (ext === '.gif')
|
|
67
|
+
return 'image/gif';
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
async function fileExists(file) {
|
|
71
|
+
try {
|
|
72
|
+
const st = await fs.stat(file);
|
|
73
|
+
return st.isFile();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function walk(dir, visit) {
|
|
80
|
+
let entries;
|
|
81
|
+
try {
|
|
82
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const full = path.join(dir, entry.name);
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
if (['node_modules', '.git', 'dist'].includes(entry.name))
|
|
91
|
+
continue;
|
|
92
|
+
await walk(full, visit);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
await visit(full);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=image-artifact-path-contract.js.map
|