sneakoscope 6.0.2 → 6.0.3
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 +3 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +5 -0
- package/dist/bin/sks-dispatch.js +2 -0
- package/dist/cli/install-helpers.js +41 -6
- package/dist/commands/codex-app.js +8 -3
- package/dist/commands/codex-lb.js +37 -12
- package/dist/config/skills-manifest.json +57 -57
- package/dist/core/agents/agent-effort-policy.js +46 -33
- package/dist/core/agents/agent-orchestrator.js +1 -1
- package/dist/core/agents/agent-proof-evidence.js +16 -2
- package/dist/core/agents/agent-roster.js +2 -2
- package/dist/core/agents/native-cli-session-swarm.js +18 -1
- package/dist/core/agents/native-worker-backend-router.js +179 -108
- package/dist/core/agents/parallel-write-fixture.js +15 -10
- package/dist/core/agents/runtime-proof-summary.js +21 -3
- package/dist/core/codex-app/codex-app-restart.js +38 -8
- package/dist/core/codex-app/sks-menubar.js +92 -37
- package/dist/core/codex-app.js +26 -4
- package/dist/core/codex-control/codex-model-metadata.js +8 -4
- package/dist/core/codex-lb/codex-lb-env.js +78 -0
- package/dist/core/commands/basic-cli.js +15 -0
- package/dist/core/commands/naruto-command.js +37 -9
- package/dist/core/naruto/naruto-write-e2e.js +3 -4
- package/dist/core/provider/model-router.js +64 -3
- package/dist/core/release/package-size-budget.js +2 -0
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/model-mode-router.js +3 -0
- package/dist/core/routes.js +7 -9
- package/dist/core/update/update-migration-state.js +1 -1
- package/dist/core/update/update-notice.js +23 -0
- package/dist/core/update-check.js +46 -10
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-app-provider-model-ui-check.js +17 -2
- package/dist/scripts/doctor-fix-proves-codex-read-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +2 -2
- package/dist/scripts/legacy-upgrade-matrix-check.js +1 -1
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +2 -1
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +88 -50
- package/dist/scripts/npm-publish-performance-check.js +2 -1
- package/dist/scripts/packlist-performance-check.js +5 -2
- package/dist/scripts/release-dag-full-coverage-check.js +7 -7
- package/dist/scripts/release-gate-existence-audit.js +8 -9
- package/dist/scripts/scheduler-batch-dispatch-check.js +1 -1
- package/dist/scripts/sizecheck.js +7 -2
- package/dist/scripts/sks-menubar-install-check.js +7 -0
- package/dist/scripts/update-default-command-check.js +4 -2
- package/package.json +3 -16
|
@@ -151,8 +151,8 @@ export function buildNarutoCloneRoster(opts = {}) {
|
|
|
151
151
|
const cloneReadonly = readonly || !writeAllowed;
|
|
152
152
|
const role = mapNarutoRoleToAgentRole(narutoRole);
|
|
153
153
|
const allowedTools = writeAllowed ? ['read', 'search', 'edit', 'test'] : narutoRole === 'verifier' ? ['read', 'search', 'test'] : ['read', 'search'];
|
|
154
|
-
// Dynamic
|
|
155
|
-
const effort = decideNarutoCloneEffort({ persona: { ...base, role, allowed_tools: allowedTools, read_only: cloneReadonly, write_policy: writeAllowed ? 'exclusive Naruto patch-envelope lease required' : 'read-only Naruto role' }, prompt: opts.prompt || '', agentId: id, readonly: cloneReadonly });
|
|
154
|
+
// Dynamic Naruto-only GPT-5.6 model/effort policy; there is no legacy low/medium cap.
|
|
155
|
+
const effort = decideNarutoCloneEffort({ persona: { ...base, role, naruto_role: narutoRole, allowed_tools: allowedTools, read_only: cloneReadonly, write_policy: writeAllowed ? 'exclusive Naruto patch-envelope lease required' : 'read-only Naruto role' }, prompt: opts.prompt || '', agentId: id, readonly: cloneReadonly });
|
|
156
156
|
const persona = {
|
|
157
157
|
...base,
|
|
158
158
|
id,
|
|
@@ -13,6 +13,7 @@ import { appendZellijSlotTelemetry } from '../zellij/zellij-slot-telemetry.js';
|
|
|
13
13
|
import { appendParallelRuntimeEvent } from './parallel-runtime-proof.js';
|
|
14
14
|
import { appendAgentMessage } from './agent-message-bus.js';
|
|
15
15
|
import { markLoopWorkerInterrupted, registerLoopActiveWorker } from '../loops/loop-interrupt-registry.js';
|
|
16
|
+
import { readCodexLbModelCatalog } from '../codex-lb/codex-lb-env.js';
|
|
16
17
|
export const NATIVE_CLI_SESSION_SWARM_SCHEMA = 'sks.agent-native-cli-session-swarm.v1';
|
|
17
18
|
export function createNativeCliSessionSwarmRecorder(root, input) {
|
|
18
19
|
return new NativeCliSessionSwarmRecorder(root, input);
|
|
@@ -26,11 +27,26 @@ class NativeCliSessionSwarmRecorder {
|
|
|
26
27
|
writeLock = Promise.resolve();
|
|
27
28
|
nextPaneToken = -1;
|
|
28
29
|
visibleZellijReservations = new Set();
|
|
30
|
+
narutoModelCatalog = null;
|
|
29
31
|
constructor(root, input) {
|
|
30
32
|
this.root = root;
|
|
31
33
|
this.input = input;
|
|
32
34
|
}
|
|
33
35
|
async initialize() {
|
|
36
|
+
if (/\$?naruto/i.test(this.input.route) && (this.input.backend === 'codex-sdk' || this.input.backend === 'zellij')) {
|
|
37
|
+
this.narutoModelCatalog = await readCodexLbModelCatalog().catch(() => ({
|
|
38
|
+
schema: 'sks.codex-lb-model-catalog.v1',
|
|
39
|
+
ok: false,
|
|
40
|
+
status: 'blocked',
|
|
41
|
+
models: [],
|
|
42
|
+
model_efforts: {},
|
|
43
|
+
blockers: ['codex_lb_model_catalog_unavailable']
|
|
44
|
+
}));
|
|
45
|
+
if (this.narutoModelCatalog?.ok !== true) {
|
|
46
|
+
const blockers = Array.isArray(this.narutoModelCatalog?.blockers) ? this.narutoModelCatalog.blockers.join(',') : 'unavailable';
|
|
47
|
+
throw new Error(`naruto_model_catalog_preflight_failed:${blockers}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
34
50
|
await this.persist();
|
|
35
51
|
}
|
|
36
52
|
async launchWorker(ctx) {
|
|
@@ -72,7 +88,8 @@ class NativeCliSessionSwarmRecorder {
|
|
|
72
88
|
source_intelligence_refs: ctx.agent.source_intelligence_refs || null,
|
|
73
89
|
goal_mode_ref: ctx.agent.goal_mode_ref || null,
|
|
74
90
|
strategy_refs: ctx.slice?.strategy_refs || null,
|
|
75
|
-
recursion_guard_env: true
|
|
91
|
+
recursion_guard_env: true,
|
|
92
|
+
naruto_model_catalog: this.narutoModelCatalog
|
|
76
93
|
};
|
|
77
94
|
await writeJsonAtomic(path.join(this.root, intakeRel), intake);
|
|
78
95
|
const cliPath = await resolveWorkerCliPath();
|
|
@@ -10,8 +10,8 @@ import { normalizeAgentPatchEnvelope } from './agent-patch-schema.js';
|
|
|
10
10
|
import { runCodexTask } from '../codex-control/codex-control-plane.js';
|
|
11
11
|
import { CODEX_AGENT_WORKER_RESULT_SCHEMA_ID, codexAgentWorkerResultSchema } from '../codex-control/schemas/agent-worker-result.schema.js';
|
|
12
12
|
import { leanEngineeringCompactText, leanPolicyReference } from '../lean-engineering-policy.js';
|
|
13
|
-
import { readLbHealth } from '../codex-lb/codex-lb-env.js';
|
|
14
|
-
import { categoryForWorkerRole, modelRouteReason, routeModel } from '../provider/model-router.js';
|
|
13
|
+
import { readCodexLbModelCatalog, readLbHealth } from '../codex-lb/codex-lb-env.js';
|
|
14
|
+
import { categoryForWorkerRole, isNarutoGpt56Model, modelRouteReason, routeModel } from '../provider/model-router.js';
|
|
15
15
|
export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
|
|
16
16
|
export async function runNativeWorkerBackendRouter(input) {
|
|
17
17
|
const root = path.resolve(input.agentRoot);
|
|
@@ -19,6 +19,7 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
19
19
|
const requestedBackend = String(input.backend || '');
|
|
20
20
|
let backend = normalizeBackend(requestedBackend);
|
|
21
21
|
backend = await maybeAutoSelectOllamaBackend(backend, input);
|
|
22
|
+
const narutoRequest = isNarutoWorkerRequest(input);
|
|
22
23
|
const reportRel = path.join(input.workerDirRel, 'worker-backend-router-report.json');
|
|
23
24
|
const startedAt = nowIso();
|
|
24
25
|
let result;
|
|
@@ -27,7 +28,12 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
27
28
|
let proofLevel = 'blocked';
|
|
28
29
|
let outputLastMessagePath = null;
|
|
29
30
|
let modelRouting = null;
|
|
30
|
-
|
|
31
|
+
const narutoBackendBlocker = narutoWorkerBackendBlocker(backend, narutoRequest);
|
|
32
|
+
if (narutoBackendBlocker) {
|
|
33
|
+
const blocker = narutoBackendBlocker;
|
|
34
|
+
result = validateAgentWorkerResult(blockedResult(input, [blocker]));
|
|
35
|
+
}
|
|
36
|
+
else if (!input.guard?.ok) {
|
|
31
37
|
result = validateAgentWorkerResult(blockedResult(input, ['native_cli_worker_recursion_guard_missing']));
|
|
32
38
|
}
|
|
33
39
|
else if (requestedBackend === 'codex-exec') {
|
|
@@ -96,104 +102,110 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
96
102
|
else if (backend === 'codex-sdk' || backend === 'zellij' || backend === 'local-llm') {
|
|
97
103
|
const localPreferred = backend === 'local-llm';
|
|
98
104
|
modelRouting = await resolveWorkerModelRouting(input);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
generationIndex: Number(input.agent.generation_index || 1),
|
|
106
|
-
sessionId: String(input.agent.session_id || ''),
|
|
107
|
-
cwd: String(input.intake.cwd || root),
|
|
108
|
-
prompt: buildWorkerPrompt(input.slice),
|
|
109
|
-
model: modelRouting.choice.model,
|
|
110
|
-
reasoningEffort: modelRouting.choice.reasoning,
|
|
111
|
-
modelReasoningEffort: modelRouting.choice.reasoning,
|
|
112
|
-
serviceTier: modelRouting.choice.serviceTier,
|
|
113
|
-
inputFiles: input.intake.input_files || [],
|
|
114
|
-
inputImages: input.intake.input_images || [],
|
|
115
|
-
outputSchemaId: CODEX_AGENT_WORKER_RESULT_SCHEMA_ID,
|
|
116
|
-
outputSchema: codexAgentWorkerResultSchema,
|
|
117
|
-
sandboxPolicy: hasWriteLease(input.slice, input.intake) ? 'workspace-write' : 'read-only',
|
|
118
|
-
requestedScopeContract: {
|
|
119
|
-
id: String(input.intake.lease_id || input.slice?.id || ''),
|
|
105
|
+
if (modelRouting.blockers.length) {
|
|
106
|
+
proofLevel = 'blocked';
|
|
107
|
+
result = validateAgentWorkerResult(blockedResult(input, modelRouting.blockers));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const sdkTask = await runCodexTask({
|
|
120
111
|
route: String(input.intake.route || '$Agent'),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
tier: 'worker',
|
|
113
|
+
missionId: String(input.intake.mission_id || input.intake.parent_mission_id || ''),
|
|
114
|
+
workItemId: String(input.slice?.id || ''),
|
|
115
|
+
slotId: String(input.agent.slot_id || input.agent.id || ''),
|
|
116
|
+
generationIndex: Number(input.agent.generation_index || 1),
|
|
117
|
+
sessionId: String(input.agent.session_id || ''),
|
|
118
|
+
cwd: String(input.intake.cwd || root),
|
|
119
|
+
prompt: buildWorkerPrompt(input.slice),
|
|
120
|
+
model: modelRouting.choice.model,
|
|
121
|
+
reasoningEffort: modelRouting.choice.reasoning,
|
|
122
|
+
modelReasoningEffort: modelRouting.choice.reasoning,
|
|
123
|
+
serviceTier: modelRouting.choice.serviceTier,
|
|
124
|
+
inputFiles: input.intake.input_files || [],
|
|
125
|
+
inputImages: input.intake.input_images || [],
|
|
126
|
+
outputSchemaId: CODEX_AGENT_WORKER_RESULT_SCHEMA_ID,
|
|
127
|
+
outputSchema: codexAgentWorkerResultSchema,
|
|
128
|
+
sandboxPolicy: hasWriteLease(input.slice, input.intake) ? 'workspace-write' : 'read-only',
|
|
129
|
+
requestedScopeContract: {
|
|
130
|
+
id: String(input.intake.lease_id || input.slice?.id || ''),
|
|
131
|
+
route: String(input.intake.route || '$Agent'),
|
|
132
|
+
read_only: !hasWriteLease(input.slice, input.intake),
|
|
133
|
+
allowed_paths: writePaths(input.slice, input.intake),
|
|
134
|
+
write_paths: writePaths(input.slice, input.intake),
|
|
135
|
+
model_route_reason: modelRouting.reason,
|
|
136
|
+
service_tier: modelRouting.choice.serviceTier,
|
|
137
|
+
user_confirmed_full_access: false,
|
|
138
|
+
mad_sks_authorized: input.intake.mad_sks_authorized === true || process.env.SKS_MAD_SKS_ACTIVE === '1'
|
|
139
|
+
},
|
|
140
|
+
backendPreference: localPreferred ? ['local-llm', 'codex-sdk'] : ['codex-sdk'],
|
|
141
|
+
allowLocalLlm: localPreferred,
|
|
142
|
+
...(localPreferred ? { localLlmPolicy: {
|
|
143
|
+
mode: 'local_preferred',
|
|
144
|
+
requiresGptFinal: true
|
|
145
|
+
} } : {}),
|
|
146
|
+
mutationLedgerRoot: path.join(root, input.workerDirRel),
|
|
147
|
+
zellijPaneId: await readZellijPaneId(root, input.workerDirRel),
|
|
148
|
+
reliabilityPolicy: {
|
|
149
|
+
maxEmptyResultRetries: 1,
|
|
150
|
+
timeoutClass: 'standard'
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
outputLastMessagePath = sdkTask.workerResultPath;
|
|
154
|
+
const sdkWorkerResult = normalizeSdkWorkerResultPaths(await readJson(sdkTask.workerResultPath, null), input, pathRoot);
|
|
155
|
+
patchEnvelopes = normalizeSdkPatchEnvelopes(sdkWorkerResult?.patch_envelopes || [], input, sdkTask.sdkThreadId, pathRoot);
|
|
156
|
+
proofLevel = sdkTask.ok ? (patchEnvelopes.length ? 'model_authored' : sdkTask.backend === 'local-llm' ? 'local_llm_worker_proven' : 'codex_sdk_thread_proven') : 'blocked';
|
|
157
|
+
const sdkReport = {
|
|
158
|
+
schema: 'sks.codex-sdk-worker-adapter.v1',
|
|
159
|
+
backend: sdkTask.backend,
|
|
160
|
+
backend_family: sdkTask.backend_family,
|
|
161
|
+
sdk_thread_id: sdkTask.sdkThreadId,
|
|
162
|
+
sdk_run_id: sdkTask.sdkRunId,
|
|
163
|
+
local_llm_proof_path: sdkTask.localLlmProofPath || null,
|
|
164
|
+
stream_event_count: sdkTask.streamEventCount,
|
|
165
|
+
structured_output_valid: sdkTask.structuredOutputValid,
|
|
166
|
+
worker_result_path: sdkTask.workerResultPath,
|
|
167
|
+
patch_envelope_path: sdkTask.patchEnvelopePath || null,
|
|
168
|
+
model: modelRouting.choice.model,
|
|
169
|
+
model_reasoning_effort: modelRouting.choice.reasoning,
|
|
125
170
|
service_tier: modelRouting.choice.serviceTier,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
result = validateAgentWorkerResult({
|
|
165
|
-
...sdkWorkerResult,
|
|
166
|
-
agent_id: workerOwnerId(input),
|
|
167
|
-
session_id: input.agent.session_id,
|
|
168
|
-
persona_id: input.agent.persona_id || input.agent.id,
|
|
169
|
-
task_slice_id: input.slice?.id || '',
|
|
170
|
-
backend: sdkTask.backend === 'local-llm' ? 'local-llm' : 'codex-sdk',
|
|
171
|
-
patch_envelopes: patchEnvelopes,
|
|
172
|
-
...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, sdkTask.backend || backend) }),
|
|
173
|
-
codex_child_report: sdkReport,
|
|
174
|
-
codex_sdk_thread: sdkReport,
|
|
175
|
-
model_authored_patch_envelopes: patchEnvelopes.length > 0,
|
|
176
|
-
fixture_patch_envelopes: false,
|
|
177
|
-
artifacts: [...new Set([
|
|
178
|
-
...(sdkWorkerResult?.artifacts || []),
|
|
179
|
-
path.relative(root, sdkTask.workerResultPath),
|
|
180
|
-
path.join(input.workerDirRel, 'codex-control-proof.json'),
|
|
181
|
-
path.join(input.workerDirRel, 'codex-thread-registry.json'),
|
|
182
|
-
sdkTask.backend === 'local-llm' ? path.join(input.workerDirRel, 'local-llm-events.jsonl') : path.join(input.workerDirRel, 'codex-sdk-events.jsonl'),
|
|
183
|
-
...(sdkTask.localLlmProofPath ? [path.relative(root, sdkTask.localLlmProofPath)] : [])
|
|
184
|
-
])],
|
|
185
|
-
blockers: [...(sdkWorkerResult?.blockers || []), ...sdkTask.blockers],
|
|
186
|
-
verification: {
|
|
187
|
-
status: sdkTask.ok ? 'passed' : 'failed',
|
|
188
|
-
checks: [
|
|
189
|
-
...(sdkWorkerResult?.verification?.checks || []),
|
|
190
|
-
sdkTask.backend === 'local-llm' ? 'local-llm-control-plane' : 'codex-sdk-control-plane',
|
|
191
|
-
sdkTask.backend === 'local-llm' ? 'local-llm-event-stream' : 'codex-sdk-event-stream',
|
|
192
|
-
sdkTask.backend === 'local-llm' ? 'local-llm-structured-output' : 'codex-sdk-structured-output',
|
|
193
|
-
...(sdkTask.backend === 'local-llm' ? ['gpt-final-required-before-acceptance'] : [])
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
});
|
|
171
|
+
model_route_reason: modelRouting.reason,
|
|
172
|
+
blockers: sdkTask.blockers
|
|
173
|
+
};
|
|
174
|
+
childReports = [sdkReport];
|
|
175
|
+
result = validateAgentWorkerResult({
|
|
176
|
+
...sdkWorkerResult,
|
|
177
|
+
agent_id: workerOwnerId(input),
|
|
178
|
+
session_id: input.agent.session_id,
|
|
179
|
+
persona_id: input.agent.persona_id || input.agent.id,
|
|
180
|
+
task_slice_id: input.slice?.id || '',
|
|
181
|
+
backend: sdkTask.backend === 'local-llm' ? 'local-llm' : 'codex-sdk',
|
|
182
|
+
patch_envelopes: patchEnvelopes,
|
|
183
|
+
...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, sdkTask.backend || backend) }),
|
|
184
|
+
codex_child_report: sdkReport,
|
|
185
|
+
codex_sdk_thread: sdkReport,
|
|
186
|
+
model_authored_patch_envelopes: patchEnvelopes.length > 0,
|
|
187
|
+
fixture_patch_envelopes: false,
|
|
188
|
+
artifacts: [...new Set([
|
|
189
|
+
...(sdkWorkerResult?.artifacts || []),
|
|
190
|
+
path.relative(root, sdkTask.workerResultPath),
|
|
191
|
+
path.join(input.workerDirRel, 'codex-control-proof.json'),
|
|
192
|
+
path.join(input.workerDirRel, 'codex-thread-registry.json'),
|
|
193
|
+
sdkTask.backend === 'local-llm' ? path.join(input.workerDirRel, 'local-llm-events.jsonl') : path.join(input.workerDirRel, 'codex-sdk-events.jsonl'),
|
|
194
|
+
...(sdkTask.localLlmProofPath ? [path.relative(root, sdkTask.localLlmProofPath)] : [])
|
|
195
|
+
])],
|
|
196
|
+
blockers: [...(sdkWorkerResult?.blockers || []), ...sdkTask.blockers],
|
|
197
|
+
verification: {
|
|
198
|
+
status: sdkTask.ok ? 'passed' : 'failed',
|
|
199
|
+
checks: [
|
|
200
|
+
...(sdkWorkerResult?.verification?.checks || []),
|
|
201
|
+
sdkTask.backend === 'local-llm' ? 'local-llm-control-plane' : 'codex-sdk-control-plane',
|
|
202
|
+
sdkTask.backend === 'local-llm' ? 'local-llm-event-stream' : 'codex-sdk-event-stream',
|
|
203
|
+
sdkTask.backend === 'local-llm' ? 'local-llm-structured-output' : 'codex-sdk-structured-output',
|
|
204
|
+
...(sdkTask.backend === 'local-llm' ? ['gpt-final-required-before-acceptance'] : [])
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
197
209
|
}
|
|
198
210
|
else {
|
|
199
211
|
const zellijRun = await runZellijAgent(input.agent, input.slice, {
|
|
@@ -262,24 +274,69 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
262
274
|
patchEnvelopes
|
|
263
275
|
};
|
|
264
276
|
}
|
|
265
|
-
async function resolveWorkerModelRouting(input) {
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
export async function resolveWorkerModelRouting(input, deps = {}) {
|
|
278
|
+
const narutoOnly = Boolean(input.agent?.naruto_role) || /\$?naruto/i.test(String(input.intake?.route || ''));
|
|
279
|
+
const taskKindText = [
|
|
280
|
+
input.slice?.work_item_kind,
|
|
281
|
+
input.slice?.kind,
|
|
282
|
+
input.slice?.type,
|
|
283
|
+
input.slice?.title,
|
|
284
|
+
input.slice?.required_role,
|
|
285
|
+
input.slice?.role,
|
|
286
|
+
input.agent?.naruto_role,
|
|
287
|
+
input.agent?.role,
|
|
288
|
+
input.agent?.persona_id
|
|
289
|
+
].map((value) => String(value || '')).join(' ');
|
|
290
|
+
const riskText = [taskKindText, input.slice?.risk, input.slice?.risk_focus, input.slice?.acceptance, input.slice?.parent_prompt, input.slice?.description, input.intake?.prompt].map((value) => String(value || '')).join(' ');
|
|
291
|
+
const category = categoryForWorkerRole(String(input.agent?.role || 'executor'), taskKindText);
|
|
292
|
+
const env = deps.env || process.env;
|
|
293
|
+
const lbHealth = Object.prototype.hasOwnProperty.call(deps, 'lbHealth') ? deps.lbHealth : await readLbHealth().catch(() => null);
|
|
294
|
+
const lbCatalog = narutoOnly
|
|
295
|
+
? Object.prototype.hasOwnProperty.call(deps, 'lbCatalog')
|
|
296
|
+
? deps.lbCatalog
|
|
297
|
+
: input.intake?.naruto_model_catalog || await readCodexLbModelCatalog().catch(() => null)
|
|
298
|
+
: null;
|
|
299
|
+
const explicitModel = String(env.SKS_WORKER_MODEL || env.SKS_AGENT_MODEL || '').trim();
|
|
300
|
+
const explicitReasoningRaw = String(env.SKS_WORKER_REASONING || env.SKS_WORKER_MODEL_REASONING || '').trim();
|
|
301
|
+
const explicitTierRaw = String(env.SKS_WORKER_SERVICE_TIER || env.SKS_SERVICE_TIER || '').trim();
|
|
302
|
+
const explicitReasoning = normalizeModelReasoning(explicitReasoningRaw);
|
|
303
|
+
const explicitTier = normalizeServiceTier(explicitTierRaw);
|
|
304
|
+
const explicitNarutoModelInvalid = narutoOnly && Boolean(explicitModel) && !isNarutoGpt56Model(explicitModel);
|
|
305
|
+
const explicitNarutoReasoningInvalid = narutoOnly && Boolean(explicitReasoningRaw) && !explicitReasoning;
|
|
306
|
+
const explicitNarutoTierInvalid = narutoOnly && Boolean(explicitTierRaw) && !explicitTier;
|
|
307
|
+
const routed = explicitModel && !narutoOnly
|
|
272
308
|
? {
|
|
273
309
|
model: explicitModel,
|
|
274
310
|
reasoning: explicitReasoning || normalizeModelReasoning(input.agent?.model_reasoning_effort) || 'medium',
|
|
275
311
|
serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
|
|
276
312
|
}
|
|
277
|
-
: await routeModel(category, {
|
|
313
|
+
: await routeModel(category, {
|
|
314
|
+
lbHealth,
|
|
315
|
+
...(narutoOnly ? {
|
|
316
|
+
narutoOnly: true,
|
|
317
|
+
taskText: taskKindText,
|
|
318
|
+
riskText,
|
|
319
|
+
availableModels: lbCatalog?.models || [],
|
|
320
|
+
availableModelEfforts: lbCatalog?.model_efforts || {},
|
|
321
|
+
...(explicitModel ? { model: explicitModel } : {})
|
|
322
|
+
} : { model: input.agent?.model || null })
|
|
323
|
+
});
|
|
324
|
+
const blockers = [
|
|
325
|
+
...(narutoOnly && !lbCatalog?.ok ? (lbCatalog?.blockers || ['codex_lb_model_catalog_unavailable']) : []),
|
|
326
|
+
...(explicitNarutoModelInvalid ? ['naruto_worker_model_outside_gpt_5_6_family'] : []),
|
|
327
|
+
...(explicitNarutoReasoningInvalid ? ['naruto_reasoning_override_invalid'] : []),
|
|
328
|
+
...(explicitNarutoTierInvalid ? ['naruto_service_tier_override_invalid'] : []),
|
|
329
|
+
...(narutoOnly && explicitReasoning && explicitReasoning !== routed.reasoning ? ['naruto_reasoning_override_conflicts_with_policy'] : []),
|
|
330
|
+
...(narutoOnly && explicitTier && explicitTier !== 'fast' ? ['naruto_service_tier_override_conflicts_with_policy'] : []),
|
|
331
|
+
...(narutoOnly && !routed.model ? ['naruto_required_gpt_5_6_model_unavailable'] : [])
|
|
332
|
+
];
|
|
278
333
|
return {
|
|
279
334
|
category,
|
|
280
335
|
choice: routed,
|
|
281
336
|
explicit: Boolean(explicitModel),
|
|
282
337
|
lb_health: lbHealth,
|
|
338
|
+
lb_catalog: lbCatalog,
|
|
339
|
+
blockers: [...new Set(blockers)],
|
|
283
340
|
reason: modelRouteReason(category, routed, {
|
|
284
341
|
explicit: Boolean(explicitModel),
|
|
285
342
|
quotaLow: lbHealth?.quota_low === true,
|
|
@@ -289,7 +346,7 @@ async function resolveWorkerModelRouting(input) {
|
|
|
289
346
|
}
|
|
290
347
|
function normalizeModelReasoning(value) {
|
|
291
348
|
const text = String(value || '').toLowerCase();
|
|
292
|
-
return text === 'low' || text === 'medium' || text === 'high' || text === 'xhigh' ? text : null;
|
|
349
|
+
return text === 'low' || text === 'medium' || text === 'high' || text === 'xhigh' || text === 'max' || text === 'ultra' ? text : null;
|
|
293
350
|
}
|
|
294
351
|
function normalizeServiceTier(value) {
|
|
295
352
|
const text = String(value || '').toLowerCase();
|
|
@@ -298,6 +355,8 @@ function normalizeServiceTier(value) {
|
|
|
298
355
|
async function maybeAutoSelectOllamaBackend(backend, input) {
|
|
299
356
|
if (backend !== 'codex-sdk')
|
|
300
357
|
return backend;
|
|
358
|
+
if (isNarutoWorkerRequest(input))
|
|
359
|
+
return backend;
|
|
301
360
|
if (input.intake?.backend_explicit === true || input.intake?.no_ollama === true)
|
|
302
361
|
return backend;
|
|
303
362
|
const config = await resolveOllamaWorkerConfig({
|
|
@@ -310,6 +369,18 @@ async function maybeAutoSelectOllamaBackend(backend, input) {
|
|
|
310
369
|
const policy = classifyOllamaWorkerSlice(input.slice, { route: input.intake?.route, agent: input.agent });
|
|
311
370
|
return policy.ok ? 'local-llm' : backend;
|
|
312
371
|
}
|
|
372
|
+
function isNarutoWorkerRequest(input) {
|
|
373
|
+
return Boolean(input.agent?.naruto_role) || /\$?naruto/i.test(String(input.intake?.route || ''));
|
|
374
|
+
}
|
|
375
|
+
export function narutoWorkerBackendBlocker(backend, narutoRequest = true) {
|
|
376
|
+
if (!narutoRequest)
|
|
377
|
+
return null;
|
|
378
|
+
if (backend === 'process')
|
|
379
|
+
return 'naruto_gpt_5_6_family_only_process_backend_forbidden';
|
|
380
|
+
if (backend === 'ollama' || backend === 'local-llm')
|
|
381
|
+
return 'naruto_gpt_5_6_family_only_local_backend_forbidden';
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
313
384
|
function normalizeBackend(value) {
|
|
314
385
|
return value === 'fake' || value === 'process' || value === 'codex-sdk' || value === 'zellij' || value === 'ollama' || value === 'local-llm' ? value : null;
|
|
315
386
|
}
|
|
@@ -175,24 +175,29 @@ async function runTypecheck(repoRoot) {
|
|
|
175
175
|
return runCommand([localTsc, '-p', 'tsconfig.json', '--noEmit'], repoRoot);
|
|
176
176
|
}
|
|
177
177
|
async function cleanupWorktreesAndBranches(repoRoot, tmpRoot, workers, before, keepTmp) {
|
|
178
|
+
const removeResults = [];
|
|
178
179
|
for (const worker of workers) {
|
|
179
|
-
await runCommand(['git', 'worktree', 'remove', '--force', path.join(tmpRoot, 'worktrees', worker.id)], repoRoot);
|
|
180
|
+
removeResults.push(await runCommand(['git', 'worktree', 'remove', '--force', path.join(tmpRoot, 'worktrees', worker.id)], repoRoot));
|
|
180
181
|
}
|
|
181
|
-
await runCommand(['git', 'worktree', 'prune'], repoRoot);
|
|
182
|
+
const prune = await runCommand(['git', 'worktree', 'prune'], repoRoot);
|
|
183
|
+
const branchResults = [];
|
|
182
184
|
for (const worker of workers) {
|
|
183
|
-
await runCommand(['git', 'branch', '-D', worker.branch], repoRoot);
|
|
185
|
+
branchResults.push(await runCommand(['git', 'branch', '-D', worker.branch], repoRoot));
|
|
184
186
|
}
|
|
185
187
|
const after = await runCommand(['git', 'worktree', 'list', '--porcelain'], repoRoot);
|
|
188
|
+
const branches = await runCommand(['git', 'branch', '--list', ...workers.map((worker) => worker.branch)], repoRoot);
|
|
186
189
|
const status = await runCommand(['git', 'status', '--short'], repoRoot);
|
|
187
190
|
if (!keepTmp)
|
|
188
|
-
await fsp.rm(tmpRoot, { recursive: true, force: true });
|
|
191
|
+
await fsp.rm(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
189
192
|
const tmpRemoved = keepTmp ? false : !(await exists(tmpRoot));
|
|
193
|
+
const workerWorktreesRemoved = after.ok && workers.every((worker) => !after.stdout_tail.includes(worker.id));
|
|
194
|
+
const workerBranchesRemoved = branches.ok && branches.stdout_tail.trim().length === 0;
|
|
190
195
|
return {
|
|
191
|
-
ok:
|
|
196
|
+
ok: removeResults.every((result) => result.ok) && prune.ok && branchResults.every((result) => result.ok) && workerWorktreesRemoved && workerBranchesRemoved && status.ok && status.stdout_tail.trim().length === 0 && tmpRemoved,
|
|
192
197
|
worktree_list_before_cleanup: before,
|
|
193
198
|
worktree_list_after_cleanup: after.stdout_tail,
|
|
194
|
-
worker_worktrees_removed:
|
|
195
|
-
worker_branches_removed:
|
|
199
|
+
worker_worktrees_removed: workerWorktreesRemoved,
|
|
200
|
+
worker_branches_removed: workerBranchesRemoved,
|
|
196
201
|
parent_dirty_after_cleanup: status.stdout_tail.trim().length > 0,
|
|
197
202
|
tmp_removed: tmpRemoved
|
|
198
203
|
};
|
|
@@ -200,12 +205,12 @@ async function cleanupWorktreesAndBranches(repoRoot, tmpRoot, workers, before, k
|
|
|
200
205
|
async function bestEffortCleanup(repoRoot, tmpRoot, keepTmp) {
|
|
201
206
|
const before = await runCommand(['git', 'worktree', 'list', '--porcelain'], repoRoot);
|
|
202
207
|
if (!keepTmp)
|
|
203
|
-
await fsp.rm(tmpRoot, { recursive: true, force: true });
|
|
208
|
+
await fsp.rm(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
204
209
|
return {
|
|
205
|
-
ok:
|
|
210
|
+
ok: false,
|
|
206
211
|
worktree_list_before_cleanup: before.stdout_tail,
|
|
207
212
|
worktree_list_after_cleanup: '',
|
|
208
|
-
worker_worktrees_removed:
|
|
213
|
+
worker_worktrees_removed: false,
|
|
209
214
|
worker_branches_removed: false,
|
|
210
215
|
parent_dirty_after_cleanup: false,
|
|
211
216
|
tmp_removed: !keepTmp && !(await exists(tmpRoot))
|
|
@@ -15,6 +15,7 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
|
|
|
15
15
|
const scheduler = await readJson(path.join(agentsDir, 'agent-scheduler-state.json'), null);
|
|
16
16
|
const swarm = await readJson(path.join(agentsDir, 'agent-native-cli-session-swarm.json'), null);
|
|
17
17
|
const telemetry = await readJson(path.join(dir, 'zellij', 'slot-telemetry.snapshot.json'), null);
|
|
18
|
+
const stopGate = await readJson(path.join(dir, 'stop-gate.json'), null);
|
|
18
19
|
const governor = await readJson(path.join(agentsDir, 'naruto-concurrency-governor.json'), null);
|
|
19
20
|
const messagesAll = await readAgentMessageBus(root, missionId, { max: 500 });
|
|
20
21
|
const recentMessages = await readAgentMessageBus(root, missionId, { max: opts.maxMessages || 8 });
|
|
@@ -23,15 +24,17 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
|
|
|
23
24
|
const failedMessages = messagesAll.filter((row) => row.event_type === 'worker_failed');
|
|
24
25
|
const errorMessages = messagesAll.filter((row) => row.level === 'error');
|
|
25
26
|
const telemetryAgeMs = telemetry?.updated_at ? Math.max(0, Date.now() - Date.parse(telemetry.updated_at)) : Number.MAX_SAFE_INTEGER;
|
|
27
|
+
const terminalProofAccepted = canonicalTerminalProofAccepted(stopGate, missionId);
|
|
26
28
|
const visiblePanes = Number(parallel?.visible_panes ?? swarm?.zellij_pane_worker_sessions ?? telemetryVisiblePaneCount(telemetry) ?? 0);
|
|
27
29
|
const targetActive = Number(scheduler?.target_active_slots ?? parallel?.target_active_slots ?? swarm?.target_active_slots ?? governor?.target_active_slots ?? 0);
|
|
28
30
|
const headlessWorkers = Number(parallel?.headless_workers ?? swarm?.headless_overflow_worker_count ?? Math.max(0, targetActive - visiblePanes));
|
|
31
|
+
const parallelBlockers = parallel?.passed === false ? parallel.blockers || ['parallel_runtime_proof_failed'] : [];
|
|
29
32
|
const blockers = [
|
|
30
33
|
...(!parallel ? ['parallel_runtime_proof_missing'] : []),
|
|
31
34
|
...(!scheduler ? ['agent_scheduler_state_missing'] : []),
|
|
32
|
-
...(
|
|
35
|
+
...(terminalProofAccepted ? parallelBlockers.filter((blocker) => String(blocker) !== 'speedup_ratio_below_target') : parallelBlockers),
|
|
33
36
|
...(errorMessages.length ? ['agent_message_bus_error_blockers'] : []),
|
|
34
|
-
...(telemetryAgeMs > 3000 ? ['zellij_telemetry_stale'] : []),
|
|
37
|
+
...(telemetryAgeMs > 3000 && !terminalProofAccepted ? ['zellij_telemetry_stale'] : []),
|
|
35
38
|
...(zellijSummary?.blockers || [])
|
|
36
39
|
].map(String);
|
|
37
40
|
const summary = {
|
|
@@ -76,6 +79,11 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
|
|
|
76
79
|
duplicate_slot_anchor_count: Number(zellijSummary?.duplicate_slot_anchor_count || 0)
|
|
77
80
|
},
|
|
78
81
|
loops: loopSummary,
|
|
82
|
+
terminal_proof: {
|
|
83
|
+
accepted: terminalProofAccepted,
|
|
84
|
+
gate_file: terminalProofAccepted ? 'stop-gate.json' : null,
|
|
85
|
+
terminal_state: terminalProofAccepted ? String(stopGate?.terminal_state || 'completed') : null
|
|
86
|
+
},
|
|
79
87
|
blockers
|
|
80
88
|
};
|
|
81
89
|
await writeJsonAtomic(path.join(agentsDir, 'runtime-proof-summary.json'), summary);
|
|
@@ -83,7 +91,8 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
|
|
|
83
91
|
}
|
|
84
92
|
export function renderRuntimeProofSummary(summary) {
|
|
85
93
|
return [
|
|
86
|
-
`Parallel proof: ${summary.parallel.proof_passed ? 'passed' : 'blocked'}`,
|
|
94
|
+
`Parallel proof: ${summary.parallel.proof_passed ? 'passed' : summary.terminal_proof?.accepted ? 'terminal gate accepted' : 'blocked'}`,
|
|
95
|
+
`Canonical terminal proof: ${summary.terminal_proof?.accepted ? 'accepted' : 'not available'}`,
|
|
87
96
|
`Active workers: ${summary.parallel.max_active_workers}`,
|
|
88
97
|
`Unique PIDs: ${summary.parallel.unique_worker_pids}`,
|
|
89
98
|
`Speedup: ${summary.parallel.speedup_ratio}x`,
|
|
@@ -102,6 +111,15 @@ export function renderRuntimeProofSummary(summary) {
|
|
|
102
111
|
...(summary.blockers.length ? [`Blockers: ${summary.blockers.join(', ')}`] : [])
|
|
103
112
|
].join('\n');
|
|
104
113
|
}
|
|
114
|
+
function canonicalTerminalProofAccepted(gate, missionId) {
|
|
115
|
+
const blockers = Array.isArray(gate?.blockers) ? gate.blockers : [];
|
|
116
|
+
return gate?.schema === 'sks.stop-gate.v1'
|
|
117
|
+
&& String(gate?.mission_id || '') === missionId
|
|
118
|
+
&& gate?.passed === true
|
|
119
|
+
&& gate?.terminal === true
|
|
120
|
+
&& String(gate?.terminal_state || '') === 'completed'
|
|
121
|
+
&& blockers.length === 0;
|
|
122
|
+
}
|
|
105
123
|
function messageStatusLabel(row) {
|
|
106
124
|
if (row.event_type === 'worker_completed')
|
|
107
125
|
return '[done]';
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { exists, runProcess, which } from '../fsx.js';
|
|
2
2
|
export async function restartCodexApp(opts = {}) {
|
|
3
3
|
const env = opts.env || process.env;
|
|
4
|
-
const appName = String(opts.appName || env.SKS_CODEX_APP_NAME || '
|
|
4
|
+
const appName = String(opts.appName || env.SKS_CODEX_APP_NAME || 'ChatGPT');
|
|
5
|
+
const bundleId = String(opts.bundleId || env.SKS_CODEX_APP_BUNDLE_ID || 'com.openai.codex');
|
|
5
6
|
if (opts.enabled === false || env.SKS_SKIP_CODEX_APP_RESTART === '1') {
|
|
6
7
|
return skipped(appName, 'disabled');
|
|
7
8
|
}
|
|
8
|
-
if (process.platform !== 'darwin')
|
|
9
|
+
if ((opts.platform || process.platform) !== 'darwin')
|
|
9
10
|
return skipped(appName, 'not_macos');
|
|
10
11
|
const run = opts.runProcessImpl || runProcess;
|
|
11
|
-
const osascript = await which('osascript').catch(() => null) || await exists('/usr/bin/osascript').then((ok) => ok ? '/usr/bin/osascript' : null).catch(() => null);
|
|
12
|
-
const open = await which('open').catch(() => null) || await exists('/usr/bin/open').then((ok) => ok ? '/usr/bin/open' : null).catch(() => null);
|
|
12
|
+
const osascript = opts.osascriptPath || await which('osascript').catch(() => null) || await exists('/usr/bin/osascript').then((ok) => ok ? '/usr/bin/osascript' : null).catch(() => null);
|
|
13
|
+
const open = opts.openPath || await which('open').catch(() => null) || await exists('/usr/bin/open').then((ok) => ok ? '/usr/bin/open' : null).catch(() => null);
|
|
13
14
|
if (!osascript || !open) {
|
|
14
15
|
return {
|
|
15
16
|
schema: 'sks.codex-app-restart.v1',
|
|
@@ -22,25 +23,54 @@ export async function restartCodexApp(opts = {}) {
|
|
|
22
23
|
]
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
|
-
const
|
|
26
|
+
const appTarget = bundleId ? `application id ${JSON.stringify(bundleId)}` : `application ${JSON.stringify(appName)}`;
|
|
27
|
+
const quit = await run(osascript, ['-e', `tell ${appTarget} to quit`], { timeoutMs: 5000, maxOutputBytes: 8192 }).catch((err) => ({
|
|
26
28
|
code: 1,
|
|
27
29
|
stdout: '',
|
|
28
30
|
stderr: err?.message || String(err)
|
|
29
31
|
}));
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
+
const quitOk = quit.code === 0 || /not running|Can't get application|application isn't running/i.test(String(quit.stderr || quit.stdout || ''));
|
|
33
|
+
const exitTimeoutMs = Math.max(250, Number(opts.exitTimeoutMs ?? env.SKS_CODEX_APP_EXIT_TIMEOUT_MS ?? 5000));
|
|
34
|
+
const pollMs = Math.max(50, Number(opts.pollMs ?? 100));
|
|
35
|
+
let exited = !quitOk;
|
|
36
|
+
let attempts = 0;
|
|
37
|
+
const deadline = Date.now() + exitTimeoutMs;
|
|
38
|
+
while (quitOk && Date.now() < deadline) {
|
|
39
|
+
attempts += 1;
|
|
40
|
+
const probe = await run(osascript, ['-e', `${appTarget} is running`], { timeoutMs: 1000, maxOutputBytes: 1024 }).catch(() => null);
|
|
41
|
+
if (probe?.code === 0 && String(probe.stdout || '').trim().toLowerCase() === 'false') {
|
|
42
|
+
exited = true;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
await sleep(pollMs);
|
|
46
|
+
}
|
|
47
|
+
if (quitOk && !exited) {
|
|
48
|
+
return {
|
|
49
|
+
schema: 'sks.codex-app-restart.v1',
|
|
50
|
+
ok: false,
|
|
51
|
+
status: 'blocked',
|
|
52
|
+
app_name: appName,
|
|
53
|
+
bundle_id: bundleId,
|
|
54
|
+
quit: { ok: true, code: quit.code, error: null },
|
|
55
|
+
exit_wait: { ok: false, attempts, error: 'codex_app_exit_timeout' },
|
|
56
|
+
blockers: ['codex_app_exit_timeout']
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
await sleep(Number(opts.delayMs ?? env.SKS_CODEX_APP_RESTART_DELAY_MS ?? 150));
|
|
60
|
+
const launched = await run(open, bundleId ? ['-b', bundleId] : ['-a', appName], { timeoutMs: 10000, maxOutputBytes: 8192 }).catch((err) => ({
|
|
32
61
|
code: 1,
|
|
33
62
|
stdout: '',
|
|
34
63
|
stderr: err?.message || String(err)
|
|
35
64
|
}));
|
|
36
|
-
const quitOk = quit.code === 0 || /not running|Can't get application|application isn't running/i.test(String(quit.stderr || quit.stdout || ''));
|
|
37
65
|
const openOk = launched.code === 0;
|
|
38
66
|
return {
|
|
39
67
|
schema: 'sks.codex-app-restart.v1',
|
|
40
68
|
ok: quitOk && openOk,
|
|
41
69
|
status: quitOk && openOk ? 'restarted' : 'blocked',
|
|
42
70
|
app_name: appName,
|
|
71
|
+
bundle_id: bundleId,
|
|
43
72
|
quit: { ok: quitOk, code: quit.code, error: quitOk ? null : String(quit.stderr || quit.stdout || '').trim() },
|
|
73
|
+
exit_wait: { ok: exited, attempts, error: exited ? null : 'codex_app_exit_timeout' },
|
|
44
74
|
open: { ok: openOk, code: launched.code, error: openOk ? null : String(launched.stderr || launched.stdout || '').trim() },
|
|
45
75
|
blockers: [
|
|
46
76
|
...(quitOk ? [] : ['codex_app_quit_failed']),
|