sneakoscope 10.3.9 → 10.3.10
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 +5 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agent-bridge/async-command.js +5 -3
- package/dist/core/agents/agent-effort-policy.js +3 -6
- package/dist/core/agents/agent-orchestrator.js +0 -1
- package/dist/core/agents/agent-roster.js +2 -4
- package/dist/core/agents/native-worker-backend-router.js +5 -5
- package/dist/core/codex-app/codex-model-catalog.js +0 -3
- package/dist/core/codex-app/openrouter-provider.js +0 -1
- package/dist/core/codex-compat/codex-capability-matrix.js +0 -9
- package/dist/core/codex-compat/codex-runtime-contract.js +0 -1
- package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
- package/dist/core/codex-lb/combined-catalog/builder.js +2 -2
- package/dist/core/codex-lb/combined-catalog/latest-generation.js +53 -0
- package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +2 -3
- package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +2 -3
- package/dist/core/codex-lb/desktop-controller-v3/probe-route.js +23 -0
- package/dist/core/codex-model-guard.js +0 -9
- package/dist/core/commands/naruto-command.js +2 -2
- package/dist/core/doctor/doctor-codex-startup-repair.js +8 -0
- package/dist/core/doctor/stale-subagent-default.js +30 -0
- package/dist/core/harness-conflicts.js +1 -1
- package/dist/core/hooks-runtime/exclusive-gui-surface-gate.js +155 -0
- package/dist/core/hooks-runtime/global-role-pin-refresh.js +25 -0
- package/dist/core/hooks-runtime/hook-context.js +3 -1
- package/dist/core/hooks-runtime/jev-spawn-routing.js +2 -6
- package/dist/core/hooks-runtime/managed-guidance-preflight.js +20 -2
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +3 -3
- package/dist/core/hooks-runtime/payload-signals.js +1 -1
- package/dist/core/hooks-runtime/spawn-role-pin-heal.js +15 -0
- package/dist/core/hooks-runtime/spawn-tool-name.js +7 -0
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +7 -4
- package/dist/core/hooks-runtime/subagent-stop-hook.js +2 -0
- package/dist/core/hooks-runtime.js +12 -1
- package/dist/core/init.js +1 -0
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
- package/dist/core/pipeline-internals/runtime-core.js +2 -3
- package/dist/core/provider/model-router.js +8 -11
- package/dist/core/recallpulse/policy.js +3 -3
- package/dist/core/recallpulse.js +1 -1
- package/dist/core/research/mock-result.js +3 -3
- package/dist/core/research/research-adversarial-review.js +12 -12
- package/dist/core/research/research-claim-synthesizer.js +5 -5
- package/dist/core/research/research-falsification-runner.js +5 -5
- package/dist/core/research/research-super-search.js +2 -2
- package/dist/core/research/research-synthesis-writer.js +4 -4
- package/dist/core/research.js +7 -7
- package/dist/core/routes.js +5 -3
- package/dist/core/subagents/agent-catalog.js +3 -0
- package/dist/core/subagents/exclusive-surface-rule.js +3 -0
- package/dist/core/subagents/exclusive-tool-surface.js +19 -0
- package/dist/core/subagents/model-policy.js +8 -11
- package/dist/core/subagents/model-tiers.js +99 -22
- package/dist/core/subagents/naruto-help-contract.js +7 -7
- package/dist/core/subagents/naruto-host-credentials.js +3 -3
- package/dist/core/subagents/official-subagent-config.js +11 -5
- package/dist/core/subagents/official-subagent-prompt.js +3 -1
- package/dist/core/subagents/role-model-pins.js +84 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +1 -1
- package/dist/scripts/codex-sdk-backend-router-check.js +4 -5
- package/package.json +1 -1
- package/dist/core/codex-app/codex-desktop-routing-ownership.js +0 -90
- package/dist/core/codex-app/codex-models-cache.js +0 -153
- package/dist/core/codex-app/multi-provider-router-support.js +0 -25
- package/dist/core/codex-app/role-model-catalog-priority.js +0 -61
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { withFileLock } from '../locks/file-lock.js';
|
|
4
|
+
import { ensureConfinedDirectory } from '../managed-path-safety.js';
|
|
5
|
+
import { EXCLUSIVE_SURFACE_LABEL, exclusiveSurfaceOfRole } from '../subagents/exclusive-tool-surface.js';
|
|
6
|
+
import { officialSubagentArtifactDir } from './official-subagent-lifecycle.js';
|
|
7
|
+
import { isSpawnAgentToolName, spawnPayloadToolName } from './spawn-tool-name.js';
|
|
8
|
+
const EXCLUSIVE_GUI_SURFACE_LEDGER_SCHEMA = 'sks.exclusive-gui-surface-ledger.v1';
|
|
9
|
+
export const EXCLUSIVE_GUI_SURFACE_LEDGER_FILENAME = 'exclusive-gui-surfaces.json';
|
|
10
|
+
export const PENDING_CLAIM_TTL_MS = 30_000;
|
|
11
|
+
export const RUNNING_CLAIM_TTL_MS = 20 * 60_000;
|
|
12
|
+
const CLAIM_REFRESH_INTERVAL_MS = 30_000;
|
|
13
|
+
const ALLOW = { action: 'allow' };
|
|
14
|
+
function text(value) {
|
|
15
|
+
return String(value ?? '').trim();
|
|
16
|
+
}
|
|
17
|
+
function spawnToolInput(payload) {
|
|
18
|
+
const input = payload?.tool_input || payload?.toolInput || payload?.tool?.input || payload?.input;
|
|
19
|
+
return input && typeof input === 'object' && !Array.isArray(input) ? input : {};
|
|
20
|
+
}
|
|
21
|
+
function ledgerPaths(root, sessionKey) {
|
|
22
|
+
const dir = officialSubagentArtifactDir(root, {}, sessionKey);
|
|
23
|
+
return {
|
|
24
|
+
dir,
|
|
25
|
+
file: path.join(dir, EXCLUSIVE_GUI_SURFACE_LEDGER_FILENAME),
|
|
26
|
+
lock: path.join(dir, '.exclusive-gui-surface.lock')
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function validClaim(raw) {
|
|
30
|
+
return Boolean(raw)
|
|
31
|
+
&& (raw.surface === 'computer_use' || raw.surface === 'browser')
|
|
32
|
+
&& (raw.state === 'pending' || raw.state === 'running')
|
|
33
|
+
&& (raw.agent_id === null || typeof raw.agent_id === 'string')
|
|
34
|
+
&& Number.isFinite(Date.parse(raw.claimed_at))
|
|
35
|
+
&& Number.isFinite(Date.parse(raw.last_seen_at));
|
|
36
|
+
}
|
|
37
|
+
async function readClaims(file) {
|
|
38
|
+
const raw = await readJson(file, null).catch(() => null);
|
|
39
|
+
return Array.isArray(raw?.claims) ? raw.claims.filter(validClaim) : [];
|
|
40
|
+
}
|
|
41
|
+
function isLive(claim, at) {
|
|
42
|
+
return claim.state === 'pending'
|
|
43
|
+
? at - Date.parse(claim.claimed_at) <= PENDING_CLAIM_TTL_MS
|
|
44
|
+
: at - Date.parse(claim.last_seen_at) <= RUNNING_CLAIM_TTL_MS;
|
|
45
|
+
}
|
|
46
|
+
async function withClaims(input, change) {
|
|
47
|
+
const paths = ledgerPaths(input.root, input.sessionKey);
|
|
48
|
+
await ensureConfinedDirectory(path.resolve(input.root), paths.dir);
|
|
49
|
+
return withFileLock({ lockPath: paths.lock, timeoutMs: 5_000, staleMs: 60_000 }, async () => {
|
|
50
|
+
const at = input.now ?? Date.now();
|
|
51
|
+
const before = await readClaims(paths.file);
|
|
52
|
+
const beforeJson = JSON.stringify(before);
|
|
53
|
+
const outcome = change(before.filter((claim) => isLive(claim, at)), at);
|
|
54
|
+
if (JSON.stringify(outcome.claims) !== beforeJson) {
|
|
55
|
+
await writeJsonAtomic(paths.file, { schema: EXCLUSIVE_GUI_SURFACE_LEDGER_SCHEMA, claims: outcome.claims });
|
|
56
|
+
}
|
|
57
|
+
return outcome.result;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function denyMessage(surface, agentType, owner, at) {
|
|
61
|
+
const label = EXCLUSIVE_SURFACE_LABEL[surface];
|
|
62
|
+
const seconds = Math.max(0, Math.round((at - Date.parse(owner.claimed_at)) / 1000));
|
|
63
|
+
const who = owner.agent_id
|
|
64
|
+
? `child ${owner.agent_id}, which has not stopped (it frees itself ${RUNNING_CLAIM_TTL_MS / 60_000} minutes after its last tool call)`
|
|
65
|
+
: `a spawn allowed ${seconds} s ago that has not started yet (if Codex rejected that spawn, the surface frees itself ${PENDING_CLAIM_TTL_MS / 1000} s after it)`;
|
|
66
|
+
return `SKS exclusive GUI surface gate denied spawn_agent (agent_type "${agentType}"): the ${label} surface is already owned by ${who}. ${label} drives one shared screen or session with one pointer and keyboard focus, so only one child may operate it at a time and a second operator would only repeat the same task. Wait for that child to finish and use its returned evidence, or fold this task into that single operator slice; spawn another ${label} child only after it stops. Do not give the same task to another child and do not do it in the parent while that child runs.`;
|
|
67
|
+
}
|
|
68
|
+
export async function claimExclusiveGuiSurface(input) {
|
|
69
|
+
if (!isSpawnAgentToolName(spawnPayloadToolName(input.payload)))
|
|
70
|
+
return ALLOW;
|
|
71
|
+
const spawn = spawnToolInput(input.payload);
|
|
72
|
+
const agentType = text(spawn.agent_type ?? spawn.agentType);
|
|
73
|
+
const surface = exclusiveSurfaceOfRole(agentType);
|
|
74
|
+
if (!surface)
|
|
75
|
+
return ALLOW;
|
|
76
|
+
const toolUseId = text(input.payload?.tool_use_id ?? input.payload?.toolUseId) || null;
|
|
77
|
+
try {
|
|
78
|
+
return await withClaims(input, (claims, at) => {
|
|
79
|
+
const owner = claims.find((claim) => claim.surface === surface);
|
|
80
|
+
if (owner?.state === 'pending' && toolUseId && owner.tool_use_id === toolUseId)
|
|
81
|
+
return { claims, result: ALLOW };
|
|
82
|
+
if (owner)
|
|
83
|
+
return { claims, result: { action: 'block', message: denyMessage(surface, agentType, owner, at) } };
|
|
84
|
+
const iso = new Date(at).toISOString();
|
|
85
|
+
const pending = { surface, state: 'pending', agent_type: agentType, agent_id: null, tool_use_id: toolUseId, claimed_at: iso, last_seen_at: iso };
|
|
86
|
+
return { claims: [...claims, pending], result: ALLOW };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return ALLOW;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function bindExclusiveGuiSurfaceStart(input) {
|
|
94
|
+
const agentId = text(input.payload?.agent_id ?? input.payload?.agentId);
|
|
95
|
+
const agentType = text(input.payload?.agent_type ?? input.payload?.agentType);
|
|
96
|
+
const surface = exclusiveSurfaceOfRole(agentType);
|
|
97
|
+
if (!agentId || !surface)
|
|
98
|
+
return;
|
|
99
|
+
await withClaims(input, (claims, at) => {
|
|
100
|
+
const iso = new Date(at).toISOString();
|
|
101
|
+
const rest = claims.filter((claim) => claim.agent_id !== agentId || claim.surface === surface);
|
|
102
|
+
const target = rest.find((claim) => claim.agent_id === agentId)
|
|
103
|
+
?? rest
|
|
104
|
+
.filter((claim) => claim.state === 'pending' && claim.surface === surface)
|
|
105
|
+
.sort((left, right) => Date.parse(left.claimed_at) - Date.parse(right.claimed_at))[0];
|
|
106
|
+
if (target) {
|
|
107
|
+
target.state = 'running';
|
|
108
|
+
target.agent_id = agentId;
|
|
109
|
+
target.last_seen_at = iso;
|
|
110
|
+
return { claims: rest, result: undefined };
|
|
111
|
+
}
|
|
112
|
+
rest.push({ surface, state: 'running', agent_type: agentType, agent_id: agentId, claimed_at: iso, last_seen_at: iso });
|
|
113
|
+
return { claims: rest, result: undefined };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
export async function releaseExclusiveGuiSurface(input) {
|
|
117
|
+
const agentId = text(input.payload?.agent_id ?? input.payload?.agentId);
|
|
118
|
+
if (!agentId)
|
|
119
|
+
return;
|
|
120
|
+
const { file } = ledgerPaths(input.root, input.sessionKey);
|
|
121
|
+
if (!(await readClaims(file)).some((claim) => claim.agent_id === agentId))
|
|
122
|
+
return;
|
|
123
|
+
await withClaims(input, (claims) => ({
|
|
124
|
+
claims: claims.filter((claim) => claim.agent_id !== agentId),
|
|
125
|
+
result: undefined
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
export async function refreshExclusiveGuiSurfaceOwner(input) {
|
|
129
|
+
const agentId = text(input.payload?.agent_id ?? input.payload?.agentId);
|
|
130
|
+
if (!agentId)
|
|
131
|
+
return;
|
|
132
|
+
const agentType = text(input.payload?.agent_type ?? input.payload?.agentType);
|
|
133
|
+
const surface = exclusiveSurfaceOfRole(agentType);
|
|
134
|
+
const { file } = ledgerPaths(input.root, input.sessionKey);
|
|
135
|
+
const at = input.now ?? Date.now();
|
|
136
|
+
const claims = await readClaims(file);
|
|
137
|
+
const mine = claims.find((claim) => claim.agent_id === agentId);
|
|
138
|
+
if (mine) {
|
|
139
|
+
if (at - Date.parse(mine.last_seen_at) < CLAIM_REFRESH_INTERVAL_MS)
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
else if (!surface || claims.some((claim) => claim.surface === surface && isLive(claim, at))) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
await withClaims(input, (live, now) => {
|
|
146
|
+
const iso = new Date(now).toISOString();
|
|
147
|
+
const claim = live.find((row) => row.agent_id === agentId);
|
|
148
|
+
if (claim)
|
|
149
|
+
claim.last_seen_at = iso;
|
|
150
|
+
else if (surface && !live.some((row) => row.surface === surface)) {
|
|
151
|
+
live.push({ surface, state: 'running', agent_type: agentType, agent_id: agentId, claimed_at: iso, last_seen_at: iso });
|
|
152
|
+
}
|
|
153
|
+
return { claims: live, result: undefined };
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { globalSksRoot, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { tierModelsFingerprint } from '../subagents/model-tiers.js';
|
|
4
|
+
import { refreshStaleManagedRolePins } from '../subagents/role-model-pins.js';
|
|
5
|
+
export const GLOBAL_ROLE_PINS_STAMP_SCHEMA = 'sks.global-role-pins-stamp.v1';
|
|
6
|
+
export function globalRolePinsStampPath() {
|
|
7
|
+
return path.join(globalSksRoot(), 'state', 'global-role-pins.json');
|
|
8
|
+
}
|
|
9
|
+
export async function maybeRefreshGlobalRolePins() {
|
|
10
|
+
const tierModels = tierModelsFingerprint();
|
|
11
|
+
if (tierModels === null)
|
|
12
|
+
return null;
|
|
13
|
+
const stampPath = globalRolePinsStampPath();
|
|
14
|
+
const stamp = await readJson(stampPath, null).catch(() => null);
|
|
15
|
+
if (stamp?.schema === GLOBAL_ROLE_PINS_STAMP_SCHEMA && stamp?.tier_models === tierModels)
|
|
16
|
+
return null;
|
|
17
|
+
const result = await refreshStaleManagedRolePins({ globalOnly: true });
|
|
18
|
+
await writeJsonAtomic(stampPath, {
|
|
19
|
+
schema: GLOBAL_ROLE_PINS_STAMP_SCHEMA,
|
|
20
|
+
tier_models: tierModels,
|
|
21
|
+
role_pins: result,
|
|
22
|
+
checked_at: nowIso()
|
|
23
|
+
});
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
@@ -9,6 +9,7 @@ import { resolveManagedSkillSourcesForAdmission } from './managed-skill-admissio
|
|
|
9
9
|
import { managedSkillDigestBlocksEnforced } from '../verification-profile.js';
|
|
10
10
|
import { looksLikeActiveContinuationPrompt } from './naruto-decision-gate.js';
|
|
11
11
|
import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
|
|
12
|
+
import { EXCLUSIVE_SURFACE_SPAWN_LINE } from '../subagents/exclusive-surface-rule.js';
|
|
12
13
|
import { renderChildModelCriteria, SUBAGENT_MODELS_SETTINGS_HINT } from './subagent-spawn-policy.js';
|
|
13
14
|
import { extractUserPrompt, looksLikeCodexGitAction, looksLikeCodexUiSettingsEvent } from './payload-signals.js';
|
|
14
15
|
const STANDALONE_PARENT_BASE_SKILLS = [
|
|
@@ -20,7 +21,8 @@ const STANDALONE_PARENT_BASE_SKILLS = [
|
|
|
20
21
|
const OFFICIAL_SUBAGENT_SPAWN_CONTRACT_HEAD = [
|
|
21
22
|
'SKS official-subagent spawn contract:',
|
|
22
23
|
'- Full-history forks (`fork_turns="all"`, including the omitted/default full-history mode) inherit the parent agent type, model, and reasoning effort.',
|
|
23
|
-
'- When selecting a custom `agent_type` or overriding `model`/`reasoning_effort`, set `fork_turns="none"` or a positive bounded turn count and put the complete bounded slice contract in `message`.'
|
|
24
|
+
'- When selecting a custom `agent_type` or overriding `model`/`reasoning_effort`, set `fork_turns="none"` or a positive bounded turn count and put the complete bounded slice contract in `message`.',
|
|
25
|
+
`- ${EXCLUSIVE_SURFACE_SPAWN_LINE}`
|
|
24
26
|
];
|
|
25
27
|
const OFFICIAL_SUBAGENT_SPAWN_COMPATIBILITY_CONTEXT = [
|
|
26
28
|
...OFFICIAL_SUBAGENT_SPAWN_CONTRACT_HEAD,
|
|
@@ -7,10 +7,9 @@ import { effortForTier, latestModelForTier, latestTierModelSet } from '../subage
|
|
|
7
7
|
import { readRoleModelPreferences } from '../subagents/role-model-preferences.js';
|
|
8
8
|
import { READ_ONLY_LIST_ROLE } from '../subagents/read-only-list-role.js';
|
|
9
9
|
import { boundedForkTurns, fullHistoryForkContext, renderChildModelList, SUBAGENT_MODELS_SETTINGS_HINT } from './subagent-spawn-policy.js';
|
|
10
|
-
|
|
10
|
+
import { isSpawnAgentToolName, spawnPayloadToolName } from './spawn-tool-name.js';
|
|
11
11
|
function spawnInput(payload) {
|
|
12
|
-
|
|
13
|
-
if (!SPAWN_TOOLS.has(name))
|
|
12
|
+
if (!isSpawnAgentToolName(spawnPayloadToolName(payload)))
|
|
14
13
|
return null;
|
|
15
14
|
const input = payload?.tool_input || payload?.toolInput || payload?.tool?.input;
|
|
16
15
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
@@ -54,9 +53,6 @@ export async function jevSpawnRouting(root, state, payload) {
|
|
|
54
53
|
return openRouterOnlySpawnRouting(root, state, input, allowlist);
|
|
55
54
|
return { input: await tierSpawnRewrite(root, state, input), route: null };
|
|
56
55
|
}
|
|
57
|
-
export async function jevSpawnModelRewrite(root, state, payload) {
|
|
58
|
-
return (await jevSpawnRouting(root, state, payload)).input;
|
|
59
|
-
}
|
|
60
56
|
function listState(allowlist) {
|
|
61
57
|
return { schema: OPENROUTER_ONLY_SCHEMA, enabled: true, subagent_models: allowlist.entries, restore: null, updated_at: null };
|
|
62
58
|
}
|
|
@@ -3,21 +3,37 @@ import path from 'node:path';
|
|
|
3
3
|
import { nowIso, PACKAGE_VERSION, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
4
|
import { ensureConfinedDirectory } from '../managed-path-safety.js';
|
|
5
5
|
import { reconcileManagedProjectPromptGuidance } from '../doctor/current-project-guidance.js';
|
|
6
|
+
import { tierModelsFingerprint } from '../subagents/model-tiers.js';
|
|
7
|
+
import { refreshStaleManagedRolePins } from '../subagents/role-model-pins.js';
|
|
8
|
+
import { maybeRefreshGlobalRolePins } from './global-role-pin-refresh.js';
|
|
6
9
|
export const MANAGED_GUIDANCE_STAMP_SCHEMA = 'sks.managed-guidance-generation.v1';
|
|
7
10
|
export function managedGuidanceStampPath(root) {
|
|
8
11
|
return path.join(root, '.sneakoscope', 'state', 'managed-guidance-generation.json');
|
|
9
12
|
}
|
|
10
13
|
export async function maybeReconcileManagedGuidancePreflight(root) {
|
|
14
|
+
await maybeRefreshGlobalRolePins().catch(() => null);
|
|
11
15
|
const projectRoot = path.resolve(root);
|
|
12
16
|
const stampPath = managedGuidanceStampPath(projectRoot);
|
|
13
17
|
const stamp = await readJson(stampPath, null).catch(() => null);
|
|
14
|
-
|
|
18
|
+
const tierModels = tierModelsFingerprint();
|
|
19
|
+
if (stamp?.schema === MANAGED_GUIDANCE_STAMP_SCHEMA && stamp?.version === PACKAGE_VERSION
|
|
20
|
+
&& (tierModels === null || stamp?.tier_models === tierModels))
|
|
15
21
|
return null;
|
|
16
22
|
if (!(await isSksProject(projectRoot)))
|
|
17
23
|
return null;
|
|
18
24
|
const report = await reconcileManagedProjectPromptGuidance(projectRoot);
|
|
19
25
|
const migration = await queueProjectMigration(projectRoot).catch(() => null);
|
|
20
|
-
|
|
26
|
+
let rolePins = null;
|
|
27
|
+
let rolePinsFailed = false;
|
|
28
|
+
if (tierModels !== null) {
|
|
29
|
+
try {
|
|
30
|
+
rolePins = await refreshStaleManagedRolePins({ root: projectRoot });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
rolePinsFailed = true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (report.errors > 0 || rolePinsFailed)
|
|
21
37
|
return { refreshed: report.refreshed, migration };
|
|
22
38
|
await ensureConfinedDirectory(projectRoot, path.dirname(stampPath));
|
|
23
39
|
await writeJsonAtomic(stampPath, {
|
|
@@ -25,6 +41,8 @@ export async function maybeReconcileManagedGuidancePreflight(root) {
|
|
|
25
41
|
version: PACKAGE_VERSION,
|
|
26
42
|
refreshed: report.refreshed,
|
|
27
43
|
migration,
|
|
44
|
+
tier_models: tierModels,
|
|
45
|
+
role_pins: rolePins,
|
|
28
46
|
checked_at: nowIso()
|
|
29
47
|
});
|
|
30
48
|
return { refreshed: report.refreshed, migration };
|
|
@@ -5,11 +5,11 @@ import { nowIso, readJson, sha256, writeJsonAtomic } from '../fsx.js';
|
|
|
5
5
|
import { ensureConfinedDirectory } from '../managed-path-safety.js';
|
|
6
6
|
import { missionDir } from '../mission.js';
|
|
7
7
|
import { readSubagentEvents } from '../subagents/subagent-evidence.js';
|
|
8
|
+
import { isSpawnAgentToolName } from './spawn-tool-name.js';
|
|
8
9
|
export const PARENT_ORCHESTRATION_GATE_FILENAME = 'parent-orchestration-gate.json';
|
|
9
10
|
export const PARENT_ORCHESTRATION_GATE_SCHEMA = 'sks.parent-orchestration-gate.v1';
|
|
10
11
|
export const PARENT_ORCHESTRATION_MAX_BLOCKS = 2;
|
|
11
12
|
const MAX_TARGETS = 16;
|
|
12
|
-
const SPAWN_TOOL_RE = /^(?:functions\.|collaboration\.)?spawn_agent$/;
|
|
13
13
|
const FILE_EDIT_TOOL_RE = /^(?:functions\.)?(?:apply_patch|edit|write|multiedit|notebookedit|str_replace_editor|str_replace_based_edit_tool|file_write|fs_write|write_file|create_file|edit_file|update_file|delete_file)$/i;
|
|
14
14
|
const SHELL_TOOL_RE = /^(?:functions\.)?(?:shell|shell_command|exec_command|local_shell|bash|container\.exec)$/i;
|
|
15
15
|
const SHELL_WRITE_RE = new RegExp([
|
|
@@ -37,7 +37,7 @@ function spawnRouteOf(raw) {
|
|
|
37
37
|
return route.mode && route.model && route.source ? route : null;
|
|
38
38
|
}
|
|
39
39
|
export function isSpawnToolPayload(payload = {}) {
|
|
40
|
-
return
|
|
40
|
+
return isSpawnAgentToolName(toolNameOf(payload));
|
|
41
41
|
}
|
|
42
42
|
function toolNameOf(payload = {}) {
|
|
43
43
|
return String(payload?.tool_name || payload?.toolName || payload?.tool?.name || payload?.name || '').trim();
|
|
@@ -138,7 +138,7 @@ function collectShellTargets(command, targets) {
|
|
|
138
138
|
}
|
|
139
139
|
export function parentMutationIntent(payload = {}) {
|
|
140
140
|
const toolName = toolNameOf(payload);
|
|
141
|
-
if (!toolName ||
|
|
141
|
+
if (!toolName || isSpawnAgentToolName(toolName))
|
|
142
142
|
return null;
|
|
143
143
|
const input = toolInputOf(payload);
|
|
144
144
|
const command = commandText(input, payload);
|
|
@@ -155,7 +155,7 @@ export function observedParentModelMismatch(model, expectedModel) {
|
|
|
155
155
|
const value = String(model || '').trim();
|
|
156
156
|
if (!value)
|
|
157
157
|
return false;
|
|
158
|
-
return value.toLowerCase() !== expectedModel
|
|
158
|
+
return value.toLowerCase() !== String(expectedModel || '').trim().toLowerCase();
|
|
159
159
|
}
|
|
160
160
|
export function looksLikeCodexUiSettingsEvent(payload = {}) {
|
|
161
161
|
const prompt = stripVisibleDecisionAnswerBlocks(extractUserPrompt(payload));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
|
|
2
|
+
import { refreshStaleManagedRolePins, stalePinForAgentType } from '../subagents/role-model-pins.js';
|
|
3
|
+
import { isSpawnAgentToolName, spawnPayloadToolName } from './spawn-tool-name.js';
|
|
4
|
+
export async function healStaleRolePinForSpawn(payload, root) {
|
|
5
|
+
if (!isSpawnAgentToolName(spawnPayloadToolName(payload)))
|
|
6
|
+
return false;
|
|
7
|
+
const input = payload?.tool_input || payload?.toolInput || payload?.tool?.input || {};
|
|
8
|
+
const agent = String(input.agent_type || input.agentType || '').trim();
|
|
9
|
+
if (!agent || !stalePinForAgentType(agent, { root }))
|
|
10
|
+
return false;
|
|
11
|
+
if (effectiveChildModelAllowlist().mode === 'openrouter_only')
|
|
12
|
+
return false;
|
|
13
|
+
await refreshStaleManagedRolePins({ root });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const SPAWN_AGENT_TOOL_RE = /^(?:functions\.)?(?:(?:collaboration|multi_agent_v\d+)[._]?)?spawn_agent$/;
|
|
2
|
+
export function isSpawnAgentToolName(name) {
|
|
3
|
+
return SPAWN_AGENT_TOOL_RE.test(String(name ?? '').trim());
|
|
4
|
+
}
|
|
5
|
+
export function spawnPayloadToolName(payload = {}) {
|
|
6
|
+
return String(payload?.tool_name || payload?.toolName || payload?.tool?.name || '');
|
|
7
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
|
|
2
2
|
import { READ_ONLY_LIST_ROLE } from '../subagents/read-only-list-role.js';
|
|
3
|
+
import { stalePinBlockReason, stalePinForAgentType } from '../subagents/role-model-pins.js';
|
|
4
|
+
import { isSpawnAgentToolName, spawnPayloadToolName } from './spawn-tool-name.js';
|
|
3
5
|
import { effectiveChildModelAllowlist, isAllowedChildModel } from '../subagents/child-model-allowlist.js';
|
|
4
|
-
const SPAWN_TOOLS = new Set(['spawn_agent', 'collaboration.spawn_agent', 'functions.spawn_agent']);
|
|
5
6
|
export const SUBAGENT_MODELS_SETTINGS_HINT = 'SKS Control Center > Subagent Models';
|
|
6
7
|
export function renderChildModelList(allowlist) {
|
|
7
8
|
if (allowlist.mode !== 'openrouter_only')
|
|
@@ -31,9 +32,8 @@ export function boundedForkTurns(value) {
|
|
|
31
32
|
return value === 'none' || /^[1-9]\d*$/.test(String(value || ''));
|
|
32
33
|
}
|
|
33
34
|
const FORK_BLOCK_REASON = 'SKS child spawns require fork_turns="none" or a positive bounded turn count. Retry with the complete slice contract in message; full-history/default forks cannot carry an explicit child model.';
|
|
34
|
-
export function subagentSpawnPolicyBlockReason(payload = {}) {
|
|
35
|
-
|
|
36
|
-
if (!SPAWN_TOOLS.has(name))
|
|
35
|
+
export function subagentSpawnPolicyBlockReason(payload = {}, opts = {}) {
|
|
36
|
+
if (!isSpawnAgentToolName(spawnPayloadToolName(payload)))
|
|
37
37
|
return null;
|
|
38
38
|
const input = payload.tool_input || payload.toolInput || payload.tool?.input || {};
|
|
39
39
|
const allowlist = effectiveChildModelAllowlist();
|
|
@@ -52,6 +52,9 @@ export function subagentSpawnPolicyBlockReason(payload = {}) {
|
|
|
52
52
|
if (!allowed.has(String(input.model || ''))) {
|
|
53
53
|
return `SKS children must name a current model: ${[...allowed].join(', ')} (the latest fast, balanced, context, or deep tier). Retry spawn_agent with the slice contract model, which the SKS hook re-seals when Jev mode is on, and fork_turns="none" or a positive bounded turn count. Include the complete slice contract in message; do not inherit the parent model.`;
|
|
54
54
|
}
|
|
55
|
+
const stale = opts.root ? stalePinForAgentType(String(input.agent_type || input.agentType || ''), { root: opts.root }) : null;
|
|
56
|
+
if (stale)
|
|
57
|
+
return stalePinBlockReason(stale);
|
|
55
58
|
}
|
|
56
59
|
if (!boundedForkTurns(input.fork_turns))
|
|
57
60
|
return FORK_BLOCK_REASON;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { releaseExclusiveGuiSurface } from './exclusive-gui-surface-gate.js';
|
|
1
2
|
import { officialSubagentArtifactDir, recordAndRefreshSubagentEvidence, recordOfficialSubagentLifecycleCaptureFailure } from './official-subagent-lifecycle.js';
|
|
2
3
|
import { clearSubagentSkillAvailabilityGuards } from './subagent-skill-availability.js';
|
|
3
4
|
export async function handleSubagentStop(root, state, payload = {}, sessionKey = null) {
|
|
5
|
+
await releaseExclusiveGuiSurface({ root, sessionKey, payload }).catch(() => null);
|
|
4
6
|
const artifactDir = officialSubagentArtifactDir(root, state, sessionKey);
|
|
5
7
|
let lifecycleFailure = null;
|
|
6
8
|
let capturedEvent = null;
|
|
@@ -36,6 +36,9 @@ import { effectiveChildModelAllowlist } from './subagents/child-model-allowlist.
|
|
|
36
36
|
import { maybeReconcileManagedGuidancePreflight } from './hooks-runtime/managed-guidance-preflight.js';
|
|
37
37
|
import { evaluateParentOrchestrationGate, isSpawnToolPayload, recordParentOrchestrationSpawn } from './hooks-runtime/parent-orchestration-gate.js';
|
|
38
38
|
import { subagentSpawnPolicyBlockReason } from './hooks-runtime/subagent-spawn-policy.js';
|
|
39
|
+
import { healStaleRolePinForSpawn } from './hooks-runtime/spawn-role-pin-heal.js';
|
|
40
|
+
import { bindExclusiveGuiSurfaceStart, claimExclusiveGuiSurface, refreshExclusiveGuiSurfaceOwner } from './hooks-runtime/exclusive-gui-surface-gate.js';
|
|
41
|
+
import { EXCLUSIVE_SURFACE_CHILD_RULE } from './subagents/exclusive-surface-rule.js';
|
|
39
42
|
import { withFileLock } from './locks/file-lock.js';
|
|
40
43
|
import { ensureConfinedDirectory, inspectConfinedPath, removeManagedPathVerified } from './managed-path-safety.js';
|
|
41
44
|
import { buildBoundWaveParentGuidance, renderWaveParentGuidance } from './subagents/wave-parent-guidance.js';
|
|
@@ -156,6 +159,7 @@ async function evaluateHookPayloadWithPlan(name, payload, opts, jevPlan) {
|
|
|
156
159
|
return withNarutoDecision({ continue: true });
|
|
157
160
|
}
|
|
158
161
|
async function hookSubagentStart(root, state, payload = {}, sessionKey = null) {
|
|
162
|
+
await bindExclusiveGuiSurfaceStart({ root, sessionKey, payload }).catch(() => null);
|
|
159
163
|
const artifactDir = officialSubagentArtifactDir(root, state, sessionKey);
|
|
160
164
|
const sessionArtifactDir = officialSubagentArtifactDir(root, {}, sessionKey);
|
|
161
165
|
const skillGuardBinding = officialSubagentSkillGuardBinding(state);
|
|
@@ -184,6 +188,7 @@ async function hookSubagentStart(root, state, payload = {}, sessionKey = null) {
|
|
|
184
188
|
'Keep the model and reasoning effort your spawn call sealed; do not retarget them.',
|
|
185
189
|
'Use max_depth=1. Naruto children must not spawn children.',
|
|
186
190
|
'Do not duplicate an already assigned slice.',
|
|
191
|
+
EXCLUSIVE_SURFACE_CHILD_RULE,
|
|
187
192
|
'Parallel writes require disjoint paths; serialize overlapping paths.',
|
|
188
193
|
'Finish only your assigned slice, return a concise result, then stop so the Naruto parent can close this thread.'
|
|
189
194
|
].join(' ') : '';
|
|
@@ -648,12 +653,14 @@ async function consumeActiveOfficialWorkflowQueue(root, missionId, workflowRunId
|
|
|
648
653
|
}
|
|
649
654
|
}
|
|
650
655
|
async function hookPreTool(root, state, payload, noQuestion, sessionKey = null) {
|
|
656
|
+
await refreshExclusiveGuiSurfaceOwner({ root, sessionKey, payload }).catch(() => null);
|
|
657
|
+
await healStaleRolePinForSpawn(payload, root).catch(() => false);
|
|
651
658
|
const jevSpawn = await jevSpawnRouting(root, state, payload).catch(() => null);
|
|
652
659
|
const jevSpawnInput = jevSpawn?.input ?? null;
|
|
653
660
|
const spawnPayload = jevSpawnInput
|
|
654
661
|
? { ...payload, tool_input: jevSpawnInput, toolInput: jevSpawnInput }
|
|
655
662
|
: payload;
|
|
656
|
-
const spawnPolicyBlock = subagentSpawnPolicyBlockReason(spawnPayload);
|
|
663
|
+
const spawnPolicyBlock = subagentSpawnPolicyBlockReason(spawnPayload, { root });
|
|
657
664
|
if (spawnPolicyBlock)
|
|
658
665
|
return { decision: 'block', permissionDecision: 'deny', reason: spawnPolicyBlock };
|
|
659
666
|
const artifactDir = officialSubagentArtifactDir(root, state, sessionKey);
|
|
@@ -725,6 +732,10 @@ async function hookPreTool(root, state, payload, noQuestion, sessionKey = null)
|
|
|
725
732
|
if (hostCapabilityDecision && hostCapabilityDecision.continue !== true)
|
|
726
733
|
return hostCapabilityDecision;
|
|
727
734
|
if (isSpawnToolPayload(spawnPayload)) {
|
|
735
|
+
const guiSurface = await claimExclusiveGuiSurface({ root, sessionKey, payload: spawnPayload }).catch(() => null);
|
|
736
|
+
if (guiSurface?.action === 'block') {
|
|
737
|
+
return { decision: 'block', permissionDecision: 'deny', reason: guiSurface.message };
|
|
738
|
+
}
|
|
728
739
|
await recordParentOrchestrationSpawn(root, state, jevSpawn?.route ? { spawnRoute: jevSpawn.route } : {}).catch(() => null);
|
|
729
740
|
}
|
|
730
741
|
const waveGuidance = await parentWaveGuidanceContext(root, state, sessionKey).catch(() => '');
|
package/dist/core/init.js
CHANGED
|
@@ -214,6 +214,7 @@ const AGENTS_BLOCK = [
|
|
|
214
214
|
'- Codex native `/goal` is the only persisted goal owner. Goal objectives must state the outcome, scope, constraints, verification, done-when conditions, stop conditions, and non-goals.',
|
|
215
215
|
'- Answer, Help, Goal, and tiny DFix stay parent-owned. Implementation routes to Naruto (`$sks-naruto`, alias `$sks-work`; standalone `sks naruto run`), which is parent orchestration only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement those slices in the parent thread. The SKS PreToolUse hook denies parent-thread source edits until the first child thread starts and while children are still running; .sneakoscope artifacts stay parent-writable, and Jev may release a confirmed orchestration-scaffolding edit.',
|
|
216
216
|
'- The parent owns decomposition, integration, verification, and the final answer. Delegate implementation slices with disjoint write scopes, reuse capacity across root-owned waves, and never nest subagents.',
|
|
217
|
+
'- Computer Use and browser are exclusive GUI surfaces: one child per surface at a time, with all of that surface\'s work in one slice. Never fan the same GUI task out to several children.',
|
|
217
218
|
'- 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 Naruto child spawn and SKS seals it. A user role preference stays authoritative. Do not pick a child model yourself.',
|
|
218
219
|
'- Preserve the user-selected parent model, reasoning effort, and service tier. Parent settings stay on the parent thread. A role preference wins over the Jev seal for that role.',
|
|
219
220
|
'- SKS child spawns must pass the sealed model and reasoning effort with `fork_turns="none"` or a positive bounded turn count; carry the complete bounded slice contract in `message`. Never use full-history inheritance for children. Full-history forks (`fork_turns="all"`, including the omitted default) inherit the parent agent type, model, and reasoning effort and must not be combined with `agent_type`, `model`, or `reasoning_effort`.',
|
|
@@ -590,7 +590,7 @@ function officialSubagentRole(input) {
|
|
|
590
590
|
codex_name: input.codexName,
|
|
591
591
|
description: input.description,
|
|
592
592
|
model_policy: profile.policy,
|
|
593
|
-
model
|
|
593
|
+
get model() { return subagentModelProfile(input.policy).model; },
|
|
594
594
|
model_reasoning_effort: profile.modelReasoningEffort,
|
|
595
595
|
...(input.sandbox ? { sandbox: input.sandbox } : {}),
|
|
596
596
|
nickname_candidates: input.nicknames,
|
|
@@ -27,7 +27,6 @@ import { coreEngineeringDirectiveReferenceText, engineeringSanityPolicyText } fr
|
|
|
27
27
|
import { classifyTaskProfile, gateProfileForTask } from '../runtime/task-profile.js';
|
|
28
28
|
import { chooseVerificationBudget } from '../runtime/verification-budget.js';
|
|
29
29
|
import { stopFinalizationRitualsEnforced } from '../verification-profile.js';
|
|
30
|
-
import { NARUTO_PARENT_MODEL } from '../subagents/model-policy.js';
|
|
31
30
|
import { HARD_NARUTO_MAX_THREADS } from '../subagents/thread-budget.js';
|
|
32
31
|
import { NARUTO_GATE_FILENAME, NARUTO_SUMMARY_FILENAME, SUBAGENT_PLAN_FILENAME, prepareOfficialSubagentMission } from '../subagents/official-subagent-preparation.js';
|
|
33
32
|
import { buildSubagentEvidence, readSubagentEvents, recordSubagentEvent, SUBAGENT_EVIDENCE_FILENAME, SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME } from '../subagents/subagent-evidence.js';
|
|
@@ -1320,7 +1319,7 @@ async function prepareNaruto(root, route, task, required, opts = {}) {
|
|
|
1320
1319
|
prompt: cleanTask
|
|
1321
1320
|
})
|
|
1322
1321
|
});
|
|
1323
|
-
const { budget, delegationPrompt, workflowRunId, taskProfile, officialConfig, triwikiAttention, parentModelMatch } = preparation;
|
|
1322
|
+
const { budget, delegationPrompt, workflowRunId, taskProfile, officialConfig, triwikiAttention, parentModelMatch, plan } = preparation;
|
|
1324
1323
|
const routeContextPayload = {
|
|
1325
1324
|
route: 'Naruto',
|
|
1326
1325
|
command: '$Naruto',
|
|
@@ -1337,7 +1336,7 @@ async function prepareNaruto(root, route, task, required, opts = {}) {
|
|
|
1337
1336
|
requested_subagents_explicit: requestedSubagents !== undefined,
|
|
1338
1337
|
max_threads: budget.maxThreads,
|
|
1339
1338
|
max_depth: budget.maxDepth,
|
|
1340
|
-
parent_model_policy:
|
|
1339
|
+
parent_model_policy: plan.parent_model_policy,
|
|
1341
1340
|
observed_parent_model: observedParentModel,
|
|
1342
1341
|
parent_model_match: parentModelMatch,
|
|
1343
1342
|
config_sources: officialConfig.sources,
|
|
@@ -17,7 +17,7 @@ export function narutoModels() {
|
|
|
17
17
|
const E2E_WORK_RE = /(e2e|end[-\s]?to[-\s]?end|test_execution|browser|chrome|computer[-\s]?use|computer\s+use|cross[-\s]?app|playwright|selenium|puppeteer|브라우저|컴퓨터\s*유즈)/i;
|
|
18
18
|
export async function routeModel(category, opts = {}) {
|
|
19
19
|
if (opts.narutoOnly) {
|
|
20
|
-
return
|
|
20
|
+
return routeNarutoTierModel({
|
|
21
21
|
category,
|
|
22
22
|
...(opts.taskText !== undefined ? { taskText: opts.taskText } : {}),
|
|
23
23
|
...(opts.riskText !== undefined ? { riskText: opts.riskText } : {}),
|
|
@@ -33,10 +33,10 @@ export async function routeModel(category, opts = {}) {
|
|
|
33
33
|
const reasoning = opts.lbHealth?.quota_low && policy.reasoning === 'xhigh' ? 'high' : policy.reasoning;
|
|
34
34
|
return { model, reasoning, serviceTier: policy.serviceTier };
|
|
35
35
|
}
|
|
36
|
-
export function
|
|
36
|
+
export function routeNarutoTierModel(input = {}) {
|
|
37
37
|
const category = input.category || 'agentic';
|
|
38
38
|
const explicitRequested = String(input.explicitModel || '').trim();
|
|
39
|
-
const explicit =
|
|
39
|
+
const explicit = normalizeNarutoTierModel(input.explicitModel);
|
|
40
40
|
const invalidExplicit = Boolean(explicitRequested && !explicit);
|
|
41
41
|
const explicitHighRisk = /critical|forensic|security|database|migration|release|production|high[- ]?risk|data\s*loss|permission|auth|보안|데이터베이스|마이그레이션|릴리스|운영|고위험/i.test(String(input.riskText || ''));
|
|
42
42
|
const automatic = decideSubagentModel({
|
|
@@ -53,7 +53,7 @@ export function routeNarutoGpt56Model(input = {}) {
|
|
|
53
53
|
const preferred = explicit || automatic.model;
|
|
54
54
|
const available = input.availableModels == null
|
|
55
55
|
? narutoModels()
|
|
56
|
-
: input.availableModels.map(
|
|
56
|
+
: input.availableModels.map(normalizeNarutoTierModel).filter((model) => Boolean(model));
|
|
57
57
|
const degraded = new Set((input.degradedModels || []).map((model) => String(model).toLowerCase()));
|
|
58
58
|
const usable = available.filter((model) => !degraded.has(model));
|
|
59
59
|
const availableEfforts = effortsForModel(input.availableModelEfforts, preferred);
|
|
@@ -63,16 +63,13 @@ export function routeNarutoGpt56Model(input = {}) {
|
|
|
63
63
|
const model = !invalidExplicit && usable.includes(preferred) && (availableEfforts == null || availableEfforts.includes(intendedReasoning)) ? preferred : '';
|
|
64
64
|
return { model, reasoning: intendedReasoning, serviceTier: 'fast' };
|
|
65
65
|
}
|
|
66
|
-
export function
|
|
67
|
-
return
|
|
66
|
+
export function isNarutoTierModel(value) {
|
|
67
|
+
return normalizeNarutoTierModel(value) !== null;
|
|
68
68
|
}
|
|
69
|
-
export function
|
|
69
|
+
export function normalizeNarutoTierModel(value) {
|
|
70
70
|
const model = String(value || '').trim().toLowerCase();
|
|
71
71
|
return narutoModels().includes(model) ? model : null;
|
|
72
72
|
}
|
|
73
|
-
export function childInheritsActiveMainModel(_value) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
73
|
export function categoryForWorkerRole(role, taskText = '') {
|
|
77
74
|
const text = `${String(role || '')} ${String(taskText || '')}`.toLowerCase();
|
|
78
75
|
if (/(refactor|re-?architect|리팩터|아키텍처)/i.test(text))
|
|
@@ -95,7 +92,7 @@ export function modelRouteReason(category, choice, opts = {}) {
|
|
|
95
92
|
return `${category}->blocked (explicit model unavailable)`;
|
|
96
93
|
if (opts.explicit)
|
|
97
94
|
return `${category}->${model} (explicit model preserved)`;
|
|
98
|
-
if (
|
|
95
|
+
if (isNarutoTierModel(choice.model))
|
|
99
96
|
return `${category}->${model}@${choice.reasoning} (official subagent model policy)`;
|
|
100
97
|
const suffix = opts.quotaLow ? 'quota discipline' : 'Codex catalog passthrough';
|
|
101
98
|
return `${category}->${model} (${suffix})`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { judgmentSubagentModel, DEEP_SUBAGENT_EFFORT } from '../subagents/model-policy.js';
|
|
2
2
|
export const RECALLPULSE_DECISION_ARTIFACT = 'recallpulse-decision.json';
|
|
3
3
|
export const RECALLPULSE_HISTORY_ARTIFACT = 'recallpulse-history.jsonl';
|
|
4
4
|
export const MISSION_STATUS_LEDGER_ARTIFACT = 'mission-status-ledger.json';
|
|
@@ -201,7 +201,7 @@ export const RESEARCH_REVIEWER_CONTRACT = Object.freeze([
|
|
|
201
201
|
...agent,
|
|
202
202
|
persona_boundary: 'Apply only the assigned review dimension and report evidence-bound findings.',
|
|
203
203
|
custom_agent: 'research_reviewer',
|
|
204
|
-
model:
|
|
205
|
-
reasoning_effort:
|
|
204
|
+
model: judgmentSubagentModel(),
|
|
205
|
+
reasoning_effort: DEEP_SUBAGENT_EFFORT,
|
|
206
206
|
service_tier: 'fast'
|
|
207
207
|
})));
|
package/dist/core/recallpulse.js
CHANGED
|
@@ -199,7 +199,7 @@ export async function evaluateRecallPulseFixtures(root, opts = {}) {
|
|
|
199
199
|
fixture('repeated-stop-hook-blocker', true, 'Duplicate suppression keys collapse repeated blocker text into one durable status row.'),
|
|
200
200
|
fixture('hook-only-status-visibility', true, 'mission-status-ledger.json preserves recoverable user-visible status.'),
|
|
201
201
|
fixture('research-persona-missing', true, 'Research validation blocks missing agent display_name/persona/persona_boundary.'),
|
|
202
|
-
fixture('research-model-policy-not-
|
|
202
|
+
fixture('research-model-policy-not-deep-max', true, 'Research validation blocks reviewer rows that are not bound to the research_reviewer latest deep-tier max policy.'),
|
|
203
203
|
fixture('research-review-evidence-missing', true, 'Research validation blocks reviewer outcomes without source evidence, falsifiers, or probes.'),
|
|
204
204
|
fixture('research-impersonation', true, 'Research validation blocks persona-boundary violations.'),
|
|
205
205
|
fixture('oversized-l1', true, 'L1 token and item limits reject oversized active recall.'),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { judgmentSubagentModel, DEEP_SUBAGENT_EFFORT } from '../subagents/model-policy.js';
|
|
3
3
|
import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
4
|
import { buildClaimEvidenceMatrixFromLedgers, writeClaimEvidenceMatrix } from './claim-evidence-matrix.js';
|
|
5
5
|
import { DEFAULT_RESEARCH_QUALITY_CONTRACT, writeResearchQualityContract } from './research-quality-contract.js';
|
|
@@ -174,8 +174,8 @@ export async function writeMockResearchResult(dir, plan) {
|
|
|
174
174
|
mandate: agent.mandate,
|
|
175
175
|
model_policy: {
|
|
176
176
|
custom_agent: RESEARCH_REVIEWER_CUSTOM_AGENT,
|
|
177
|
-
model:
|
|
178
|
-
reasoning_effort:
|
|
177
|
+
model: judgmentSubagentModel(),
|
|
178
|
+
reasoning_effort: DEEP_SUBAGENT_EFFORT,
|
|
179
179
|
enforcement_source: 'mock_fixture'
|
|
180
180
|
},
|
|
181
181
|
query_set: sourceLedger.queries.filter((query) => query.agent_id === agent.id).map((query) => query.query),
|