@opengsd/gsd-core 1.6.0 → 1.7.0-rc.1
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/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-verifier.md +1 -0
- package/bin/gsd-mcp-server.js +31 -0
- package/bin/install.js +293 -1145
- package/commands/gsd/review.md +6 -0
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +116 -1
- package/gsd-core/bin/lib/adapter-declarative.cjs +35 -0
- package/gsd-core/bin/lib/adapter-imperative.cjs +52 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +231 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +7 -7
- package/gsd-core/bin/lib/capability-loader.cjs +18 -0
- package/gsd-core/bin/lib/capability-lock.cjs +2 -2
- package/gsd-core/bin/lib/capability-registry.cjs +889 -82
- package/gsd-core/bin/lib/capability-source.cjs +4 -4
- package/gsd-core/bin/lib/capability-validator.cjs +198 -0
- package/gsd-core/bin/lib/cli-skew-check.cjs +44 -0
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/config.cjs +27 -0
- package/gsd-core/bin/lib/embedding-adapter.cjs +27 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +70 -0
- package/gsd-core/bin/lib/hook-bus.cjs +81 -0
- package/gsd-core/bin/lib/host-integration.cjs +408 -0
- package/gsd-core/bin/lib/init.cjs +1 -1
- package/gsd-core/bin/lib/install-engine.cjs +755 -0
- package/gsd-core/bin/lib/install-profiles.cjs +35 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/mcp-server.cjs +194 -0
- package/gsd-core/bin/lib/milestone.cjs +27 -30
- package/gsd-core/bin/lib/model-adapter.cjs +50 -0
- package/gsd-core/bin/lib/phase.cjs +41 -72
- package/gsd-core/bin/lib/planning-workspace.cjs +1 -1
- package/gsd-core/bin/lib/probe-core.cjs +91 -1
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +129 -13
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +3 -2
- package/gsd-core/bin/lib/roadmap.cjs +17 -3
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +65 -9
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +54 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +5 -2
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/gsd-core/bin/lib/runtime-name-policy.cjs +160 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -1
- package/gsd-core/bin/lib/stale-bake-guard.cjs +254 -0
- package/gsd-core/bin/lib/state-command-router.cjs +4 -0
- package/gsd-core/bin/lib/state-io.cjs +55 -0
- package/gsd-core/bin/lib/state-transition.cjs +1588 -0
- package/gsd-core/bin/lib/state.cjs +306 -681
- package/gsd-core/bin/lib/surface.cjs +4 -1
- package/gsd-core/bin/lib/workstream.cjs +4 -4
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -0
- package/gsd-core/references/honest-verifier.md +105 -0
- package/gsd-core/references/reviewer-instances.md +99 -0
- package/gsd-core/workflows/autonomous.md +9 -9
- package/gsd-core/workflows/manager.md +15 -15
- package/gsd-core/workflows/plan-phase.md +1 -1
- package/gsd-core/workflows/review.md +26 -0
- package/gsd-core/workflows/thread.md +4 -4
- package/gsd-core/workflows/verify-phase.md +11 -4
- package/hooks/dist/gsd-graphify-update.sh +7 -1
- package/hooks/gsd-graphify-update.sh +7 -1
- package/package.json +4 -4
- package/scripts/ci-test-scope.cjs +38 -9
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-regression-test-names.allowlist.json +3 -0
- package/scripts/lint-test-file-count.allowlist.json +19 -5
- package/scripts/mutation-matrix.cjs +45 -3
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/lint-windows-test-portability.cjs +0 -178
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Host Integration module — ADR-1239 Phase A.
|
|
4
|
+
*
|
|
5
|
+
* Pure, additive, no-I/O module providing a closed vocabulary for host
|
|
6
|
+
* integration axes, degradation ladder, profile classification, and
|
|
7
|
+
* capability negotiation.
|
|
8
|
+
*
|
|
9
|
+
* The SINGLE source of truth for integration axes and degradation levels.
|
|
10
|
+
* All functions are pure (no side effects, no I/O).
|
|
11
|
+
*
|
|
12
|
+
* Per-CLI sourced axis VALUES (with citations) live in docs/reference/host-integration-capability-matrix.md — every value is documented or explicitly 'undocumented'.
|
|
13
|
+
*/
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Protocol version
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const PROTOCOL_VERSION = 1;
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Undocumented sentinel — fail-closed when a host omits CLI docs for an axis
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Sentinel value used when a host descriptor's CLI docs do not state a value
|
|
23
|
+
* for an axis. It VALIDATES (accepted by the validator) but NEVER propagates
|
|
24
|
+
* into effective axes — it fails closed exactly like an unknown/missing value.
|
|
25
|
+
*
|
|
26
|
+
* Do NOT add to HOST_INTEGRATION_AXES (which is the documented vocabulary).
|
|
27
|
+
*/
|
|
28
|
+
const UNDOCUMENTED = 'undocumented';
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Closed vocabulary — axes and interface points
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
const HOST_INTEGRATION_AXES = Object.freeze({
|
|
33
|
+
embeddingMode: Object.freeze(['imperative', 'declarative']),
|
|
34
|
+
commandSurface: Object.freeze(['slash-file', 'slash-programmatic', 'slash-toml', 'palette', 'prose-only']),
|
|
35
|
+
modelMode: Object.freeze(['active', 'passive']),
|
|
36
|
+
hookBus: Object.freeze(['host', 'engine', 'none']),
|
|
37
|
+
stateIO: Object.freeze(['filesystem', 'sandboxed-storage', 'session-log-append']),
|
|
38
|
+
transport: Object.freeze(['mcp', 'native-extension']),
|
|
39
|
+
runtime: Object.freeze(['node', 'bun', 'sandboxed-web', 'python', 'go', 'rust', 'electron', 'other']),
|
|
40
|
+
subagentToolkit: Object.freeze(['full', 'read-only']),
|
|
41
|
+
});
|
|
42
|
+
const INTERFACE_POINTS = Object.freeze(['command', 'dispatch', 'model', 'hooks', 'state', 'artifact']);
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Profile baselines
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Fail-closed floor: the most restrictive known value per axis, injected when a host omits an axis (degrade-closed, never assume capability).
|
|
47
|
+
const SAFE_DEFAULTS = {
|
|
48
|
+
embeddingMode: 'declarative',
|
|
49
|
+
commandSurface: 'prose-only',
|
|
50
|
+
dispatch: { namedDispatch: false, nested: false, maxDepth: 0, background: false, subagentToolkit: 'read-only', backgroundDispatch: false },
|
|
51
|
+
modelMode: 'passive',
|
|
52
|
+
hookBus: 'none',
|
|
53
|
+
stateIO: 'session-log-append',
|
|
54
|
+
transport: 'mcp',
|
|
55
|
+
runtime: 'node',
|
|
56
|
+
};
|
|
57
|
+
const PROFILE_BASELINES = Object.freeze({
|
|
58
|
+
'programmatic-cli': Object.freeze({
|
|
59
|
+
embeddingMode: 'imperative',
|
|
60
|
+
commandSurface: 'slash-file',
|
|
61
|
+
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
|
|
62
|
+
modelMode: 'passive',
|
|
63
|
+
hookBus: 'host',
|
|
64
|
+
stateIO: 'filesystem',
|
|
65
|
+
transport: 'mcp',
|
|
66
|
+
runtime: 'node',
|
|
67
|
+
}),
|
|
68
|
+
'declarative-cli': Object.freeze({
|
|
69
|
+
embeddingMode: 'declarative',
|
|
70
|
+
commandSurface: 'slash-file',
|
|
71
|
+
dispatch: Object.freeze({ namedDispatch: true, nested: false, maxDepth: 1, background: false, subagentToolkit: 'full', backgroundDispatch: false }),
|
|
72
|
+
modelMode: 'passive',
|
|
73
|
+
hookBus: 'host',
|
|
74
|
+
stateIO: 'filesystem',
|
|
75
|
+
transport: 'mcp',
|
|
76
|
+
runtime: 'node',
|
|
77
|
+
}),
|
|
78
|
+
'ide': Object.freeze({
|
|
79
|
+
embeddingMode: 'imperative',
|
|
80
|
+
commandSurface: 'palette',
|
|
81
|
+
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: 5, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
|
|
82
|
+
modelMode: 'active',
|
|
83
|
+
hookBus: 'engine',
|
|
84
|
+
stateIO: 'sandboxed-storage',
|
|
85
|
+
transport: 'mcp',
|
|
86
|
+
runtime: 'sandboxed-web',
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// degradationFor — plain data-table lookup (NOT clever code)
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
/**
|
|
93
|
+
* Look up the degradation level for a given interface point and partial axes.
|
|
94
|
+
*
|
|
95
|
+
* NEVER throws. Returns { level:'absent', fallback:'...', unknown:true } for
|
|
96
|
+
* any missing or unrecognised axis value.
|
|
97
|
+
*/
|
|
98
|
+
function degradationFor(point, axes) {
|
|
99
|
+
const UNKNOWN = {
|
|
100
|
+
level: 'absent',
|
|
101
|
+
fallback: 'unknown capability — degraded closed',
|
|
102
|
+
unknown: true,
|
|
103
|
+
};
|
|
104
|
+
switch (point) {
|
|
105
|
+
case 'command': {
|
|
106
|
+
const cs = axes.commandSurface;
|
|
107
|
+
if (cs === 'slash-file' || cs === 'slash-programmatic')
|
|
108
|
+
return { level: 'full', fallback: '' };
|
|
109
|
+
if (cs === 'slash-toml' || cs === 'palette')
|
|
110
|
+
return { level: 'degraded', fallback: 'toml/palette surface — limited command routing' };
|
|
111
|
+
if (cs === 'prose-only')
|
|
112
|
+
return { level: 'absent', fallback: 'AGENTS.md prose + skills menu' };
|
|
113
|
+
return UNKNOWN;
|
|
114
|
+
}
|
|
115
|
+
case 'dispatch': {
|
|
116
|
+
const d = axes.dispatch;
|
|
117
|
+
if (!d || typeof d !== 'object')
|
|
118
|
+
return UNKNOWN;
|
|
119
|
+
const disp = d;
|
|
120
|
+
if (disp.namedDispatch !== true || disp.maxDepth === 0) {
|
|
121
|
+
return { level: 'absent', fallback: 'single-agent inline / SDK sub-session' };
|
|
122
|
+
}
|
|
123
|
+
// maxDepth < 0 means unbounded
|
|
124
|
+
const isUnbounded = typeof disp.maxDepth === 'number' && Number.isFinite(disp.maxDepth) && disp.maxDepth < 0;
|
|
125
|
+
const depth = (typeof disp.maxDepth === 'number' && Number.isFinite(disp.maxDepth)) ? disp.maxDepth : 0;
|
|
126
|
+
const isFullDepth = isUnbounded || (disp.nested === true && depth >= 2);
|
|
127
|
+
if (isFullDepth) {
|
|
128
|
+
// Fail-closed: return 'full' ONLY when subagentToolkit is explicitly 'full';
|
|
129
|
+
// any other value (read-only, undocumented, unknown, missing) → degraded.
|
|
130
|
+
if (disp.subagentToolkit === 'full') {
|
|
131
|
+
return { level: 'full', fallback: '' };
|
|
132
|
+
}
|
|
133
|
+
return { level: 'degraded', fallback: 'restricted/undocumented subagent toolkit — limited dispatch surface' };
|
|
134
|
+
}
|
|
135
|
+
// flat (maxDepth===1)
|
|
136
|
+
return { level: 'degraded', fallback: 'flat dispatch — waves run inline' };
|
|
137
|
+
}
|
|
138
|
+
case 'model': {
|
|
139
|
+
const mm = axes.modelMode;
|
|
140
|
+
if (mm === 'active')
|
|
141
|
+
return { level: 'full', fallback: '' };
|
|
142
|
+
if (mm === 'passive')
|
|
143
|
+
return { level: 'degraded', fallback: 'instruction-injection / per-agent model field' };
|
|
144
|
+
return UNKNOWN;
|
|
145
|
+
}
|
|
146
|
+
case 'hooks': {
|
|
147
|
+
const hb = axes.hookBus;
|
|
148
|
+
if (hb === 'host')
|
|
149
|
+
return { level: 'full', fallback: '' };
|
|
150
|
+
if (hb === 'engine')
|
|
151
|
+
return { level: 'degraded', fallback: 'engine-owned bus' };
|
|
152
|
+
if (hb === 'none')
|
|
153
|
+
return { level: 'absent', fallback: 'rule-text instructions' };
|
|
154
|
+
return UNKNOWN;
|
|
155
|
+
}
|
|
156
|
+
case 'state': {
|
|
157
|
+
const si = axes.stateIO;
|
|
158
|
+
if (si === 'filesystem')
|
|
159
|
+
return { level: 'full', fallback: '' };
|
|
160
|
+
if (si === 'sandboxed-storage')
|
|
161
|
+
return { level: 'degraded', fallback: 'sandboxed storage' };
|
|
162
|
+
if (si === 'session-log-append')
|
|
163
|
+
return { level: 'degraded', fallback: 'append-only session log' };
|
|
164
|
+
return UNKNOWN;
|
|
165
|
+
}
|
|
166
|
+
case 'artifact': {
|
|
167
|
+
const cs = axes.commandSurface;
|
|
168
|
+
if (cs === 'slash-file' || cs === 'slash-programmatic')
|
|
169
|
+
return { level: 'full', fallback: '' };
|
|
170
|
+
if (cs === 'slash-toml' || cs === 'prose-only')
|
|
171
|
+
return { level: 'degraded', fallback: 'menu / @-only' };
|
|
172
|
+
if (cs === 'palette')
|
|
173
|
+
return { level: 'absent', fallback: 'palette + chat participant; skills become LM tools' };
|
|
174
|
+
return UNKNOWN;
|
|
175
|
+
}
|
|
176
|
+
default:
|
|
177
|
+
return UNKNOWN;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// profileOf
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
/**
|
|
184
|
+
* Classify a partial set of integration axes into a named profile.
|
|
185
|
+
* Returns null when no profile can be determined.
|
|
186
|
+
*/
|
|
187
|
+
function profileOf(axes) {
|
|
188
|
+
const a = axes;
|
|
189
|
+
if (a.embeddingMode === 'imperative' && a.runtime === 'sandboxed-web')
|
|
190
|
+
return 'ide';
|
|
191
|
+
if (a.embeddingMode === 'imperative')
|
|
192
|
+
return 'programmatic-cli';
|
|
193
|
+
if (a.embeddingMode === 'declarative')
|
|
194
|
+
return 'declarative-cli';
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const DEFAULT_ENGINE = {
|
|
198
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
199
|
+
axes: {
|
|
200
|
+
embeddingMode: 'imperative',
|
|
201
|
+
commandSurface: 'slash-file',
|
|
202
|
+
dispatch: { namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true },
|
|
203
|
+
modelMode: 'active',
|
|
204
|
+
hookBus: 'host',
|
|
205
|
+
stateIO: 'filesystem',
|
|
206
|
+
transport: 'mcp',
|
|
207
|
+
runtime: 'node',
|
|
208
|
+
},
|
|
209
|
+
known: HOST_INTEGRATION_AXES,
|
|
210
|
+
};
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// negotiateHostCapabilities
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
/**
|
|
215
|
+
* Negotiate host integration capabilities against an engine.
|
|
216
|
+
*
|
|
217
|
+
* POST-CONDITION: every effective scalar axis value is in engine.known[axis].
|
|
218
|
+
* effective never contains a value the host didn't declare AND the engine
|
|
219
|
+
* cannot drive.
|
|
220
|
+
*
|
|
221
|
+
* NEVER throws. Returns a fresh object each call (mutation-safe).
|
|
222
|
+
*/
|
|
223
|
+
function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
224
|
+
const warnings = [];
|
|
225
|
+
const h = host;
|
|
226
|
+
// Warn if protocolVersion is present but not a finite number
|
|
227
|
+
if (h.protocolVersion !== undefined && (typeof h.protocolVersion !== 'number' || !Number.isFinite(h.protocolVersion))) {
|
|
228
|
+
warnings.push(`host protocolVersion is not a finite number — using engine version ${engine.protocolVersion}`);
|
|
229
|
+
}
|
|
230
|
+
const hostPV = (typeof h.protocolVersion === 'number' && Number.isFinite(h.protocolVersion)) ? h.protocolVersion : engine.protocolVersion;
|
|
231
|
+
const enginePV = engine.protocolVersion;
|
|
232
|
+
// Warn if host declares a newer protocol version
|
|
233
|
+
if (hostPV > enginePV) {
|
|
234
|
+
warnings.push(`host protocolVersion ${hostPV} newer than engine ${enginePV} — capabilities beyond version ${enginePV} not trusted`);
|
|
235
|
+
}
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Helper: negotiate a single scalar axis
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
function negotiateScalar(axis) {
|
|
240
|
+
const knownValues = engine.known[axis];
|
|
241
|
+
const hostVal = h[axis];
|
|
242
|
+
const engineVal = engine.axes[axis];
|
|
243
|
+
const safeDefault = SAFE_DEFAULTS[axis];
|
|
244
|
+
if (hostVal === undefined || hostVal === null) {
|
|
245
|
+
// Host did not declare this axis
|
|
246
|
+
warnings.push(`host did not declare '${axis}'`);
|
|
247
|
+
return safeDefault;
|
|
248
|
+
}
|
|
249
|
+
if (hostVal === UNDOCUMENTED) {
|
|
250
|
+
// Host declared the undocumented sentinel — treat as fail-closed (degrade to safe default)
|
|
251
|
+
warnings.push(`host axis '${axis}' is undocumented — degraded closed`);
|
|
252
|
+
return safeDefault;
|
|
253
|
+
}
|
|
254
|
+
if (!knownValues.includes(hostVal)) {
|
|
255
|
+
// Host declared an unknown/future value — NEVER copy into effective
|
|
256
|
+
warnings.push(`host declared unknown '${axis}' value '${String(hostVal)}' — not trusted (host protocolVersion ${hostPV} vs engine ${enginePV})`);
|
|
257
|
+
return safeDefault;
|
|
258
|
+
}
|
|
259
|
+
// Engine capability cap: if the engine can't drive the host's value,
|
|
260
|
+
// use the engine's lesser capability.
|
|
261
|
+
// For modelMode: 'active' > 'passive' — if host wants active but engine
|
|
262
|
+
// is passive, cap to passive.
|
|
263
|
+
if (axis === 'modelMode') {
|
|
264
|
+
if (hostVal === 'active' && engineVal === 'passive')
|
|
265
|
+
return 'passive';
|
|
266
|
+
}
|
|
267
|
+
return hostVal;
|
|
268
|
+
}
|
|
269
|
+
// Negotiate all scalar axes
|
|
270
|
+
const effectiveEmbeddingMode = negotiateScalar('embeddingMode');
|
|
271
|
+
const effectiveCommandSurface = negotiateScalar('commandSurface');
|
|
272
|
+
const effectiveModelMode = negotiateScalar('modelMode');
|
|
273
|
+
const effectiveHookBus = negotiateScalar('hookBus');
|
|
274
|
+
const effectiveStateIO = negotiateScalar('stateIO');
|
|
275
|
+
const effectiveTransport = negotiateScalar('transport');
|
|
276
|
+
const effectiveRuntime = negotiateScalar('runtime');
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Dispatch struct negotiation
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
const hostDispatch = (typeof h.dispatch === 'object' && h.dispatch !== null)
|
|
281
|
+
? h.dispatch
|
|
282
|
+
: null;
|
|
283
|
+
const engineDispatch = engine.axes.dispatch;
|
|
284
|
+
let effectiveNamedDispatch;
|
|
285
|
+
let effectiveNested;
|
|
286
|
+
let effectiveBackground;
|
|
287
|
+
let effectiveBackgroundDispatch;
|
|
288
|
+
let effectiveSubagentToolkit;
|
|
289
|
+
let effectiveMaxDepth;
|
|
290
|
+
if (hostDispatch === null) {
|
|
291
|
+
// Host didn't declare dispatch at all — fail-closed to most-restrictive values
|
|
292
|
+
warnings.push(`host did not declare 'dispatch'`);
|
|
293
|
+
effectiveNamedDispatch = false;
|
|
294
|
+
effectiveNested = false;
|
|
295
|
+
effectiveBackground = false;
|
|
296
|
+
effectiveBackgroundDispatch = false;
|
|
297
|
+
effectiveSubagentToolkit = 'read-only';
|
|
298
|
+
effectiveMaxDepth = 0;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// N1: observability warnings for 'undocumented' sentinel on dispatch fields
|
|
302
|
+
if (hostDispatch.namedDispatch === 'undocumented') {
|
|
303
|
+
warnings.push(`dispatch.namedDispatch is undocumented — degraded closed`);
|
|
304
|
+
}
|
|
305
|
+
if (hostDispatch.nested === 'undocumented') {
|
|
306
|
+
warnings.push(`dispatch.nested is undocumented — degraded closed`);
|
|
307
|
+
}
|
|
308
|
+
if (hostDispatch.background === 'undocumented') {
|
|
309
|
+
warnings.push(`dispatch.background is undocumented — degraded closed`);
|
|
310
|
+
}
|
|
311
|
+
if (hostDispatch.subagentToolkit === 'undocumented') {
|
|
312
|
+
warnings.push(`dispatch.subagentToolkit is undocumented — degraded closed (read-only)`);
|
|
313
|
+
}
|
|
314
|
+
if (hostDispatch.backgroundDispatch === 'undocumented') {
|
|
315
|
+
warnings.push(`dispatch.backgroundDispatch is undocumented — degraded closed`);
|
|
316
|
+
}
|
|
317
|
+
effectiveNamedDispatch = (hostDispatch.namedDispatch === true) && engineDispatch.namedDispatch;
|
|
318
|
+
effectiveNested = (hostDispatch.nested === true) && engineDispatch.nested;
|
|
319
|
+
effectiveBackground = (hostDispatch.background === true) && engineDispatch.background;
|
|
320
|
+
effectiveBackgroundDispatch = (hostDispatch.backgroundDispatch === true) && engineDispatch.backgroundDispatch;
|
|
321
|
+
// subagentToolkit: fail closed to read-only unless explicitly 'full'
|
|
322
|
+
// (an 'undocumented' or 'read-only' value → read-only)
|
|
323
|
+
const hostToolkit = hostDispatch.subagentToolkit === 'full' ? 'full' : 'read-only';
|
|
324
|
+
const engineToolkit = engineDispatch.subagentToolkit === 'read-only' ? 'read-only' : 'full';
|
|
325
|
+
effectiveSubagentToolkit = (hostToolkit === 'read-only' || engineToolkit === 'read-only') ? 'read-only' : 'full';
|
|
326
|
+
// maxDepth: missing/non-number/non-finite → 0 + warning
|
|
327
|
+
let hostMaxDepth;
|
|
328
|
+
if (typeof hostDispatch.maxDepth !== 'number' || !Number.isFinite(hostDispatch.maxDepth)) {
|
|
329
|
+
warnings.push(`host dispatch.maxDepth is missing or not a number — treating as 0`);
|
|
330
|
+
hostMaxDepth = 0;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
hostMaxDepth = hostDispatch.maxDepth;
|
|
334
|
+
}
|
|
335
|
+
// Treat negative as +Infinity for the min, then if result is +Infinity emit -1
|
|
336
|
+
const hDepthNum = hostMaxDepth < 0 ? Infinity : hostMaxDepth;
|
|
337
|
+
const eDepthNum = engineDispatch.maxDepth < 0 ? Infinity : engineDispatch.maxDepth;
|
|
338
|
+
const minDepth = Math.min(hDepthNum, eDepthNum);
|
|
339
|
+
effectiveMaxDepth = minDepth === Infinity ? -1 : minDepth;
|
|
340
|
+
// If namedDispatch is false, cap maxDepth/nested/background/backgroundDispatch to 0/false/false/false (struct consistency)
|
|
341
|
+
if (!effectiveNamedDispatch) {
|
|
342
|
+
effectiveMaxDepth = 0;
|
|
343
|
+
effectiveNested = false;
|
|
344
|
+
effectiveBackground = false;
|
|
345
|
+
effectiveBackgroundDispatch = false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const effectiveDispatch = {
|
|
349
|
+
namedDispatch: effectiveNamedDispatch,
|
|
350
|
+
nested: effectiveNested,
|
|
351
|
+
maxDepth: effectiveMaxDepth,
|
|
352
|
+
background: effectiveBackground,
|
|
353
|
+
subagentToolkit: effectiveSubagentToolkit,
|
|
354
|
+
backgroundDispatch: effectiveBackgroundDispatch,
|
|
355
|
+
};
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// Assemble effective axes
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
const effective = {
|
|
360
|
+
embeddingMode: effectiveEmbeddingMode,
|
|
361
|
+
commandSurface: effectiveCommandSurface,
|
|
362
|
+
dispatch: effectiveDispatch,
|
|
363
|
+
modelMode: effectiveModelMode,
|
|
364
|
+
hookBus: effectiveHookBus,
|
|
365
|
+
stateIO: effectiveStateIO,
|
|
366
|
+
transport: effectiveTransport,
|
|
367
|
+
runtime: effectiveRuntime,
|
|
368
|
+
};
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
// Compute points (fresh objects — mutation-safe)
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
const points = {};
|
|
373
|
+
for (const point of INTERFACE_POINTS) {
|
|
374
|
+
const hostDeg = degradationFor(point, host);
|
|
375
|
+
const effectiveDeg = degradationFor(point, effective);
|
|
376
|
+
points[point] = {
|
|
377
|
+
hostLevel: hostDeg.level,
|
|
378
|
+
effectiveLevel: effectiveDeg.level,
|
|
379
|
+
fallback: effectiveDeg.fallback,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
// protocolVersion: min of host and engine
|
|
383
|
+
const resultProtocolVersion = Math.min(hostPV, enginePV);
|
|
384
|
+
return {
|
|
385
|
+
protocolVersion: resultProtocolVersion,
|
|
386
|
+
effective,
|
|
387
|
+
points,
|
|
388
|
+
warnings: [...warnings], // fresh copy
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function shouldFlattenDispatch(dispatch) {
|
|
392
|
+
if (!dispatch || typeof dispatch !== 'object')
|
|
393
|
+
return true;
|
|
394
|
+
const canBackground = dispatch.background === true && dispatch.backgroundDispatch === true;
|
|
395
|
+
return !canBackground;
|
|
396
|
+
}
|
|
397
|
+
module.exports = {
|
|
398
|
+
PROTOCOL_VERSION,
|
|
399
|
+
UNDOCUMENTED,
|
|
400
|
+
HOST_INTEGRATION_AXES,
|
|
401
|
+
INTERFACE_POINTS,
|
|
402
|
+
PROFILE_BASELINES,
|
|
403
|
+
DEFAULT_ENGINE,
|
|
404
|
+
degradationFor,
|
|
405
|
+
profileOf,
|
|
406
|
+
negotiateHostCapabilities,
|
|
407
|
+
shouldFlattenDispatch,
|
|
408
|
+
};
|
|
@@ -1756,7 +1756,7 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
1756
1756
|
if (entry.kind === 'directive') {
|
|
1757
1757
|
return `- Load the \`${entry.name}\` skill via the Skill tool before proceeding (plugin-provided).`;
|
|
1758
1758
|
}
|
|
1759
|
-
return `- @${entry.ref}`;
|
|
1759
|
+
return `- @${String(entry.ref).replace(/\\/g, '/')}`;
|
|
1760
1760
|
}).join('\n');
|
|
1761
1761
|
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
|
1762
1762
|
}
|