claude-dev-env 7.6.1 → 7.7.0
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/AGENTS.md +48 -71
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/advisor/reference/advisor-block.md +1 -1
- package/_shared/advisor/reference/astra-rung.md +2 -2
- package/_shared/advisor/scripts/codex_astra_advisor.py +122 -23
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +76 -9
- package/_shared/advisor/scripts/tests/test_advisor_route_constants.py +51 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +52 -2
- package/bin/install-constants.mjs +3 -0
- package/bin/install-transaction.mjs +41 -44
- package/bin/install.cursor-rules.test.mjs +311 -3
- package/bin/install.mjs +156 -95
- package/bin/install.transaction.test.mjs +61 -1
- package/bin/pstack-model-preferences.codex.json +1 -0
- package/bin/pstack.mjs +28 -6
- package/bin/pstack.test.mjs +48 -0
- package/bin/select-pstack-models.test.mjs +375 -26
- package/bin/subagent-model-policy.test.mjs +399 -0
- package/docs/codex-compatibility.md +19 -5
- package/docs/subagent-model-routing.md +50 -0
- package/hooks/hooks.json +10 -0
- package/hooks/routing/subagent_model_routing.mjs +81 -0
- package/hooks/routing/subagent_model_routing.test.mjs +218 -0
- package/package.json +1 -1
- package/rules/pstack-models.md +22 -0
- package/rules/subagent-model-policy.json +85 -0
- package/scripts/resolve_advisor_model_route.mjs +52 -0
- package/scripts/select_pstack_models.mjs +203 -38
- package/scripts/subagent_model_policy.mjs +620 -0
- package/scripts/tests/resolve_advisor_model_route.test.mjs +51 -0
- package/scripts/tests/test_codex_compat_materializer.py +15 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { buildSubagentModelRoutingResponse } from './subagent_model_routing.mjs';
|
|
7
|
+
|
|
8
|
+
const targetToolName = 'multi_agent_v1__spawn_agent';
|
|
9
|
+
|
|
10
|
+
function buildHookPayload(toolInput, metadata = {}) {
|
|
11
|
+
return { tool_name: targetToolName, tool_input: toolInput, ...metadata };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test('remaps Terra Medium to Luna Xhigh', () => {
|
|
15
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
|
|
16
|
+
model: 'Terra',
|
|
17
|
+
reasoning_effort: 'Medium',
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
assert.equal(hookResponse.hookSpecificOutput.updatedInput.model, 'gpt-5.6-luna');
|
|
21
|
+
assert.equal(
|
|
22
|
+
hookResponse.hookSpecificOutput.updatedInput.reasoning_effort,
|
|
23
|
+
'xhigh',
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('emits only the allow response shape after a remap', () => {
|
|
28
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
|
|
29
|
+
model: 'Terra',
|
|
30
|
+
reasoning_effort: 'medium',
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
assert.deepEqual(Object.keys(hookResponse), ['hookSpecificOutput']);
|
|
34
|
+
assert.deepEqual(Object.keys(hookResponse.hookSpecificOutput), [
|
|
35
|
+
'hookEventName',
|
|
36
|
+
'permissionDecision',
|
|
37
|
+
'updatedInput',
|
|
38
|
+
]);
|
|
39
|
+
assert.deepEqual(hookResponse.hookSpecificOutput, {
|
|
40
|
+
hookEventName: 'PreToolUse',
|
|
41
|
+
permissionDecision: 'allow',
|
|
42
|
+
updatedInput: {
|
|
43
|
+
model: 'gpt-5.6-luna',
|
|
44
|
+
reasoning_effort: 'xhigh',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('preserves every non-routing tool field', () => {
|
|
50
|
+
const toolInput = {
|
|
51
|
+
assignment: 'preserve',
|
|
52
|
+
message: 'do the work',
|
|
53
|
+
child_permissions: { shell: 'ask' },
|
|
54
|
+
output_contract: ['summary'],
|
|
55
|
+
model: 'Terra',
|
|
56
|
+
reasoning_effort: 'medium',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload(toolInput));
|
|
60
|
+
|
|
61
|
+
assert.deepEqual(hookResponse.hookSpecificOutput.updatedInput, {
|
|
62
|
+
assignment: 'preserve',
|
|
63
|
+
message: 'do the work',
|
|
64
|
+
child_permissions: { shell: 'ask' },
|
|
65
|
+
output_contract: ['summary'],
|
|
66
|
+
model: 'gpt-5.6-luna',
|
|
67
|
+
reasoning_effort: 'xhigh',
|
|
68
|
+
});
|
|
69
|
+
assert.deepEqual(toolInput, {
|
|
70
|
+
assignment: 'preserve',
|
|
71
|
+
message: 'do the work',
|
|
72
|
+
child_permissions: { shell: 'ask' },
|
|
73
|
+
output_contract: ['summary'],
|
|
74
|
+
model: 'Terra',
|
|
75
|
+
reasoning_effort: 'medium',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('passes unrelated tool calls through', () => {
|
|
80
|
+
const hookResponse = buildSubagentModelRoutingResponse({
|
|
81
|
+
tool_name: 'Task',
|
|
82
|
+
tool_input: { model: 'Terra', reasoning_effort: 'medium' },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
assert.deepEqual(hookResponse, {});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('blocks malformed policy, unknown input, unavailable replacement, and untrusted advisor roles', () => {
|
|
89
|
+
const malformedPolicyResponse = buildSubagentModelRoutingResponse(
|
|
90
|
+
buildHookPayload({ model: 'Terra', reasoning_effort: 'medium' }),
|
|
91
|
+
{ policy: {} },
|
|
92
|
+
);
|
|
93
|
+
const unknownInputResponse = buildSubagentModelRoutingResponse(
|
|
94
|
+
buildHookPayload({ model: 'Unknown', reasoning_effort: 'medium' }),
|
|
95
|
+
);
|
|
96
|
+
const unavailableReplacementResponse = buildSubagentModelRoutingResponse(
|
|
97
|
+
buildHookPayload({ model: 'Terra', reasoning_effort: 'medium' }),
|
|
98
|
+
{ availableModelIds: ['gpt-5.6-terra'] },
|
|
99
|
+
);
|
|
100
|
+
const untrustedAdvisorResponse = buildSubagentModelRoutingResponse(
|
|
101
|
+
buildHookPayload({
|
|
102
|
+
agent_type: 'advisor',
|
|
103
|
+
model: 'Astra',
|
|
104
|
+
reasoning_effort: 'medium',
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const allBlockedResponses = [
|
|
109
|
+
[malformedPolicyResponse, /policy/],
|
|
110
|
+
[unknownInputResponse, /model is unknown/],
|
|
111
|
+
[unavailableReplacementResponse, /replacement model is unavailable/],
|
|
112
|
+
[untrustedAdvisorResponse, /advisor role is not trusted/],
|
|
113
|
+
];
|
|
114
|
+
for (const [eachBlockedResponse, eachExpectedDiagnostic] of allBlockedResponses) {
|
|
115
|
+
assert.equal(eachBlockedResponse.hookSpecificOutput.permissionDecision, 'deny');
|
|
116
|
+
assert.equal(eachBlockedResponse.hookSpecificOutput.hookEventName, 'PreToolUse');
|
|
117
|
+
assert.equal(typeof eachBlockedResponse.hookSpecificOutput.permissionDecisionReason, 'string');
|
|
118
|
+
assert.match(
|
|
119
|
+
eachBlockedResponse.hookSpecificOutput.permissionDecisionReason,
|
|
120
|
+
eachExpectedDiagnostic,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('allows a registered session advisor with trusted session metadata', () => {
|
|
126
|
+
const hookResponse = buildSubagentModelRoutingResponse(
|
|
127
|
+
buildHookPayload({
|
|
128
|
+
agent_type: 'session-advisor',
|
|
129
|
+
model: 'Astra',
|
|
130
|
+
reasoning_effort: 'high',
|
|
131
|
+
}),
|
|
132
|
+
{
|
|
133
|
+
trustedSessionMetadata: {
|
|
134
|
+
authorized: true,
|
|
135
|
+
registeredAgentType: 'session-advisor',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
assert.deepEqual(hookResponse.hookSpecificOutput.updatedInput, {
|
|
141
|
+
agent_type: 'session-advisor',
|
|
142
|
+
model: 'gpt-6-astra',
|
|
143
|
+
reasoning_effort: 'high',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('a session advisor name without trusted session metadata is blocked', () => {
|
|
148
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
|
|
149
|
+
agent_type: 'session-advisor',
|
|
150
|
+
model: 'Astra',
|
|
151
|
+
reasoning_effort: 'high',
|
|
152
|
+
}));
|
|
153
|
+
assert.equal(hookResponse.hookSpecificOutput.permissionDecision, 'deny');
|
|
154
|
+
assert.equal(
|
|
155
|
+
hookResponse.hookSpecificOutput.permissionDecisionReason,
|
|
156
|
+
'advisor role is not trusted',
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('payload advisor metadata cannot grant advisory authorization', () => {
|
|
161
|
+
const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload(
|
|
162
|
+
{
|
|
163
|
+
agent_type: 'advisor',
|
|
164
|
+
model: 'Astra',
|
|
165
|
+
reasoning_effort: 'high',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
trustedSessionMetadata: {
|
|
169
|
+
authorized: true,
|
|
170
|
+
registeredAgentType: 'advisor',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
));
|
|
174
|
+
assert.equal(hookResponse.hookSpecificOutput.permissionDecision, 'deny');
|
|
175
|
+
assert.equal(
|
|
176
|
+
hookResponse.hookSpecificOutput.permissionDecisionReason,
|
|
177
|
+
'advisor role is not trusted',
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('the standalone hook command blocks an advisor without host metadata', () => {
|
|
182
|
+
const output = execFileSync(
|
|
183
|
+
process.execPath,
|
|
184
|
+
[fileURLToPath(new URL('./subagent_model_routing.mjs', import.meta.url))],
|
|
185
|
+
{
|
|
186
|
+
encoding: 'utf8',
|
|
187
|
+
input: JSON.stringify(buildHookPayload({
|
|
188
|
+
agent_type: 'session-advisor',
|
|
189
|
+
model: 'Astra',
|
|
190
|
+
reasoning_effort: 'high',
|
|
191
|
+
})),
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
const hookResponse = JSON.parse(output);
|
|
195
|
+
assert.equal(hookResponse.hookSpecificOutput.permissionDecision, 'deny');
|
|
196
|
+
assert.equal(
|
|
197
|
+
hookResponse.hookSpecificOutput.permissionDecisionReason,
|
|
198
|
+
'advisor role is not trusted',
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('keeps a second routing pass unchanged', () => {
|
|
203
|
+
const firstResponse = buildSubagentModelRoutingResponse(buildHookPayload({
|
|
204
|
+
model: 'Terra',
|
|
205
|
+
reasoning_effort: 'medium',
|
|
206
|
+
message: 'keep',
|
|
207
|
+
}));
|
|
208
|
+
const secondResponse = buildSubagentModelRoutingResponse(buildHookPayload(
|
|
209
|
+
firstResponse.hookSpecificOutput.updatedInput,
|
|
210
|
+
));
|
|
211
|
+
|
|
212
|
+
assert.deepEqual(secondResponse, {});
|
|
213
|
+
assert.deepEqual(firstResponse.hookSpecificOutput.updatedInput, {
|
|
214
|
+
model: 'gpt-5.6-luna',
|
|
215
|
+
reasoning_effort: 'xhigh',
|
|
216
|
+
message: 'keep',
|
|
217
|
+
});
|
|
218
|
+
});
|
package/package.json
CHANGED
package/rules/pstack-models.md
CHANGED
|
@@ -21,12 +21,21 @@ The file contains this shape:
|
|
|
21
21
|
```json
|
|
22
22
|
{
|
|
23
23
|
"host": "current-host",
|
|
24
|
+
"defaultEffort": "medium",
|
|
24
25
|
"modelsByRole": {
|
|
25
26
|
"feature, refactoring": ["confirmed-host-model"]
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
Automatic model and effort routing reads
|
|
32
|
+
`rules/subagent-model-policy.json` from the same agents home. Edit that file to
|
|
33
|
+
change the next routing decision. The installer creates it only when it is
|
|
34
|
+
absent. The native hook blocks advisor role claims because the current Codex hook
|
|
35
|
+
input has no host-provided role binding. The Python advisor helper passes trusted
|
|
36
|
+
session metadata to the shared resolver. The native advisor hook path stays
|
|
37
|
+
pending until the host supplies that binding.
|
|
38
|
+
|
|
30
39
|
Offer only model IDs the active native tool accepts. Keep the user's order.
|
|
31
40
|
Treat `inherit-parent` and `auto` as parent inheritance, not model IDs. A full
|
|
32
41
|
install seeds the Claude file and the Codex file from the package when a host
|
|
@@ -55,6 +64,7 @@ $selectorInput = @'
|
|
|
55
64
|
"agentCount": 3,
|
|
56
65
|
"requiresDistinctModels": false
|
|
57
66
|
},
|
|
67
|
+
"requestedEffort": "medium",
|
|
58
68
|
"availableModelIds": ["confirmed-host-model"],
|
|
59
69
|
"confirmedSuitableModelIds": [],
|
|
60
70
|
"parentModelId": "current-parent-model",
|
|
@@ -71,12 +81,24 @@ $selection = $selectorInput | node "$HOME/.agents/scripts/select_pstack_models.m
|
|
|
71
81
|
`confirmedSuitableModelIds` contains available alternatives whose suitability
|
|
72
82
|
for this role the host metadata or user has confirmed. Do not rank or classify a
|
|
73
83
|
model from its name.
|
|
84
|
+
Keep the host's default effort in the preference file as `defaultEffort`, or
|
|
85
|
+
pass it as trusted runtime data in `requestedEffort`. The selector applies that
|
|
86
|
+
effort to a model-only entry before it picks a candidate. It holds the entry
|
|
87
|
+
when neither source supplies an effort and the policy has no single approved
|
|
88
|
+
effort for that model and role.
|
|
74
89
|
|
|
75
90
|
Use the returned `nativeSpawnArguments` in the native subagent call. An empty
|
|
76
91
|
object with `omitNativeModelArgument: true` means parent inheritance, so omit
|
|
77
92
|
the native `model` argument. Stop when `canDelegate` is false. Ask the user
|
|
78
93
|
when `requiresUserChoice` is true.
|
|
79
94
|
|
|
95
|
+
When a preference entry includes `{ "model": "model-id", "effort": "medium" }`,
|
|
96
|
+
the selector routes the pair before it deduplicates candidates and checks panel
|
|
97
|
+
diversity. The selected pair appears in `panel.selectedModelPairs`, and the
|
|
98
|
+
native argument uses `reasoning_effort`. The policy's `selectorExclusions` list
|
|
99
|
+
removes matching routed pairs from panel contenders without blocking direct
|
|
100
|
+
resolver or spawn-hook use.
|
|
101
|
+
|
|
80
102
|
Run the selector again before the next agent call. Pass the same panel contract
|
|
81
103
|
and the new delegation index with the refreshed inventory. Report
|
|
82
104
|
`panel.selectedModelIds` and `panel.isCrossModelDiverse` as returned. A panel
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"models": {
|
|
4
|
+
"astra": {
|
|
5
|
+
"id": "gpt-6-astra",
|
|
6
|
+
"aliases": ["Astra"]
|
|
7
|
+
},
|
|
8
|
+
"sol": {
|
|
9
|
+
"id": "gpt-5.6-sol",
|
|
10
|
+
"aliases": ["Sol"]
|
|
11
|
+
},
|
|
12
|
+
"terra": {
|
|
13
|
+
"id": "gpt-5.6-terra",
|
|
14
|
+
"aliases": ["Terra"]
|
|
15
|
+
},
|
|
16
|
+
"luna": {
|
|
17
|
+
"id": "gpt-5.6-luna",
|
|
18
|
+
"aliases": ["Luna"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"efforts": {
|
|
22
|
+
"values": ["low", "medium", "high", "xhigh", "max"],
|
|
23
|
+
"aliases": {
|
|
24
|
+
"light": "low"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"inheritanceAliases": ["auto", "inherit-parent"],
|
|
28
|
+
"roles": {
|
|
29
|
+
"default": "worker",
|
|
30
|
+
"worker": [
|
|
31
|
+
"worker",
|
|
32
|
+
"default",
|
|
33
|
+
"explorer",
|
|
34
|
+
"caveman",
|
|
35
|
+
"clean-coder",
|
|
36
|
+
"code-quality-agent",
|
|
37
|
+
"code-verifier",
|
|
38
|
+
"deep-research",
|
|
39
|
+
"docs-agent",
|
|
40
|
+
"git-commit-crafter",
|
|
41
|
+
"issue-tracker",
|
|
42
|
+
"plan-packet-validator",
|
|
43
|
+
"pr-description-writer",
|
|
44
|
+
"skill-writer-agent",
|
|
45
|
+
"theme-prompt-agent",
|
|
46
|
+
"theme-prompt-generator",
|
|
47
|
+
"translator"
|
|
48
|
+
],
|
|
49
|
+
"advisor": ["advisor", "session-advisor", "team-advisor", "code-advisor"],
|
|
50
|
+
"trustedAdvisor": ["session-advisor", "team-advisor", "code-advisor"]
|
|
51
|
+
},
|
|
52
|
+
"approvedPairs": [
|
|
53
|
+
{"model": "luna", "effort": "high"},
|
|
54
|
+
{"model": "luna", "effort": "xhigh"},
|
|
55
|
+
{"model": "luna", "effort": "max"},
|
|
56
|
+
{"model": "sol", "effort": "medium"},
|
|
57
|
+
{"model": "astra", "effort": "low"},
|
|
58
|
+
{"model": "astra", "effort": "medium"},
|
|
59
|
+
{"model": "astra", "effort": "high", "roles": ["advisor"]}
|
|
60
|
+
],
|
|
61
|
+
"selectorExclusions": [
|
|
62
|
+
{"model": "sol", "effort": "medium"}
|
|
63
|
+
],
|
|
64
|
+
"advisorDefault": {"model": "astra", "effort": "medium"},
|
|
65
|
+
"replacements": [
|
|
66
|
+
{"requested": {"model": "luna", "effort": "low"}, "selected": {"model": "luna", "effort": "high"}},
|
|
67
|
+
{"requested": {"model": "luna", "effort": "medium"}, "selected": {"model": "luna", "effort": "high"}},
|
|
68
|
+
{"requested": {"model": "sol", "effort": "low"}, "selected": {"model": "luna", "effort": "xhigh"}},
|
|
69
|
+
{"requested": {"model": "terra", "effort": "low"}, "selected": {"model": "luna", "effort": "xhigh"}},
|
|
70
|
+
{"requested": {"model": "terra", "effort": "medium"}, "selected": {"model": "luna", "effort": "xhigh"}},
|
|
71
|
+
{"requested": {"model": "terra", "effort": "high"}, "selected": {"model": "luna", "effort": "xhigh"}},
|
|
72
|
+
{"requested": {"model": "terra", "effort": "xhigh"}, "selected": {"model": "luna", "effort": "max"}},
|
|
73
|
+
{"requested": {"model": "sol", "effort": "high"}, "selected": {"model": "astra", "effort": "low"}},
|
|
74
|
+
{"requested": {"model": "sol", "effort": "xhigh"}, "selected": {"model": "astra", "effort": "low"}},
|
|
75
|
+
{"requested": {"model": "sol", "effort": "max"}, "selected": {"model": "astra", "effort": "low"}},
|
|
76
|
+
{"requested": {"model": "terra", "effort": "max"}, "selected": {"model": "astra", "effort": "low"}}
|
|
77
|
+
],
|
|
78
|
+
"roleReplacements": [
|
|
79
|
+
{"role": "advisor", "requested": {"model": "astra", "effort": "xhigh"}, "selected": {"model": "astra", "effort": "medium"}},
|
|
80
|
+
{"role": "advisor", "requested": {"model": "astra", "effort": "max"}, "selected": {"model": "astra", "effort": "medium"}},
|
|
81
|
+
{"role": "worker", "requested": {"model": "astra", "effort": "xhigh"}, "selected": {"model": "astra", "effort": "low"}},
|
|
82
|
+
{"role": "worker", "requested": {"model": "astra", "effort": "max"}, "selected": {"model": "astra", "effort": "low"}},
|
|
83
|
+
{"role": "worker", "requested": {"model": "astra", "effort": "high"}, "selected": {"model": "astra", "effort": "low"}}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { resolveSubagentModelRoute } from './subagent_model_policy.mjs';
|
|
4
|
+
|
|
5
|
+
function blockedRoute(diagnostic) {
|
|
6
|
+
return { status: 'blocked', diagnostic };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isObject(objectCandidate) {
|
|
10
|
+
return objectCandidate !== null && typeof objectCandidate === 'object' && !Array.isArray(objectCandidate);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function validatePolicyPath(input) {
|
|
14
|
+
if (!Object.hasOwn(input, 'policyPath')) return null;
|
|
15
|
+
if (typeof input.policyPath !== 'string' || input.policyPath.trim() === '') {
|
|
16
|
+
return 'advisor route policyPath must be a non-empty string';
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let input;
|
|
22
|
+
try {
|
|
23
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
24
|
+
} catch {
|
|
25
|
+
const routingDecision = blockedRoute('advisor route input is not valid JSON');
|
|
26
|
+
process.stdout.write(JSON.stringify(routingDecision));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (input !== undefined) {
|
|
31
|
+
let routingDecision;
|
|
32
|
+
if (!isObject(input)) {
|
|
33
|
+
routingDecision = blockedRoute('advisor route input must be an object');
|
|
34
|
+
} else {
|
|
35
|
+
const routingInputIssue = validatePolicyPath(input);
|
|
36
|
+
routingDecision = routingInputIssue
|
|
37
|
+
? blockedRoute(routingInputIssue)
|
|
38
|
+
: resolveSubagentModelRoute({
|
|
39
|
+
agent_type: 'session-advisor',
|
|
40
|
+
...(input.model !== undefined && input.model !== null ? { model: input.model } : {}),
|
|
41
|
+
...(input.reasoning_effort !== undefined ? { reasoning_effort: input.reasoning_effort } : {}),
|
|
42
|
+
}, {
|
|
43
|
+
policyPath: input.policyPath,
|
|
44
|
+
trustedSessionMetadata: {
|
|
45
|
+
authorized: true,
|
|
46
|
+
registeredAgentType: 'session-advisor',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write(JSON.stringify(routingDecision));
|
|
51
|
+
if (routingDecision.status === 'blocked') process.exitCode = 1;
|
|
52
|
+
}
|