sneakoscope 2.0.2 → 2.0.5
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 +12 -8
- 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/build-manifest.json +86 -8
- package/dist/commands/doctor.js +14 -0
- package/dist/core/agents/agent-orchestrator.js +70 -4
- package/dist/core/agents/agent-patch-proof.js +5 -0
- package/dist/core/agents/agent-proof-evidence.js +61 -0
- package/dist/core/agents/agent-roster.js +35 -6
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/native-worker-backend-router.js +31 -9
- package/dist/core/agents/ollama-worker-config.js +164 -15
- package/dist/core/codex/codex-0-137-compat.js +119 -0
- package/dist/core/codex-control/codex-control-proof.js +4 -1
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +20 -0
- package/dist/core/codex-control/codex-output-schemas.js +5 -1
- package/dist/core/codex-control/codex-sdk-capability.js +1 -1
- package/dist/core/codex-control/codex-task-runner.js +329 -5
- package/dist/core/codex-control/gpt-final-arbiter.js +160 -0
- package/dist/core/codex-control/gpt-final-context-compressor.js +17 -0
- package/dist/core/codex-control/gpt-final-proof-pack.js +120 -0
- package/dist/core/codex-control/gpt-final-review-schema.js +71 -0
- package/dist/core/codex-control/python-codex-sdk-adapter.js +197 -0
- package/dist/core/codex-control/python-codex-sdk-event-translator.js +14 -0
- package/dist/core/commands/local-model-command.js +79 -18
- package/dist/core/commands/naruto-command.js +195 -12
- package/dist/core/commands/run-command.js +6 -2
- package/dist/core/doctor/doctor-readiness-matrix.js +34 -0
- package/dist/core/feature-fixtures.js +4 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/git-simple.js +143 -4
- package/dist/core/local-llm/local-collaboration-policy.js +93 -0
- package/dist/core/local-llm/local-llm-backpressure.js +20 -0
- package/dist/core/local-llm/local-llm-capability.js +29 -0
- package/dist/core/local-llm/local-llm-client.js +100 -0
- package/dist/core/local-llm/local-llm-config.js +20 -0
- package/dist/core/local-llm/local-llm-context-cache.js +21 -0
- package/dist/core/local-llm/local-llm-control-adapter.js +101 -0
- package/dist/core/local-llm/local-llm-json-repair.js +52 -0
- package/dist/core/local-llm/local-llm-metrics.js +42 -0
- package/dist/core/local-llm/local-llm-ollama-client.js +67 -0
- package/dist/core/local-llm/local-llm-openai-compatible-client.js +30 -0
- package/dist/core/local-llm/local-llm-prompt-cache.js +12 -0
- package/dist/core/local-llm/local-llm-scheduler.js +29 -0
- package/dist/core/local-llm/local-llm-schema-enforcer.js +15 -0
- package/dist/core/local-llm/local-llm-smoke.js +83 -0
- package/dist/core/local-llm/local-llm-warmup.js +20 -0
- package/dist/core/local-llm/local-worker-eligibility.js +27 -0
- package/dist/core/naruto/hardware-capacity-probe.js +36 -0
- package/dist/core/naruto/naruto-active-pool.js +118 -0
- package/dist/core/naruto/naruto-backpressure.js +13 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +65 -0
- package/dist/core/naruto/naruto-finalizer.js +18 -0
- package/dist/core/naruto/naruto-generation-scheduler.js +18 -0
- package/dist/core/naruto/naruto-gpt-final-pack.js +49 -0
- package/dist/core/naruto/naruto-parallel-patch-apply.js +95 -0
- package/dist/core/naruto/naruto-patch-transaction-batch.js +42 -0
- package/dist/core/naruto/naruto-role-policy.js +107 -0
- package/dist/core/naruto/naruto-verification-dag.js +42 -0
- package/dist/core/naruto/naruto-verification-pool.js +18 -0
- package/dist/core/naruto/naruto-work-graph.js +198 -0
- package/dist/core/naruto/naruto-work-item.js +40 -0
- package/dist/core/naruto/naruto-work-stealing.js +11 -0
- package/dist/core/naruto/resource-pressure-monitor.js +32 -0
- package/dist/core/pipeline/final-gpt-patch-stage.js +31 -0
- package/dist/core/pipeline/final-gpt-review-stage.js +5 -0
- package/dist/core/pipeline/finalize-pipeline-result.js +58 -0
- package/dist/core/pipeline/gpt-final-required.js +12 -0
- package/dist/core/prompt/prompt-placeholder-guard.js +30 -0
- package/dist/core/router/capability-card.js +13 -0
- package/dist/core/router/route-cache.js +3 -0
- package/dist/core/router/ultra-router.js +2 -1
- package/dist/core/routes.js +4 -4
- package/dist/core/safety/mutation-guard.js +2 -0
- package/dist/core/update-check.js +60 -25
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-lane-runtime.js +2 -2
- package/dist/core/zellij/zellij-naruto-dashboard.js +36 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +4 -4
- package/dist/scripts/blackbox-command-import-smoke.js +10 -1
- package/dist/scripts/check-package-boundary.js +12 -3
- package/dist/scripts/codex-0-137-compat-check.js +27 -0
- package/dist/scripts/codex-environment-scoped-approvals-check.js +10 -0
- package/dist/scripts/codex-plugin-list-json-check.js +8 -0
- package/dist/scripts/codex-sdk-team-naruto-agent-pipeline-check.js +2 -1
- package/dist/scripts/codex-thread-runtime-choice-check.js +10 -0
- package/dist/scripts/gpt-final-arbiter-check.js +63 -0
- package/dist/scripts/gpt-final-arbiter-performance-check.js +36 -0
- package/dist/scripts/local-collab-all-pipelines-final-gpt-check.js +21 -0
- package/dist/scripts/local-collab-gpt-final-availability-check.js +58 -0
- package/dist/scripts/local-collab-no-local-only-final-check.js +27 -0
- package/dist/scripts/local-collab-policy-check.js +17 -0
- package/dist/scripts/local-llm-all-pipelines-check.js +11 -0
- package/dist/scripts/local-llm-cache-performance-check.js +10 -0
- package/dist/scripts/local-llm-capability-check.js +14 -0
- package/dist/scripts/local-llm-smoke-check.js +23 -0
- package/dist/scripts/local-llm-structured-output-check.js +11 -0
- package/dist/scripts/local-llm-throughput-check.js +10 -0
- package/dist/scripts/local-llm-tool-call-repair-check.js +10 -0
- package/dist/scripts/local-llm-warmup-check.js +11 -0
- package/dist/scripts/naruto-active-pool-check.js +27 -0
- package/dist/scripts/naruto-concurrency-governor-check.js +52 -0
- package/dist/scripts/naruto-gpt-final-pack-check.js +34 -0
- package/dist/scripts/naruto-parallel-patch-apply-check.js +41 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +16 -0
- package/dist/scripts/naruto-role-distribution-check.js +23 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +6 -0
- package/dist/scripts/naruto-verification-pool-check.js +36 -0
- package/dist/scripts/naruto-work-graph-check.js +24 -0
- package/dist/scripts/naruto-zellij-massive-ui-check.js +23 -0
- package/dist/scripts/prompt-placeholder-guard-check.js +33 -0
- package/dist/scripts/python-codex-sdk-all-pipelines-check.js +47 -0
- package/dist/scripts/python-codex-sdk-capability-check.js +75 -0
- package/dist/scripts/python-codex-sdk-sandbox-policy-check.js +10 -0
- package/dist/scripts/python-codex-sdk-stream-bridge-check.js +12 -0
- package/dist/scripts/release-parallel-check.js +1 -1
- package/dist/scripts/release-real-check.js +5 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +1 -1
- package/package.json +38 -4
- package/schemas/local-llm/local-collaboration-policy.schema.json +57 -0
- package/schemas/local-llm/local-model-config.schema.json +74 -0
- package/schemas/naruto/naruto-concurrency-governor.schema.json +21 -0
- package/schemas/naruto/naruto-work-graph.schema.json +22 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { nowIso, sha256 } from '../fsx.js';
|
|
2
|
+
export const GPT_FINAL_PROOF_PACK_SCHEMA = 'sks.gpt-final-proof-pack.v1';
|
|
3
|
+
export function buildGptFinalProofPack(input, opts = {}) {
|
|
4
|
+
const maxCriticalLogChars = Math.max(1000, Number(opts.maxCriticalLogChars || 6000));
|
|
5
|
+
const localOutputs = Array.isArray(input.local_outputs) ? input.local_outputs : [];
|
|
6
|
+
const candidatePatchEnvelopes = Array.isArray(input.candidate_patch_envelopes) ? input.candidate_patch_envelopes : [];
|
|
7
|
+
const verificationResults = Array.isArray(input.verification_results) ? input.verification_results : [];
|
|
8
|
+
const pack = {
|
|
9
|
+
schema: GPT_FINAL_PROOF_PACK_SCHEMA,
|
|
10
|
+
generated_at: nowIso(),
|
|
11
|
+
route: String(input.route || ''),
|
|
12
|
+
mission_id: String(input.mission_id || ''),
|
|
13
|
+
local_mode: String(input.local_mode || ''),
|
|
14
|
+
worker_count: localOutputs.length,
|
|
15
|
+
changed_files: sortedUnique([
|
|
16
|
+
...candidatePatchEnvelopes.flatMap((envelope) => envelopeChangedFiles(envelope)),
|
|
17
|
+
...localOutputs.flatMap((output) => Array.isArray(output?.changed_files) ? output.changed_files.map(String) : [])
|
|
18
|
+
]),
|
|
19
|
+
local_output_summaries: localOutputs.map((output, index) => ({
|
|
20
|
+
worker_id: String(output?.worker_id || output?.agent_id || output?.id || `worker-${index + 1}`),
|
|
21
|
+
backend: String(output?.backend || 'local-llm'),
|
|
22
|
+
status: String(output?.status || 'unknown'),
|
|
23
|
+
summary: trim(String(output?.summary || ''), 600),
|
|
24
|
+
proof: trim(String(output?.proof || output?.verification?.status || ''), 600),
|
|
25
|
+
blocker_count: Array.isArray(output?.blockers) ? output.blockers.length : 0,
|
|
26
|
+
patch_envelope_count: Array.isArray(output?.patch_envelopes) ? output.patch_envelopes.length : output?.patch_envelope ? 1 : 0
|
|
27
|
+
})),
|
|
28
|
+
candidate_diff_sha256: sha256(String(input.candidate_diff || '')),
|
|
29
|
+
candidate_diff_tail: tail(String(input.candidate_diff || ''), maxCriticalLogChars),
|
|
30
|
+
candidate_patch_envelopes: candidatePatchEnvelopes.map((envelope, index) => summarizeEnvelope(envelope, index)),
|
|
31
|
+
verification_failures: verificationResults.filter((result) => result?.ok === false || result?.status === 'failed').map((result) => ({
|
|
32
|
+
id: String(result?.id || result?.patch_entry_id || result?.name || 'verification'),
|
|
33
|
+
status: String(result?.status || (result?.ok === false ? 'failed' : 'unknown')),
|
|
34
|
+
blockers: stringArray(result?.blockers || result?.violations)
|
|
35
|
+
})),
|
|
36
|
+
side_effect_ledger_summary: summarizeObject(input.side_effect_report),
|
|
37
|
+
mutation_ledger_summary: summarizeObject(input.mutation_ledger),
|
|
38
|
+
rollback_plan_summary: summarizeObject(input.rollback_plan),
|
|
39
|
+
conflict_map: buildConflictMap(candidatePatchEnvelopes),
|
|
40
|
+
critical_logs_tail: tail(JSON.stringify({
|
|
41
|
+
local_outputs: localOutputs.map((output) => ({
|
|
42
|
+
id: output?.worker_id || output?.agent_id || output?.id,
|
|
43
|
+
blockers: output?.blockers || [],
|
|
44
|
+
unverified: output?.unverified || []
|
|
45
|
+
})),
|
|
46
|
+
verification_results: verificationResults
|
|
47
|
+
}), maxCriticalLogChars),
|
|
48
|
+
token_budget_estimate: 0
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
...pack,
|
|
52
|
+
token_budget_estimate: estimateTokens(pack)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function buildGptFinalLatencyBudgetReport(input) {
|
|
56
|
+
const workerCount = Math.max(0, Number(input.workerCount || 0));
|
|
57
|
+
const tokenBudgetEstimate = Math.max(0, Number(input.tokenBudgetEstimate || 0));
|
|
58
|
+
const capAdjustment = tokenBudgetEstimate > 8000 || workerCount > 20 ? 'reduce_local_parallelism_or_pack_size' : 'within_budget';
|
|
59
|
+
return {
|
|
60
|
+
schema: 'sks.gpt-final-latency-budget.v1',
|
|
61
|
+
generated_at: nowIso(),
|
|
62
|
+
worker_count: workerCount,
|
|
63
|
+
token_budget_estimate: tokenBudgetEstimate,
|
|
64
|
+
latency_ms: input.latencyMs ?? null,
|
|
65
|
+
cap_adjustment: capAdjustment,
|
|
66
|
+
ok: capAdjustment === 'within_budget'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function summarizeEnvelope(envelope, index) {
|
|
70
|
+
return {
|
|
71
|
+
id: String(envelope?.id || envelope?.patch_id || `patch-${index + 1}`),
|
|
72
|
+
agent_id: String(envelope?.agent_id || 'unknown'),
|
|
73
|
+
source: String(envelope?.source || 'local-llm'),
|
|
74
|
+
operations: Array.isArray(envelope?.operations) ? envelope.operations.length : 0,
|
|
75
|
+
write_paths: envelopeChangedFiles(envelope),
|
|
76
|
+
lease_id: envelope?.lease_id || envelope?.lease_proof?.lease_id || null,
|
|
77
|
+
rollback_ready: Boolean(envelope?.rollback_hint || envelope?.lease_proof?.rollback_node_id)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function envelopeChangedFiles(envelope) {
|
|
81
|
+
return Array.isArray(envelope?.operations) ? envelope.operations.map((operation) => String(operation?.path || '')).filter(Boolean) : [];
|
|
82
|
+
}
|
|
83
|
+
function buildConflictMap(envelopes) {
|
|
84
|
+
const owners = new Map();
|
|
85
|
+
for (const envelope of envelopes) {
|
|
86
|
+
const agent = String(envelope?.agent_id || 'unknown');
|
|
87
|
+
for (const file of envelopeChangedFiles(envelope))
|
|
88
|
+
owners.set(file, [...(owners.get(file) || []), agent]);
|
|
89
|
+
}
|
|
90
|
+
return [...owners.entries()]
|
|
91
|
+
.filter(([, agents]) => new Set(agents).size > 1)
|
|
92
|
+
.map(([file, agents]) => ({ file, agents: sortedUnique(agents) }));
|
|
93
|
+
}
|
|
94
|
+
function summarizeObject(value) {
|
|
95
|
+
if (!value || typeof value !== 'object')
|
|
96
|
+
return null;
|
|
97
|
+
return {
|
|
98
|
+
schema: value.schema || null,
|
|
99
|
+
ok: typeof value.ok === 'boolean' ? value.ok : null,
|
|
100
|
+
status: value.status || null,
|
|
101
|
+
blockers: stringArray(value.blockers).slice(0, 20),
|
|
102
|
+
keys: Object.keys(value).slice(0, 30)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function estimateTokens(value) {
|
|
106
|
+
return Math.ceil(JSON.stringify(value).length / 4);
|
|
107
|
+
}
|
|
108
|
+
function tail(value, max) {
|
|
109
|
+
return value.length <= max ? value : value.slice(value.length - max);
|
|
110
|
+
}
|
|
111
|
+
function trim(value, max) {
|
|
112
|
+
return value.length <= max ? value : value.slice(0, max);
|
|
113
|
+
}
|
|
114
|
+
function sortedUnique(values) {
|
|
115
|
+
return [...new Set(values.map(String).filter(Boolean))].sort();
|
|
116
|
+
}
|
|
117
|
+
function stringArray(value) {
|
|
118
|
+
return Array.isArray(value) ? value.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=gpt-final-proof-pack.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const GPT_FINAL_ARBITER_RESULT_SCHEMA_ID = 'sks.gpt-final-arbiter-result.v1';
|
|
2
|
+
export const GPT_FINAL_ARBITER_INPUT_SCHEMA = 'sks.gpt-final-arbiter-input.v1';
|
|
3
|
+
export const gptFinalArbiterResultSchema = {
|
|
4
|
+
type: 'object',
|
|
5
|
+
required: [
|
|
6
|
+
'schema',
|
|
7
|
+
'status',
|
|
8
|
+
'summary',
|
|
9
|
+
'gpt_review_findings',
|
|
10
|
+
'accepted_patch_envelopes',
|
|
11
|
+
'modified_patch_envelopes',
|
|
12
|
+
'rejected_patch_envelopes',
|
|
13
|
+
'required_followup_work',
|
|
14
|
+
'verification_plan',
|
|
15
|
+
'rollback_notes',
|
|
16
|
+
'blockers',
|
|
17
|
+
'confidence'
|
|
18
|
+
],
|
|
19
|
+
properties: {
|
|
20
|
+
schema: { const: GPT_FINAL_ARBITER_RESULT_SCHEMA_ID },
|
|
21
|
+
status: { enum: ['approved', 'modified', 'rejected', 'needs_more_work'] },
|
|
22
|
+
summary: { type: 'string' },
|
|
23
|
+
gpt_review_findings: { type: 'array', items: { type: 'object' } },
|
|
24
|
+
accepted_patch_envelopes: { type: 'array', items: { type: 'object' } },
|
|
25
|
+
modified_patch_envelopes: { type: 'array', items: { type: 'object' } },
|
|
26
|
+
rejected_patch_envelopes: { type: 'array', items: { type: 'object' } },
|
|
27
|
+
required_followup_work: { type: 'array', items: { type: 'object' } },
|
|
28
|
+
verification_plan: { type: 'array', items: { type: 'string' } },
|
|
29
|
+
rollback_notes: { type: 'array', items: { type: 'string' } },
|
|
30
|
+
blockers: { type: 'array', items: { type: 'string' } },
|
|
31
|
+
confidence: { enum: ['low', 'medium', 'high'] }
|
|
32
|
+
},
|
|
33
|
+
additionalProperties: true
|
|
34
|
+
};
|
|
35
|
+
export function normalizeGptFinalArbiterResult(value) {
|
|
36
|
+
const status = normalizeStatus(value?.status);
|
|
37
|
+
return {
|
|
38
|
+
schema: GPT_FINAL_ARBITER_RESULT_SCHEMA_ID,
|
|
39
|
+
status,
|
|
40
|
+
summary: String(value?.summary || defaultSummary(status)),
|
|
41
|
+
gpt_review_findings: array(value?.gpt_review_findings),
|
|
42
|
+
accepted_patch_envelopes: array(value?.accepted_patch_envelopes),
|
|
43
|
+
modified_patch_envelopes: array(value?.modified_patch_envelopes),
|
|
44
|
+
rejected_patch_envelopes: array(value?.rejected_patch_envelopes),
|
|
45
|
+
required_followup_work: array(value?.required_followup_work),
|
|
46
|
+
verification_plan: stringArray(value?.verification_plan),
|
|
47
|
+
rollback_notes: stringArray(value?.rollback_notes),
|
|
48
|
+
blockers: stringArray(value?.blockers),
|
|
49
|
+
confidence: normalizeConfidence(value?.confidence)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function normalizeStatus(value) {
|
|
53
|
+
return value === 'approved' || value === 'modified' || value === 'rejected' || value === 'needs_more_work'
|
|
54
|
+
? value
|
|
55
|
+
: 'needs_more_work';
|
|
56
|
+
}
|
|
57
|
+
function normalizeConfidence(value) {
|
|
58
|
+
return value === 'low' || value === 'medium' || value === 'high' ? value : 'medium';
|
|
59
|
+
}
|
|
60
|
+
function array(value) {
|
|
61
|
+
return Array.isArray(value) ? value : [];
|
|
62
|
+
}
|
|
63
|
+
function stringArray(value) {
|
|
64
|
+
return Array.isArray(value) ? value.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
|
|
65
|
+
}
|
|
66
|
+
function defaultSummary(status) {
|
|
67
|
+
return status === 'approved' || status === 'modified'
|
|
68
|
+
? 'GPT final arbiter accepted the candidate result.'
|
|
69
|
+
: 'GPT final arbiter did not approve the candidate result.';
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=gpt-final-review-schema.js.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { packageRoot, runProcess, which } from '../fsx.js';
|
|
4
|
+
import { translatePythonCodexSdkEvents } from './python-codex-sdk-event-translator.js';
|
|
5
|
+
export async function detectPythonCodexSdkCapability() {
|
|
6
|
+
const python = await resolvePythonCodexSdkPython();
|
|
7
|
+
if (!python.path)
|
|
8
|
+
return capability(false, null, '', ['python_missing'], null, 'Install Python >= 3.10 and install the Codex Python SDK.');
|
|
9
|
+
const pyOk = parsePythonVersion(python.versionText) >= 3.10;
|
|
10
|
+
const probes = [];
|
|
11
|
+
let detected = null;
|
|
12
|
+
if (pyOk) {
|
|
13
|
+
for (const candidate of PYTHON_CODEX_SDK_CANDIDATES) {
|
|
14
|
+
const importProbe = await runProcess(python.path, ['-c', `import ${candidate.importName}; print("ok")`], { timeoutMs: 5000, maxOutputBytes: 4096 })
|
|
15
|
+
.catch((err) => ({ code: 1, stdout: '', stderr: err.message || String(err) }));
|
|
16
|
+
probes.push({
|
|
17
|
+
package_name: candidate.packageName,
|
|
18
|
+
import_name: candidate.importName,
|
|
19
|
+
ok: importProbe.code === 0,
|
|
20
|
+
stderr: String(importProbe.stderr || '').slice(-500)
|
|
21
|
+
});
|
|
22
|
+
if (importProbe.code === 0) {
|
|
23
|
+
detected = candidate;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const blockers = [
|
|
29
|
+
...(pyOk ? [] : ['python_version_below_3_10']),
|
|
30
|
+
...(detected ? [] : ['python_codex_sdk_unavailable'])
|
|
31
|
+
];
|
|
32
|
+
return capability(blockers.length === 0, python.path, python.versionText, blockers, detected, blockers.length ? setupAction(python.path) : null, probes);
|
|
33
|
+
}
|
|
34
|
+
export async function runPythonCodexSdkTask(input, opts = {}) {
|
|
35
|
+
const cap = await detectPythonCodexSdkCapability();
|
|
36
|
+
if (!cap.ok && process.env.SKS_PYTHON_CODEX_SDK_FAKE !== '1') {
|
|
37
|
+
return { ok: false, events: [], translatedEvents: [], finalResponse: '', blockers: cap.blockers, capability: cap };
|
|
38
|
+
}
|
|
39
|
+
const python = opts.pythonBin || cap.python_bin || await which('python3') || 'python3';
|
|
40
|
+
const request = {
|
|
41
|
+
schema: 'sks.python-codex-sdk-request.v1',
|
|
42
|
+
route: input.route,
|
|
43
|
+
thread_policy: input.requestedScopeContract?.resume_thread_id ? 'resume' : 'new',
|
|
44
|
+
sandbox: mapSandbox(input.sandboxPolicy),
|
|
45
|
+
cwd: input.cwd,
|
|
46
|
+
model: typeof opts.config?.model === 'string' ? opts.config.model : '',
|
|
47
|
+
model_reasoning_effort: typeof opts.config?.model_reasoning_effort === 'string' ? opts.config.model_reasoning_effort : 'minimal',
|
|
48
|
+
prompt: input.prompt,
|
|
49
|
+
output_schema: input.outputSchema || {}
|
|
50
|
+
};
|
|
51
|
+
const events = await runPythonRunner(python, request, opts.env);
|
|
52
|
+
const translatedEvents = translatePythonCodexSdkEvents(events);
|
|
53
|
+
const last = [...events].reverse().find((event) => event?.event === 'turn_completed');
|
|
54
|
+
const errors = events.filter((event) => event?.event === 'error').map((event) => String(event.message || 'python_codex_sdk_error'));
|
|
55
|
+
return {
|
|
56
|
+
ok: errors.length === 0 && Boolean(last),
|
|
57
|
+
events,
|
|
58
|
+
translatedEvents,
|
|
59
|
+
finalResponse: String(last?.final_response || ''),
|
|
60
|
+
threadId: String(events.find((event) => event?.thread_id)?.thread_id || ''),
|
|
61
|
+
turnId: String(last?.turn_id || ''),
|
|
62
|
+
blockers: errors,
|
|
63
|
+
capability: cap
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function runPythonRunner(python, request, envOverride) {
|
|
67
|
+
const runner = path.join(packageRoot(), 'pytools', 'codex_sdk_runner.py');
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const child = spawn(python, [runner], {
|
|
70
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
71
|
+
env: envOverride ? { ...process.env, ...envOverride } : process.env,
|
|
72
|
+
detached: process.platform !== 'win32'
|
|
73
|
+
});
|
|
74
|
+
const events = [];
|
|
75
|
+
let stderr = '';
|
|
76
|
+
let timedOut = false;
|
|
77
|
+
const timeoutMs = Number(process.env.SKS_PYTHON_CODEX_SDK_TIMEOUT_MS || 120000);
|
|
78
|
+
const timer = setTimeout(() => {
|
|
79
|
+
timedOut = true;
|
|
80
|
+
events.push({ event: 'error', retryable: true, message: `python_codex_sdk_timeout:${timeoutMs}` });
|
|
81
|
+
terminatePythonRunner(child);
|
|
82
|
+
}, timeoutMs);
|
|
83
|
+
timer.unref?.();
|
|
84
|
+
child.stdout.setEncoding('utf8');
|
|
85
|
+
child.stderr.setEncoding('utf8');
|
|
86
|
+
child.stdout.on('data', (chunk) => {
|
|
87
|
+
for (const line of String(chunk).split(/\n/).filter(Boolean)) {
|
|
88
|
+
try {
|
|
89
|
+
events.push(JSON.parse(line));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
events.push({ event: 'error', retryable: false, message: `invalid_python_jsonl:${line.slice(0, 200)}` });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
child.stderr.on('data', (chunk) => { stderr += String(chunk); });
|
|
97
|
+
child.on('error', reject);
|
|
98
|
+
child.on('close', () => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
if (stderr.trim())
|
|
101
|
+
events.push({ event: 'stderr', message: stderr.slice(-1000) });
|
|
102
|
+
if (timedOut && !events.some((event) => String(event?.message || '').startsWith('python_codex_sdk_timeout:'))) {
|
|
103
|
+
events.push({ event: 'error', retryable: true, message: `python_codex_sdk_timeout:${timeoutMs}` });
|
|
104
|
+
}
|
|
105
|
+
resolve(events);
|
|
106
|
+
});
|
|
107
|
+
child.stdin.end(JSON.stringify(request));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function terminatePythonRunner(child) {
|
|
111
|
+
if (!child.pid)
|
|
112
|
+
return;
|
|
113
|
+
try {
|
|
114
|
+
if (process.platform !== 'win32')
|
|
115
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
116
|
+
else
|
|
117
|
+
child.kill('SIGTERM');
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
try {
|
|
121
|
+
child.kill('SIGTERM');
|
|
122
|
+
}
|
|
123
|
+
catch { }
|
|
124
|
+
}
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
try {
|
|
127
|
+
if (process.platform !== 'win32')
|
|
128
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
129
|
+
else
|
|
130
|
+
child.kill('SIGKILL');
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}, 5000).unref?.();
|
|
134
|
+
}
|
|
135
|
+
function mapSandbox(value) {
|
|
136
|
+
if (value === 'workspace-write')
|
|
137
|
+
return 'workspace_write';
|
|
138
|
+
if (value === 'full-access')
|
|
139
|
+
return 'full_access';
|
|
140
|
+
return 'read_only';
|
|
141
|
+
}
|
|
142
|
+
function parsePythonVersion(text) {
|
|
143
|
+
const match = text.match(/Python\s+(\d+)\.(\d+)/i);
|
|
144
|
+
return match ? Number(`${match[1]}.${match[2]}`) : 0;
|
|
145
|
+
}
|
|
146
|
+
async function resolvePythonCodexSdkPython() {
|
|
147
|
+
const requested = [
|
|
148
|
+
process.env.SKS_PYTHON_CODEX_SDK_PYTHON,
|
|
149
|
+
process.env.PYTHON
|
|
150
|
+
].filter((value) => Boolean(value && value.trim()));
|
|
151
|
+
const candidates = [...requested, 'python3.12', 'python3.11', 'python3.10', 'python3', 'python'];
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
const probes = [];
|
|
154
|
+
for (const candidate of candidates) {
|
|
155
|
+
const resolved = candidate.includes('/') ? candidate : await which(candidate);
|
|
156
|
+
if (!resolved || seen.has(resolved))
|
|
157
|
+
continue;
|
|
158
|
+
seen.add(resolved);
|
|
159
|
+
const version = await runProcess(resolved, ['--version'], { timeoutMs: 5000, maxOutputBytes: 4096 })
|
|
160
|
+
.catch((err) => ({ code: 1, stdout: '', stderr: err.message || String(err) }));
|
|
161
|
+
const versionText = `${version.stdout || ''}${version.stderr || ''}`.trim();
|
|
162
|
+
probes.push({ path: resolved, versionText, score: parsePythonVersion(versionText) });
|
|
163
|
+
}
|
|
164
|
+
const supported = probes.find((probe) => probe.score >= 3.10);
|
|
165
|
+
return supported || probes[0] || { path: null, versionText: '' };
|
|
166
|
+
}
|
|
167
|
+
const PYTHON_CODEX_SDK_CANDIDATES = [
|
|
168
|
+
{ packageName: 'codex-app-server', importName: 'codex_app_server', source: 'developers.openai.com/codex/sdk' },
|
|
169
|
+
{ packageName: 'openai-codex', importName: 'openai_codex', source: 'sks-2.0.5-directive' },
|
|
170
|
+
{ packageName: 'openai-codex-sdk', importName: 'openai_codex_sdk', source: 'pypi-wheel' }
|
|
171
|
+
];
|
|
172
|
+
function setupAction(pythonBin) {
|
|
173
|
+
return [
|
|
174
|
+
`Install the current Codex Python SDK in \`${pythonBin}\`.`,
|
|
175
|
+
'Preferred official source install: from the Codex repository root run `cd sdk/python && python -m pip install -e .`.',
|
|
176
|
+
`If using the published wrapper package, run \`${pythonBin} -m pip install openai-codex-sdk\`.`,
|
|
177
|
+
`If your environment provides the directive package, run \`${pythonBin} -m pip install openai-codex\`.`
|
|
178
|
+
].join(' ');
|
|
179
|
+
}
|
|
180
|
+
function capability(ok, pythonBin, versionText, blockers, detected, setupActionValue, probes = []) {
|
|
181
|
+
const selected = detected || PYTHON_CODEX_SDK_CANDIDATES[0] || { packageName: 'codex-app-server', importName: 'codex_app_server', source: 'developers.openai.com/codex/sdk' };
|
|
182
|
+
return {
|
|
183
|
+
schema: 'sks.python-codex-sdk-capability.v1',
|
|
184
|
+
ok,
|
|
185
|
+
python_bin: pythonBin,
|
|
186
|
+
python_version: versionText,
|
|
187
|
+
package_name: selected.packageName,
|
|
188
|
+
import_name: selected.importName,
|
|
189
|
+
source: selected.source,
|
|
190
|
+
supported_packages: PYTHON_CODEX_SDK_CANDIDATES.map((candidate) => candidate.packageName),
|
|
191
|
+
supported_imports: PYTHON_CODEX_SDK_CANDIDATES.map((candidate) => candidate.importName),
|
|
192
|
+
import_probes: probes,
|
|
193
|
+
setup_action: setupActionValue,
|
|
194
|
+
blockers
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=python-codex-sdk-adapter.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function translatePythonCodexSdkEvents(events = []) {
|
|
2
|
+
return events.map((event, index) => ({
|
|
3
|
+
schema: 'sks.python-codex-sdk-event.v1',
|
|
4
|
+
index,
|
|
5
|
+
event_type: String(event?.event || 'unknown'),
|
|
6
|
+
thread_id: event?.thread_id ? String(event.thread_id) : null,
|
|
7
|
+
turn_id: event?.turn_id ? String(event.turn_id) : null,
|
|
8
|
+
status: event?.status ? String(event.status) : null,
|
|
9
|
+
retryable: event?.retryable === true,
|
|
10
|
+
message: event?.message ? String(event.message) : null,
|
|
11
|
+
final_response: event?.final_response ? String(event.final_response) : null
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=python-codex-sdk-event-translator.js.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { resolveOllamaWorkerConfig, writeLocalModelConfig, readLocalModelConfig } from '../agents/ollama-worker-config.js';
|
|
1
|
+
import { applyLocalLlmSmokeResult, normalizeProvider, resolveOllamaWorkerConfig, writeLocalModelConfig, readLocalModelConfig } from '../agents/ollama-worker-config.js';
|
|
2
|
+
import { detectInstalledLocalModelCandidate, probeLocalLlmEndpoint } from '../local-llm/local-llm-client.js';
|
|
3
|
+
import { runLocalLlmGenerationSmoke, localLlmSmokeSchema } from '../local-llm/local-llm-smoke.js';
|
|
2
4
|
export async function localModelCommand(args = []) {
|
|
3
5
|
const action = normalizeLocalModelAction(args[0]);
|
|
4
6
|
if (action === 'enable')
|
|
@@ -26,18 +28,64 @@ function normalizeLocalModelAction(value) {
|
|
|
26
28
|
return text;
|
|
27
29
|
}
|
|
28
30
|
async function enable(args) {
|
|
29
|
-
const model = readOption(args, '--model', args
|
|
31
|
+
const model = readOption(args, '--model', firstPositional(args) || '');
|
|
30
32
|
const baseUrl = readOption(args, '--base-url', '');
|
|
33
|
+
const provider = readOption(args, '--provider', '');
|
|
31
34
|
const think = readBoolFlag(args, '--think', '--no-think');
|
|
32
|
-
const
|
|
35
|
+
const skipSmoke = args.includes('--skip-smoke') || process.env.SKS_LOCAL_LLM_TOGGLE_ONLY === '1';
|
|
36
|
+
const patch = { enabled: true, status: 'enabled_unverified' };
|
|
37
|
+
const explicitConfig = Boolean(model || baseUrl || provider);
|
|
38
|
+
let detection = null;
|
|
39
|
+
if (!explicitConfig) {
|
|
40
|
+
detection = await detectInstalledLocalModelCandidate();
|
|
41
|
+
if (!detection) {
|
|
42
|
+
const config = await writeLocalModelConfig({ enabled: false, blockers: ['local_model_not_found'] });
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return {
|
|
45
|
+
schema: 'sks.local-model-command.v1',
|
|
46
|
+
ok: false,
|
|
47
|
+
action: 'enable',
|
|
48
|
+
message: '확인해보니 로컬 모델이 존재하지 않아 실행할 수 없습니다.',
|
|
49
|
+
config,
|
|
50
|
+
detection: null,
|
|
51
|
+
blockers: ['local_model_not_found']
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
patch.provider = detection.provider;
|
|
55
|
+
patch.model = detection.model;
|
|
56
|
+
patch.base_url = detection.base_url;
|
|
57
|
+
patch.endpoint = detection.endpoint;
|
|
58
|
+
}
|
|
59
|
+
if (provider)
|
|
60
|
+
patch.provider = normalizeProvider(provider);
|
|
33
61
|
if (model)
|
|
34
62
|
patch.model = model;
|
|
35
|
-
if (baseUrl)
|
|
63
|
+
if (baseUrl) {
|
|
36
64
|
patch.base_url = baseUrl;
|
|
65
|
+
patch.endpoint = baseUrl;
|
|
66
|
+
}
|
|
37
67
|
if (think !== null)
|
|
38
68
|
patch.think = think;
|
|
39
69
|
const config = await writeLocalModelConfig(patch);
|
|
40
|
-
|
|
70
|
+
const smoke = skipSmoke
|
|
71
|
+
? { ok: false, skipped: true, status: 'enabled_unverified', reason: 'operator_skip_smoke', schema_valid: false, blockers: ['operator_skip_smoke'] }
|
|
72
|
+
: await runLocalLlmGenerationSmoke(config, {
|
|
73
|
+
prompt: 'Return strict JSON: {"status":"ok","summary":"local smoke passed"}',
|
|
74
|
+
schema: localLlmSmokeSchema,
|
|
75
|
+
timeoutMs: 20_000
|
|
76
|
+
});
|
|
77
|
+
const next = await writeLocalModelConfig(applyLocalLlmSmokeResult(config, smoke));
|
|
78
|
+
if (!skipSmoke && smoke.ok !== true)
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
return {
|
|
81
|
+
schema: 'sks.local-model-command.v1',
|
|
82
|
+
ok: skipSmoke ? true : smoke.ok === true,
|
|
83
|
+
action: 'enable',
|
|
84
|
+
config: next,
|
|
85
|
+
detection,
|
|
86
|
+
smoke,
|
|
87
|
+
blockers: next.blockers
|
|
88
|
+
};
|
|
41
89
|
}
|
|
42
90
|
async function disable() {
|
|
43
91
|
const config = await writeLocalModelConfig({ enabled: false });
|
|
@@ -55,37 +103,35 @@ async function setModel(args) {
|
|
|
55
103
|
async function status() {
|
|
56
104
|
const config = await readLocalModelConfig();
|
|
57
105
|
const resolved = await resolveOllamaWorkerConfig();
|
|
58
|
-
const api = await
|
|
106
|
+
const api = await probeLocalLlmEndpoint(resolved);
|
|
59
107
|
return { schema: 'sks.local-model-command.v1', ok: true, action: 'status', config, resolved, api };
|
|
60
108
|
}
|
|
61
|
-
async function probeOllama(baseUrl) {
|
|
62
|
-
try {
|
|
63
|
-
const response = await fetch(`${baseUrl}/api/version`, { signal: AbortSignal.timeout(3000) });
|
|
64
|
-
const text = await response.text();
|
|
65
|
-
return { ok: response.ok, status: response.status, data: response.ok ? JSON.parse(text) : null };
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
109
|
function emit(result, args) {
|
|
72
110
|
if (args.includes('--json')) {
|
|
73
111
|
console.log(JSON.stringify(result, null, 2));
|
|
74
112
|
return result;
|
|
75
113
|
}
|
|
76
114
|
if (result.ok !== true) {
|
|
115
|
+
if (result.message)
|
|
116
|
+
console.log(result.message);
|
|
77
117
|
console.log(`Local model: blocked (${(result.blockers || []).join(', ') || 'unknown'})`);
|
|
78
118
|
return result;
|
|
79
119
|
}
|
|
80
120
|
const config = result.config || result.resolved || {};
|
|
81
121
|
console.log(`Local model: ${config.enabled ? 'enabled' : 'disabled'}`);
|
|
82
|
-
console.log(`Provider:
|
|
122
|
+
console.log(`Provider: ${config.provider || 'unknown'}`);
|
|
83
123
|
console.log(`Model: ${config.model || 'unknown'}`);
|
|
84
124
|
console.log(`Base URL: ${config.base_url || config.baseUrl || 'unknown'}`);
|
|
125
|
+
if (config.status)
|
|
126
|
+
console.log(`Status: ${config.status}`);
|
|
127
|
+
if (result.detection)
|
|
128
|
+
console.log(`Detected: ${result.detection.source}`);
|
|
129
|
+
if (config.last_smoke?.result_path)
|
|
130
|
+
console.log(`Smoke: ${config.last_smoke.ok ? 'ok' : 'failed'} ${config.last_smoke.result_path}`);
|
|
85
131
|
if (typeof config.think === 'boolean')
|
|
86
132
|
console.log(`Think: ${config.think ? 'enabled' : 'disabled'}`);
|
|
87
133
|
if (result.api)
|
|
88
|
-
console.log(`
|
|
134
|
+
console.log(`Local model API: ${result.api.ok ? 'ok' : 'not reachable'}`);
|
|
89
135
|
return result;
|
|
90
136
|
}
|
|
91
137
|
function readOption(args, name, fallback) {
|
|
@@ -102,4 +148,19 @@ function readBoolFlag(args, trueName, falseName) {
|
|
|
102
148
|
return false;
|
|
103
149
|
return null;
|
|
104
150
|
}
|
|
151
|
+
function firstPositional(args = []) {
|
|
152
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
153
|
+
const arg = String(args[i] || '');
|
|
154
|
+
if (arg === '--model' || arg === '--base-url' || arg === '--provider') {
|
|
155
|
+
if (args[i + 1] && !String(args[i + 1]).startsWith('--'))
|
|
156
|
+
i += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (arg.startsWith('--model=') || arg.startsWith('--base-url=') || arg.startsWith('--provider='))
|
|
160
|
+
continue;
|
|
161
|
+
if (!arg.startsWith('--'))
|
|
162
|
+
return arg;
|
|
163
|
+
}
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
105
166
|
//# sourceMappingURL=local-model-command.js.map
|