sneakoscope 10.3.3 → 10.3.4
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 +22 -12
- package/config/skills-hash-ledger.v1.json +4 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/config/skills-manifest.json +9 -6
- package/dist/core/agents/agent-effort-policy.js +15 -13
- package/dist/core/agents/native-worker-backend-router.js +13 -8
- package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +1 -0
- package/dist/core/codex-native/core-skill-manifest.js +4 -4
- package/dist/core/decisions/cli.js +9 -2
- package/dist/core/decisions/config.js +7 -0
- package/dist/core/decisions/integration.js +54 -12
- package/dist/core/decisions/policy.js +27 -4
- package/dist/core/decisions/questions.js +39 -9
- package/dist/core/decisions/routing.js +15 -10
- package/dist/core/decisions/types.js +11 -25
- package/dist/core/doctor/current-project-guidance.js +38 -1
- package/dist/core/doctor/skill-legacy-surface.js +2 -1
- package/dist/core/hooks-runtime/hook-context.js +1 -1
- package/dist/core/hooks-runtime/jev-spawn-routing.js +21 -2
- package/dist/core/hooks-runtime/managed-guidance-preflight.js +30 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -3
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +372 -0
- package/dist/core/hooks-runtime/subagent-context.js +13 -3
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +5 -10
- package/dist/core/hooks-runtime.js +46 -8
- package/dist/core/init/skills/inventory.js +5 -1
- package/dist/core/init/skills.js +3 -3
- package/dist/core/init.js +36 -4
- package/dist/core/managed-assets/managed-assets-manifest.js +17 -17
- package/dist/core/pipeline-internals/runtime-core.js +2 -2
- package/dist/core/provider/model-router.js +8 -5
- package/dist/core/recallpulse/policy.js +2 -2
- package/dist/core/recallpulse.js +3 -3
- package/dist/core/release/gate-affected-globs.js +0 -1
- package/dist/core/research/mock-result.js +2 -2
- package/dist/core/research/research-adversarial-review.js +7 -7
- package/dist/core/research/research-claim-synthesizer.js +3 -3
- package/dist/core/research/research-falsification-runner.js +3 -3
- package/dist/core/research/research-super-search.js +5 -2
- package/dist/core/research/research-synthesis-writer.js +2 -2
- package/dist/core/research.js +5 -5
- package/dist/core/routes/dollar-manifest-lite.js +1 -1
- package/dist/core/routes.js +10 -10
- package/dist/core/runtime/task-profile.js +9 -5
- package/dist/core/subagents/model-policy.js +27 -45
- package/dist/core/subagents/model-tiers.js +153 -0
- package/dist/core/subagents/naruto-command-args.js +3 -3
- package/dist/core/subagents/naruto-help-contract.js +18 -13
- package/dist/core/subagents/naruto-host-credentials.js +8 -13
- package/dist/core/subagents/official-subagent-config.js +11 -7
- package/dist/core/subagents/official-subagent-preparation.js +12 -8
- package/dist/core/subagents/official-subagent-prompt.js +33 -33
- package/dist/core/subagents/official-subagent-runner.js +3 -3
- package/dist/core/subagents/role-model-preferences.js +26 -21
- package/dist/core/update/managed-permission-repair.js +462 -0
- package/dist/core/update/update-migration-state/retired-local-decision.js +32 -2
- package/dist/core/update/update-migration-state/simple-stages.js +25 -1
- package/dist/core/update/update-migration-state.js +63 -1
- package/dist/core/update-check.js +35 -8
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-native-agent-role-content-check.js +10 -5
- package/dist/scripts/codex-native-gate-lib.js +3 -1
- package/dist/scripts/current-surface-update-e2e-check.js +1 -0
- package/dist/scripts/mutation-callsite-coverage-check.js +2 -2
- package/dist/scripts/release-affected-selector-check.js +1 -2
- package/dist/scripts/typed-routing-gate-lib.js +3 -1
- package/package.json +1 -1
- package/release-gates.v2.json +2 -6
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
|
|
17
17
|
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
18
18
|
|
|
19
|
-
Current package: **SKS 10.3.
|
|
19
|
+
Current package: **SKS 10.3.4**. Install the latest stable release from npm.
|
|
20
20
|
|
|
21
21
|
[Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
|
|
22
22
|
|
|
@@ -39,7 +39,7 @@ sks bootstrap --yes
|
|
|
39
39
|
|
|
40
40
|
| Capability | What you get |
|
|
41
41
|
| --- | --- |
|
|
42
|
-
| Focused execution |
|
|
42
|
+
| Focused execution | Answers and tiny edits stay lightweight; implementation work runs through official Codex subagents while the parent orchestrates and integrates. |
|
|
43
43
|
| Project context | TriWiki indexes repository code and supplies bounded context that can be checked against source. |
|
|
44
44
|
| Verification | Tests, diagnostics, and release evidence support completion claims. Security and data-integrity checks stay in place. |
|
|
45
45
|
| Native controls | SKS Center brings connections, updates, MCP servers, and diagnostics together on macOS. |
|
|
@@ -138,16 +138,26 @@ configuration, transport checks, and recovery commands.
|
|
|
138
138
|
|
|
139
139
|
## Naruto workflow
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
141
|
+
The parent orchestrates: it decomposes the task, spawns a child for each
|
|
142
|
+
disjoint slice, waits, and owns integration and final verification. It does not
|
|
143
|
+
implement slices itself. The SKS PreToolUse hook denies parent source edits
|
|
144
|
+
until the first child starts and while children are still running. Children
|
|
145
|
+
receive bounded tasks and do not spawn children.
|
|
146
|
+
|
|
147
|
+
No model family is pinned. Every child runs the newest model of the tier its
|
|
148
|
+
work needs, read from the Codex models cache, so a new model family is used as
|
|
149
|
+
soon as Codex lists it:
|
|
150
|
+
|
|
151
|
+
| Work | Tier | Effort | Today |
|
|
152
|
+
| --- | --- | --- | --- |
|
|
153
|
+
| Tiny mechanical tasks | fast | low | `gpt-6-luna` |
|
|
154
|
+
| Instructed implementation | balanced | low | `gpt-6-sol` |
|
|
155
|
+
| Exploration, large-context reads, and direct tool operation | context | medium | `gpt-6-sol` |
|
|
156
|
+
| Planning, review, debugging, and focused judgment | deep | max | `gpt-6-astra` |
|
|
157
|
+
|
|
158
|
+
With Jev mode on, Jev picks the tier for every spawn, every gated parent edit,
|
|
159
|
+
the plan, and the context, so the parent spends no time on those choices. A
|
|
160
|
+
stored role-model preference on a current model wins in both modes.
|
|
151
161
|
|
|
152
162
|
An active Codex task keeps the user's selected main model, effort, and service
|
|
153
163
|
tier. Codex native `/goal` remains the persisted goal owner. Parallelism depends
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
{
|
|
5
5
|
"canonical_name": "sks",
|
|
6
6
|
"trusted_sha256": [
|
|
7
|
+
"8aa34586ba331f0447668c2ce863fe6c9ec4f881828ebed26ca5ecc754d66840",
|
|
7
8
|
"d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
|
|
8
9
|
"905fc5472d36f0780b56f0e09404f42ea20293b1a4615d2050b7db3043231dbd",
|
|
9
10
|
"6d90142f3617a8ae92d9af8cb2fc18ab58a7d0070868504e1fe89e0effc305a5",
|
|
@@ -324,6 +325,7 @@
|
|
|
324
325
|
{
|
|
325
326
|
"canonical_name": "sks-naruto",
|
|
326
327
|
"trusted_sha256": [
|
|
328
|
+
"b2eb207b2773ab1995ee6e113ba6f3b64de43389e76aef4a0f06d4845ff86223",
|
|
327
329
|
"65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
|
|
328
330
|
"da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
|
|
329
331
|
"b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
|
|
@@ -331,7 +333,6 @@
|
|
|
331
333
|
"a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
|
|
332
334
|
"d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
|
|
333
335
|
"34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
|
|
334
|
-
"f8e808622e222b4ad7dcb2907e5f06a890db83139ae443ec6c8c33781dfa3e37",
|
|
335
336
|
"49b5e3dcc1b537b49c2cf09e5b875cb16acb05c622b22798e265e64dacd8ac90"
|
|
336
337
|
]
|
|
337
338
|
},
|
|
@@ -346,6 +347,7 @@
|
|
|
346
347
|
{
|
|
347
348
|
"canonical_name": "sks-pipeline-runner",
|
|
348
349
|
"trusted_sha256": [
|
|
350
|
+
"6f38d3387e4ab020291afe7f74713b135543e52707f5bfbffa6b3584542f45a5",
|
|
349
351
|
"7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
|
|
350
352
|
"fd06dfb27be41e5ffec3e3ec8c5362289f5919cf2afa9da5ab6b97e7baa4a0ac",
|
|
351
353
|
"66e105773b8e042eec5d5679b97b7cb305cad8d45671034907fbfa728fc4d68a",
|
|
@@ -373,6 +375,7 @@
|
|
|
373
375
|
{
|
|
374
376
|
"canonical_name": "sks-prompt-pipeline",
|
|
375
377
|
"trusted_sha256": [
|
|
378
|
+
"f82a103e7962e416961add515d7b3e65df89848dec1fbb9319bd49d7f1dac982",
|
|
376
379
|
"93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
|
|
377
380
|
"033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
|
|
378
381
|
"e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
|
package/dist/commands/doctor.js
CHANGED
|
@@ -127,7 +127,8 @@ export async function run(_command, args = [], deps = {}) {
|
|
|
127
127
|
const doctorFix = flag(args, '--fix');
|
|
128
128
|
const homeDir = path.resolve(deps.home || process.env.HOME || os.homedir());
|
|
129
129
|
const globalOnly = doctorGlobalOnlySelection({ args, doctorFix, root, home: homeDir }).global_only;
|
|
130
|
-
|
|
130
|
+
const doctorProfile = doctorProfileFromArgs(args, doctorFix);
|
|
131
|
+
if (doctorFix && doctorProfile !== 'migration') {
|
|
131
132
|
const conflictScan = await scanHarnessConflicts(root);
|
|
132
133
|
if (conflictScan.hard_block) {
|
|
133
134
|
const blocked = {
|
|
@@ -151,7 +152,6 @@ export async function run(_command, args = [], deps = {}) {
|
|
|
151
152
|
return blocked;
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
|
-
const doctorProfile = doctorProfileFromArgs(args, doctorFix);
|
|
155
155
|
if (!flag(args, '--json')) {
|
|
156
156
|
cliUi.banner('doctor');
|
|
157
157
|
cliUi.step(doctorFix ? 'repairing and validating' : 'validating');
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.skills-manifest.v1",
|
|
3
|
-
"package_version": "10.3.
|
|
3
|
+
"package_version": "10.3.4",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
6
6
|
"canonical_name": "sks",
|
|
7
7
|
"type": "official",
|
|
8
|
-
"content_sha256": "
|
|
8
|
+
"content_sha256": "8aa34586ba331f0447668c2ce863fe6c9ec4f881828ebed26ca5ecc754d66840",
|
|
9
9
|
"hash_history": [
|
|
10
|
+
"d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
|
|
10
11
|
"905fc5472d36f0780b56f0e09404f42ea20293b1a4615d2050b7db3043231dbd",
|
|
11
12
|
"6d90142f3617a8ae92d9af8cb2fc18ab58a7d0070868504e1fe89e0effc305a5",
|
|
12
13
|
"5b17aeb1484e9e2b3791bff6dcb175f30857f47e71b36eb0340e575385e8e82b"
|
|
@@ -393,15 +394,15 @@
|
|
|
393
394
|
{
|
|
394
395
|
"canonical_name": "sks-naruto",
|
|
395
396
|
"type": "core",
|
|
396
|
-
"content_sha256": "
|
|
397
|
+
"content_sha256": "b2eb207b2773ab1995ee6e113ba6f3b64de43389e76aef4a0f06d4845ff86223",
|
|
397
398
|
"hash_history": [
|
|
399
|
+
"65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
|
|
398
400
|
"da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
|
|
399
401
|
"b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
|
|
400
402
|
"5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
|
|
401
403
|
"a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
|
|
402
404
|
"d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
|
|
403
405
|
"34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
|
|
404
|
-
"f8e808622e222b4ad7dcb2907e5f06a890db83139ae443ec6c8c33781dfa3e37",
|
|
405
406
|
"49b5e3dcc1b537b49c2cf09e5b875cb16acb05c622b22798e265e64dacd8ac90"
|
|
406
407
|
],
|
|
407
408
|
"deprecated_aliases": []
|
|
@@ -419,8 +420,9 @@
|
|
|
419
420
|
{
|
|
420
421
|
"canonical_name": "sks-pipeline-runner",
|
|
421
422
|
"type": "official",
|
|
422
|
-
"content_sha256": "
|
|
423
|
+
"content_sha256": "6f38d3387e4ab020291afe7f74713b135543e52707f5bfbffa6b3584542f45a5",
|
|
423
424
|
"hash_history": [
|
|
425
|
+
"7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
|
|
424
426
|
"fd06dfb27be41e5ffec3e3ec8c5362289f5919cf2afa9da5ab6b97e7baa4a0ac",
|
|
425
427
|
"66e105773b8e042eec5d5679b97b7cb305cad8d45671034907fbfa728fc4d68a",
|
|
426
428
|
"020ba4da2f96bcda23cee4596b1e790816780178f1917dee748ff4b289cba0e5",
|
|
@@ -452,8 +454,9 @@
|
|
|
452
454
|
{
|
|
453
455
|
"canonical_name": "sks-prompt-pipeline",
|
|
454
456
|
"type": "official",
|
|
455
|
-
"content_sha256": "
|
|
457
|
+
"content_sha256": "f82a103e7962e416961add515d7b3e65df89848dec1fbb9319bd49d7f1dac982",
|
|
456
458
|
"hash_history": [
|
|
459
|
+
"93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
|
|
457
460
|
"033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
|
|
458
461
|
"e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
|
|
459
462
|
"480c8afffafa9d52c483a963b4b8fafdffc0a47cd6d2efd3fb6d7924c11832c5",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
|
|
2
2
|
import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
|
|
3
|
-
import {
|
|
3
|
+
import { decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
|
|
4
|
+
import { MODEL_TIERS, resolveLatestModelTiers } from '../subagents/model-tiers.js';
|
|
4
5
|
export function decideAgentEffort(input = {}) {
|
|
5
6
|
return decideOfficialSubagentModel(input);
|
|
6
7
|
}
|
|
@@ -38,14 +39,14 @@ export function decideOfficialSubagentModel(input = {}) {
|
|
|
38
39
|
reason: `managed_role:${managedRole.codex_name}:${profile.policy}`,
|
|
39
40
|
dynamic: true,
|
|
40
41
|
escalation_triggers: [
|
|
41
|
-
'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects
|
|
42
|
+
'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects the deep tier',
|
|
42
43
|
'incidental judgment vocabulary does not override a clearly classified implementation or context/tools slice',
|
|
43
44
|
'requested model/effort profile unavailable blocks instead of silently falling back'
|
|
44
45
|
],
|
|
45
46
|
downshift_triggers: [
|
|
46
|
-
'instructed UI, logic, backend, or native implementation selects
|
|
47
|
-
'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects
|
|
48
|
-
'tiny short-context mechanical search/typing/rename work selects
|
|
47
|
+
'instructed UI, logic, backend, or native implementation selects the balanced tier',
|
|
48
|
+
'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects the context tier',
|
|
49
|
+
'tiny short-context mechanical search/typing/rename work selects the balanced tier'
|
|
49
50
|
]
|
|
50
51
|
};
|
|
51
52
|
}
|
|
@@ -92,14 +93,14 @@ export function decideOfficialSubagentModel(input = {}) {
|
|
|
92
93
|
reason: routed.reason,
|
|
93
94
|
dynamic: true,
|
|
94
95
|
escalation_triggers: [
|
|
95
|
-
'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects
|
|
96
|
+
'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects the deep tier',
|
|
96
97
|
'incidental judgment vocabulary does not override a clearly classified implementation or context/tools slice',
|
|
97
98
|
'requested model/effort profile unavailable blocks instead of silently falling back'
|
|
98
99
|
],
|
|
99
100
|
downshift_triggers: [
|
|
100
|
-
'instructed UI, logic, backend, or native implementation selects
|
|
101
|
-
'long-context, Browser/Chrome, Computer Use, or image-generation execution selects
|
|
102
|
-
'tiny short-context mechanical work selects
|
|
101
|
+
'instructed UI, logic, backend, or native implementation selects the balanced tier',
|
|
102
|
+
'long-context, Browser/Chrome, Computer Use, or image-generation execution selects the context tier',
|
|
103
|
+
'tiny short-context mechanical work selects the balanced tier'
|
|
103
104
|
]
|
|
104
105
|
};
|
|
105
106
|
}
|
|
@@ -118,21 +119,22 @@ export function buildAgentEffortPolicy(roster = {}) {
|
|
|
118
119
|
reason: agent.reasoning_reason,
|
|
119
120
|
dynamic: true
|
|
120
121
|
})) : [];
|
|
122
|
+
const latest = resolveLatestModelTiers();
|
|
121
123
|
return {
|
|
122
124
|
schema: 'sks.agent-effort-policy.v1',
|
|
123
125
|
policy_version: 1,
|
|
124
126
|
dynamic: true,
|
|
125
127
|
service_tier: 'fast',
|
|
126
128
|
model_catalog_policy: 'official_subagent_four_profile_matrix',
|
|
127
|
-
model_constraint: [
|
|
128
|
-
model_tiers:
|
|
129
|
+
model_constraint: [...new Set(MODEL_TIERS.map((tier) => latest.models[tier]))],
|
|
130
|
+
model_tiers: MODEL_TIERS.map((tier) => `${tier}:${latest.models[tier]}-${latest.efforts[tier]}`),
|
|
129
131
|
allowed_efforts: ['low', 'medium', 'high', 'max'],
|
|
130
|
-
model_effort_capability: codexModelEffortCapability({ model:
|
|
132
|
+
model_effort_capability: codexModelEffortCapability({ model: latest.models.deep }),
|
|
131
133
|
max_agents: roster.max_agents || 20,
|
|
132
134
|
agent_count: roster.agent_count || decisions.length,
|
|
133
135
|
concurrency: roster.concurrency || decisions.length,
|
|
134
136
|
decisions,
|
|
135
|
-
rule: '
|
|
137
|
+
rule: 'Every child uses the newest model of the tier its work needs: fast for tiny mechanical work, balanced for instructed implementation, context for reads and tool execution, and deep for focused judgment. With Jev mode on, Jev picks the tier for each spawn. The parent keeps its user-selected model, reasoning effort, and service tier; stored role preferences may override role defaults.'
|
|
136
138
|
};
|
|
137
139
|
}
|
|
138
140
|
export function reasoningProfileName(effort) {
|
|
@@ -10,7 +10,8 @@ import { leanEngineeringCompactText, leanPolicyReference } from '../lean-enginee
|
|
|
10
10
|
import { readCodexLbModelCatalog, readLbHealth } from '../codex-lb/codex-lb-env.js';
|
|
11
11
|
import { categoryForWorkerRole, isNarutoGpt56Model, modelRouteReason, routeModel } from '../provider/model-router.js';
|
|
12
12
|
import { codexTimeoutClassForRoute } from '../codex-control/codex-reliability-shield.js';
|
|
13
|
-
import {
|
|
13
|
+
import { decideSubagentModel } from '../subagents/model-policy.js';
|
|
14
|
+
import { latestTierModelSet } from '../subagents/model-tiers.js';
|
|
14
15
|
export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
|
|
15
16
|
export async function runNativeWorkerBackendRouter(input) {
|
|
16
17
|
const root = path.resolve(input.agentRoot);
|
|
@@ -241,15 +242,19 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
|
|
|
241
242
|
const explicitNarutoModelInvalid = narutoOnly && Boolean(explicitModel) && !isNarutoGpt56Model(explicitModel);
|
|
242
243
|
const explicitNarutoReasoningInvalid = narutoOnly && Boolean(explicitReasoningRaw) && !explicitReasoning;
|
|
243
244
|
const explicitNarutoTierInvalid = narutoOnly && Boolean(explicitTierRaw) && !explicitTier;
|
|
244
|
-
const
|
|
245
|
-
|
|
245
|
+
const currentModels = latestTierModelSet();
|
|
246
|
+
const savedPreferenceModel = input.agent?.routed_model_policy === 'user_role_model_preference'
|
|
247
|
+
&& currentModels.has(String(input.agent?.routed_model || ''))
|
|
248
|
+
? String(input.agent?.routed_model)
|
|
249
|
+
: '';
|
|
250
|
+
const savedAstraEffort = savedPreferenceModel
|
|
246
251
|
? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort)
|
|
247
252
|
: null;
|
|
248
253
|
const jevModel = !explicitModel && input.agent?.routed_model_policy === 'jev_sealed_routing'
|
|
249
254
|
? String(input.agent.routed_model || '').trim()
|
|
250
255
|
: '';
|
|
251
256
|
const jevEffort = jevModel ? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort) : null;
|
|
252
|
-
const selectedModel = explicitModel || jevModel;
|
|
257
|
+
const selectedModel = explicitModel || jevModel || savedPreferenceModel;
|
|
253
258
|
const taskPolicy = decideSubagentModel({ title: taskKindText, description: riskText, role: input.agent?.role });
|
|
254
259
|
const routed = narutoOnly
|
|
255
260
|
? await routeModel(category, {
|
|
@@ -263,14 +268,14 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
|
|
|
263
268
|
...(selectedModel ? { model: selectedModel } : {})
|
|
264
269
|
})
|
|
265
270
|
: {
|
|
266
|
-
model:
|
|
271
|
+
model: (explicitModel && currentModels.has(explicitModel) ? explicitModel : '') || savedPreferenceModel || taskPolicy.model,
|
|
267
272
|
reasoning: explicitReasoning || savedAstraEffort || taskPolicy.modelReasoningEffort,
|
|
268
273
|
serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
|
|
269
274
|
};
|
|
270
275
|
const blockers = [
|
|
271
276
|
...(narutoOnly && !lbCatalog?.ok ? (lbCatalog?.blockers || ['codex_lb_model_catalog_unavailable']) : []),
|
|
272
|
-
...(explicitNarutoModelInvalid ? ['
|
|
273
|
-
...(!narutoOnly && explicitModel && explicitModel
|
|
277
|
+
...(explicitNarutoModelInvalid ? ['naruto_worker_model_not_current'] : []),
|
|
278
|
+
...(!narutoOnly && explicitModel && !currentModels.has(explicitModel) ? ['subagent_model_must_be_current'] : []),
|
|
274
279
|
...(explicitNarutoReasoningInvalid ? ['naruto_reasoning_override_invalid'] : []),
|
|
275
280
|
...(explicitNarutoTierInvalid ? ['naruto_service_tier_override_invalid'] : []),
|
|
276
281
|
...(narutoOnly && explicitReasoning && explicitReasoning !== routed.reasoning ? ['naruto_reasoning_override_conflicts_with_policy'] : []),
|
|
@@ -306,7 +311,7 @@ export function narutoWorkerBackendBlocker(backend, narutoRequest = true) {
|
|
|
306
311
|
if (!narutoRequest)
|
|
307
312
|
return null;
|
|
308
313
|
if (backend === 'process')
|
|
309
|
-
return '
|
|
314
|
+
return 'naruto_process_backend_forbidden';
|
|
310
315
|
return null;
|
|
311
316
|
}
|
|
312
317
|
function normalizeBackend(value) {
|
|
@@ -18,6 +18,7 @@ const SETTINGS_V2_KEYS = new Set([
|
|
|
18
18
|
'schema', 'listen_host', 'listen_port', 'provider_registry', 'route_policy',
|
|
19
19
|
'provider_session_pins', 'client_capability_sha256', 'allowed_origins',
|
|
20
20
|
'connect_timeout_ms', 'idle_timeout_ms', 'official_passthrough',
|
|
21
|
+
'auth_priority_enabled',
|
|
21
22
|
]);
|
|
22
23
|
const TRANSFERABLE_V1_KEYS = [
|
|
23
24
|
'listen_host', 'listen_port', 'allowed_origins', 'connect_timeout_ms', 'idle_timeout_ms',
|
|
@@ -14,13 +14,13 @@ const CORE_SKILL_DEFINITIONS = [
|
|
|
14
14
|
display_name: 'naruto',
|
|
15
15
|
route: '$Naruto',
|
|
16
16
|
purpose: 'run a Codex official subagent workflow with official agent threads while parent integration remains owner.',
|
|
17
|
-
discovery: 'Run $sks-naruto
|
|
18
|
-
when: 'Use when the user
|
|
19
|
-
workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent orchestrates only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement slice work in the parent thread. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity.
|
|
17
|
+
discovery: 'Run $sks-naruto to delegate implementation to child subagents.',
|
|
18
|
+
when: 'Use when the user invokes $sks-naruto or $sks-work, or the selected route is Naruto: ordinary implementation work, where the parent orchestrates and children implement.',
|
|
19
|
+
workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent orchestrates only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement slice work in the parent thread; the SKS PreToolUse gate denies parent-thread source edits until the first child thread starts and while children are still running, and .sneakoscope artifacts stay parent-writable. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity. Every child runs the newest model of the tier its work needs (fast, balanced, context, or deep); no model family is pinned. When Jev mode is on, Jev picks the tier for each new spawn and SKS seals it. A user role preference stays authoritative. Do not choose child models yourself. Keep the four task-class profiles and compatibility IDs; max_depth=1 blocks nested delegation. Wait for every planned thread before final. In an active Codex App Naruto mission, commit the strict parent evidence with sks naruto parent-summary --mission <id> --stdin, then return localized Markdown without exposing the JSON.',
|
|
20
20
|
safety: 'Preserve user-authored content, inherit the parent permission mode, do not spawn nested subagents, do not inject the full pack or the full TriWiki context into every child, and do not fall back to another model, process runtime, custom scheduler, or worker pool. The historical Naruto process runtime is removed; stop with explicit blocker evidence when the official path is unavailable.',
|
|
21
21
|
cli: 'sks naruto run "<task>" [--agents N] [--max-threads N] [--json]; sks naruto status|subagents|proof [--mission <id>] [--json]; sks naruto parent-summary --mission <id> --stdin [--json]',
|
|
22
22
|
evidence: 'subagent-plan.json, subagent-events.jsonl, subagent-parent-summary.json, subagent-evidence.json, naruto-summary.json, and naruto-gate.json.',
|
|
23
|
-
fallback: '
|
|
23
|
+
fallback: 'When official subagents are unavailable, report the explicit availability blocker; the orchestration gate releases parent edits only after its bounded denials, and that release is recorded. Never fabricate process, PID, or subagent evidence.'
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
id: 'sks-core-qa-loop',
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { printJson } from '../../cli/output.js';
|
|
4
4
|
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
|
|
6
|
-
import { jevEnabled, readDecisionConfig, writeDecisionConfig } from './config.js';
|
|
6
|
+
import { jevEnabled, readDecisionConfig, writeDecisionConfig, jevCapabilityActive } from './config.js';
|
|
7
7
|
import { buildEvaluationReport } from './evaluation.js';
|
|
8
8
|
import { OPENROUTER_DECISIONS_ENDPOINT, OPENROUTER_DECISIONS_MODEL, requestOpenRouterDecision } from './openrouter.js';
|
|
9
9
|
import { buildDecisionBundle } from './questions.js';
|
|
@@ -152,7 +152,14 @@ export async function statusReport(env) {
|
|
|
152
152
|
source: resolved.source,
|
|
153
153
|
preview: resolved.key_preview
|
|
154
154
|
},
|
|
155
|
-
capabilities:
|
|
155
|
+
capabilities: {
|
|
156
|
+
context: { ...config.capabilities.context, ready: jevCapabilityActive(config, 'context') },
|
|
157
|
+
plan: { ...config.capabilities.plan, ready: jevCapabilityActive(config, 'plan') },
|
|
158
|
+
recovery: config.capabilities.recovery
|
|
159
|
+
},
|
|
160
|
+
decision_points: jevEnabled(config)
|
|
161
|
+
? ['turn_tier', 'spawn_tier', 'role_tiers', 'role_omission', 'plan', 'context', 'parent_edit_delegation']
|
|
162
|
+
: [],
|
|
156
163
|
recovery: RECOVERY_CAPABILITY,
|
|
157
164
|
nextStep,
|
|
158
165
|
notes: [
|
|
@@ -70,6 +70,13 @@ export async function writeDecisionConfig(patch, env = process.env) {
|
|
|
70
70
|
export function jevEnabled(config) {
|
|
71
71
|
return config.mode === 'jev' && config.consentCloud === true;
|
|
72
72
|
}
|
|
73
|
+
export function jevCapabilityActive(config, capability) {
|
|
74
|
+
if (!jevEnabled(config))
|
|
75
|
+
return false;
|
|
76
|
+
if (capability === 'recovery')
|
|
77
|
+
return config.capabilities.recovery.ready;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
73
80
|
function normalizeConfig(raw) {
|
|
74
81
|
const fallback = defaultDecisionConfig();
|
|
75
82
|
if (!isRecord(raw) || raw.schema !== DECISION_CONFIG_SCHEMA)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sha256 } from '../fsx.js';
|
|
2
2
|
import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
|
|
3
|
-
import { jevEnabled, readDecisionConfig } from './config.js';
|
|
3
|
+
import { jevCapabilityActive, jevEnabled, readDecisionConfig } from './config.js';
|
|
4
4
|
import { requestOpenRouterDecision } from './openrouter.js';
|
|
5
5
|
import { compileDecision } from './policy.js';
|
|
6
6
|
import { buildDecisionBundle, validatePlanCoverage } from './questions.js';
|
|
@@ -8,7 +8,7 @@ import { applyRecoveryEffect, listProductionRecoveryCandidates } from './recover
|
|
|
8
8
|
import { assembleRoutingSelection, buildRoutingCandidates } from './routing.js';
|
|
9
9
|
import { buildDecisionReceipt } from './receipt.js';
|
|
10
10
|
import { applyOptionalContextSelection, graphFileDigest, hydrateContextCandidates, sourceSnapshotDigest } from './state.js';
|
|
11
|
-
import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE
|
|
11
|
+
import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE } from './types.js';
|
|
12
12
|
let testOverrides = null;
|
|
13
13
|
const appliedEffects = new Set();
|
|
14
14
|
export function setDecisionTestOverrides(overrides) {
|
|
@@ -43,16 +43,16 @@ async function decideOfficialSubagentPreparationInner(input, started) {
|
|
|
43
43
|
overrides?.observe?.({ mode: config.mode, eligible: false, compiled: missing.compiled, receipt: missing.receipt });
|
|
44
44
|
return missing;
|
|
45
45
|
}
|
|
46
|
-
const contextCandidates = config
|
|
46
|
+
const contextCandidates = jevCapabilityActive(config, 'context')
|
|
47
47
|
? await hydrateContextCandidates(input.root, input.attention, input.changedPaths || [])
|
|
48
48
|
: [];
|
|
49
|
-
const planCandidates = config
|
|
49
|
+
const planCandidates = jevCapabilityActive(config, 'plan') && input.requestedSource === 'automatic'
|
|
50
50
|
? buildAutomaticPlanCandidates(input)
|
|
51
51
|
: [];
|
|
52
52
|
const routingCandidates = input.requestedSource === 'automatic'
|
|
53
53
|
? buildRoutingCandidates({ roles: input.routingRoles || [] })
|
|
54
54
|
: [];
|
|
55
|
-
const recoveryCandidates = config
|
|
55
|
+
const recoveryCandidates = jevCapabilityActive(config, 'recovery')
|
|
56
56
|
? [...listProductionRecoveryCandidates()]
|
|
57
57
|
: [];
|
|
58
58
|
const optionalContext = contextCandidates.filter((row) => !row.pinned && row.excerpt);
|
|
@@ -134,7 +134,7 @@ export async function consultJevTurnModel(input) {
|
|
|
134
134
|
const overrides = activeOverrides();
|
|
135
135
|
const env = input.env || process.env;
|
|
136
136
|
const config = overrides?.config ?? await readDecisionConfig(env);
|
|
137
|
-
const none = (reason) => ({ called: false, model: null, effort: null, reason });
|
|
137
|
+
const none = (reason) => ({ called: false, model: null, effort: null, tier: null, reason });
|
|
138
138
|
if (!jevEnabled(config))
|
|
139
139
|
return none('off');
|
|
140
140
|
const resolved = await resolveOpenRouterApiKey({ env });
|
|
@@ -159,14 +159,56 @@ export async function consultJevTurnModel(input) {
|
|
|
159
159
|
...(overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {})
|
|
160
160
|
});
|
|
161
161
|
if (!transport.ok)
|
|
162
|
-
return { called: true, model: null, effort: null, reason: transport.reason };
|
|
162
|
+
return { called: true, model: null, effort: null, tier: null, reason: transport.reason };
|
|
163
163
|
const compiled = compileDecision(bundle, transport.response);
|
|
164
164
|
if (compiled.kind !== 'apply')
|
|
165
|
-
return { called: true, model: null, effort: null, reason: compiled.reason };
|
|
166
|
-
const selected = assembleRoutingSelection(compiled.effects.flatMap((effect) => (effect.kind === 'select_routing' ? [{ roleId: effect.roleId,
|
|
165
|
+
return { called: true, model: null, effort: null, tier: null, reason: compiled.reason };
|
|
166
|
+
const selected = assembleRoutingSelection(compiled.effects.flatMap((effect) => (effect.kind === 'select_routing' ? [{ roleId: effect.roleId, tier: effect.tier }] : [])));
|
|
167
167
|
const model = selected?.models[roleId] || null;
|
|
168
|
-
const
|
|
169
|
-
|
|
168
|
+
const effort = selected?.efforts[roleId] || null;
|
|
169
|
+
const tier = selected?.tiers[roleId] || null;
|
|
170
|
+
if (!model || !effort || !tier)
|
|
171
|
+
return { called: true, model: null, effort: null, tier: null, reason: 'keep_baseline' };
|
|
172
|
+
return { called: true, model, effort, tier, reason: 'applied' };
|
|
173
|
+
}
|
|
174
|
+
export async function consultJevToolDelegation(input) {
|
|
175
|
+
const overrides = activeOverrides();
|
|
176
|
+
const env = input.env || process.env;
|
|
177
|
+
const config = overrides?.config ?? await readDecisionConfig(env);
|
|
178
|
+
const none = (reason) => ({ called: false, choice: null, reason });
|
|
179
|
+
if (!jevEnabled(config))
|
|
180
|
+
return none('off');
|
|
181
|
+
const resolved = await resolveOpenRouterApiKey({ env });
|
|
182
|
+
if (!resolved.key)
|
|
183
|
+
return none('missing_key');
|
|
184
|
+
const goal = String(input.missionGoal || '').trim();
|
|
185
|
+
const toolName = String(input.toolName || '').trim();
|
|
186
|
+
if (!goal || !toolName)
|
|
187
|
+
return none('empty_candidate');
|
|
188
|
+
const targets = input.targets.map((row) => String(row || '').trim()).filter(Boolean);
|
|
189
|
+
const bundle = buildDecisionBundle({
|
|
190
|
+
projectId: sha256(input.root).slice(0, 32),
|
|
191
|
+
workflowRunId: 'delegation',
|
|
192
|
+
workflowRevision: 'delegation',
|
|
193
|
+
sourceDigest: sha256(JSON.stringify([toolName, targets])).slice(0, 32),
|
|
194
|
+
graphDigest: null,
|
|
195
|
+
goal,
|
|
196
|
+
delegationCandidate: { toolName, targets, missionGoal: goal }
|
|
197
|
+
});
|
|
198
|
+
const transport = await requestOpenRouterDecision(bundle, {
|
|
199
|
+
env,
|
|
200
|
+
deadlineMs: DESIGN_DEFAULTS.deadlineMs,
|
|
201
|
+
...(overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {})
|
|
202
|
+
});
|
|
203
|
+
if (!transport.ok)
|
|
204
|
+
return { called: true, choice: null, reason: transport.reason };
|
|
205
|
+
const compiled = compileDecision(bundle, transport.response);
|
|
206
|
+
if (compiled.kind !== 'apply')
|
|
207
|
+
return { called: true, choice: null, reason: compiled.reason };
|
|
208
|
+
const effect = compiled.effects.find((row) => row.kind === 'select_delegation');
|
|
209
|
+
if (!effect || effect.kind !== 'select_delegation')
|
|
210
|
+
return { called: true, choice: null, reason: 'keep_baseline' };
|
|
211
|
+
return { called: true, choice: effect.choice, reason: 'applied' };
|
|
170
212
|
}
|
|
171
213
|
export function effectAlreadyConsumed(identity) {
|
|
172
214
|
return appliedEffects.has(identity);
|
|
@@ -210,7 +252,7 @@ function applyEffects(attention, contextCandidates, planCandidates, compiled) {
|
|
|
210
252
|
evidence.push(`plan:${selectedPlan.id}`);
|
|
211
253
|
}
|
|
212
254
|
if (effect.kind === 'select_routing') {
|
|
213
|
-
routingEffects.push({ roleId: effect.roleId,
|
|
255
|
+
routingEffects.push({ roleId: effect.roleId, tier: effect.tier });
|
|
214
256
|
}
|
|
215
257
|
if (effect.kind === 'omit_role') {
|
|
216
258
|
omittedRoutingRoles.push(effect.roleId);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CHOICE_MIN_CONFIDENCE, CHOICE_MIN_PROBABILITY, CONTEXT_DROP_NOUL_MAX, DISTRIBUTION_SUM_TOLERANCE, KEEP_BASELINE_CHOICE, NEEDS_EVIDENCE_CHOICE, POLICY_REVISION, ROUTING_RISK_NOUL_MIN, ROUTING_ROLE_OMIT_NOUL_MAX, UNKNOWN_USAGE,
|
|
1
|
+
import { CHOICE_MIN_CONFIDENCE, CHOICE_MIN_PROBABILITY, CONTEXT_DROP_NOUL_MAX, DISTRIBUTION_SUM_TOLERANCE, DELEGATION_CHOICES, KEEP_BASELINE_CHOICE, NEEDS_EVIDENCE_CHOICE, POLICY_REVISION, ROUTING_RISK_NOUL_MIN, ROUTING_ROLE_OMIT_NOUL_MAX, UNKNOWN_USAGE, ESCALATION_ROUTING_TIER, routingTier } from './types.js';
|
|
2
2
|
export function compileDecision(bundle, response) {
|
|
3
3
|
const usage = usageFromWire(response.usage);
|
|
4
4
|
const decoded = decodeWireResponse(bundle, response);
|
|
@@ -33,6 +33,14 @@ export function compileDecision(bundle, response) {
|
|
|
33
33
|
else if (!fallbackReason)
|
|
34
34
|
fallbackReason = compiled.reason;
|
|
35
35
|
}
|
|
36
|
+
const delegationBinding = Object.entries(bundle.questionBindings).find(([, binding]) => binding.kind === 'delegation');
|
|
37
|
+
if (delegationBinding) {
|
|
38
|
+
const compiled = compileDelegation(decoded.response.answers[delegationBinding[0]]);
|
|
39
|
+
if (compiled.kind === 'effect')
|
|
40
|
+
effects.push(compiled.effect);
|
|
41
|
+
else if (!fallbackReason)
|
|
42
|
+
fallbackReason = compiled.reason;
|
|
43
|
+
}
|
|
36
44
|
if (effects.length === 0) {
|
|
37
45
|
return {
|
|
38
46
|
kind: 'keep_baseline',
|
|
@@ -172,7 +180,7 @@ function compileRoleRouting(bundle, answers, roleId) {
|
|
|
172
180
|
return { kind: 'baseline', reason: 'invalid_response' };
|
|
173
181
|
if (answer.choice === KEEP_BASELINE_CHOICE)
|
|
174
182
|
return { kind: 'baseline', reason: 'keep_baseline_selected' };
|
|
175
|
-
const selected =
|
|
183
|
+
const selected = routingTier(answer.choice);
|
|
176
184
|
if (!selected)
|
|
177
185
|
return { kind: 'baseline', reason: 'invalid_response' };
|
|
178
186
|
const choiceQuestion = choiceId ? bundle.request.questions[choiceId] : undefined;
|
|
@@ -180,8 +188,8 @@ function compileRoleRouting(bundle, answers, roleId) {
|
|
|
180
188
|
const uncertainty = requiredChoiceUncertainty(answer, labels);
|
|
181
189
|
if (!uncertainty.ok)
|
|
182
190
|
return { kind: 'baseline', reason: uncertainty.reason };
|
|
183
|
-
const
|
|
184
|
-
return { kind: 'effect', effect: { kind: 'select_routing', roleId,
|
|
191
|
+
const tier = escalateRole(bundle, answers, roleId) ? ESCALATION_ROUTING_TIER : selected.id;
|
|
192
|
+
return { kind: 'effect', effect: { kind: 'select_routing', roleId, tier } };
|
|
185
193
|
}
|
|
186
194
|
function escalateRole(bundle, answers, roleId) {
|
|
187
195
|
const difficultyId = questionIdFor(bundle, 'routing_difficulty', roleId);
|
|
@@ -264,6 +272,21 @@ function compileRecovery(bundle, answer) {
|
|
|
264
272
|
return { kind: 'baseline', reason: uncertainty.reason };
|
|
265
273
|
return { kind: 'effect', effect: { kind: 'dispatch_recovery', actionId: candidate.id } };
|
|
266
274
|
}
|
|
275
|
+
function compileDelegation(answer) {
|
|
276
|
+
if (!answer)
|
|
277
|
+
return { kind: 'baseline', reason: 'missing_answer' };
|
|
278
|
+
if (answer.type !== 'choice')
|
|
279
|
+
return { kind: 'baseline', reason: 'invalid_response' };
|
|
280
|
+
if (answer.choice === KEEP_BASELINE_CHOICE)
|
|
281
|
+
return { kind: 'baseline', reason: 'keep_baseline_selected' };
|
|
282
|
+
const choice = DELEGATION_CHOICES.find((row) => row === answer.choice);
|
|
283
|
+
if (!choice)
|
|
284
|
+
return { kind: 'baseline', reason: 'invalid_response' };
|
|
285
|
+
const uncertainty = requiredChoiceUncertainty(answer, [...DELEGATION_CHOICES, KEEP_BASELINE_CHOICE]);
|
|
286
|
+
if (!uncertainty.ok)
|
|
287
|
+
return { kind: 'baseline', reason: uncertainty.reason };
|
|
288
|
+
return { kind: 'effect', effect: { kind: 'select_delegation', choice } };
|
|
289
|
+
}
|
|
267
290
|
function canDropOptional(candidate, noul) {
|
|
268
291
|
return !candidate.pinned
|
|
269
292
|
&& candidate.fresh
|